CLI
The pleach binary ships four subcommands — tui opens the terminal UI (and is what a bare pleach runs); dev boots a self-contained local playground; init scaffolds a route, page, and plugin stub for your framework; schema copies the Postgres bundle.
@pleach/core ships one binary, pleach, registered through the
package's bin field. It has four subcommands today:
pleach tui— the terminal UI. This is what a barenpx pleachruns. It talks to the same runtime the web route does, through a Node sidecar.pleach dev— boot a self-contained local playground in your browser: the four-stage lattice visualizer, the event log, an inspector, a runtime console, and the audit ledger, all driven by a real graph turn. Requires a provider key — there is no keyless mode.pleach init— interactive wizard. Detects your stack and scaffolds a starter project (route handler, page, optional plugin stub, optional schema copy). Reached by name; it is no longer what a barenpx pleachruns.pleach schema— copy the harness Postgres SQL bundle into your project so you can apply it against your database.
Bare npx pleach changed. It used to run the init wizard;
it now opens the TUI. Scripts that relied on the old behavior
should call npx pleach init explicitly.
Two routing rules are worth knowing. A leading --flag (rather
than a subcommand name) routes to schema, preserving the older
npx harness init --apply form. An unrecognized bare word
prints the help banner rather than erroring.
The binary name is pleach. The script lives at
scripts/harness-init.mjs in the package — that internal path
shows up in stack traces, but the user-facing name is the bin
name.
pleach dev — the local playground
pleach dev boots a self-contained local playground in your
browser — no host app, no build step, no database. It runs a
real graph turn through the shipped createPleachRoute() and
folds the resulting StreamEvent stream into five DX panels:
- Lattice visualizer — the four stages (
anchor-plan,tool-loop,synthesize,post-turn) light up live as nodes fire and channels are written. - Event log — the raw
StreamEventstream, the same shape the ledger and@pleach/reacthooks consume. - Inspector — per-node / per-channel state at each superstep.
- Runtime console — the bracketed runtime log breadcrumbs.
- Audit ledger — one
AuditableCallrow per LLM call, rendered as it lands.
npx pleach dev # → opens the playground in your browser
npx pleach dev --headless --prompt "…" # emit-stream capture, no browserFlags
| Flag | Default | Effect |
|---|---|---|
--sql | off | Durable persistence. Back the route's storage + checkpoint + event-log seams with a local pglite (Postgres-in-WASM) store, so chats and checkpoints survive a restart — no Docker, no cloud account. Needs the optional peer @electric-sql/pglite (npm install @electric-sql/pglite); without it, falls back to the in-memory default with a hint. |
--sql-dir <path> | ./.pleach-dev-sql | Where the pglite store lives. Implies --sql. Point it somewhere stable so a restart resumes the same chats. |
--headless | off | No browser. Drive one turn and dump the captured emit stream to stdout — for CI, sharing, or piping into another tool. |
--prompt <text> (alias --once) | a built-in multi-tool chain prompt | The user turn to run. Pairs with --headless; omitted, headless runs the default chain so the full lattice fires. |
--markdown (alias --md) | off | With --headless, render the emit capture as a Markdown session document instead of raw events. |
--plugin <path> | – | Load YOUR HarnessPlugin module into the runtime (repeatable). Test it in the browser, or headlessly with the flags below. The module exports its plugin as default, a named plugin, or a zero-arg factory. |
--scenarios <path> | – | A JSON battery of prompts (array of strings, or { name, prompt }, or { scenarios: [...] }). Drives each headlessly and prints a per-scenario report. Implies headless. |
--report <json|md> | json | Harness report format when running a battery. |
--assert <path> | – | An expectations file. Exits non-zero on any failed expectation — gate it in CI or an agent loop. |
--watch | off | Re-run the battery and DIFF the report on every --plugin file edit (run → observe → edit). |
--model <id> | the provider's default | Pin the model the playground turn runs on. |
--base-url <url> | the provider's default | Point the provider stream at a different base URL (self-hosted gateway, proxy). |
--port <n> (alias -p) | auto | Port for the playground server. |
--open / --no-open | open | Auto-open the browser, or start the server without doing so. |
--help, -h | – | Print the dev help banner and exit 0. |
Every value-taking flag also accepts the --flag=value form.
Note that --plugin on its own keeps the interactive server —
load your plugin and exercise it in the browser. Headless battery
mode is not requested directly; it engages when any of
--scenarios, --assert, or --watch is present. --headless is
a separate one-shot path.
Key handling
pleach dev reads the active provider's <PROVIDER>_API_KEY from
the environment first, then from the in-page key panel you can
paste into without restarting. Precedence is process.env first,
then the opt-in persistence store, then none.
| Provider id | Env var |
|---|---|
openrouter | OPENROUTER_API_KEY |
anthropic | ANTHROPIC_API_KEY |
openai | OPENAI_API_KEY |
google | GOOGLE_GENERATIVE_AI_API_KEY |
deepseek | DEEPSEEK_API_KEY |
mistral | MISTRAL_API_KEY |
moonshot | MOONSHOT_API_KEY |
Persistence is opt-in. When you paste a key into the panel you
can choose to save it to ~/.pleach/dev.env (written 0600, one
<PROVIDER>_API_KEY=<value> line per provider); the key is never
logged. Without saving, the key lives only for the running session.
If no key is detected, the playground prints guidance naming the
provider env vars to set. There is no keyless mode — a canned turn
proves no value prop, so the playground requires a real key.
Plugin-test harness — headless, continuous
Point the runtime at your own HarnessPlugin and drive a battery
of prompts with no browser. Each scenario folds the same surfaces
the playground tabs expose — conversation, runtime log, event log,
channel snapshot, audit ledger, and the inspectRuntime report
(capabilities wired n/total) — into one structured report, plus
derived signals (reachedTerminal, stalled, notContributed,
eventTypes, terminalSyntheses, auditFamilies, …):
# load your plugin into the live playground (test it in the browser)
npx pleach dev --plugin ./my-plugin.mjs
# headless battery → a JSON (or --report md) report per scenario
npx pleach dev --plugin ./my-plugin.mjs --scenarios ./scenarios.json --report md
# gate in CI / for an agent loop: non-zero exit on a failed expectation
npx pleach dev --plugin ./my-plugin.mjs --scenarios ./scenarios.json --assert ./expect.json
# iterate: re-run the battery + DIFF the report on every plugin edit
npx pleach dev --plugin ./my-plugin.mjs --scenarios ./scenarios.json --watchscenarios.json is an array of prompts (or { name, prompt });
expect.json declares per-scenario or all expectations. Each
predicate maps to a documented guarantee, so a green battery is a
continuously-enforced value-prop contract:
- Lifecycle —
reachedTerminal(the turn reaches a terminal answer — assert this, not the rarer naturalconverged),notStalled,finalContentIncludes. - Capability wiring —
minCapabilitiesWired,contributes,eventTypes. - Channels —
snapshotChannels(the channels a feature promises are written, e.g.costEvents/costRollup). - Audit ledger —
minAuditRows,everyAuditRowClassified(every LLM call carries acallClass),everyAuditRowTenantScoped(every row carries atenantId— isolation on every row). - Call classes —
exactlyOneTerminalSynthesis(one final answer per turn — countsterminalSynthesisrows, so a thin-draft recovery retry doesn't trip it),atMostOneTerminalSynthesis(the robust "never two final answers"; passes a passthrough turn too),maxSynthesizeCalls(caps the retry budget),auditCallClassesIn(every call's class stays within theutility | reasoning | converse | synthesizetaxonomy). - Stage lattice —
auditStagesIn(every call is stamped with a stage from theanchor-plan | tool-loop | synthesize | post-turnlattice). - Family lock —
singleFamily(the turn never silently widens across matrix families — at most one resolvedfamilyacross its audit rows).
The report is exactly the feedback an agent needs to run a
run → observe → edit loop on your plugin. To run the same loop
inside your own runtime (no CLI), flip the harness chatinit
flag.
pleach init — the wizard
The wizard detects what you already have and asks four questions:
- Template — chatbot (route + page), headless smoke script, or plugin-only.
- Provider —
anthropic,openai,openrouter,google. The default is whichever provider env var it finds first. - Plugin stub? — optional
pleach.plugin.ts(or.mjs) demonstrating three HarnessPlugin hooks. - Schema scaffold? — optional. Runs the
schemasubcommand inline. Default is on when asupabase/directory already exists.
Then it prints a write plan, asks for confirmation, and writes only files that don't already exist. Existing files are skipped, not overwritten.
npx pleach init [--template <name>] [--yes] [--cwd <dir>] [--help]Flags
| Flag | Default | Effect |
|---|---|---|
--template <name> | chatbot (with --yes) | Preselect template. One of chatbot, headless, plugin-only. Also flat / bundles to override the plugin-stub shape (default: bundles). |
--facets <list> | prompts,safety,tools | Comma-separated facet list for the plugin stub. Valid: prompts, safety, tools, stream, lifecycle. |
--facets-file <mode> | inline | Single-file inline (default) or split to scaffold facets/<name>.ts files. |
--license <SPDX> | Apache-2.0 | License for the consumer's scaffolded package.json (only when --publishing is not none). Independent of the @pleach/* substrate's own FSL-1.1-Apache-2.0 posture. |
--publishing <mode> | none (under --yes) | One of none, npm-private, npm-public. Controls package.json + README.md scaffold. |
--author <name> | null (under --yes) | Author for scaffolded package.json. Interactive mode reads npm config get init-author-name as a default. |
--repository <url> | null (under --yes) | Repository URL for scaffolded package.json. Interactive mode reads npm config get init-repository. |
--yes, -y | off | CI mode — accept all defaults without prompting. |
--cwd <dir> | process.cwd() | Run in a different directory. |
--help, -h | – | Print the init help banner and exit 0. |
What it detects
All best-effort, all from the filesystem and env:
| Signal | Values | How |
|---|---|---|
| Framework | next-app, next-pages, astro, sveltekit, remix, vite, react-other, node | package.json deps + presence of app/ |
| Package manager | bun, pnpm, yarn, npm | Lockfile presence |
@pleach/core installed | yes / no | package.json deps |
| TypeScript project | yes / no | tsconfig.json exists |
| Supabase migrations dir | yes / no | supabase/ exists |
| Provider env vars | which provider keys are reachable | process.env first, then .env.local, .env.development, .env in that order |
Detection runs most-specific to least-specific. Next.js wins over Vite over plain React, because plain React often hides under a Next or Vite shell. The fallback for "nothing matched" is plain Node.
The .env* walk is intentionally minimal — no interpolation, no
multi-line values, first occurrence wins. Just enough to surface
a provider key you set but never exported.
Provider env-var matrix
| Provider id | Env var |
|---|---|
anthropic | ANTHROPIC_API_KEY |
openai | OPENAI_API_KEY |
openrouter | OPENROUTER_API_KEY |
google | GOOGLE_GENERATIVE_AI_API_KEY |
The wizard offers every provider whose env var it can find. The
first reachable one is the default selection; if none is set,
the default is anthropic.
The wizard's 4-provider list is narrower than the runtime's. The
quickstart subpath's
providerDetection also covers deepseek,
mistral, and moonshot. If only a non-wizard provider env var is
set, the wizard reports "none detected" and defaults to
anthropic — but the runtime createPleachRoute your app boots
will still detect and use the provider correctly. The wizard's
list only drives the prompt question; the deployed handler reads
the broader runtime list.
Templates
| Template | Framework | Files written |
|---|---|---|
chatbot | next-app | app/api/chat/route.ts, app/page.tsx |
chatbot | next-pages | pages/api/chat.ts, pages/index.tsx |
chatbot | astro | src/pages/api/chat.ts, src/pages/index.astro |
chatbot | sveltekit | src/routes/api/chat/+server.ts, src/routes/+page.svelte |
chatbot | remix | app/routes/api.chat.ts, app/routes/_index.tsx |
chatbot | vite / react-other | src/App.tsx (no server route — pair with a Node server) |
headless | any | agent.mjs — smoke script using SessionRuntime |
plugin-only | any | pleach.plugin.{ts,mjs} (bundled stub by default; --template flat for legacy single-facet) |
The chatbot route templates import createPleachRoute from
@pleach/core/quickstart; the page
templates import ChatBox from the same subpath. The headless
template uses SessionRuntime from
@pleach/core directly. The plugin stub uses createPlugin,
appendPrompt, and gatedPrompt — three of the
plugin contract hooks. For the full
quickstart subpath surface (createPleachRoute, useChat,
<ChatBox />, defaultPlugin, createBenchmarkPlugin,
providerDetection), see
Getting started → The quickstart subpath.
Idempotence
Files that already exist are skipped, never overwritten. The
wizard's done block reports written, skipped, and failed
counts. For a clean re-run, delete the files the wizard wrote
the first time.
CI mode
--yes runs without prompts. Defaults: template = chatbot,
provider = whichever the env detection picked (or anthropic),
withPlugin = true for chatbot or plugin-only,
withSchema = true if supabase/ exists.
npx pleach init --yes --template plugin-only --cwd ./my-appNon-TTY invocations (CI runners, piped stdin) take the --yes
path automatically even without the flag.
Exit codes
| Code | When |
|---|---|
| 0 | Success, help, or --yes with zero write errors |
| 1 | One or more files failed to write |
Worked example — Next.js App Router
$ npx pleach init
pleach init — charm-style wizard for @pleach/core
┌─ environment
│ directory › /Users/you/my-app
│ framework › Next.js (App Router) (tsconfig.json)
│ package mgr › pnpm
│ @pleach/core › not yet installed
│ provider env › anthropic (.env.local)
│ supabase/ › found
└─
? Template
● 1) Chatbot (route + page) default — streaming UI
○ 2) Headless smoke script node agent.mjs, no UI
○ 3) Plugin stub only add to an existing app
› (1-3, default 1)
? Provider
● 1) anthropic ANTHROPIC_API_KEY set
○ 2) openai OPENAI_API_KEY unset
○ 3) openrouter OPENROUTER_API_KEY unset
○ 4) google GOOGLE_GENERATIVE_AI_API_KEY unset
› (1-4, default 1)
? Generate a sample plugin stub? (y/N) y
› plugin name (default: my-plugin) triage-plugin
? Scaffold Postgres schema into supabase/migrations? (Y/n)
┌─ write plan
│ • app/api/chat/route.ts
│ • app/page.tsx
│ • pleach.plugin.ts
│ • supabase/migrations/20260608120000_pleach_<bundle-file>.sql (from schema bundle)
└─
? Write 4 file(s)? (Y/n)
done
✓ wrote app/api/chat/route.ts
✓ wrote app/page.tsx
✓ wrote pleach.plugin.ts
✓ wrote supabase/migrations/20260608120000_pleach_<bundle-file>.sql
next steps
1. pnpm add @pleach/core
2. ANTHROPIC_API_KEY loaded from .env.local (framework auto-loads at dev time)
3. pnpm dev — chat lives at the root pageThe "next steps" block adapts to the package manager the wizard
detected. With bun it suggests bun add and bun run dev; with
npm, the npm equivalents. Same with the provider-key line — if
the key was found in process.env, the line is dropped from
next-steps entirely.
Plugin stub shape
When template = plugin-only (or chatbot with --add-plugin), the
wizard scaffolds a pleach.plugin.ts file. The default shape is now
bundle-aware: each facet (prompts, safety, tools by default)
gets its own definePleachPlugin const, composed into a single
exported plugin array.
// pleach.plugin.ts (default — bundle-aware shape)
import { definePleachPlugin } from "@pleach/core";
const promptsFacet = definePleachPlugin("my-plugin", { prompts: [] });
const safetyFacet = definePleachPlugin("my-plugin", { safetyPolicies: [] });
const toolsFacet = definePleachPlugin("my-plugin", { tools: [] });
export const myPlugin = [promptsFacet, safetyFacet, toolsFacet];Wire at the runtime registration site via array spread:
new SessionRuntime({ plugins: [...myPlugin] });This shape teaches the multi-facet pattern from day 1 — when a facet
grows past ~50 lines, move its const to facets/<facet>.ts per
plugin authoring standards.
The single-facet shape (one definePleachPlugin call covering all
hooks) is still available via --template flat:
npx pleach init --template flatSee plugin bundles for the rationale behind
the multi-facet pattern and the planned composePlugin upgrade path.
pleach init plugin — monorepo subcommand
For an existing project that needs to add a plugin without re-running framework detection:
npx pleach init plugin --name corpus-safety --facets safety,promptsScaffolds plugins/corpus-safety/ with four files:
| File | Content |
|---|---|
src/pleach.plugin.ts | Bundle-aware stub with the requested facets |
package.json | License + peer-dep + scripts, standards-aware defaults |
README.md | Install + use + standards reference seed |
tsconfig.json | TypeScript config seed (ES2022, strict, declaration emit) |
If a tsconfig.json exists at the top level, the wizard also inserts
a path-map entry (compilerOptions.paths["corpus-safety"]) as a
courtesy. Best-effort: skips and warns if the tsconfig can't be parsed.
Subcommand flags
| Flag | Default | Effect |
|---|---|---|
--name <kebab> | required | Plugin identity. Required under --yes. |
--facets <list> | prompts,safety,tools | Same vocabulary as pleach init. |
--template flat|bundles | bundles | Stub shape override. |
--facets-file inline|split | inline | Single-file vs split-file layout. |
--at <path> | plugins/<name>/ | Scaffold target. |
--license <SPDX> | Apache-2.0 | License id for the consumer's generated package.json. (Independent of the @pleach/* substrate's own FSL-1.1-Apache-2.0 posture.) |
--publishing <mode> | npm-private | One of none, npm-private, npm-public. |
--author <name> | (none) | Skipped under --yes unless supplied. |
--repository <url> | (none) | Skipped under --yes unless supplied. |
--yes, -y | off | CI mode. --name becomes required. |
--cwd <dir> | process.cwd() | Run in a different directory. |
--help, -h | – | Print the subcommand help banner and exit 0. |
The subcommand never modifies the top-level package.json or app
code beyond the optional path-map insertion. It writes only inside
the target directory.
pleach schema — copy the SQL bundle
Scaffolds the harness storage schema into a target directory.
This is the surface that used to live under pleach init;
pleach init --apply still routes here so existing scripts
don't break.
npx pleach schema [--target <dir>] [--dry-run|--apply] [--timestamped] [--help]Flags
| Flag | Default | Effect |
|---|---|---|
--target <dir> | ./supabase/migrations if it exists, else ./harness-migrations | Destination for the copied SQL files |
--dry-run | on | Print the file-copy plan; write nothing |
--apply | off | Actually write the files |
--timestamped | off | Prefix each file with YYYYMMDDHHMMSS_ (UTC) so Supabase migration ordering is stable |
--help, -h | – | Print the banner and exit 0 |
What it does
- Locates the schema bundle inside the installed package
(
node_modules/@pleach/core/dist/schema/postgres/). Falls back to the in-repo source tree when running against an unbuilt checkout. - Optionally rewrites each filename with a UTC timestamp prefix. Multiple files in the same second get monotonically incremented suffixes — Supabase's lexicographic order stays stable.
- Copies the files into your target directory (creating it if needed).
What it doesn't do
- Connect to your database.
- Run the migrations.
- Overwrite existing files. If a target file is already there,
schema --applyrefuses and exits 3 — read the manual-apply block below.
The full apply path is intentionally not in the CLI. Until a real consumer asks for it, the dry-run + manual-apply flow is the supported path and the surface stays small.
Exit codes
| Code | When |
|---|---|
| 0 | Success or dry-run completed |
| 2 | Schema bundle missing (is @pleach/core installed?) or no SQL files in the bundle |
| 3 | A target file already exists — refused to overwrite |
Applying the migrations by hand
After pleach schema --apply:
for f in supabase/migrations/*pleach*.sql; do
psql "$DATABASE_URL" -f "$f"
doneIf you're using the Supabase CLI:
pleach schema --apply --timestamped
supabase db pushThe schema bundle is idempotent at the DDL level — each file
uses CREATE TABLE IF NOT EXISTS and CREATE INDEX IF NOT EXISTS,
so re-applying is safe but won't migrate column shapes. Schema
evolution lands as new files in the bundle; the package's
CHANGELOG.md records the version each new migration shipped
in.
pleach tui — the terminal UI
npx pleach # bare invocation opens the TUI
npx pleach tui # the same thing, named
npx pleach tui --help # args pass straight through to the TUI binaryEverything after tui is forwarded verbatim to the binary, so the
TUI owns its own flag surface.
The TUI is a compiled Go binary. It does not reimplement the runtime — it starts a Node sidecar and drives it, so a terminal session and an HTTP route execute the same code:
pleach tui → the Go binary → the Node sidecar → your route handlerThat is why TUI behavior and server-route behavior stay in step: the sidecar resolves configuration (provider key, filesystem root, runtime toggles) and then delegates unmodified. There is one execute path, not two implementations that have to agree.
Flags
Everything after tui is forwarded verbatim, so this is the binary's
own surface, not a wrapper's.
| Flag | What it does |
|---|---|
--url <server> | Connect to a remote @pleach/core server instead of starting the local sidecar. |
--continue, -c | Resume the last session. Local sessions persist in the durable SQLite store. |
--resume <id> | Resume one specific prior session id. |
--print <msg> | Headless. Send one message, stream just the answer to stdout, exit — no TUI. This is the scriptable smoke/verify path. |
--events | With --print (or a positional/stdin message): stream every graph event — nodes, stages, channels, tools, deltas — instead of only the answer. Requests the server's verbose diagnostics tier. |
--tune | Print what this machine's ledger would derive for the agent's tuning knobs, beside the values in force, then exit. Read-only — never writes a knob. |
--heartwood-url <url> | Absolute URL of a persisted-identity endpoint for /heartwood. Falls back to the local ~/.pleach/heartwood.md. |
--print with --events is the pairing to reach for when you want to
see the four-stage lattice rather than read about it: one command,
every node firing and every stage transition on stdout.
npx pleach --print "What is 47 x 89?" # just the answer
npx pleach --print "What is 47 x 89?" --events # the full event latticeEvery one of these runs a real, billed model turn. There is no
keyless mode on any pleach CLI — headless output is a different
projection of a live turn, never a canned one.
A deprecated --demo alias for --print --events still parses and
prints a deprecation notice. Do not reach for it: the name reads
keyless and the turn is billed. Use --print --events.
The binary is built at install time
The Go binary is materialized by the package's postinstall
script. Installs that skip lifecycle scripts — npm ci --ignore-scripts, common in regulated and SBOM-generating
environments — will not have it.
A missing TUI binary is an expected state, not a failure.
Bare npx pleach falls back to the help banner rather than
erroring, so it never dead-ends. Rebuild it with npm rebuild @pleach/core. Every other subcommand is pure Node and keeps
working regardless.
Local development without a database
If you don't want to run any migrations during development, skip
the CLI entirely and omit storage and checkpointer when you
construct the runtime. With both absent it falls back to
MemoryAdapter + MemorySaver, so nothing touches a database.
See environment variables for the full list of
runtime-read env vars.
Project layout — after the wizard
The baseline plus
the files pleach init writes. This is what a Next.js App
Router project looks like one command after npx pleach init
on the chatbot template with plugin stub and schema scaffold:
my-app/
app/
api/
chat/route.ts # wizard-written — createPleachRoute({ provider, plugins })
page.tsx # wizard-written — <ChatBox apiUrl="/api/chat" />
pleach.plugin.ts # wizard-written — createPlugin + appendPrompt + gatedPrompt
supabase/
migrations/
20260608120000_pleach_*.sql # wizard-copied from the schema bundle
.env.local # ANTHROPIC_API_KEY (or your provider key)
package.json
tsconfig.jsonWhat changes from the baseline:
- The route handler is one line.
createPleachRoute({...})is what you'd normally write by hand; the wizard writes it for you. Wire your ownHarnessPluginset into theplugins:array as you grow. pleach.plugin.tsis the starting point, not the final shape. The stub shows three hooks —prompts,runtimePrompts, and the plugin-construction call. The plugin contract covers the full set (contributeTools,contributeSafetyPolicies, etc.); replace the stub when you outgrow it.supabase/migrations/holds the schema bundle. Filenames are timestamped so Supabase's lexicographic migration order matches the bundle order. Without--timestamped, the files keep their bundle names — fine if you're applying them by hand but loses ordering when fed throughsupabase db push.
Sibling-SKU binaries
Sibling SKUs (@pleach/compliance@0.1.0,
@pleach/eval@0.1.0, @pleach/gateway@0.1.0,
@pleach/replay@0.1.0, @pleach/mcp@0.1.0,
@pleach/coding-agent@0.1.0) are shipping today. The
CLI convention is one bin per package under its own name in the
@pleach/ scope — @pleach/compliance exposes
pleach-compliance, @pleach/eval exposes pleach-eval, and so
on. The pleach bin in core is reserved for substrate-level
operations, and the terminal UI is one of them: it ships inside
@pleach/core rather than as a sibling SKU, so pleach tui
needs no additional install. Per-sibling bins land alongside each SKU's first
useful command surface; check the package's npm page for the
current bin inventory.
Where to go next
Getting started
Install, wire a SessionRuntime, stream your first message.
Project layout
The orienting layout the wizard produces — described, not prescribed.
Plugin contract
The full HarnessPlugin surface the wizard's plugin stub demonstrates three hooks of.
Environment variables
Every env var the runtime reads, with defaults.
Subpath exports
The schema bundle and every other named subpath you can import.
DevTools
`window.__HARNESS_DEVTOOLS__` — the browser-console surface for inspecting sessions, walking checkpoints, forcing syncs, and listing tools during development.
Agent shapes
Reference agent designs Pleach is built for — each pairs a recurring production pattern with the runtime primitive that handles it and the team shape it shows up in.