# Pipecircuit CLI
Create and run a workspace with a portable core and a routing-only switchboard:
```sh
pipec new my-program
cd my-program
cargo run
cargo test --workspace
pipec diagram Latch --crate core
```
The finite example activates a core machine, reports its outcome, and exits via
`pipecircuit.conduct()`. `core/src` owns primitives, machines and pipe contracts;
`core/switchboards` owns routing; `src` owns physical assembly. Both semantic crates
are `no_std` and every package shares strict Rust/Clippy lints. Architecture and
behavior snapshots are derived from the actual source and compiled tests.
For a matching unpublished checkout:
```sh
pipec new my-program --framework-path /path/to/Pipecircuit/pipecircuit
```
Without this option, the template uses the exact registry version pinned in
`src/new.rs`. `--name` overrides the destination's package name. Existing destinations
are refused. Generation uses embedded assets and performs no network requests,
Cargo execution or Git initialization. On I/O failure it retains the partial new
directory for inspection.
Choose the existing Mio host instead of the portable startup reactor:
```sh
pipec new native-program --template native
cd native-program
cargo run
```
This uses the same core and named pipe roster, with a one-slot SPSC exchange and
real Mio wake. It is a finite, single-threaded example: one local request, one
reply, then exit. The generated application chooses bindings, queue bounds,
submission policy and completion; the framework supplies polling and dispatch.
Output errors preserve the confirmed reply. No worker thread or socket is created.
These variants are starting examples, not production service templates. Long-running
intake needs admission and lifecycle policy. With `--framework-path`, native mode
also uses `libraries/mio` from that checkout. Separate physical executables may
later live under `apps/`, sharing portable cores.
Run the same semantic program in a dedicated browser worker:
```sh
pipec new browser-program --template browser
cd browser-program/web
npm install
npm run dev
```
Requires Node, Rust's `wasm32-unknown-unknown` target, and the wasm-bindgen CLI
matching the generated manifest's exact version. `npm run dev` builds the Rust
worker before starting Vite, then automatically rebuilds on Rust/Cargo edits.
Edits during compilation coalesce into one follow-up build. A failed build keeps
the previous worker running; a successful build reloads the page with fresh state. `npm run build` creates static output in
`web/dist`; `npm run preview` serves that output locally.
```text
web/src/main.ts page: events and presentation
⇅ generated codec / bounded transferable messages
src/lib.rs worker: installed Rust program
⇅
core/switchboards same routes as portable/native
⇅
core/src same authoritative Latch
```
The page has one pending request slot; no polling or retry loop. Disconnect closes
pending work, while initialization failures surface as unavailable confirmation.
A hung worker remains pending until explicit close; there is no automatic timeout
or retry. Reload starts a fresh worker. `web/vite.config.mjs` installs `rustWorker` from
`pipecircuit/vite`, using `web/worker.config.mjs` for both dev and manual generation.
It also rejects generated worker/WASM modules in production page chunks. This is
a build guard for that boundary, not a sandbox
against arbitrary application JavaScript.
The explicit request/reply fitting requires one immediate worker reply. It is not
a general streaming browser host. `flow_to_worker_reply` conducts the unchanged
switchboard; the worker export and generated codecs enforce the actual pipe types.
The small generator uses the plain pipe API because a unit command does not need
an artificial command/reply enum wrapper. Core serialization/binding derives are
optional; generation runs on the host. JavaScript owns DOM and browser bootstrap,
not domain state. Buffers transfer between realms; WASM boundary copies and codec
allocations remain—this is not an end-to-end zero-copy claim.
Run `npm test` after `npm run build` and installing Chromium with
`npx playwright install chromium`. It exercises real WASM workers in development
and production builds, repeat requests, capacity rejection, transferable buffer
detachment, pending shutdown, and initialization failure. Commit the generated
project's npm/Cargo lockfiles after installation. With `--framework-path`, the
browser variant uses the checkout's browser crate and binding generator; its npm
transport remains pinned to the published package.
The starter uses `generate_worker_pipe::<ActivationWorker>()` from
`pipecircuit-browser` 0.0.9. The framework derives the binding inventory and worker
entry from that export; the page connects through generated `startWorker`.
`tools/check-browser-scaffold` checks real development and production workers;
`--registry` verifies the published dependencies without checkout paths. The
check also edits a disposable starter to verify compile-error recovery and live
Rust rebuilds; it never edits a consumer application. See the [browser tooling
contract](../../libraries/browser/typescript/README.md#generated-output-ownership)
for generated-directory ownership, extra watch inputs and interrupted-build recovery.
## One behavior, three placements
Start with the behavior: a user asks to activate something, and repeated requests
must tell them it was already active. `Latch` is only this starter's example owner,
not a framework type. The core owns these steps:
```text
inactive + Activate → Activated, now active
active + Activate → AlreadyActive, still active
```
The `Client` pipe sends `Activate` out and accepts `Activation` in, from the
client's perspective. `Activator` exposes the opposite direction. The switchboard
routes the two closed families:
```rust
match from {
From::Client(input) => To::Activator(input),
From::Activator(output) => To::Client(output),
}
```
The application chooses the placement:
| Portable | One startup request; stdout reply | Inline in the executable | Startup reactor and `pipecircuit.conduct()` |
| Native | One-slot queues; local peer prints reply | Inline in the same native thread | Mio wake and hosted `pipecircuit.conduct()` |
| Browser | Button request; rendered reply | Inline inside a dedicated Rust/WASM worker | Browser message event; `flow_to_worker_reply` conducts the synchronous route |
```text
Browser main thread Worker thread
button → generated pipe ══ ArrayBuffer ══▶ decode → switchboard → Latch
status ← generated pipe ◀═ ArrayBuffer ═══ encode ← switchboard ← outcome
```
The worker retains the machine between messages. The page does not remember
whether activation happened; it renders the authority's reply. Moving between
variants leaves the core steps and switchboard route unchanged. The application
still chooses capacities, completion/shutdown and response presentation. Browser
serialization and worker boot are representation/placement work, not new domain
behavior.
Render other formats from the same source:
```sh
pipec diagram Latch --crate core --target mermaid
pipec diagram Latch --crate core --target graphviz
```