# CLI (/docs/cli)



`@pleach/core` ships one binary, `pleach`, registered through the
package's `bin` field. It has three subcommands today:

* **`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. `--demo` runs keyless against a canned model.
* **`pleach init`** — interactive wizard. Detects your stack and
  scaffolds a starter project (route handler, page, optional
  plugin stub, optional schema copy). This is the default;
  `npx pleach` with no args runs it.
* **`pleach schema`** — copy the harness Postgres SQL bundle into
  your project so you can apply it against your database.

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.

<SourceMeta
  source="[
  { label: &#x22;src/cli/&#x22;, href: &#x22;https://github.com/pleachhq/core/tree/main/src/cli&#x22; },
  { label: &#x22;scripts/harness-init.mjs&#x22;, href: &#x22;https://github.com/pleachhq/core/blob/main/scripts/harness-init.mjs&#x22; },
]"
/>

## `pleach dev` — the local playground [#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 `StreamEvent` stream, the same shape the
  ledger and `@pleach/react` hooks consume.
* **Inspector** — per-node / per-channel state at each superstep.
* **Runtime console** — the bracketed runtime log breadcrumbs.
* **Audit ledger** — one `AuditableCall` row per LLM call, rendered
  as it lands.

```bash
npx pleach dev                          # → opens the playground in your browser
npx pleach dev --demo                   # keyless preview — REAL graph, canned model text
npx pleach dev --headless --prompt "…"  # emit-stream capture, no browser
```

### Flags [#flags]

| Flag                               | Default                            | Effect                                                                                                                                                                                                                                                                                                                                                                           |
| ---------------------------------- | ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--demo`                           | off                                | Keyless mode. The real graph runs end-to-end; only the model text is scripted (canned, deterministic). Use it to see the lattice execute with no API key.                                                                                                                                                                                                                        |
| `--sql`                            | off                                | Durable persistence. Back the route's storage + checkpoint + event-log seams with a local [pglite](https://pglite.dev) (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`).                                                                                                                                                                                                                                                                                   |
| `--port <n>` (alias `-p`)          | auto                               | Port for the playground server.                                                                                                                                                                                                                                                                                                                                                  |
| `--no-open`                        | off                                | Start the server but don't auto-open the browser.                                                                                                                                                                                                                                                                                                                                |
| `--help`, `-h`                     | –                                  | Print the dev help banner and exit 0.                                                                                                                                                                                                                                                                                                                                            |

### Key handling [#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 and you didn't pass `--demo`, the playground
prints guidance and points you at `--demo` for a keyless run.

### Keyless demo [#keyless-demo]

`--demo` is the zero-config entry point: the **real** compiled graph
executes a full turn — anchor-plan, tool-loop, synthesize,
post-turn, channel writes, audit rows — and only the model's text
output is canned. It's the honest preview: you watch the actual
lattice run, not a mock of it. Headless demo (`--headless --demo`,
no `--prompt`) runs the default multi-tool chain so the whole
lattice fires for a CI smoke or a shareable capture.

### Plugin-test harness — headless, continuous [#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`, …):

```bash
# 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 --watch
```

`scenarios.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 natural `converged`),
  `notStalled`, `finalContentIncludes`.
* **Capability wiring** — `minCapabilitiesWired`, `contributes`,
  `eventTypes`.
* **[Channels](/docs/channels)** — `snapshotChannels` (the channels
  a feature promises are written, e.g. `costEvents`/`costRollup`).
* **[Audit ledger](/docs/audit-ledger)** — `minAuditRows`,
  `everyAuditRowClassified` (every LLM call carries a `callClass`),
  `everyAuditRowTenantScoped` (every row carries a `tenantId` —
  [isolation on every row](/docs/multi-tenant)).
* **[Call classes](/docs/call-classes)** —
  `exactlyOneTerminalSynthesis` (one final answer per turn — counts
  `terminalSynthesis` rows, 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 the `utility | reasoning | converse | synthesize`
  taxonomy).
* **Stage lattice** — `auditStagesIn` (every call is stamped with a
  stage from the `anchor-plan | tool-loop | synthesize | post-turn`
  lattice).
* **[Family lock](/docs/family-lock)** — `singleFamily` (the turn
  never silently widens across matrix families — at most one
  resolved `family` across 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](/docs/playground-and-devtools#the-harness-chatinit-flag).

## `pleach init` — the wizard [#pleach-init--the-wizard]

The wizard detects what you already have and asks four questions:

1. **Template** — chatbot (route + page), headless smoke script,
   or plugin-only.
2. **Provider** — `anthropic`, `openai`, `openrouter`, `google`.
   The default is whichever provider env var it finds first.
3. **Plugin stub?** — optional `pleach.plugin.ts` (or `.mjs`)
   demonstrating three [HarnessPlugin](/docs/plugin-contract)
   hooks.
4. **Schema scaffold?** — optional. Runs the `schema` subcommand
   inline. Default is on when a `supabase/` 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.

```bash
npx pleach init [--template <name>] [--yes] [--cwd <dir>] [--help]
```

### Flags [#flags-1]

| 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 [#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-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](/docs/getting-started#the-quickstart-subpath)'s
`providerDetection` covers 7 providers (adds `deepseek`,
`mistral`, `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 [#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`](/docs/subpath-exports); the page
templates import `ChatBox` from the same subpath. The headless
template uses [`SessionRuntime`](/docs/session-runtime) from
`@pleach/core` directly. The plugin stub uses `createPlugin`,
`appendPrompt`, and `gatedPrompt` — three of the
[plugin contract](/docs/plugin-contract) hooks. For the full
quickstart subpath surface (`createPleachRoute`, `useChat`,
`<ChatBox />`, `defaultPlugin`, `createBenchmarkPlugin`,
`providerDetection`), see
[Getting started → The quickstart subpath](/docs/getting-started#the-quickstart-subpath).

### Idempotence [#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 [#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.

```bash
npx pleach init --yes --template plugin-only --cwd ./my-app
```

Non-TTY invocations (CI runners, piped stdin) take the `--yes`
path automatically even without the flag.

### Exit codes [#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 [#worked-example--nextjs-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 page
```

The "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 [#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.

```ts
// 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:

```ts
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](/docs/plugin-authoring-standards).

The single-facet shape (one `definePleachPlugin` call covering all
hooks) is still available via `--template flat`:

```bash
npx pleach init --template flat
```

See [plugin bundles](/docs/plugin-bundles) for the rationale behind
the multi-facet pattern and the planned `composePlugin` upgrade path.

## `pleach init plugin` — monorepo subcommand [#pleach-init-plugin--monorepo-subcommand]

For an existing project that needs to add a plugin without re-running
framework detection:

```bash
npx pleach init plugin --name corpus-safety --facets safety,prompts
```

Scaffolds `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 [#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 [#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.

```bash
npx pleach schema [--target <dir>] [--dry-run|--apply] [--timestamped] [--help]
```

### Flags [#flags-2]

| 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 [#what-it-does]

1. 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.
2. 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.
3. Copies the files into your target directory (creating it if
   needed).

### What it doesn't do [#what-it-doesnt-do]

* Connect to your database.
* Run the migrations.
* Overwrite existing files. If a target file is already there,
  `schema --apply` refuses 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 [#exit-codes-1]

| 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 [#applying-the-migrations-by-hand]

After `pleach schema --apply`:

```bash
for f in supabase/migrations/*pleach*.sql; do
  psql "$DATABASE_URL" -f "$f"
done
```

If you're using the Supabase CLI:

```bash
pleach schema --apply --timestamped
supabase db push
```

The 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.

## Local development without a database [#local-development-without-a-database]

If you don't want to run any migrations during development, skip
the CLI entirely and set:

```bash
HARNESS_MOCK_MODE=true
```

The runtime wires `MemoryAdapter` + `MemorySaver` automatically.
See [environment variables](/docs/env-vars) for the full list of
runtime-read env vars.

## Project layout — after the wizard [#project-layout--after-the-wizard]

The [baseline](/docs/project-layout#a-layout-that-works) 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.json
```

What 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 own [`HarnessPlugin`](/docs/plugin-contract)
  set into the `plugins:` array as you grow.
* **`pleach.plugin.ts` is the starting point**, not the final
  shape. The stub shows three hooks — `prompts`, `runtimePrompts`,
  and the plugin-construction call. The
  [plugin contract](/docs/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 through `supabase db push`.

## Sibling-SKU binaries [#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. 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 [#where-to-go-next]

<Cards>
  <Card title="Getting started" href="/docs/getting-started" description="Install, wire a SessionRuntime, stream your first message." />

  <Card title="Project layout" href="/docs/project-layout" description="The orienting layout the wizard produces — described, not prescribed." />

  <Card title="Plugin contract" href="/docs/plugin-contract" description="The full HarnessPlugin surface the wizard's plugin stub demonstrates three hooks of." />

  <Card title="Environment variables" href="/docs/env-vars" description="Every env var the runtime reads, with defaults." />

  <Card title="Subpath exports" href="/docs/subpath-exports" description="The schema bundle and every other named subpath you can import." />
</Cards>
