pipecircuit-cli 0.0.11

Command-line architectural tools for Pipecircuit programs
pipecircuit-cli-0.0.11 is not a library.

Pipecircuit CLI

Install the released command from crates.io; no framework checkout is needed:

cargo install pipecircuit-cli --locked
pipec --version

If you use Binstall, cargo binstall pipecircuit-cli also works. This release provides source packages, not first-party prebuilt binaries; Binstall may fall back to compiling the published crate. For a reproducible tool version, append --version 0.0.11 to either installation command.

From an application's Cargo workspace, run pipec arch check directly. Cargo and the workspace's resolved dependencies must be available; analysis is offline and does not build the application or modify its lockfile.

Create and run a workspace with a portable core and a routing-only switchboard:

pipec new my-program
cd my-program
cargo run
cargo test --workspace
pipec arch visualize --machine Latch --manifest-path core/Cargo.toml

The finite example activates a core machine, reports its outcome, and exits via pipecircuit.conduct(). core/src owns primitives and machines; apps/<app>/pipes owns contracts, apps/<app>/switchboard owns routing, and apps/<app>/wiring owns physical assembly. apps/<app>/src/main.rs starts the app. All 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:

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:

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. Physical applications live under apps/, sharing portable core libraries.

Run the same semantic program in a dedicated browser worker:

pipec new browser-program --template browser
cd browser-program/apps/browser-program/wiring/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.

web/src/main.ts       page: events and presentation
        ⇅ generated codec / bounded transferable messages
apps/<app>/wiring/src/lib.rs  worker: installed Rust program
        ⇅
apps/<app>/switchboard       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.11. 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 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:

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:

match from {
    From::Client(request) => To::Activator(request),
    From::Activator(activation) => To::Client(activation),
}

The application chooses the placement:

Variant External trigger and reply Authority placement Who drives it?
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
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:

pipec arch visualize --machine Latch --manifest-path core/Cargo.toml --format mermaid
pipec arch visualize --machine Latch --manifest-path core/Cargo.toml --format graphviz

Check and explore architecture

pipec arch check
pipec arch check --format json
pipec arch visualize
pipec arch visualize --format mermaid
pipec arch check --timings

The check is static and offline: it does not compile code, run build scripts, fetch dependencies, or modify lockfiles. It examines all declared production and build dependencies, including optional and target-specific edges. Development dependencies are shown but do not establish production reachability.

Canonical core/ and apps/<app>/{pipes,switchboard,wiring} paths establish roles. Shared contracts belong under apps/shared/pipes/. Empty shared crates are not created. Custom layouts declare the equivalent responsibility in Cargo:

[package.metadata.pipecircuit]
role = "switchboard"
app = "bakery"

Roles are core, pipes, switchboard, wiring, and application. Core has no application owner. Shared pipes or wiring use scope = "shared" instead of app. A declaration cannot contradict its canonical location. Every inspected workspace package requires a role, including local helper crates. External dependencies are not claimed to have been exhaustively audited; pipe and routing dependencies are restricted to local semantic crates and approved no_std Pipecircuit capabilities.

A multi-workspace repository can select its project root and sibling workspaces:

[workspace.metadata.pipecircuit]
root = ".."
workspaces = ["../core/Cargo.toml", "vault/Cargo.toml"]

Paths are relative to the declaring workspace. List the same project from each independently usable workspace entry point. No recursive repository scan occurs.

Execution implementations can declare dependency isolation:

[package.metadata.pipecircuit]
role = "wiring"
app = "browser"
forbid_dependencies = ["client-worker", "audio-worklet"]

These names must identify inspected packages. The check rejects transitive reachability through production/build dependencies; it does not prove where code runs. Keep browser bundler placement guards and target compilation as separate checks.

Text and JSON share findings, stable codes, manifest paths, and explanatory next steps. JSON uses schema pipecircuit.arch/1. Exit codes are 0 for a completed check without violations, 1 for violations, and 2 for an incomplete check or invalid invocation. Timings go to stderr only when requested. They separate Cargo discovery, validation, and rendering; work counts report metadata invocations, parsed manifests, library roots, and inspected dependency edges.

Project diagrams show dependencies, not message flow. The text overview focuses on local production connections; JSON retains external and development details. Semantic routes and actual execution placement remain explicitly unknown in this static project overview. --machine reuses source-derived machine diagrams. Existing compiled ProgramDiagram/installation inspection remains available for typed pipe and binding inventories; the CLI does not compile or execute consumer programs to obtain them. pipec diagram remains an alias for the older machine surface.

Start an invariant change in the core owner. Start a new interaction at the app's pipe contract, then follow its switchboard route to the machine that decides. A machine's result is an outcome; once routed, that value is a message. Prefer concrete domain names and bindings over mandatory Command/Effect suffixes. Architecture checks establish boundaries, not application correctness: behavior, coverage, mutations, proofs, portability builds, and performance remain explicit forms of evidence.