diff --git a/content/docs/reference/cli/auth.mdx b/content/docs/reference/cli/auth.mdx new file mode 100644 index 0000000..2ba2a00 --- /dev/null +++ b/content/docs/reference/cli/auth.mdx @@ -0,0 +1,88 @@ +--- +title: stash auth +description: "Reference for the `stash auth` commands." +type: reference +components: [cli] +verifiedAgainst: + cli: "0.17.0" +--- + +{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash manifest --json` (v0.17.0). Re-run `bun run generate-docs:cli` to refresh from the latest published CLI. */} + + +Generated from **`stash` v0.17.0** via `npx stash@0.17.0 manifest --json`. Run `npx stash@0.17.0 --help` to see the live command surface. + + +The `stash auth` command group. + +### `auth login` + +Authenticate with CipherStash + +```bash +npx stash auth login [flags] +``` + +#### Flags + +| Flag | Description | +| --- | --- | +| `--region ` | Region to authenticate against (e.g. us-east-1). Skips the interactive region picker. (env: `STASH_REGION`) | +| `--json` | Emit newline-delimited JSON events instead of prose. The first event (authorization_required) carries the device verification URL for a human to open. Implies no prompt and no browser auto-open. | +| `--no-open` | Don't auto-open the verification URL in a browser (already implied by --json). | +| `--supabase` | Track Supabase as the referrer. | +| `--drizzle` | Track Drizzle as the referrer. | + + +#### Examples + +```bash +npx stash auth login +npx stash auth login --region us-east-1 +npx stash auth login --supabase +npx stash auth login --region us-east-1 --json +``` + +### `auth regions` + +List the regions you can authenticate against + +```bash +npx stash auth regions [flags] +``` + +#### Flags + +| Flag | Description | +| --- | --- | +| `--json` | Emit machine-readable [\{ slug, label \}] instead of a text list. | + + +#### Examples + +```bash +npx stash auth regions +npx stash auth regions --json +``` + +## How authentication works + +`stash auth login` runs the OAuth 2.0 **device authorization flow**: + +1. You pick a **region** for your CipherStash workspace. +2. The CLI opens your browser to a verification URL (and prints it, so it also + works over SSH or in a headless/agent environment) where you approve the + request. +3. Meanwhile the CLI polls CipherStash until you approve, then receives a + short-lived access token (it reports the token's expiry). +4. Your device is **bound to the workspace's default keyset**, so later commands + (`stash eql install`, `stash db push`, …) authenticate without a fresh login. + + +Login is device- and workspace-scoped. Authenticating from a new machine, or for a different workspace, re-runs the device flow. + + +{/* TODO(verify with product): profiles, multiple workspaces, and switching +between them (where they're stored and how they're selected) belong here, or +in a linked CLI concept page. The CLI currently exposes only `auth login`; +confirm the profile / workspace-switching surface before documenting it. */} diff --git a/content/docs/reference/cli/db.mdx b/content/docs/reference/cli/db.mdx new file mode 100644 index 0000000..e0c26b2 --- /dev/null +++ b/content/docs/reference/cli/db.mdx @@ -0,0 +1,86 @@ +--- +title: stash db +description: "Reference for the `stash db` commands." +type: reference +components: [cli, eql] +verifiedAgainst: + cli: "0.17.0" +--- + +{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash manifest --json` (v0.17.0). Re-run `bun run generate-docs:cli` to refresh from the latest published CLI. */} + + +Generated from **`stash` v0.17.0** via `npx stash@0.17.0 manifest --json`. Run `npx stash@0.17.0 --help` to see the live command surface. + + +The `stash db` command group. + +### `db push` + +Push encryption schema (writes pending if active config already exists) + +```bash +npx stash db push [flags] +``` + +#### Flags + +| Flag | Description | +| --- | --- | +| `--dry-run` | Show what would happen without making changes. | +| `--database-url ` | Override DATABASE_URL for this run only — never written to disk. (env: `DATABASE_URL`) | + + +### `db activate` + +Promote pending → active without renames (use after additive db push) + +```bash +npx stash db activate [flags] +``` + +#### Flags + +| Flag | Description | +| --- | --- | +| `--database-url ` | Override DATABASE_URL for this run only — never written to disk. (env: `DATABASE_URL`) | + + +### `db validate` + +Validate encryption schema + +```bash +npx stash db validate [flags] +``` + +#### Flags + +| Flag | Description | +| --- | --- | +| `--supabase` | Use Supabase-compatible mode. | +| `--exclude-operator-family` | Skip operator family creation. | +| `--database-url ` | Override DATABASE_URL for this run only — never written to disk. (env: `DATABASE_URL`) | + + +### `db migrate` + +Run pending encrypt config migrations (not yet implemented) + +```bash +npx stash db migrate +``` + +### `db test-connection` + +Test database connectivity + +```bash +npx stash db test-connection [flags] +``` + +#### Flags + +| Flag | Description | +| --- | --- | +| `--database-url ` | Override DATABASE_URL for this run only — never written to disk. (env: `DATABASE_URL`) | diff --git a/content/docs/reference/cli/doctor.mdx b/content/docs/reference/cli/doctor.mdx new file mode 100644 index 0000000..1c6efb8 --- /dev/null +++ b/content/docs/reference/cli/doctor.mdx @@ -0,0 +1,20 @@ +--- +title: stash doctor +description: "Diagnose install problems (native binaries, runtime)" +type: reference +components: [cli] +verifiedAgainst: + cli: "0.17.0" +--- + +{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash manifest --json` (v0.17.0). Re-run `bun run generate-docs:cli` to refresh from the latest published CLI. */} + + +Generated from **`stash` v0.17.0** via `npx stash@0.17.0 manifest --json`. Run `npx stash@0.17.0 --help` to see the live command surface. + + +Diagnose install problems (native binaries, runtime) + +```bash +npx stash doctor +``` diff --git a/content/docs/reference/cli/encrypt.mdx b/content/docs/reference/cli/encrypt.mdx new file mode 100644 index 0000000..9c03ae8 --- /dev/null +++ b/content/docs/reference/cli/encrypt.mdx @@ -0,0 +1,88 @@ +--- +title: stash encrypt +description: "Reference for the `stash encrypt` commands." +type: reference +components: [cli, eql] +verifiedAgainst: + cli: "0.17.0" +--- + +{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash manifest --json` (v0.17.0). Re-run `bun run generate-docs:cli` to refresh from the latest published CLI. */} + + +Generated from **`stash` v0.17.0** via `npx stash@0.17.0 manifest --json`. Run `npx stash@0.17.0 --help` to see the live command surface. + + +The `stash encrypt` command group. + +### `encrypt status` + +Show per-column migration status (phase, progress, drift) + +```bash +npx stash encrypt status +``` + +### `encrypt plan` + +Diff intent (.cipherstash/migrations.json) vs observed state + +```bash +npx stash encrypt plan +``` + +### `encrypt backfill` + +Resumably encrypt plaintext into the encrypted column + +```bash +npx stash encrypt backfill [flags] +``` + +#### Flags + +| Flag | Description | +| --- | --- | +| `--table ` | Target table. | +| `--column ` | Target column. | +| `--pk-column ` | Primary-key column used to page through rows. | +| `--chunk-size ` | Rows encrypted per batch. | +| `--encrypted-column ` | Destination encrypted column. | +| `--schema-column-key ` | Schema key identifying the column config. | +| `--confirm-dual-writes-deployed` | Assert the app is dual-writing before backfilling (safety gate). | +| `--force` | Proceed past non-fatal safety checks. | + + +### `encrypt cutover` + +Rename swap encrypted → primary column + +```bash +npx stash encrypt cutover [flags] +``` + +#### Flags + +| Flag | Description | +| --- | --- | +| `--table ` | Target table. | +| `--column ` | Target column. | +| `--proxy-url ` | Proxy URL to verify against. | +| `--migrations-dir ` | Directory to write the rename migration into. | + + +### `encrypt drop` + +Generate a migration to drop the plaintext column + +```bash +npx stash encrypt drop [flags] +``` + +#### Flags + +| Flag | Description | +| --- | --- | +| `--table ` | Target table. | +| `--column ` | Target column. | +| `--migrations-dir ` | Directory to write the drop migration into. | diff --git a/content/docs/reference/cli/env.mdx b/content/docs/reference/cli/env.mdx new file mode 100644 index 0000000..fe277a9 --- /dev/null +++ b/content/docs/reference/cli/env.mdx @@ -0,0 +1,26 @@ +--- +title: stash env +description: "(experimental) Print production env vars for deployment" +type: reference +components: [cli] +verifiedAgainst: + cli: "0.17.0" +--- + +{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash manifest --json` (v0.17.0). Re-run `bun run generate-docs:cli` to refresh from the latest published CLI. */} + + +Generated from **`stash` v0.17.0** via `npx stash@0.17.0 manifest --json`. Run `npx stash@0.17.0 --help` to see the live command surface. + + +(experimental) Print production env vars for deployment + +```bash +npx stash env [flags] +``` + +### Flags + +| Flag | Description | +| --- | --- | +| `--write` | Write the vars to a file instead of printing them. | diff --git a/content/docs/reference/cli/eql.mdx b/content/docs/reference/cli/eql.mdx new file mode 100644 index 0000000..63bf8be --- /dev/null +++ b/content/docs/reference/cli/eql.mdx @@ -0,0 +1,77 @@ +--- +title: stash eql +description: "Reference for the `stash eql` commands." +type: reference +components: [cli, eql] +verifiedAgainst: + cli: "0.17.0" +--- + +{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash manifest --json` (v0.17.0). Re-run `bun run generate-docs:cli` to refresh from the latest published CLI. */} + + +Generated from **`stash` v0.17.0** via `npx stash@0.17.0 manifest --json`. Run `npx stash@0.17.0 --help` to see the live command surface. + + +The `stash eql` command group. + +### `eql install` + +Scaffold stash.config.ts (if missing) and install EQL extensions + +```bash +npx stash eql install [flags] +``` + +#### Flags + +| Flag | Description | +| --- | --- | +| `--force` | Reinstall / overwrite even if already installed. | +| `--dry-run` | Show what would happen without making changes. | +| `--supabase` | Use Supabase-compatible mode (auto-detected from DATABASE_URL). | +| `--drizzle` | Generate a Drizzle migration instead of direct install (auto-detected from project). | +| `--migration` | Write a Supabase migration file instead of running SQL directly (requires --supabase). | +| `--direct` | Run the SQL directly against the database (requires --supabase; mutually exclusive with --migration). | +| `--migrations-dir ` | Override the Supabase migrations directory (requires --supabase). (default: `supabase/migrations`) | +| `--exclude-operator-family` | Skip operator family creation. | +| `--eql-version <2\|3>` | EQL generation to target. v3 is the native eql_v3.* domain schema (direct install only for now). (default: `2`) | +| `--latest` | Fetch the latest EQL from GitHub (v2 only). | +| `--name ` | With --drizzle: name for the generated migration (defaults to a scaffold name). | +| `--out ` | With --drizzle: directory to write the generated migration into. | +| `--database-url ` | Override DATABASE_URL for this run only — never written to disk. (env: `DATABASE_URL`) | + + +### `eql upgrade` + +Upgrade EQL extensions to the latest version + +```bash +npx stash eql upgrade [flags] +``` + +#### Flags + +| Flag | Description | +| --- | --- | +| `--dry-run` | Show what would happen without making changes. | +| `--supabase` | Use Supabase-compatible mode. | +| `--exclude-operator-family` | Skip operator family creation. | +| `--eql-version <2\|3>` | EQL generation to target. (default: `2`) | +| `--latest` | Fetch the latest EQL from GitHub (v2 only). | +| `--database-url ` | Override DATABASE_URL for this run only — never written to disk. (env: `DATABASE_URL`) | + + +### `eql status` + +Show EQL installation status + +```bash +npx stash eql status [flags] +``` + +#### Flags + +| Flag | Description | +| --- | --- | +| `--database-url ` | Override DATABASE_URL for this run only — never written to disk. (env: `DATABASE_URL`) | diff --git a/content/docs/reference/cli/impl.mdx b/content/docs/reference/cli/impl.mdx new file mode 100644 index 0000000..b3ce114 --- /dev/null +++ b/content/docs/reference/cli/impl.mdx @@ -0,0 +1,36 @@ +--- +title: stash impl +description: "Execute the plan with a local agent" +type: reference +components: [cli] +verifiedAgainst: + cli: "0.17.0" +--- + +{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash manifest --json` (v0.17.0). Re-run `bun run generate-docs:cli` to refresh from the latest published CLI. */} + + +Generated from **`stash` v0.17.0** via `npx stash@0.17.0 manifest --json`. Run `npx stash@0.17.0 --help` to see the live command surface. + + +Execute the plan with a local agent + +```bash +npx stash impl [flags] +``` + +### Flags + +| Flag | Description | +| --- | --- | +| `--continue-without-plan` | Skip planning and go straight to implementation (interactively confirms before proceeding). | +| `--target ` | Skip the agent-target picker and hand off directly to one of claude-code \| codex \| agents-md \| wizard. Safe in non-TTY contexts. | + + +## Examples + +```bash +npx stash impl +npx stash impl --continue-without-plan +npx stash impl --target claude-code +``` diff --git a/content/docs/reference/cli/index.mdx b/content/docs/reference/cli/index.mdx index 8eeffd3..88764db 100644 --- a/content/docs/reference/cli/index.mdx +++ b/content/docs/reference/cli/index.mdx @@ -1,8 +1,75 @@ --- title: CLI -description: "CLI documentation — being built as part of the docs V2 overhaul." +description: "Command reference for the stash CLI, generated from v0.17.0." +type: reference +components: [cli] +verifiedAgainst: + cli: "0.17.0" --- -This section is being built as part of the docs V2 overhaul ([CIP-3307](https://linear.app/cipherstash/issue/CIP-3307)). Track progress in [IA.md](https://github.com/cipherstash/docs/blob/v2/IA.md). +{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash manifest --json` (v0.17.0). Re-run `bun run generate-docs:cli` to refresh from the latest published CLI. */} -Until it lands, current documentation lives in the [existing docs](/stack). + +Generated from **`stash` v0.17.0** via `npx stash@0.17.0 manifest --json`. Run `npx stash@0.17.0 --help` to see the live command surface. + + +The `stash` CLI. Install with `npx stash@0.17.0`. Every command accepts `--help` and `--version`. + +### Setup & workflow + +| Command | Description | +| --- | --- | +| [`init`](/reference/cli/init) | Initialize CipherStash for your project | +| [`plan`](/reference/cli/plan) | Draft a reviewable encryption plan at .cipherstash/plan.md | +| [`impl`](/reference/cli/impl) | Execute the plan with a local agent | +| [`status`](/reference/cli/status) | Displays implementation status | +| [`wizard`](/reference/cli/wizard) | AI-guided encryption setup (reads your codebase) | +| [`doctor`](/reference/cli/doctor) | Diagnose install problems (native binaries, runtime) | +| [`manifest`](/reference/cli/manifest) | Print the structured, versioned command surface | + +### Auth + +| Command | Description | +| --- | --- | +| [`auth login`](/reference/cli/auth#auth-login) | Authenticate with CipherStash | +| [`auth regions`](/reference/cli/auth#auth-regions) | List the regions you can authenticate against | + +### EQL + +| Command | Description | +| --- | --- | +| [`eql install`](/reference/cli/eql#eql-install) | Scaffold stash.config.ts (if missing) and install EQL extensions | +| [`eql upgrade`](/reference/cli/eql#eql-upgrade) | Upgrade EQL extensions to the latest version | +| [`eql status`](/reference/cli/eql#eql-status) | Show EQL installation status | + +### Database + +| Command | Description | +| --- | --- | +| [`db push`](/reference/cli/db#db-push) | Push encryption schema (writes pending if active config already exists) | +| [`db activate`](/reference/cli/db#db-activate) | Promote pending → active without renames (use after additive db push) | +| [`db validate`](/reference/cli/db#db-validate) | Validate encryption schema | +| [`db migrate`](/reference/cli/db#db-migrate) | Run pending encrypt config migrations (not yet implemented) | +| [`db test-connection`](/reference/cli/db#db-test-connection) | Test database connectivity | + +### Schema + +| Command | Description | +| --- | --- | +| [`schema build`](/reference/cli/schema#schema-build) | Build an encryption schema from your database | + +### Encrypt + +| Command | Description | +| --- | --- | +| [`encrypt status`](/reference/cli/encrypt#encrypt-status) | Show per-column migration status (phase, progress, drift) | +| [`encrypt plan`](/reference/cli/encrypt#encrypt-plan) | Diff intent (.cipherstash/migrations.json) vs observed state | +| [`encrypt backfill`](/reference/cli/encrypt#encrypt-backfill) | Resumably encrypt plaintext into the encrypted column | +| [`encrypt cutover`](/reference/cli/encrypt#encrypt-cutover) | Rename swap encrypted → primary column | +| [`encrypt drop`](/reference/cli/encrypt#encrypt-drop) | Generate a migration to drop the plaintext column | + +### Experimental + +| Command | Description | +| --- | --- | +| [`env`](/reference/cli/env) | (experimental) Print production env vars for deployment | diff --git a/content/docs/reference/cli/init.mdx b/content/docs/reference/cli/init.mdx new file mode 100644 index 0000000..518544b --- /dev/null +++ b/content/docs/reference/cli/init.mdx @@ -0,0 +1,41 @@ +--- +title: stash init +description: "Initialize CipherStash for your project" +type: reference +components: [cli] +verifiedAgainst: + cli: "0.17.0" +--- + +{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash manifest --json` (v0.17.0). Re-run `bun run generate-docs:cli` to refresh from the latest published CLI. */} + + +Generated from **`stash` v0.17.0** via `npx stash@0.17.0 manifest --json`. Run `npx stash@0.17.0 --help` to see the live command surface. + + +Initialize CipherStash for your project + +```bash +npx stash init [flags] +``` + +### Flags + +| Flag | Description | +| --- | --- | +| `--supabase` | Use Supabase-specific setup flow. | +| `--drizzle` | Use Drizzle-specific setup flow. | +| `--prisma-next` | Use Prisma Next-specific setup flow (EQL bundle installed via prisma-next migration apply). | +| `--proxy` | Query encrypted data via CipherStash Proxy. | +| `--no-proxy` | Query encrypted data directly via the SDK. (default: `true`) | +| `--region ` | Region to authenticate against (e.g. us-east-1). Skips the interactive region picker. Required for non-interactive init when not already logged in. (env: `STASH_REGION`) | + + +## Examples + +```bash +npx stash init +npx stash init --supabase +npx stash init --prisma-next +npx stash init --region us-east-1 +``` diff --git a/content/docs/reference/cli/manifest.mdx b/content/docs/reference/cli/manifest.mdx new file mode 100644 index 0000000..4c7578d --- /dev/null +++ b/content/docs/reference/cli/manifest.mdx @@ -0,0 +1,34 @@ +--- +title: stash manifest +description: "Print the structured, versioned command surface" +type: reference +components: [cli] +verifiedAgainst: + cli: "0.17.0" +--- + +{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash manifest --json` (v0.17.0). Re-run `bun run generate-docs:cli` to refresh from the latest published CLI. */} + + +Generated from **`stash` v0.17.0** via `npx stash@0.17.0 manifest --json`. Run `npx stash@0.17.0 --help` to see the live command surface. + + +Print the structured, versioned command surface + +```bash +npx stash manifest [flags] +``` + +### Flags + +| Flag | Description | +| --- | --- | +| `--json` | Emit the structured JSON manifest instead of a text list. | + + +## Examples + +```bash +npx stash manifest --json +npx stash manifest +``` diff --git a/content/docs/reference/cli/meta.json b/content/docs/reference/cli/meta.json index 0a67892..6b90890 100644 --- a/content/docs/reference/cli/meta.json +++ b/content/docs/reference/cli/meta.json @@ -1,4 +1,25 @@ { "title": "CLI", - "pages": ["..."] + "pages": [ + "---Setup & workflow---", + "init", + "plan", + "impl", + "status", + "wizard", + "doctor", + "manifest", + "---Auth---", + "auth", + "---EQL---", + "eql", + "---Database---", + "db", + "---Schema---", + "schema", + "---Encrypt---", + "encrypt", + "---Experimental---", + "env" + ] } diff --git a/content/docs/reference/cli/plan.mdx b/content/docs/reference/cli/plan.mdx new file mode 100644 index 0000000..4904f7a --- /dev/null +++ b/content/docs/reference/cli/plan.mdx @@ -0,0 +1,35 @@ +--- +title: stash plan +description: "Draft a reviewable encryption plan at .cipherstash/plan.md" +type: reference +components: [cli] +verifiedAgainst: + cli: "0.17.0" +--- + +{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash manifest --json` (v0.17.0). Re-run `bun run generate-docs:cli` to refresh from the latest published CLI. */} + + +Generated from **`stash` v0.17.0** via `npx stash@0.17.0 manifest --json`. Run `npx stash@0.17.0 --help` to see the live command surface. + + +Draft a reviewable encryption plan at .cipherstash/plan.md + +```bash +npx stash plan [flags] +``` + +### Flags + +| Flag | Description | +| --- | --- | +| `--complete-rollout` | Plan the entire encryption lifecycle (schema-add through drop) in one document. Skips the production-deploy gate; only safe when this database is not backing a deployed application. | +| `--target ` | Skip the agent-target picker and hand off directly to one of claude-code \| codex \| agents-md \| wizard. Safe in non-TTY contexts. | + + +## Examples + +```bash +npx stash plan +npx stash plan --target claude-code +``` diff --git a/content/docs/reference/cli/schema.mdx b/content/docs/reference/cli/schema.mdx new file mode 100644 index 0000000..9b5e360 --- /dev/null +++ b/content/docs/reference/cli/schema.mdx @@ -0,0 +1,31 @@ +--- +title: stash schema +description: "Reference for the `stash schema` commands." +type: reference +components: [cli, eql] +verifiedAgainst: + cli: "0.17.0" +--- + +{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash manifest --json` (v0.17.0). Re-run `bun run generate-docs:cli` to refresh from the latest published CLI. */} + + +Generated from **`stash` v0.17.0** via `npx stash@0.17.0 manifest --json`. Run `npx stash@0.17.0 --help` to see the live command surface. + + +The `stash schema` command group. + +### `schema build` + +Build an encryption schema from your database + +```bash +npx stash schema build [flags] +``` + +#### Flags + +| Flag | Description | +| --- | --- | +| `--supabase` | Use Supabase-compatible mode. | +| `--database-url ` | Override DATABASE_URL for this run only — never written to disk. (env: `DATABASE_URL`) | diff --git a/content/docs/reference/cli/status.mdx b/content/docs/reference/cli/status.mdx new file mode 100644 index 0000000..ba033d7 --- /dev/null +++ b/content/docs/reference/cli/status.mdx @@ -0,0 +1,28 @@ +--- +title: stash status +description: "Displays implementation status" +type: reference +components: [cli] +verifiedAgainst: + cli: "0.17.0" +--- + +{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash manifest --json` (v0.17.0). Re-run `bun run generate-docs:cli` to refresh from the latest published CLI. */} + + +Generated from **`stash` v0.17.0** via `npx stash@0.17.0 manifest --json`. Run `npx stash@0.17.0 --help` to see the live command surface. + + +Displays implementation status + +```bash +npx stash status [flags] +``` + +### Flags + +| Flag | Description | +| --- | --- | +| `--quest` | Force the quest-log output (emoji + progress bars) even in non-TTY contexts. | +| `--plain` | Force the plain-text output even in TTY contexts. | +| `--json` | Emit a structured JSON document instead. | diff --git a/content/docs/reference/cli/wizard.mdx b/content/docs/reference/cli/wizard.mdx new file mode 100644 index 0000000..05678ed --- /dev/null +++ b/content/docs/reference/cli/wizard.mdx @@ -0,0 +1,20 @@ +--- +title: stash wizard +description: "AI-guided encryption setup (reads your codebase)" +type: reference +components: [cli] +verifiedAgainst: + cli: "0.17.0" +--- + +{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash manifest --json` (v0.17.0). Re-run `bun run generate-docs:cli` to refresh from the latest published CLI. */} + + +Generated from **`stash` v0.17.0** via `npx stash@0.17.0 manifest --json`. Run `npx stash@0.17.0 --help` to see the live command surface. + + +AI-guided encryption setup (reads your codebase) + +```bash +npx stash wizard +``` diff --git a/package.json b/package.json index 3b4681a..d3d8965 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "0.0.0", "private": true, "scripts": { - "prebuild": "bun run generate-docs && bun run generate-docs:eql && bun run validate-links && bun run validate-redirects", + "prebuild": "bun run generate-docs && bun run generate-docs:eql && bun run generate-docs:cli && bun run validate-links && bun run validate-redirects", "build": "next build", "dev": "next dev -p 3001", "start": "next start", @@ -13,6 +13,7 @@ "format": "biome format --write", "generate-docs": "tsx scripts/generate-docs.ts", "generate-docs:eql": "tsx scripts/generate-eql-docs.ts", + "generate-docs:cli": "tsx scripts/generate-cli-docs.ts", "validate-links": "tsx scripts/validate-links.ts", "validate-redirects": "tsx scripts/validate-v2-redirects.ts" }, diff --git a/scripts/cli-supplements/auth.md b/scripts/cli-supplements/auth.md new file mode 100644 index 0000000..b17fc57 --- /dev/null +++ b/scripts/cli-supplements/auth.md @@ -0,0 +1,21 @@ +## How authentication works + +`stash auth login` runs the OAuth 2.0 **device authorization flow**: + +1. You pick a **region** for your CipherStash workspace. +2. The CLI opens your browser to a verification URL (and prints it, so it also + works over SSH or in a headless/agent environment) where you approve the + request. +3. Meanwhile the CLI polls CipherStash until you approve, then receives a + short-lived access token (it reports the token's expiry). +4. Your device is **bound to the workspace's default keyset**, so later commands + (`stash eql install`, `stash db push`, …) authenticate without a fresh login. + + +Login is device- and workspace-scoped. Authenticating from a new machine, or for a different workspace, re-runs the device flow. + + +{/* TODO(verify with product): profiles, multiple workspaces, and switching +between them (where they're stored and how they're selected) belong here, or +in a linked CLI concept page. The CLI currently exposes only `auth login`; +confirm the profile / workspace-switching surface before documenting it. */} diff --git a/scripts/fixtures/stash-manifest.json b/scripts/fixtures/stash-manifest.json new file mode 100644 index 0000000..0de5ec4 --- /dev/null +++ b/scripts/fixtures/stash-manifest.json @@ -0,0 +1,511 @@ +{ + "name": "stash", + "version": "0.17.0", + "groups": [ + { + "title": "Setup & workflow", + "commands": [ + { + "name": "init", + "summary": "Initialize CipherStash for your project", + "long": "Set up CipherStash end-to-end: authenticate, introspect your database,\ninstall dependencies, install EQL, and hand off the rest to your local\ncoding agent. Every prompt has a non-interactive escape hatch, so init\nnever blocks waiting on a TTY (CI, agents, pipes).", + "examples": [ + "init", + "init --supabase", + "init --prisma-next", + "init --region us-east-1" + ], + "flags": [ + { + "name": "--supabase", + "description": "Use Supabase-specific setup flow." + }, + { + "name": "--drizzle", + "description": "Use Drizzle-specific setup flow." + }, + { + "name": "--prisma-next", + "description": "Use Prisma Next-specific setup flow (EQL bundle installed via prisma-next migration apply)." + }, + { + "name": "--proxy", + "description": "Query encrypted data via CipherStash Proxy." + }, + { + "name": "--no-proxy", + "description": "Query encrypted data directly via the SDK.", + "default": "true" + }, + { + "name": "--region", + "value": "", + "description": "Region to authenticate against (e.g. us-east-1). Skips the interactive region picker. Required for non-interactive init when not already logged in.", + "env": "STASH_REGION" + } + ] + }, + { + "name": "plan", + "summary": "Draft a reviewable encryption plan at .cipherstash/plan.md", + "examples": [ + "plan", + "plan --target claude-code" + ], + "flags": [ + { + "name": "--complete-rollout", + "description": "Plan the entire encryption lifecycle (schema-add through drop) in one document. Skips the production-deploy gate; only safe when this database is not backing a deployed application." + }, + { + "name": "--target", + "value": "", + "description": "Skip the agent-target picker and hand off directly to one of claude-code | codex | agents-md | wizard. Safe in non-TTY contexts." + } + ] + }, + { + "name": "impl", + "summary": "Execute the plan with a local agent", + "examples": [ + "impl", + "impl --continue-without-plan", + "impl --target claude-code" + ], + "flags": [ + { + "name": "--continue-without-plan", + "description": "Skip planning and go straight to implementation (interactively confirms before proceeding)." + }, + { + "name": "--target", + "value": "", + "description": "Skip the agent-target picker and hand off directly to one of claude-code | codex | agents-md | wizard. Safe in non-TTY contexts." + } + ] + }, + { + "name": "status", + "summary": "Displays implementation status", + "flags": [ + { + "name": "--quest", + "description": "Force the quest-log output (emoji + progress bars) even in non-TTY contexts." + }, + { + "name": "--plain", + "description": "Force the plain-text output even in TTY contexts." + }, + { + "name": "--json", + "description": "Emit a structured JSON document instead." + } + ] + }, + { + "name": "wizard", + "summary": "AI-guided encryption setup (reads your codebase)" + }, + { + "name": "doctor", + "summary": "Diagnose install problems (native binaries, runtime)" + }, + { + "name": "manifest", + "summary": "Print the structured, versioned command surface", + "long": "Emit the CLI command surface as data. `--json` produces the machine-\nreadable manifest the docs generator and agents consume; without it a\ngrouped command list is printed. The manifest is stamped with the CLI\nversion, so a page generated from it always names the version it describes.", + "examples": [ + "manifest --json", + "manifest" + ], + "flags": [ + { + "name": "--json", + "description": "Emit the structured JSON manifest instead of a text list." + } + ] + } + ] + }, + { + "title": "Auth", + "commands": [ + { + "name": "auth login", + "summary": "Authenticate with CipherStash", + "long": "Runs the OAuth 2.0 device authorization flow:\n1. Pick a region for your workspace.\n2. Approve in the browser — the URL is printed, so it works over SSH/headless.\n3. The CLI polls until you approve, then stores a short-lived token.\n4. Your device is bound to the workspace's default keyset, so later\n commands authenticate without a fresh login.", + "examples": [ + "auth login", + "auth login --region us-east-1", + "auth login --supabase", + "auth login --region us-east-1 --json" + ], + "flags": [ + { + "name": "--region", + "value": "", + "description": "Region to authenticate against (e.g. us-east-1). Skips the interactive region picker.", + "env": "STASH_REGION" + }, + { + "name": "--json", + "description": "Emit newline-delimited JSON events instead of prose. The first event (authorization_required) carries the device verification URL for a human to open. Implies no prompt and no browser auto-open." + }, + { + "name": "--no-open", + "description": "Don't auto-open the verification URL in a browser (already implied by --json)." + }, + { + "name": "--supabase", + "description": "Track Supabase as the referrer." + }, + { + "name": "--drizzle", + "description": "Track Drizzle as the referrer." + } + ] + }, + { + "name": "auth regions", + "summary": "List the regions you can authenticate against", + "examples": [ + "auth regions", + "auth regions --json" + ], + "flags": [ + { + "name": "--json", + "description": "Emit machine-readable [{ slug, label }] instead of a text list." + } + ] + } + ] + }, + { + "title": "EQL", + "commands": [ + { + "name": "eql install", + "summary": "Scaffold stash.config.ts (if missing) and install EQL extensions", + "flags": [ + { + "name": "--force", + "description": "Reinstall / overwrite even if already installed." + }, + { + "name": "--dry-run", + "description": "Show what would happen without making changes." + }, + { + "name": "--supabase", + "description": "Use Supabase-compatible mode (auto-detected from DATABASE_URL)." + }, + { + "name": "--drizzle", + "description": "Generate a Drizzle migration instead of direct install (auto-detected from project)." + }, + { + "name": "--migration", + "description": "Write a Supabase migration file instead of running SQL directly (requires --supabase)." + }, + { + "name": "--direct", + "description": "Run the SQL directly against the database (requires --supabase; mutually exclusive with --migration)." + }, + { + "name": "--migrations-dir", + "value": "", + "description": "Override the Supabase migrations directory (requires --supabase).", + "default": "supabase/migrations" + }, + { + "name": "--exclude-operator-family", + "description": "Skip operator family creation." + }, + { + "name": "--eql-version", + "value": "<2|3>", + "description": "EQL generation to target. v3 is the native eql_v3.* domain schema (direct install only for now).", + "default": "2" + }, + { + "name": "--latest", + "description": "Fetch the latest EQL from GitHub (v2 only)." + }, + { + "name": "--name", + "value": "", + "description": "With --drizzle: name for the generated migration (defaults to a scaffold name)." + }, + { + "name": "--out", + "value": "", + "description": "With --drizzle: directory to write the generated migration into." + }, + { + "name": "--database-url", + "value": "", + "description": "Override DATABASE_URL for this run only — never written to disk.", + "env": "DATABASE_URL" + } + ] + }, + { + "name": "eql upgrade", + "summary": "Upgrade EQL extensions to the latest version", + "flags": [ + { + "name": "--dry-run", + "description": "Show what would happen without making changes." + }, + { + "name": "--supabase", + "description": "Use Supabase-compatible mode." + }, + { + "name": "--exclude-operator-family", + "description": "Skip operator family creation." + }, + { + "name": "--eql-version", + "value": "<2|3>", + "description": "EQL generation to target.", + "default": "2" + }, + { + "name": "--latest", + "description": "Fetch the latest EQL from GitHub (v2 only)." + }, + { + "name": "--database-url", + "value": "", + "description": "Override DATABASE_URL for this run only — never written to disk.", + "env": "DATABASE_URL" + } + ] + }, + { + "name": "eql status", + "summary": "Show EQL installation status", + "flags": [ + { + "name": "--database-url", + "value": "", + "description": "Override DATABASE_URL for this run only — never written to disk.", + "env": "DATABASE_URL" + } + ] + } + ] + }, + { + "title": "Database", + "commands": [ + { + "name": "db push", + "summary": "Push encryption schema (writes pending if active config already exists)", + "flags": [ + { + "name": "--dry-run", + "description": "Show what would happen without making changes." + }, + { + "name": "--database-url", + "value": "", + "description": "Override DATABASE_URL for this run only — never written to disk.", + "env": "DATABASE_URL" + } + ] + }, + { + "name": "db activate", + "summary": "Promote pending → active without renames (use after additive db push)", + "flags": [ + { + "name": "--database-url", + "value": "", + "description": "Override DATABASE_URL for this run only — never written to disk.", + "env": "DATABASE_URL" + } + ] + }, + { + "name": "db validate", + "summary": "Validate encryption schema", + "flags": [ + { + "name": "--supabase", + "description": "Use Supabase-compatible mode." + }, + { + "name": "--exclude-operator-family", + "description": "Skip operator family creation." + }, + { + "name": "--database-url", + "value": "", + "description": "Override DATABASE_URL for this run only — never written to disk.", + "env": "DATABASE_URL" + } + ] + }, + { + "name": "db migrate", + "summary": "Run pending encrypt config migrations (not yet implemented)" + }, + { + "name": "db test-connection", + "summary": "Test database connectivity", + "flags": [ + { + "name": "--database-url", + "value": "", + "description": "Override DATABASE_URL for this run only — never written to disk.", + "env": "DATABASE_URL" + } + ] + } + ] + }, + { + "title": "Schema", + "commands": [ + { + "name": "schema build", + "summary": "Build an encryption schema from your database", + "flags": [ + { + "name": "--supabase", + "description": "Use Supabase-compatible mode." + }, + { + "name": "--database-url", + "value": "", + "description": "Override DATABASE_URL for this run only — never written to disk.", + "env": "DATABASE_URL" + } + ] + } + ] + }, + { + "title": "Encrypt", + "commands": [ + { + "name": "encrypt status", + "summary": "Show per-column migration status (phase, progress, drift)" + }, + { + "name": "encrypt plan", + "summary": "Diff intent (.cipherstash/migrations.json) vs observed state" + }, + { + "name": "encrypt backfill", + "summary": "Resumably encrypt plaintext into the encrypted column", + "flags": [ + { + "name": "--table", + "value": "", + "description": "Target table." + }, + { + "name": "--column", + "value": "", + "description": "Target column." + }, + { + "name": "--pk-column", + "value": "", + "description": "Primary-key column used to page through rows." + }, + { + "name": "--chunk-size", + "value": "", + "description": "Rows encrypted per batch." + }, + { + "name": "--encrypted-column", + "value": "", + "description": "Destination encrypted column." + }, + { + "name": "--schema-column-key", + "value": "", + "description": "Schema key identifying the column config." + }, + { + "name": "--confirm-dual-writes-deployed", + "description": "Assert the app is dual-writing before backfilling (safety gate)." + }, + { + "name": "--force", + "description": "Proceed past non-fatal safety checks." + } + ] + }, + { + "name": "encrypt cutover", + "summary": "Rename swap encrypted → primary column", + "flags": [ + { + "name": "--table", + "value": "", + "description": "Target table." + }, + { + "name": "--column", + "value": "", + "description": "Target column." + }, + { + "name": "--proxy-url", + "value": "", + "description": "Proxy URL to verify against." + }, + { + "name": "--migrations-dir", + "value": "", + "description": "Directory to write the rename migration into." + } + ] + }, + { + "name": "encrypt drop", + "summary": "Generate a migration to drop the plaintext column", + "flags": [ + { + "name": "--table", + "value": "", + "description": "Target table." + }, + { + "name": "--column", + "value": "", + "description": "Target column." + }, + { + "name": "--migrations-dir", + "value": "", + "description": "Directory to write the drop migration into." + } + ] + } + ] + }, + { + "title": "Experimental", + "commands": [ + { + "name": "env", + "summary": "(experimental) Print production env vars for deployment", + "flags": [ + { + "name": "--write", + "description": "Write the vars to a file instead of printing them." + } + ] + } + ] + } + ] +} diff --git a/scripts/generate-cli-docs.ts b/scripts/generate-cli-docs.ts new file mode 100644 index 0000000..07effd9 --- /dev/null +++ b/scripts/generate-cli-docs.ts @@ -0,0 +1,415 @@ +#!/usr/bin/env tsx +/** + * CLI reference generator (CIP-33xx). + * + * Generates the `/reference/cli` pages from the `stash` CLI itself, so the + * reference can never drift from the shipped command surface. Every page is + * stamped with the CLI version it was generated from. + * + * ── Data source ─────────────────────────────────────────────────────────── + * We consume `stash manifest --json` (shipped in stash CLI 0.17) — the + * structured, versioned command surface the CLI builds from its own command + * registry. Groups, summaries, per-command flags (with defaults + env vars), + * and curated examples all come straight from the CLI, so the docs are a + * projection of the real command set rather than a scrape of `--help`. + * + * ── Versioning ──────────────────────────────────────────────────────────── + * Always generated from the LATEST published `stash` on npm (resolved via + * `npm view stash version`), so a new release plus a run of this script — it + * runs in `prebuild` — refreshes the docs automatically. Every page carries + * `verifiedAgainst.cli` and a visible banner, so readers and agents always + * know which version the docs describe. Offline, it falls back to the cached + * `scripts/fixtures/stash-manifest.json`. + */ +import { execSync } from "node:child_process"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; + +const CLI_NAME = "stash"; +let CLI_VERSION = ""; // resolved to the latest published npm version at run time +const RUNNER = "npx"; // normalized invocation shown in docs +const FIXTURE = path.join( + process.cwd(), + "scripts/fixtures", + "stash-manifest.json", +); +const OUT_DIR = path.join(process.cwd(), "content/docs/reference/cli"); +// Hand-authored per-command prose merged into the generated page (hybrid model): +// the generated skeleton (synopsis + flags + examples) stays drift-free; a +// supplement adds rich narrative the manifest doesn't carry. Lives outside +// content/ so it's never treated as a page or wiped by the clean step. Where +// the CLI grows per-command long-help, that prose can migrate into the CLI and +// this hook retires. +const SUPPLEMENTS_DIR = path.join(process.cwd(), "scripts/cli-supplements"); + +// ── The `stash manifest --json` contract ──────────────────────────────────── +// Mirrors packages/cli/src/cli/manifest.ts in the stack repo. Command `name` +// is the full path ("eql install"); flags are already resolved per-command. +interface CliFlag { + name: string; // "--supabase" + value?: string; // "" + description: string; + default?: string; // surfaced default, when worth showing + env?: string; // env var that also sets this, e.g. DATABASE_URL +} +interface CliCommand { + name: string; + summary: string; + long?: string; + examples?: string[]; + flags?: CliFlag[]; +} +interface CliGroup { + title: string; + commands: CliCommand[]; +} +interface CliManifest { + name: string; + version: string; + groups: CliGroup[]; +} + +// ── Internal model (what the renderer consumes) ───────────────────────────── +interface Flag { + name: string; + value?: string; + description: string; +} +interface Command { + path: string; // "eql install" + base: string; // "eql" + sub?: string; // "install" + group: string; // nav group title, from the manifest + summary: string; + flags: Flag[]; + examples: string[]; +} +interface Manifest { + name: string; + version: string; + commands: Command[]; + groupOrder: string[]; // nav group order, as the CLI declares it +} + +// EQL/Postgres command groups get the `eql` component facet too (content-model +// rule: tag `eql` for queryable-in-Postgres ciphertext). +const componentsFor = (base: string): string[] => + ["eql", "db", "schema", "encrypt"].includes(base) ? ["cli", "eql"] : ["cli"]; + +// ── Source ────────────────────────────────────────────────────────────────── +// Resolve the latest published version so the docs track releases automatically. +function latestVersion(): string { + try { + return execSync(`npm view ${CLI_NAME} version`, { + encoding: "utf8", + }).trim(); + } catch { + const cached = fs.existsSync(FIXTURE) + ? (JSON.parse(fs.readFileSync(FIXTURE, "utf8")) as CliManifest).version + : undefined; + if (cached) { + console.warn(`⚠ npm unreachable; using cached stash v${cached}.`); + return cached; + } + throw new Error( + "Cannot resolve latest stash version (offline, no fixture).", + ); + } +} + +// Run the resolved CLI and read its `manifest --json`, caching to a fixture for +// offline builds. dotenvx (the CLI's launcher) may print tips before the JSON, +// so slice from the first `{` to the last `}` defensively. +function loadRawManifest(version: string): CliManifest { + try { + const out = execSync(`npx --yes ${CLI_NAME}@${version} manifest --json`, { + encoding: "utf8", + cwd: os.tmpdir(), + stdio: ["ignore", "pipe", "ignore"], + }); + const start = out.indexOf("{"); + const end = out.lastIndexOf("}"); + if (start === -1 || end < start) { + throw new Error( + `\`${CLI_NAME}@${version} manifest --json\` did not emit a JSON object (got: ${out.trim().slice(0, 120)}…)`, + ); + } + const manifest = JSON.parse(out.slice(start, end + 1)) as CliManifest; + fs.mkdirSync(path.dirname(FIXTURE), { recursive: true }); + fs.writeFileSync(FIXTURE, `${JSON.stringify(manifest, null, 2)}\n`); + return manifest; + } catch { + if (fs.existsSync(FIXTURE)) { + console.warn(`⚠ Could not run stash@${version}; using cached fixture.`); + return JSON.parse(fs.readFileSync(FIXTURE, "utf8")) as CliManifest; + } + throw new Error( + `Could not run stash@${version} manifest --json and no cached fixture exists.`, + ); + } +} + +// Fold the manifest's richer flag metadata (default + env) into the description +// column so the page format (Flag | Description) stays a single table. +function mapFlag(f: CliFlag): Flag { + const notes: string[] = []; + if (f.default !== undefined) notes.push(`default: \`${f.default}\``); + if (f.env) notes.push(`env: \`${f.env}\``); + const description = notes.length + ? `${f.description} (${notes.join("; ")})` + : f.description; + return { name: f.name, value: f.value, description }; +} + +// Project the CLI manifest onto the internal model the renderer consumes. +function toManifest(m: CliManifest): Manifest { + const commands: Command[] = []; + const groupOrder: string[] = []; + for (const group of m.groups) { + if (!group.commands.length) continue; + if (!groupOrder.includes(group.title)) groupOrder.push(group.title); + for (const c of group.commands) { + const [base, ...rest] = c.name.split(/\s+/); + commands.push({ + path: c.name, + base, + sub: rest.length ? rest.join(" ") : undefined, + group: group.title, + summary: c.summary, + flags: (c.flags ?? []).map(mapFlag), + examples: (c.examples ?? []).map((e) => `${RUNNER} ${CLI_NAME} ${e}`), + }); + } + } + return { name: m.name, version: m.version, commands, groupOrder }; +} + +// ── Render ─────────────────────────────────────────────────────────────────── +const generatedMarker = (): string => + `{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from \`${CLI_NAME} manifest --json\` (v${CLI_VERSION}). Re-run \`bun run generate-docs:cli\` to refresh from the latest published CLI. */}`; + +function banner(): string { + return ` +Generated from **\`${CLI_NAME}\` v${CLI_VERSION}** via \`${RUNNER} ${CLI_NAME}@${CLI_VERSION} manifest --json\`. Run \`${RUNNER} ${CLI_NAME}@${CLI_VERSION} --help\` to see the live command surface. +`; +} + +// Escape characters MDX parses as JSX inside prose: `{`/`}` (expression braces — +// e.g. the `auth regions` flag description "[{ slug, label }]" would otherwise +// evaluate `slug` and crash the prerender) and stray `<` (tags). Flag names and +// values render inside code spans, which are literal, so this only applies to +// manifest-derived prose (descriptions, summaries). +const escapeMdxText = (s: string): string => s.replace(/([{}<])/g, "\\$1"); + +function flagsTable(flags: Flag[]): string { + if (!flags.length) return ""; + const rows = flags + .map((f) => { + // Escape pipes (e.g. the `<2|3>` in `--eql-version`) so they don't read + // as table-column separators, even inside the code span. + const opt = `\`${f.name}${f.value ? ` ${f.value}` : ""}\``.replace( + /\|/g, + "\\|", + ); + const description = escapeMdxText(f.description).replace(/\|/g, "\\|"); + return `| ${opt} | ${description} |`; + }) + .join("\n"); + return `\n### Flags\n\n| Flag | Description |\n| --- | --- |\n${rows}\n`; +} + +function commandSection(cmd: Command, level: "##" | "###"): string { + const synopsis = `${RUNNER} ${CLI_NAME} ${cmd.path}${cmd.flags.length ? " [flags]" : ""}`; + const parts = [ + `${level} \`${cmd.path}\``, + "", + escapeMdxText(cmd.summary), + "", + "```bash", + synopsis, + "```", + ]; + if (cmd.flags.length) + parts.push(flagsTable(cmd.flags).replace("### Flags", `${level}# Flags`)); + if (cmd.examples.length) { + parts.push( + `\n${level}# Examples\n`, + "```bash", + cmd.examples.join("\n"), + "```", + ); + } + return parts.join("\n"); +} + +function renderPage( + base: string, + cmds: Command[], +): { slug: string; body: string } { + const isGroup = cmds.some((c) => c.sub) || cmds.length > 1; + const title = base; + const components = componentsFor(base); + const description = isGroup + ? `Reference for the \`${CLI_NAME} ${base}\` commands.` + : cmds[0].summary; + + const frontmatter = [ + "---", + `title: ${CLI_NAME} ${title}`, + `description: ${JSON.stringify(description)}`, + "type: reference", + `components: [${components.join(", ")}]`, + "verifiedAgainst:", + ` cli: "${CLI_VERSION}"`, + "---", + ].join("\n"); + + const parts = [frontmatter, "", generatedMarker(), "", banner(), ""]; + + if (isGroup) { + parts.push( + `The \`${CLI_NAME} ${base}\` command group.`, + "", + cmds.map((c) => commandSection(c, "###")).join("\n\n"), + ); + } else { + const c = cmds[0]; + parts.push( + escapeMdxText(c.summary), + "", + "```bash", + `${RUNNER} ${CLI_NAME} ${c.path}${c.flags.length ? " [flags]" : ""}`, + "```", + ); + if (c.flags.length) parts.push(flagsTable(c.flags)); + if (c.examples.length) + parts.push("\n## Examples\n", "```bash", c.examples.join("\n"), "```"); + } + + const supplement = readSupplement(base); + const body = `${parts.join("\n").trimEnd()}${supplement ? `\n\n${supplement}` : ""}\n`; + return { slug: base, body }; +} + +// Optional hand-authored prose for a command, merged after its generated +// reference. Returns "" when no supplement exists. +function readSupplement(slug: string): string { + const file = path.join(SUPPLEMENTS_DIR, `${slug}.md`); + return fs.existsSync(file) ? fs.readFileSync(file, "utf8").trim() : ""; +} + +function renderIndex( + manifest: Manifest, + groups: Map, +): string { + const frontmatter = [ + "---", + "title: CLI", + `description: "Command reference for the ${CLI_NAME} CLI, generated from v${CLI_VERSION}."`, + "type: reference", + "components: [cli]", + "verifiedAgainst:", + ` cli: "${CLI_VERSION}"`, + "---", + ].join("\n"); + + const sections = manifest.groupOrder + .filter((g) => groups.has(g)) + .map((g) => { + const rows = groups + .get(g)! + .flatMap((base) => + manifest.commands + .filter((c) => c.base === base) + .map((c) => { + const anchor = c.sub ? `#${c.path.replace(/\s+/g, "-")}` : ""; + const summary = escapeMdxText(c.summary).replace(/\|/g, "\\|"); + return `| [\`${c.path}\`](/reference/cli/${base}${anchor}) | ${summary} |`; + }), + ) + .join("\n"); + return `### ${g}\n\n| Command | Description |\n| --- | --- |\n${rows}`; + }) + .join("\n\n"); + + return `${frontmatter} + +${generatedMarker()} + +${banner()} + +The \`${CLI_NAME}\` CLI. Install with \`${RUNNER} ${CLI_NAME}@${CLI_VERSION}\`. Every command accepts \`--help\` and \`--version\`. + +${sections} +`; +} + +function renderMeta(manifest: Manifest, groups: Map): string { + const pages: string[] = []; + for (const g of manifest.groupOrder) { + if (!groups.has(g)) continue; + pages.push(`---${g}---`); + pages.push(...groups.get(g)!); + } + return `${JSON.stringify({ title: "CLI", pages }, null, 2)}\n`; +} + +// ── Main ───────────────────────────────────────────────────────────────────── +function loadManifest(): Manifest { + return toManifest(loadRawManifest(CLI_VERSION)); +} + +function main() { + // latestVersion() picks which published CLI to invoke; the manifest we get + // back is authoritative for what to stamp. Reconcile CLI_VERSION to it so + // pages never claim a version different from the data they were built from + // (e.g. when the live run fails and we fall back to an older cached fixture). + CLI_VERSION = latestVersion(); + const manifest = loadManifest(); + CLI_VERSION = manifest.version; + + // Group top-level commands by base, preserving discovery order. + const bases: string[] = []; + for (const c of manifest.commands) + if (!bases.includes(c.base)) bases.push(c.base); + + // Nav groups, in the order the CLI declares them; a base inherits the group + // of its commands. + const groups = new Map(); + for (const g of manifest.groupOrder) groups.set(g, []); + for (const base of bases) { + const g = manifest.commands.find((c) => c.base === base)!.group; + groups.get(g)!.push(base); + } + for (const [g, list] of groups) if (!list.length) groups.delete(g); + + // Clean previously generated pages, then write fresh. + fs.mkdirSync(OUT_DIR, { recursive: true }); + for (const f of fs.readdirSync(OUT_DIR)) { + if (f.endsWith(".mdx") || f === "meta.json") + fs.rmSync(path.join(OUT_DIR, f)); + } + + let count = 0; + for (const base of bases) { + const cmds = manifest.commands.filter((c) => c.base === base); + const { slug, body } = renderPage(base, cmds); + fs.writeFileSync(path.join(OUT_DIR, `${slug}.mdx`), body); + count++; + } + fs.writeFileSync( + path.join(OUT_DIR, "index.mdx"), + renderIndex(manifest, groups), + ); + fs.writeFileSync( + path.join(OUT_DIR, "meta.json"), + renderMeta(manifest, groups), + ); + + console.log( + `✓ Generated ${count} CLI reference page(s) for ${CLI_NAME} v${manifest.version} → ${path.relative(process.cwd(), OUT_DIR)}`, + ); +} + +main();