From ccca2e5c80bda9986435d86893d7a8e676c8ddab Mon Sep 17 00:00:00 2001 From: Dan Draper Date: Sun, 5 Jul 2026 23:03:19 +1000 Subject: [PATCH 1/7] =?UTF-8?q?docs(v2):=20prototype=20=E2=80=94=20generat?= =?UTF-8?q?e=20the=20CLI=20reference=20from=20`stash=20--help`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PROTOTYPE for review. Generates /reference/cli from the shipped `stash` CLI so the reference can't drift from the actual command surface, and stamps every page with the CLI version it was generated from. - scripts/generate-cli-docs.ts — parses `stash --help` (captured to a fixture) into a manifest, then renders one page per top-level command (grouped Setup/Auth/Database/Schema/Encrypt/Deployment) with a "Generated from stash v0.16.0" banner, a verifiedAgainst.cli facet, and [cli] / [cli, eql] components per the content-model tagging rule. - `bun run generate-docs:cli` (package.json). - scripts/fixtures/stash-help-0.16.0.txt — captured input. - content/docs/reference/cli/* — generated output (10 command pages + index). Bootstrap note: `stash` 0.16.0 is a hand-rolled TS CLI with no machine-readable output and no per-command --help, so we parse the single top-level --help. Target: add `stash manifest --json` to the CLI, then swap loadManifest() to read it and delete the parser — the page format stays identical. Today's --help is thin (no args, no per-command examples, auth/encrypt subcommands undetailed); those gaps are exactly what the JSON manifest fills. Surfaces the drift too: 0.16.0 uses `db install` (not `eql install`) and has ~15 commands the hand-written pages never documented. Claude-Session: https://claude.ai/code/session_01CqDNqLSEEkCi7xAJFq7HJA --- content/docs/reference/cli/auth.mdx | 26 ++ content/docs/reference/cli/db.mdx | 163 ++++++++++ content/docs/reference/cli/encrypt.mdx | 56 ++++ content/docs/reference/cli/env.mdx | 20 ++ content/docs/reference/cli/impl.mdx | 36 +++ content/docs/reference/cli/index.mdx | 65 +++- content/docs/reference/cli/init.mdx | 39 +++ content/docs/reference/cli/meta.json | 19 +- content/docs/reference/cli/plan.mdx | 34 ++ content/docs/reference/cli/schema.mdx | 30 ++ content/docs/reference/cli/status.mdx | 35 +++ content/docs/reference/cli/wizard.mdx | 26 ++ package.json | 1 + scripts/fixtures/stash-help-0.16.0.txt | 98 ++++++ scripts/generate-cli-docs.ts | 410 +++++++++++++++++++++++++ 15 files changed, 1054 insertions(+), 4 deletions(-) create mode 100644 content/docs/reference/cli/auth.mdx create mode 100644 content/docs/reference/cli/db.mdx create mode 100644 content/docs/reference/cli/encrypt.mdx create mode 100644 content/docs/reference/cli/env.mdx create mode 100644 content/docs/reference/cli/impl.mdx create mode 100644 content/docs/reference/cli/init.mdx create mode 100644 content/docs/reference/cli/plan.mdx create mode 100644 content/docs/reference/cli/schema.mdx create mode 100644 content/docs/reference/cli/status.mdx create mode 100644 content/docs/reference/cli/wizard.mdx create mode 100644 scripts/fixtures/stash-help-0.16.0.txt create mode 100644 scripts/generate-cli-docs.ts diff --git a/content/docs/reference/cli/auth.mdx b/content/docs/reference/cli/auth.mdx new file mode 100644 index 0000000..338e854 --- /dev/null +++ b/content/docs/reference/cli/auth.mdx @@ -0,0 +1,26 @@ +--- +title: stash auth +description: "Authenticate with CipherStash" +type: reference +components: [cli] +verifiedAgainst: + cli: "0.16.0" +--- + +{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash --help` (v0.16.0). Bump CLI_VERSION and re-run `bun run generate-docs:cli`. */} + + +Generated from **`stash` v0.16.0**. Run `npx stash@0.16.0 --help` to see the live command surface. + + +Authenticate with CipherStash + +```bash +npx stash auth +``` + +## Examples + +```bash +npx stash auth login +``` diff --git a/content/docs/reference/cli/db.mdx b/content/docs/reference/cli/db.mdx new file mode 100644 index 0000000..82ffcd7 --- /dev/null +++ b/content/docs/reference/cli/db.mdx @@ -0,0 +1,163 @@ +--- +title: stash db +description: "Reference for the `stash db` commands." +type: reference +components: [cli, eql] +verifiedAgainst: + cli: "0.16.0" +--- + +{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash --help` (v0.16.0). Bump CLI_VERSION and re-run `bun run generate-docs:cli`. */} + + +Generated from **`stash` v0.16.0**. Run `npx stash@0.16.0 --help` to see the live command surface. + + +The `stash db` command group. + +### `db install` + +Scaffold stash.config.ts (if missing) and install EQL extensions + +```bash +npx stash db 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` | (requires --supabase) Write a Supabase migration file instead of running SQL directly | +| `--direct` | (requires --supabase) Run the SQL directly against the database (mutually exclusive with --migration) | +| `--migrations-dir ` | (requires --supabase) Override the Supabase migrations directory (default: supabase/migrations) | +| `--exclude-operator-family` | Skip operator family creation | +| `--latest` | Fetch the latest EQL from GitHub | +| `--database-url ` | Override DATABASE_URL for this run only — never written to disk | + + +#### Examples + +```bash +npx stash db install +``` + +### `db upgrade` + +Upgrade EQL extensions to the latest version + +```bash +npx stash db upgrade [flags] +``` + +#### Flags + +| Flag | Description | +| --- | --- | +| `--dry-run` | Show what would happen without making changes | +| `--supabase` | Use Supabase-compatible mode (auto-detected from DATABASE_URL) | +| `--exclude-operator-family` | Skip operator family creation | +| `--latest` | Fetch the latest EQL from GitHub | +| `--database-url ` | Override DATABASE_URL for this run only — never written to disk | + + +### `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 | + + +#### Examples + +```bash +npx stash db push +``` + +### `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 | + + +### `db validate` + +Validate encryption schema + +```bash +npx stash db validate [flags] +``` + +#### Flags + +| Flag | Description | +| --- | --- | +| `--supabase` | Use Supabase-compatible mode (auto-detected from DATABASE_URL) | +| `--exclude-operator-family` | Skip operator family creation | +| `--database-url ` | Override DATABASE_URL for this run only — never written to disk | + + +### `db migrate` + +Run pending encrypt config migrations + +```bash +npx stash db migrate [flags] +``` + +#### Flags + +| Flag | Description | +| --- | --- | +| `--database-url ` | Override DATABASE_URL for this run only — never written to disk | + + +### `db status` + +Show EQL installation status + +```bash +npx stash db status [flags] +``` + +#### Flags + +| Flag | Description | +| --- | --- | +| `--database-url ` | Override DATABASE_URL for this run only — never written to disk | + + +### `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 | diff --git a/content/docs/reference/cli/encrypt.mdx b/content/docs/reference/cli/encrypt.mdx new file mode 100644 index 0000000..887983e --- /dev/null +++ b/content/docs/reference/cli/encrypt.mdx @@ -0,0 +1,56 @@ +--- +title: stash encrypt +description: "Reference for the `stash encrypt` commands." +type: reference +components: [cli, eql] +verifiedAgainst: + cli: "0.16.0" +--- + +{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash --help` (v0.16.0). Bump CLI_VERSION and re-run `bun run generate-docs:cli`. */} + + +Generated from **`stash` v0.16.0**. Run `npx stash@0.16.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 +``` + +### `encrypt cutover` + +Rename swap encrypted → primary column + +```bash +npx stash encrypt cutover +``` + +### `encrypt drop` + +Generate a migration to drop the plaintext column + +```bash +npx stash encrypt drop +``` diff --git a/content/docs/reference/cli/env.mdx b/content/docs/reference/cli/env.mdx new file mode 100644 index 0000000..f30dc09 --- /dev/null +++ b/content/docs/reference/cli/env.mdx @@ -0,0 +1,20 @@ +--- +title: stash env +description: "(experimental) Print production env vars for deployment" +type: reference +components: [cli] +verifiedAgainst: + cli: "0.16.0" +--- + +{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash --help` (v0.16.0). Bump CLI_VERSION and re-run `bun run generate-docs:cli`. */} + + +Generated from **`stash` v0.16.0**. Run `npx stash@0.16.0 --help` to see the live command surface. + + +(experimental) Print production env vars for deployment + +```bash +npx stash env +``` diff --git a/content/docs/reference/cli/impl.mdx b/content/docs/reference/cli/impl.mdx new file mode 100644 index 0000000..5604c50 --- /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.16.0" +--- + +{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash --help` (v0.16.0). Bump CLI_VERSION and re-run `bun run generate-docs:cli`. */} + + +Generated from **`stash` v0.16.0**. Run `npx stash@0.16.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 to call from non-TTY contexts (CI, pipes). Without --target in non-TTY, the command prints a hint and exits cleanly instead of hanging. | + + +## 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..8b4ab15 100644 --- a/content/docs/reference/cli/index.mdx +++ b/content/docs/reference/cli/index.mdx @@ -1,8 +1,67 @@ --- title: CLI -description: "CLI documentation — being built as part of the docs V2 overhaul." +description: "Command reference for the stash CLI, generated from v0.16.0." +type: reference +components: [cli] +verifiedAgainst: + cli: "0.16.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 --help` (v0.16.0). Bump CLI_VERSION and re-run `bun run generate-docs:cli`. */} -Until it lands, current documentation lives in the [existing docs](/stack). + +Generated from **`stash` v0.16.0**. Run `npx stash@0.16.0 --help` to see the live command surface. + + +The `stash` CLI. Install with `npx stash@0.16.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) | + +### Auth + +| Command | Description | +| --- | --- | +| [`auth`](/reference/cli/auth) | Authenticate with CipherStash | + +### Database + +| Command | Description | +| --- | --- | +| [`db install`](/reference/cli/db#db-install) | Scaffold stash.config.ts (if missing) and install EQL extensions | +| [`db upgrade`](/reference/cli/db#db-upgrade) | Upgrade EQL extensions to the latest version | +| [`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 | +| [`db status`](/reference/cli/db#db-status) | Show EQL installation status | +| [`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 | + +### Deployment + +| 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..0e843c3 --- /dev/null +++ b/content/docs/reference/cli/init.mdx @@ -0,0 +1,39 @@ +--- +title: stash init +description: "Initialize CipherStash for your project" +type: reference +components: [cli] +verifiedAgainst: + cli: "0.16.0" +--- + +{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash --help` (v0.16.0). Bump CLI_VERSION and re-run `bun run generate-docs:cli`. */} + + +Generated from **`stash` v0.16.0**. Run `npx stash@0.16.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) | + + +## Examples + +```bash +npx stash init +npx stash init --supabase +npx stash init --prisma-next +``` diff --git a/content/docs/reference/cli/meta.json b/content/docs/reference/cli/meta.json index 0a67892..5e139d7 100644 --- a/content/docs/reference/cli/meta.json +++ b/content/docs/reference/cli/meta.json @@ -1,4 +1,21 @@ { "title": "CLI", - "pages": ["..."] + "pages": [ + "---Setup & workflow---", + "init", + "plan", + "impl", + "status", + "wizard", + "---Auth---", + "auth", + "---Database---", + "db", + "---Schema---", + "schema", + "---Encrypt---", + "encrypt", + "---Deployment---", + "env" + ] } diff --git a/content/docs/reference/cli/plan.mdx b/content/docs/reference/cli/plan.mdx new file mode 100644 index 0000000..0c536df --- /dev/null +++ b/content/docs/reference/cli/plan.mdx @@ -0,0 +1,34 @@ +--- +title: stash plan +description: "Draft a reviewable encryption plan at .cipherstash/plan.md" +type: reference +components: [cli] +verifiedAgainst: + cli: "0.16.0" +--- + +{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash --help` (v0.16.0). Bump CLI_VERSION and re-run `bun run generate-docs:cli`. */} + + +Generated from **`stash` v0.16.0**. Run `npx stash@0.16.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 that normally separates rollout from cutover. Only safe when this database is not backing a deployed application (local dev, sandbox, freshly seeded test environment). | +| `--target ` | Skip the agent-target picker and hand off directly to one of claude-code \| codex \| agents-md \| wizard. Safe to call from non-TTY contexts (CI, pipes). Without --target in non-TTY, the command prints a hint and exits cleanly instead of hanging. | + + +## Examples + +```bash +npx stash plan +``` diff --git a/content/docs/reference/cli/schema.mdx b/content/docs/reference/cli/schema.mdx new file mode 100644 index 0000000..bd8400c --- /dev/null +++ b/content/docs/reference/cli/schema.mdx @@ -0,0 +1,30 @@ +--- +title: stash schema +description: "Reference for the `stash schema` commands." +type: reference +components: [cli, eql] +verifiedAgainst: + cli: "0.16.0" +--- + +{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash --help` (v0.16.0). Bump CLI_VERSION and re-run `bun run generate-docs:cli`. */} + + +Generated from **`stash` v0.16.0**. Run `npx stash@0.16.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 +``` + +#### Examples + +```bash +npx stash schema build +``` diff --git a/content/docs/reference/cli/status.mdx b/content/docs/reference/cli/status.mdx new file mode 100644 index 0000000..8fc84b6 --- /dev/null +++ b/content/docs/reference/cli/status.mdx @@ -0,0 +1,35 @@ +--- +title: stash status +description: "Displays implementation status" +type: reference +components: [cli] +verifiedAgainst: + cli: "0.16.0" +--- + +{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash --help` (v0.16.0). Bump CLI_VERSION and re-run `bun run generate-docs:cli`. */} + + +Generated from **`stash` v0.16.0**. Run `npx stash@0.16.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. Default is auto: fancy in a terminal, plain in CI / pipes / agents. | +| `--plain` | Force the plain-text output even in TTY contexts. | +| `--json` | Emit a structured JSON document instead. | + + +## Examples + +```bash +npx stash status +``` diff --git a/content/docs/reference/cli/wizard.mdx b/content/docs/reference/cli/wizard.mdx new file mode 100644 index 0000000..e79f82d --- /dev/null +++ b/content/docs/reference/cli/wizard.mdx @@ -0,0 +1,26 @@ +--- +title: stash wizard +description: "AI-guided encryption setup (reads your codebase)" +type: reference +components: [cli] +verifiedAgainst: + cli: "0.16.0" +--- + +{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash --help` (v0.16.0). Bump CLI_VERSION and re-run `bun run generate-docs:cli`. */} + + +Generated from **`stash` v0.16.0**. Run `npx stash@0.16.0 --help` to see the live command surface. + + +AI-guided encryption setup (reads your codebase) + +```bash +npx stash wizard +``` + +## Examples + +```bash +npx stash wizard +``` diff --git a/package.json b/package.json index 3b4681a..210cfd6 100644 --- a/package.json +++ b/package.json @@ -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/fixtures/stash-help-0.16.0.txt b/scripts/fixtures/stash-help-0.16.0.txt new file mode 100644 index 0000000..ad4f17f --- /dev/null +++ b/scripts/fixtures/stash-help-0.16.0.txt @@ -0,0 +1,98 @@ +◇ injected env (0) from .env.local // tip: ⌘ multiple files { path: ['.env.local', '.env'] } +◇ injected env (0) from .env.development.local // tip: ⌘ override existing { override: true } +◇ injected env (0) from .env.development // tip: ⌘ enable debugging { debug: true } +◇ injected env (0) from .env // tip: ⌘ enable debugging { debug: true } +CipherStash CLI v0.16.0 + +Usage: bunx stash [options] + +Commands: + init Initialize CipherStash for your project + plan Draft a reviewable encryption plan at .cipherstash/plan.md + impl Execute the plan with a local agent + status Displays implementation status + auth Authenticate with CipherStash + wizard AI-guided encryption setup (reads your codebase) + + db install Scaffold stash.config.ts (if missing) and install EQL extensions + db upgrade Upgrade EQL extensions to the latest version + db push Push encryption schema (writes pending if active config already exists) + db activate Promote pending → active without renames (use after additive db push) + db validate Validate encryption schema + db migrate Run pending encrypt config migrations + db status Show EQL installation status + db test-connection Test database connectivity + + schema build Build an encryption schema from your database + + encrypt status Show per-column migration status (phase, progress, drift) + encrypt plan Diff intent (.cipherstash/migrations.json) vs observed state + encrypt backfill Resumably encrypt plaintext into the encrypted column + encrypt cutover Rename swap encrypted → primary column + encrypt drop Generate a migration to drop the plaintext column + + env (experimental) Print production env vars for deployment + +Options: + --help, -h Show help + --version, -v Show version + +Init Flags: + --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) + +Plan Flags: + --complete-rollout Plan the entire encryption lifecycle (schema-add through drop) + in one document. Skips the production-deploy gate that + normally separates rollout from cutover. Only safe when this + database is not backing a deployed application (local dev, + sandbox, freshly seeded test environment). + --target Skip the agent-target picker and hand off directly to one of + claude-code | codex | agents-md | wizard. Safe to call from + non-TTY contexts (CI, pipes). Without --target in non-TTY, + the command prints a hint and exits cleanly instead of hanging. + +Status Flags: + --quest Force the quest-log output (emoji + progress bars) + even in non-TTY contexts. Default is auto: fancy + in a terminal, plain in CI / pipes / agents. + --plain Force the plain-text output even in TTY contexts. + --json Emit a structured JSON document instead. + +Impl Flags: + --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 to call from + non-TTY contexts (CI, pipes). Without --target in non-TTY, + the command prints a hint and exits cleanly instead of hanging. + +DB Flags: + --force (install) Reinstall / overwrite even if already installed + --dry-run (install, push, upgrade) Show what would happen without making changes + --supabase (install, upgrade, validate) Use Supabase-compatible mode (auto-detected from DATABASE_URL) + --drizzle (install) Generate a Drizzle migration instead of direct install (auto-detected from project) + --migration (install, requires --supabase) Write a Supabase migration file instead of running SQL directly + --direct (install, requires --supabase) Run the SQL directly against the database (mutually exclusive with --migration) + --migrations-dir (install, requires --supabase) Override the Supabase migrations directory (default: supabase/migrations) + --exclude-operator-family (install, upgrade, validate) Skip operator family creation + --latest (install, upgrade) Fetch the latest EQL from GitHub + --database-url (all db / schema commands) Override DATABASE_URL for this run only — never written to disk + +Examples: + bunx stash init + bunx stash init --supabase + bunx stash init --prisma-next + bunx stash plan + bunx stash impl + bunx stash impl --continue-without-plan + bunx stash impl --target claude-code + bunx stash status + bunx stash auth login + bunx stash wizard + bunx stash db install + bunx stash db push + bunx stash schema build diff --git a/scripts/generate-cli-docs.ts b/scripts/generate-cli-docs.ts new file mode 100644 index 0000000..73e21bd --- /dev/null +++ b/scripts/generate-cli-docs.ts @@ -0,0 +1,410 @@ +#!/usr/bin/env tsx +/** + * CLI reference generator — PROTOTYPE (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 ─────────────────────────────────────────────────────────── + * TODAY (bootstrap): we parse `stash --help`, captured to a fixture. `stash` + * is a hand-rolled TS CLI with no machine-readable output yet, and no + * per-command `--help` (every command prints the top-level help), so the + * single top-level help is the whole surface. It is thin — no args, no + * per-command examples, `auth`/`encrypt` subcommands undetailed. + * + * TARGET: add `stash manifest --json` to the CLI (it already has the command + * registry it prints `--help` from). Then replace `loadManifest()` with: + * + * JSON.parse(execSync(`npx stash@${CLI_VERSION} manifest --json`)) + * + * and delete the parser below. The renderer and page format stay identical — + * that is the point of this prototype. + * + * ── Versioning ──────────────────────────────────────────────────────────── + * CLI_VERSION is pinned. Bump it (Renovate can watch the `stash` npm dep), + * re-run `bun run generate-docs:cli`, commit the regenerated pages. Every page + * carries `verifiedAgainst.cli` and a visible banner, so readers and agents + * always know which version the docs describe. + */ +import { execSync } from "node:child_process"; +import fs from "node:fs"; +import path from "node:path"; + +const CLI_NAME = "stash"; +const CLI_VERSION = "0.16.0"; // pinned; bump + regenerate on each release +const RUNNER = "npx"; // normalized invocation shown in docs +const FIXTURE = path.join( + process.cwd(), + "scripts/fixtures", + `stash-help-${CLI_VERSION}.txt`, +); +const OUT_DIR = path.join(process.cwd(), "content/docs/reference/cli"); + +// ── Types (this shape is the spec for `stash manifest --json`) ────────────── +interface Flag { + name: string; // "--supabase" + value?: string; // "" + description: string; + appliesTo?: string[]; // db-flag applicability: ["install", "upgrade"] or ["all"] +} +interface Command { + path: string; // "db install" + base: string; // "db" + sub?: string; // "install" + summary: string; + flags: Flag[]; + examples: string[]; +} +interface Manifest { + name: string; + version: string; + usage: string; + globalFlags: Flag[]; + commands: Command[]; +} + +// Which nav group each top-level command belongs to, and the group order. +const GROUPS: Record = { + init: "Setup & workflow", + plan: "Setup & workflow", + impl: "Setup & workflow", + status: "Setup & workflow", + wizard: "Setup & workflow", + auth: "Auth", + db: "Database", + schema: "Schema", + encrypt: "Encrypt", + env: "Deployment", +}; +const GROUP_ORDER = [ + "Setup & workflow", + "Auth", + "Database", + "Schema", + "Encrypt", + "Deployment", +]; +// Known db/schema subcommand names, used to resolve db-flag applicability. +const DB_SUBCOMMANDS = new Set([ + "install", + "upgrade", + "push", + "activate", + "validate", + "migrate", + "status", + "test-connection", +]); + +// EQL/Postgres commands get the `eql` component facet too (content-model rule: +// tag `eql` only for queryable-in-Postgres ciphertext). +const componentsFor = (base: string): string[] => + ["db", "schema", "encrypt"].includes(base) ? ["cli", "eql"] : ["cli"]; + +// ── Source ────────────────────────────────────────────────────────────────── +function loadHelp(): string { + if (fs.existsSync(FIXTURE)) return fs.readFileSync(FIXTURE, "utf8"); + // Bootstrap: fetch and cache. (Target: `stash manifest --json`.) + const out = execSync(`npx --yes ${CLI_NAME}@${CLI_VERSION} --help`, { + encoding: "utf8", + cwd: require("node:os").tmpdir(), + }); + fs.mkdirSync(path.dirname(FIXTURE), { recursive: true }); + fs.writeFileSync(FIXTURE, out); + return out; +} + +// Drop dotenvx's env-injection tips and blank leading noise. +const stripNoise = (text: string): string[] => + text + .split("\n") + .filter((l) => !/^\s*◇|injected env|dotenvx|www\.(dotenvx|vestauth)/.test(l)); + +// ── Parser (delete once `stash manifest --json` exists) ───────────────────── +function parseHelp(text: string): Manifest { + const lines = stripNoise(text); + const joined = lines.join("\n"); + + const version = joined.match(/CipherStash CLI v([0-9]+\.[0-9]+\.[0-9]+)/)?.[1] ?? CLI_VERSION; + + // Section boundaries: a line like "Commands:", "Options:", "DB Flags:", "Examples:". + const sections: Record = {}; + let current = ""; + for (const line of lines) { + const header = line.match(/^([A-Za-z][A-Za-z ]*):\s*$/); + if (header && !line.startsWith(" ")) { + current = header[1].trim(); + sections[current] = []; + } else if (current) { + sections[current].push(line); + } + } + + // Commands: " db install Scaffold ..." (name is non-greedy up to 2+ spaces) + const commands: Command[] = []; + for (const line of sections.Commands ?? []) { + const m = line.match(/^ {2}(\S.*?) {2,}(.+)$/); + if (!m) continue; + const rawName = m[1].replace(/\s*\s*/, "").trim(); + const [base, ...rest] = rawName.split(/\s+/); + commands.push({ + path: rawName, + base, + sub: rest.length ? rest.join(" ") : undefined, + summary: m[2].trim(), + flags: [], + examples: [], + }); + } + + // Global options. + const globalFlags = parseFlagBlock(sections.Options ?? []); + + // Per-command flag sections: "Init Flags", "Plan Flags", "DB Flags", … + for (const [name, body] of Object.entries(sections)) { + const fm = name.match(/^(.*) Flags$/); + if (!fm) continue; + const label = fm[1].toLowerCase(); // "init", "plan", "db" + const flags = parseFlagBlock(body); + if (label === "db") { + // DB flags carry applicability annotations; resolve onto each subcommand. + for (const cmd of commands.filter((c) => c.base === "db")) { + cmd.flags = flags.filter( + (f) => + !f.appliesTo || + f.appliesTo.includes("all") || + (cmd.sub ? f.appliesTo.includes(cmd.sub) : false), + ); + } + } else { + const cmd = commands.find((c) => c.path === label); + if (cmd) cmd.flags = flags; + } + } + + // Examples: " npx stash db install" → attach to the longest matching command. + const byLength = [...commands].sort((a, b) => b.path.length - a.path.length); + for (const line of sections.Examples ?? []) { + const inv = line.trim(); + const m = inv.match(/^(?:npx|bunx|pnpm dlx|stash)\s+(?:stash\s+)?(.+)$/); + const argPart = m ? m[1] : inv; + const cmd = byLength.find( + (c) => argPart === c.path || argPart.startsWith(`${c.path} `), + ); + if (cmd) cmd.examples.push(`${RUNNER} ${CLI_NAME} ${argPart}`); + } + + return { + name: CLI_NAME, + version, + usage: `${RUNNER} ${CLI_NAME} [options]`, + globalFlags, + commands, + }; +} + +// Parse an indented flag block, folding continuation lines into descriptions. +function parseFlagBlock(body: string[]): Flag[] { + const flags: Flag[] = []; + for (const line of body) { + if (!line.trim()) continue; + const m = line.match(/^ {2}(--[\w-]+)(?:,\s*-\w)?(?: +(<[^>]+>))? {2,}(.+)$/); + if (m) { + let description = m[3].trim(); + let appliesTo: string[] | undefined; + // Leading "(install, push, …)" on DB flags = applicability (+ conditions). + const paren = description.match(/^\(([^)]+)\)\s*(.*)$/); + if (paren) { + const inner = paren[1].trim(); + if (/^all\b/.test(inner)) { + // "(all db / schema commands)" — applies everywhere; drop the note. + appliesTo = ["all"]; + description = paren[2]; + } else { + const tokens = inner.split(/[,/]/).map((t) => t.trim()).filter(Boolean); + const applic = tokens.filter((t) => DB_SUBCOMMANDS.has(t)); + const conditions = tokens.filter((t) => !DB_SUBCOMMANDS.has(t)); + if (applic.length) appliesTo = applic; + description = + (conditions.length ? `(${conditions.join(", ")}) ` : "") + paren[2]; + } + } + flags.push({ name: m[1], value: m[2], description: description.trim(), appliesTo }); + } else { + const cont = line.trim(); + if (flags.length && cont) flags[flags.length - 1].description += ` ${cont}`; + } + } + return flags; +} + +// ── Render ─────────────────────────────────────────────────────────────────── +const GENERATED = `{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from \`${CLI_NAME} --help\` (v${CLI_VERSION}). Bump CLI_VERSION and re-run \`bun run generate-docs:cli\`. */}`; + +function banner(): string { + return ` +Generated from **\`${CLI_NAME}\` v${CLI_VERSION}**. Run \`${RUNNER} ${CLI_NAME}@${CLI_VERSION} --help\` to see the live command surface. +`; +} + +function flagsTable(flags: Flag[]): string { + if (!flags.length) return ""; + const rows = flags + .map((f) => { + const opt = `\`${f.name}${f.value ? ` ${f.value}` : ""}\``; + return `| ${opt} | ${f.description.replace(/\|/g, "\\|")} |`; + }) + .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}\``, + "", + 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, "", GENERATED, "", 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(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"), "```"); + } + + return { slug: base, body: `${parts.join("\n").trimEnd()}\n` }; +} + +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 = GROUP_ORDER.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, "-")}` : ""; + return `| [\`${c.path}\`](/reference/cli/${base}${anchor}) | ${c.summary} |`; + }), + ) + .join("\n"); + return `### ${g}\n\n| Command | Description |\n| --- | --- |\n${rows}`; + }) + .join("\n\n"); + + return `${frontmatter} + +${GENERATED} + +${banner()} + +The \`${CLI_NAME}\` CLI. Install with \`${RUNNER} ${CLI_NAME}@${CLI_VERSION}\`. Every command accepts \`--help\` and \`--version\`. + +${sections} +`; +} + +function renderMeta(groups: Map): string { + const pages: string[] = []; + for (const g of GROUP_ORDER) { + 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 { + // Swap point: return JSON.parse(execSync(`npx ${CLI_NAME}@${CLI_VERSION} manifest --json`)). + return parseHelp(loadHelp()); +} + +function main() { + const manifest = loadManifest(); + + // 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); + + const groups = new Map(); + for (const base of bases) { + const g = GROUPS[base] ?? "Other"; + if (!groups.has(g)) groups.set(g, []); + groups.get(g)!.push(base); + } + + // 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(groups)); + + console.log( + `✓ Generated ${count} CLI reference page(s) for ${CLI_NAME} v${manifest.version} → ${path.relative(process.cwd(), OUT_DIR)}`, + ); +} + +main(); From 73712570c838ec306b527434b0a1ad673cb4c1ae Mon Sep 17 00:00:00 2001 From: Dan Draper Date: Sun, 5 Jul 2026 23:23:02 +1000 Subject: [PATCH 2/7] docs(v2): CLI generator tracks latest published stash + wire into prebuild - Resolve the latest published version via `npm view stash version` and run that exact version live, so a new `stash` release refreshes the docs the next time the script runs (offline: falls back to the cached fixture). Removes the pinned CLI_VERSION constant. - Wire `generate-docs:cli` into `prebuild`, next to the EQL/TypeDoc generators, so every production build regenerates the CLI reference from the latest CLI. - Rename the fixture to a version-agnostic cache (scripts/fixtures/stash-help.txt). Claude-Session: https://claude.ai/code/session_01CqDNqLSEEkCi7xAJFq7HJA --- content/docs/reference/cli/auth.mdx | 2 +- content/docs/reference/cli/db.mdx | 2 +- content/docs/reference/cli/encrypt.mdx | 2 +- content/docs/reference/cli/env.mdx | 2 +- content/docs/reference/cli/impl.mdx | 2 +- content/docs/reference/cli/index.mdx | 2 +- content/docs/reference/cli/init.mdx | 2 +- content/docs/reference/cli/plan.mdx | 2 +- content/docs/reference/cli/schema.mdx | 2 +- content/docs/reference/cli/status.mdx | 2 +- content/docs/reference/cli/wizard.mdx | 2 +- package.json | 2 +- .../{stash-help-0.16.0.txt => stash-help.txt} | 8 +- scripts/generate-cli-docs.ts | 73 +++++++++++++------ 14 files changed, 65 insertions(+), 40 deletions(-) rename scripts/fixtures/{stash-help-0.16.0.txt => stash-help.txt} (93%) diff --git a/content/docs/reference/cli/auth.mdx b/content/docs/reference/cli/auth.mdx index 338e854..5e1ec0a 100644 --- a/content/docs/reference/cli/auth.mdx +++ b/content/docs/reference/cli/auth.mdx @@ -7,7 +7,7 @@ verifiedAgainst: cli: "0.16.0" --- -{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash --help` (v0.16.0). Bump CLI_VERSION and re-run `bun run generate-docs:cli`. */} +{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash --help` (v0.16.0). Re-run `bun run generate-docs:cli` to refresh from the latest published CLI. */} Generated from **`stash` v0.16.0**. Run `npx stash@0.16.0 --help` to see the live command surface. diff --git a/content/docs/reference/cli/db.mdx b/content/docs/reference/cli/db.mdx index 82ffcd7..d5924d0 100644 --- a/content/docs/reference/cli/db.mdx +++ b/content/docs/reference/cli/db.mdx @@ -7,7 +7,7 @@ verifiedAgainst: cli: "0.16.0" --- -{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash --help` (v0.16.0). Bump CLI_VERSION and re-run `bun run generate-docs:cli`. */} +{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash --help` (v0.16.0). Re-run `bun run generate-docs:cli` to refresh from the latest published CLI. */} Generated from **`stash` v0.16.0**. Run `npx stash@0.16.0 --help` to see the live command surface. diff --git a/content/docs/reference/cli/encrypt.mdx b/content/docs/reference/cli/encrypt.mdx index 887983e..421feb5 100644 --- a/content/docs/reference/cli/encrypt.mdx +++ b/content/docs/reference/cli/encrypt.mdx @@ -7,7 +7,7 @@ verifiedAgainst: cli: "0.16.0" --- -{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash --help` (v0.16.0). Bump CLI_VERSION and re-run `bun run generate-docs:cli`. */} +{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash --help` (v0.16.0). Re-run `bun run generate-docs:cli` to refresh from the latest published CLI. */} Generated from **`stash` v0.16.0**. Run `npx stash@0.16.0 --help` to see the live command surface. diff --git a/content/docs/reference/cli/env.mdx b/content/docs/reference/cli/env.mdx index f30dc09..bcf8961 100644 --- a/content/docs/reference/cli/env.mdx +++ b/content/docs/reference/cli/env.mdx @@ -7,7 +7,7 @@ verifiedAgainst: cli: "0.16.0" --- -{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash --help` (v0.16.0). Bump CLI_VERSION and re-run `bun run generate-docs:cli`. */} +{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash --help` (v0.16.0). Re-run `bun run generate-docs:cli` to refresh from the latest published CLI. */} Generated from **`stash` v0.16.0**. Run `npx stash@0.16.0 --help` to see the live command surface. diff --git a/content/docs/reference/cli/impl.mdx b/content/docs/reference/cli/impl.mdx index 5604c50..9190888 100644 --- a/content/docs/reference/cli/impl.mdx +++ b/content/docs/reference/cli/impl.mdx @@ -7,7 +7,7 @@ verifiedAgainst: cli: "0.16.0" --- -{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash --help` (v0.16.0). Bump CLI_VERSION and re-run `bun run generate-docs:cli`. */} +{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash --help` (v0.16.0). Re-run `bun run generate-docs:cli` to refresh from the latest published CLI. */} Generated from **`stash` v0.16.0**. Run `npx stash@0.16.0 --help` to see the live command surface. diff --git a/content/docs/reference/cli/index.mdx b/content/docs/reference/cli/index.mdx index 8b4ab15..8ee4efa 100644 --- a/content/docs/reference/cli/index.mdx +++ b/content/docs/reference/cli/index.mdx @@ -7,7 +7,7 @@ verifiedAgainst: cli: "0.16.0" --- -{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash --help` (v0.16.0). Bump CLI_VERSION and re-run `bun run generate-docs:cli`. */} +{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash --help` (v0.16.0). Re-run `bun run generate-docs:cli` to refresh from the latest published CLI. */} Generated from **`stash` v0.16.0**. Run `npx stash@0.16.0 --help` to see the live command surface. diff --git a/content/docs/reference/cli/init.mdx b/content/docs/reference/cli/init.mdx index 0e843c3..57f4171 100644 --- a/content/docs/reference/cli/init.mdx +++ b/content/docs/reference/cli/init.mdx @@ -7,7 +7,7 @@ verifiedAgainst: cli: "0.16.0" --- -{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash --help` (v0.16.0). Bump CLI_VERSION and re-run `bun run generate-docs:cli`. */} +{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash --help` (v0.16.0). Re-run `bun run generate-docs:cli` to refresh from the latest published CLI. */} Generated from **`stash` v0.16.0**. Run `npx stash@0.16.0 --help` to see the live command surface. diff --git a/content/docs/reference/cli/plan.mdx b/content/docs/reference/cli/plan.mdx index 0c536df..70cbc0a 100644 --- a/content/docs/reference/cli/plan.mdx +++ b/content/docs/reference/cli/plan.mdx @@ -7,7 +7,7 @@ verifiedAgainst: cli: "0.16.0" --- -{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash --help` (v0.16.0). Bump CLI_VERSION and re-run `bun run generate-docs:cli`. */} +{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash --help` (v0.16.0). Re-run `bun run generate-docs:cli` to refresh from the latest published CLI. */} Generated from **`stash` v0.16.0**. Run `npx stash@0.16.0 --help` to see the live command surface. diff --git a/content/docs/reference/cli/schema.mdx b/content/docs/reference/cli/schema.mdx index bd8400c..11ae03c 100644 --- a/content/docs/reference/cli/schema.mdx +++ b/content/docs/reference/cli/schema.mdx @@ -7,7 +7,7 @@ verifiedAgainst: cli: "0.16.0" --- -{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash --help` (v0.16.0). Bump CLI_VERSION and re-run `bun run generate-docs:cli`. */} +{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash --help` (v0.16.0). Re-run `bun run generate-docs:cli` to refresh from the latest published CLI. */} Generated from **`stash` v0.16.0**. Run `npx stash@0.16.0 --help` to see the live command surface. diff --git a/content/docs/reference/cli/status.mdx b/content/docs/reference/cli/status.mdx index 8fc84b6..74e2834 100644 --- a/content/docs/reference/cli/status.mdx +++ b/content/docs/reference/cli/status.mdx @@ -7,7 +7,7 @@ verifiedAgainst: cli: "0.16.0" --- -{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash --help` (v0.16.0). Bump CLI_VERSION and re-run `bun run generate-docs:cli`. */} +{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash --help` (v0.16.0). Re-run `bun run generate-docs:cli` to refresh from the latest published CLI. */} Generated from **`stash` v0.16.0**. Run `npx stash@0.16.0 --help` to see the live command surface. diff --git a/content/docs/reference/cli/wizard.mdx b/content/docs/reference/cli/wizard.mdx index e79f82d..081dc9e 100644 --- a/content/docs/reference/cli/wizard.mdx +++ b/content/docs/reference/cli/wizard.mdx @@ -7,7 +7,7 @@ verifiedAgainst: cli: "0.16.0" --- -{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash --help` (v0.16.0). Bump CLI_VERSION and re-run `bun run generate-docs:cli`. */} +{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash --help` (v0.16.0). Re-run `bun run generate-docs:cli` to refresh from the latest published CLI. */} Generated from **`stash` v0.16.0**. Run `npx stash@0.16.0 --help` to see the live command surface. diff --git a/package.json b/package.json index 210cfd6..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", diff --git a/scripts/fixtures/stash-help-0.16.0.txt b/scripts/fixtures/stash-help.txt similarity index 93% rename from scripts/fixtures/stash-help-0.16.0.txt rename to scripts/fixtures/stash-help.txt index ad4f17f..fa27406 100644 --- a/scripts/fixtures/stash-help-0.16.0.txt +++ b/scripts/fixtures/stash-help.txt @@ -1,7 +1,7 @@ -◇ injected env (0) from .env.local // tip: ⌘ multiple files { path: ['.env.local', '.env'] } -◇ injected env (0) from .env.development.local // tip: ⌘ override existing { override: true } -◇ injected env (0) from .env.development // tip: ⌘ enable debugging { debug: true } -◇ injected env (0) from .env // tip: ⌘ enable debugging { debug: true } +◇ injected env (0) from .env.local // tip: ⌁ auth for agents [www.vestauth.com] +◇ injected env (0) from .env.development.local // tip: ◈ secrets for agents [www.dotenvx.com] +◇ injected env (0) from .env.development // tip: ◈ encrypted .env [www.dotenvx.com] +◇ injected env (0) from .env // tip: ◈ encrypted .env [www.dotenvx.com] CipherStash CLI v0.16.0 Usage: bunx stash [options] diff --git a/scripts/generate-cli-docs.ts b/scripts/generate-cli-docs.ts index 73e21bd..215cb45 100644 --- a/scripts/generate-cli-docs.ts +++ b/scripts/generate-cli-docs.ts @@ -22,23 +22,22 @@ * that is the point of this prototype. * * ── Versioning ──────────────────────────────────────────────────────────── - * CLI_VERSION is pinned. Bump it (Renovate can watch the `stash` npm dep), - * re-run `bun run generate-docs:cli`, commit the regenerated pages. Every page - * carries `verifiedAgainst.cli` and a visible banner, so readers and agents - * always know which version the docs describe. + * 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-help.txt`. */ 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"; -const CLI_VERSION = "0.16.0"; // pinned; bump + regenerate on each release +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-help-${CLI_VERSION}.txt`, -); +const FIXTURE = path.join(process.cwd(), "scripts/fixtures", "stash-help.txt"); const OUT_DIR = path.join(process.cwd(), "content/docs/reference/cli"); // ── Types (this shape is the spec for `stash manifest --json`) ────────────── @@ -103,16 +102,40 @@ const componentsFor = (base: string): string[] => ["db", "schema", "encrypt"].includes(base) ? ["cli", "eql"] : ["cli"]; // ── Source ────────────────────────────────────────────────────────────────── -function loadHelp(): string { - if (fs.existsSync(FIXTURE)) return fs.readFileSync(FIXTURE, "utf8"); - // Bootstrap: fetch and cache. (Target: `stash manifest --json`.) - const out = execSync(`npx --yes ${CLI_NAME}@${CLI_VERSION} --help`, { - encoding: "utf8", - cwd: require("node:os").tmpdir(), - }); - fs.mkdirSync(path.dirname(FIXTURE), { recursive: true }); - fs.writeFileSync(FIXTURE, out); - return out; +// 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) + ? fs.readFileSync(FIXTURE, "utf8").match(/CipherStash CLI v([0-9.]+)/)?.[1] + : 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 version and cache its help. (Target: `stash manifest --json`.) +function loadHelp(version: string): string { + try { + const out = execSync(`npx --yes ${CLI_NAME}@${version} --help`, { + encoding: "utf8", + cwd: os.tmpdir(), + stdio: ["ignore", "pipe", "ignore"], + }); + fs.mkdirSync(path.dirname(FIXTURE), { recursive: true }); + fs.writeFileSync(FIXTURE, out); + return out; + } catch { + if (fs.existsSync(FIXTURE)) { + console.warn(`⚠ Could not run stash@${version}; using cached fixture.`); + return fs.readFileSync(FIXTURE, "utf8"); + } + throw new Error(`Could not run stash@${version} and no cached fixture exists.`); + } } // Drop dotenvx's env-injection tips and blank leading noise. @@ -240,7 +263,8 @@ function parseFlagBlock(body: string[]): Flag[] { } // ── Render ─────────────────────────────────────────────────────────────────── -const GENERATED = `{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from \`${CLI_NAME} --help\` (v${CLI_VERSION}). Bump CLI_VERSION and re-run \`bun run generate-docs:cli\`. */}`; +const generatedMarker = (): string => + `{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from \`${CLI_NAME} --help\` (v${CLI_VERSION}). Re-run \`bun run generate-docs:cli\` to refresh from the latest published CLI. */}`; function banner(): string { return ` @@ -297,7 +321,7 @@ function renderPage(base: string, cmds: Command[]): { slug: string; body: string "---", ].join("\n"); - const parts = [frontmatter, "", GENERATED, "", banner(), ""]; + const parts = [frontmatter, "", generatedMarker(), "", banner(), ""]; if (isGroup) { parts.push( @@ -346,7 +370,7 @@ function renderIndex(manifest: Manifest, groups: Map): string return `${frontmatter} -${GENERATED} +${generatedMarker()} ${banner()} @@ -369,10 +393,11 @@ function renderMeta(groups: Map): string { // ── Main ───────────────────────────────────────────────────────────────────── function loadManifest(): Manifest { // Swap point: return JSON.parse(execSync(`npx ${CLI_NAME}@${CLI_VERSION} manifest --json`)). - return parseHelp(loadHelp()); + return parseHelp(loadHelp(CLI_VERSION)); } function main() { + CLI_VERSION = latestVersion(); const manifest = loadManifest(); // Group top-level commands by base, preserving discovery order. From 22dd036f218aff65d1157f40502d69dd13f7ae37 Mon Sep 17 00:00:00 2001 From: Dan Draper Date: Sun, 5 Jul 2026 23:30:08 +1000 Subject: [PATCH 3/7] =?UTF-8?q?docs(v2):=20hybrid=20CLI=20reference=20?= =?UTF-8?q?=E2=80=94=20merge=20hand-authored=20supplements=20+=20examples?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a supplement hook to the CLI generator: an optional scripts/cli-supplements/.md is merged after the generated reference (synopsis + flags), so the rich per-command narrative and curated examples the thin `stash --help` can't provide can be authored now, without waiting on the CLI. Supplements live outside content/ so they're never treated as pages or wiped by the clean step. Demonstrated on `auth` (device-code flow + keyset binding, grounded in the CLI source). Content-model direction: per-command reference detail (examples, flag help) should migrate into the CLI itself (long-help + examples, GitHub-CLI/cobra style) and be generated from there; cross-command conceptual narrative (the profile / workspace model) belongs in a linked CLI concept page. The supplement hook is the interim bridge and the seam for both. Claude-Session: https://claude.ai/code/session_01CqDNqLSEEkCi7xAJFq7HJA --- content/docs/reference/cli/auth.mdx | 24 ++++++++++++++++++++++++ scripts/cli-supplements/auth.md | 23 +++++++++++++++++++++++ scripts/generate-cli-docs.ts | 17 ++++++++++++++++- 3 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 scripts/cli-supplements/auth.md diff --git a/content/docs/reference/cli/auth.mdx b/content/docs/reference/cli/auth.mdx index 5e1ec0a..4ae27d0 100644 --- a/content/docs/reference/cli/auth.mdx +++ b/content/docs/reference/cli/auth.mdx @@ -24,3 +24,27 @@ npx stash auth ```bash npx stash auth login ``` + +## 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. + +The `--supabase` / `--drizzle` flags only tag the referrer for onboarding +analytics; they don't change the flow. + +> **Good to know**: 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/cli-supplements/auth.md b/scripts/cli-supplements/auth.md new file mode 100644 index 0000000..4d13348 --- /dev/null +++ b/scripts/cli-supplements/auth.md @@ -0,0 +1,23 @@ +## 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. + +The `--supabase` / `--drizzle` flags only tag the referrer for onboarding +analytics; they don't change the flow. + +> **Good to know**: 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/generate-cli-docs.ts b/scripts/generate-cli-docs.ts index 215cb45..30a0427 100644 --- a/scripts/generate-cli-docs.ts +++ b/scripts/generate-cli-docs.ts @@ -39,6 +39,12 @@ let CLI_VERSION = ""; // resolved to the latest published npm version at run tim const RUNNER = "npx"; // normalized invocation shown in docs const FIXTURE = path.join(process.cwd(), "scripts/fixtures", "stash-help.txt"); 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) stays drift-free; a supplement adds +// rich narrative + curated examples the thin `--help` can't provide. Lives +// outside content/ so it's never treated as a page or wiped by the clean step. +// Long-term, migrate these into the CLI's own long-help/examples (see the PR). +const SUPPLEMENTS_DIR = path.join(process.cwd(), "scripts/cli-supplements"); // ── Types (this shape is the spec for `stash manifest --json`) ────────────── interface Flag { @@ -336,7 +342,16 @@ function renderPage(base: string, cmds: Command[]): { slug: string; body: string if (c.examples.length) parts.push("\n## Examples\n", "```bash", c.examples.join("\n"), "```"); } - return { slug: base, body: `${parts.join("\n").trimEnd()}\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 { From 6a7ac7b817fc561a3ed3753d25028de1ca718da8 Mon Sep 17 00:00:00 2001 From: Dan Draper Date: Wed, 8 Jul 2026 16:33:39 +1000 Subject: [PATCH 4/7] docs(cli): generate the CLI reference from `stash manifest --json` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit stash CLI 0.17 ships the `manifest` command (structured, versioned command surface), so swap the generator off `--help` scraping onto `stash manifest --json` and delete the text parser — the realization of this PR's stated target. - loadManifest() now runs `npx stash@ manifest --json` and projects it onto the renderer's model; nav groups + order come straight from the CLI. - Per-command flags carry their `default` and `env` (folded into the flag description); examples come from the manifest. Pipes in flag values (`--eql-version <2|3>`) are escaped so the GFM table renders. - Fixture swapped from stash-help.txt to stash-manifest.json. - Regenerated against 0.17.0: new `eql` group (eql install/upgrade/status), new `doctor` + `manifest` pages, `db` no longer lists install/upgrade. types:check passes; generator lints clean (bar pre-existing noNonNullAssertion style warnings shared with the original). --- content/docs/reference/cli/auth.mdx | 52 ++- content/docs/reference/cli/db.mdx | 101 +---- content/docs/reference/cli/doctor.mdx | 20 + content/docs/reference/cli/encrypt.mdx | 44 +- content/docs/reference/cli/env.mdx | 14 +- content/docs/reference/cli/eql.mdx | 77 ++++ content/docs/reference/cli/impl.mdx | 10 +- content/docs/reference/cli/index.mdx | 30 +- content/docs/reference/cli/init.mdx | 18 +- content/docs/reference/cli/manifest.mdx | 34 ++ content/docs/reference/cli/meta.json | 6 +- content/docs/reference/cli/plan.mdx | 11 +- content/docs/reference/cli/schema.mdx | 17 +- content/docs/reference/cli/status.mdx | 15 +- content/docs/reference/cli/wizard.mdx | 12 +- scripts/fixtures/stash-help.txt | 98 ----- scripts/fixtures/stash-manifest.json | 511 ++++++++++++++++++++++++ scripts/generate-cli-docs.ts | 370 ++++++++--------- 18 files changed, 967 insertions(+), 473 deletions(-) create mode 100644 content/docs/reference/cli/doctor.mdx create mode 100644 content/docs/reference/cli/eql.mdx create mode 100644 content/docs/reference/cli/manifest.mdx delete mode 100644 scripts/fixtures/stash-help.txt create mode 100644 scripts/fixtures/stash-manifest.json diff --git a/content/docs/reference/cli/auth.mdx b/content/docs/reference/cli/auth.mdx index 4ae27d0..09f0a18 100644 --- a/content/docs/reference/cli/auth.mdx +++ b/content/docs/reference/cli/auth.mdx @@ -1,28 +1,68 @@ --- title: stash auth -description: "Authenticate with CipherStash" +description: "Reference for the `stash auth` commands." type: reference components: [cli] verifiedAgainst: - cli: "0.16.0" + cli: "0.17.0" --- -{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash --help` (v0.16.0). Re-run `bun run generate-docs:cli` to refresh from the latest published CLI. */} +{/* 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.16.0**. Run `npx stash@0.16.0 --help` to see the live command surface. +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 +npx stash auth login [flags] ``` -## Examples +#### 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 diff --git a/content/docs/reference/cli/db.mdx b/content/docs/reference/cli/db.mdx index d5924d0..e0c26b2 100644 --- a/content/docs/reference/cli/db.mdx +++ b/content/docs/reference/cli/db.mdx @@ -4,66 +4,17 @@ description: "Reference for the `stash db` commands." type: reference components: [cli, eql] verifiedAgainst: - cli: "0.16.0" + cli: "0.17.0" --- -{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash --help` (v0.16.0). Re-run `bun run generate-docs:cli` to refresh from the latest published CLI. */} +{/* 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.16.0**. Run `npx stash@0.16.0 --help` to see the live command surface. +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 install` - -Scaffold stash.config.ts (if missing) and install EQL extensions - -```bash -npx stash db 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` | (requires --supabase) Write a Supabase migration file instead of running SQL directly | -| `--direct` | (requires --supabase) Run the SQL directly against the database (mutually exclusive with --migration) | -| `--migrations-dir ` | (requires --supabase) Override the Supabase migrations directory (default: supabase/migrations) | -| `--exclude-operator-family` | Skip operator family creation | -| `--latest` | Fetch the latest EQL from GitHub | -| `--database-url ` | Override DATABASE_URL for this run only — never written to disk | - - -#### Examples - -```bash -npx stash db install -``` - -### `db upgrade` - -Upgrade EQL extensions to the latest version - -```bash -npx stash db upgrade [flags] -``` - -#### Flags - -| Flag | Description | -| --- | --- | -| `--dry-run` | Show what would happen without making changes | -| `--supabase` | Use Supabase-compatible mode (auto-detected from DATABASE_URL) | -| `--exclude-operator-family` | Skip operator family creation | -| `--latest` | Fetch the latest EQL from GitHub | -| `--database-url ` | Override DATABASE_URL for this run only — never written to disk | - - ### `db push` Push encryption schema (writes pending if active config already exists) @@ -76,15 +27,9 @@ npx stash db push [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 | - +| `--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`) | -#### Examples - -```bash -npx stash db push -``` ### `db activate` @@ -98,7 +43,7 @@ npx stash db activate [flags] | Flag | Description | | --- | --- | -| `--database-url ` | Override DATABASE_URL for this run only — never written to disk | +| `--database-url ` | Override DATABASE_URL for this run only — never written to disk. (env: `DATABASE_URL`) | ### `db validate` @@ -113,41 +58,19 @@ npx stash db validate [flags] | Flag | Description | | --- | --- | -| `--supabase` | Use Supabase-compatible mode (auto-detected from DATABASE_URL) | -| `--exclude-operator-family` | Skip operator family creation | -| `--database-url ` | Override DATABASE_URL for this run only — never written to disk | +| `--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 +Run pending encrypt config migrations (not yet implemented) ```bash -npx stash db migrate [flags] +npx stash db migrate ``` -#### Flags - -| Flag | Description | -| --- | --- | -| `--database-url ` | Override DATABASE_URL for this run only — never written to disk | - - -### `db status` - -Show EQL installation status - -```bash -npx stash db status [flags] -``` - -#### Flags - -| Flag | Description | -| --- | --- | -| `--database-url ` | Override DATABASE_URL for this run only — never written to disk | - - ### `db test-connection` Test database connectivity @@ -160,4 +83,4 @@ npx stash db test-connection [flags] | Flag | Description | | --- | --- | -| `--database-url ` | Override DATABASE_URL for this run only — never written to disk | +| `--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 index 421feb5..9c03ae8 100644 --- a/content/docs/reference/cli/encrypt.mdx +++ b/content/docs/reference/cli/encrypt.mdx @@ -4,13 +4,13 @@ description: "Reference for the `stash encrypt` commands." type: reference components: [cli, eql] verifiedAgainst: - cli: "0.16.0" + cli: "0.17.0" --- -{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash --help` (v0.16.0). Re-run `bun run generate-docs:cli` to refresh from the latest published CLI. */} +{/* 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.16.0**. Run `npx stash@0.16.0 --help` to see the live command surface. +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. @@ -36,21 +36,53 @@ npx stash encrypt plan Resumably encrypt plaintext into the encrypted column ```bash -npx stash encrypt backfill +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 +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 +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 index bcf8961..fe277a9 100644 --- a/content/docs/reference/cli/env.mdx +++ b/content/docs/reference/cli/env.mdx @@ -4,17 +4,23 @@ description: "(experimental) Print production env vars for deployment" type: reference components: [cli] verifiedAgainst: - cli: "0.16.0" + cli: "0.17.0" --- -{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash --help` (v0.16.0). Re-run `bun run generate-docs:cli` to refresh from the latest published CLI. */} +{/* 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.16.0**. Run `npx stash@0.16.0 --help` to see the live command surface. +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 +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 index 9190888..b3ce114 100644 --- a/content/docs/reference/cli/impl.mdx +++ b/content/docs/reference/cli/impl.mdx @@ -4,13 +4,13 @@ description: "Execute the plan with a local agent" type: reference components: [cli] verifiedAgainst: - cli: "0.16.0" + cli: "0.17.0" --- -{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash --help` (v0.16.0). Re-run `bun run generate-docs:cli` to refresh from the latest published CLI. */} +{/* 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.16.0**. Run `npx stash@0.16.0 --help` to see the live command surface. +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 @@ -23,8 +23,8 @@ npx stash impl [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 to call from non-TTY contexts (CI, pipes). Without --target in non-TTY, the command prints a hint and exits cleanly instead of hanging. | +| `--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 diff --git a/content/docs/reference/cli/index.mdx b/content/docs/reference/cli/index.mdx index 8ee4efa..88764db 100644 --- a/content/docs/reference/cli/index.mdx +++ b/content/docs/reference/cli/index.mdx @@ -1,19 +1,19 @@ --- title: CLI -description: "Command reference for the stash CLI, generated from v0.16.0." +description: "Command reference for the stash CLI, generated from v0.17.0." type: reference components: [cli] verifiedAgainst: - cli: "0.16.0" + cli: "0.17.0" --- -{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash --help` (v0.16.0). Re-run `bun run generate-docs:cli` to refresh from the latest published CLI. */} +{/* 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.16.0**. Run `npx stash@0.16.0 --help` to see the live command surface. +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.16.0`. Every command accepts `--help` and `--version`. +The `stash` CLI. Install with `npx stash@0.17.0`. Every command accepts `--help` and `--version`. ### Setup & workflow @@ -24,24 +24,32 @@ The `stash` CLI. Install with `npx stash@0.16.0`. Every command accepts `--help` | [`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`](/reference/cli/auth) | Authenticate with CipherStash | +| [`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 install`](/reference/cli/db#db-install) | Scaffold stash.config.ts (if missing) and install EQL extensions | -| [`db upgrade`](/reference/cli/db#db-upgrade) | Upgrade EQL extensions to the latest version | | [`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 | -| [`db status`](/reference/cli/db#db-status) | Show EQL installation status | +| [`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 @@ -60,7 +68,7 @@ The `stash` CLI. Install with `npx stash@0.16.0`. Every command accepts `--help` | [`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 | -### Deployment +### Experimental | Command | Description | | --- | --- | diff --git a/content/docs/reference/cli/init.mdx b/content/docs/reference/cli/init.mdx index 57f4171..518544b 100644 --- a/content/docs/reference/cli/init.mdx +++ b/content/docs/reference/cli/init.mdx @@ -4,13 +4,13 @@ description: "Initialize CipherStash for your project" type: reference components: [cli] verifiedAgainst: - cli: "0.16.0" + cli: "0.17.0" --- -{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash --help` (v0.16.0). Re-run `bun run generate-docs:cli` to refresh from the latest published CLI. */} +{/* 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.16.0**. Run `npx stash@0.16.0 --help` to see the live command surface. +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 @@ -23,11 +23,12 @@ npx stash init [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) | +| `--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 @@ -36,4 +37,5 @@ npx stash init [flags] 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 5e139d7..6b90890 100644 --- a/content/docs/reference/cli/meta.json +++ b/content/docs/reference/cli/meta.json @@ -7,15 +7,19 @@ "impl", "status", "wizard", + "doctor", + "manifest", "---Auth---", "auth", + "---EQL---", + "eql", "---Database---", "db", "---Schema---", "schema", "---Encrypt---", "encrypt", - "---Deployment---", + "---Experimental---", "env" ] } diff --git a/content/docs/reference/cli/plan.mdx b/content/docs/reference/cli/plan.mdx index 70cbc0a..4904f7a 100644 --- a/content/docs/reference/cli/plan.mdx +++ b/content/docs/reference/cli/plan.mdx @@ -4,13 +4,13 @@ description: "Draft a reviewable encryption plan at .cipherstash/plan.md" type: reference components: [cli] verifiedAgainst: - cli: "0.16.0" + cli: "0.17.0" --- -{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash --help` (v0.16.0). Re-run `bun run generate-docs:cli` to refresh from the latest published CLI. */} +{/* 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.16.0**. Run `npx stash@0.16.0 --help` to see the live command surface. +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 @@ -23,12 +23,13 @@ npx stash plan [flags] | Flag | Description | | --- | --- | -| `--complete-rollout` | Plan the entire encryption lifecycle (schema-add through drop) in one document. Skips the production-deploy gate that normally separates rollout from cutover. Only safe when this database is not backing a deployed application (local dev, sandbox, freshly seeded test environment). | -| `--target ` | Skip the agent-target picker and hand off directly to one of claude-code \| codex \| agents-md \| wizard. Safe to call from non-TTY contexts (CI, pipes). Without --target in non-TTY, the command prints a hint and exits cleanly instead of hanging. | +| `--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 index 11ae03c..9b5e360 100644 --- a/content/docs/reference/cli/schema.mdx +++ b/content/docs/reference/cli/schema.mdx @@ -4,13 +4,13 @@ description: "Reference for the `stash schema` commands." type: reference components: [cli, eql] verifiedAgainst: - cli: "0.16.0" + cli: "0.17.0" --- -{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash --help` (v0.16.0). Re-run `bun run generate-docs:cli` to refresh from the latest published CLI. */} +{/* 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.16.0**. Run `npx stash@0.16.0 --help` to see the live command surface. +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. @@ -20,11 +20,12 @@ The `stash schema` command group. Build an encryption schema from your database ```bash -npx stash schema build +npx stash schema build [flags] ``` -#### Examples +#### Flags -```bash -npx stash schema build -``` +| 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 index 74e2834..ba033d7 100644 --- a/content/docs/reference/cli/status.mdx +++ b/content/docs/reference/cli/status.mdx @@ -4,13 +4,13 @@ description: "Displays implementation status" type: reference components: [cli] verifiedAgainst: - cli: "0.16.0" + cli: "0.17.0" --- -{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash --help` (v0.16.0). Re-run `bun run generate-docs:cli` to refresh from the latest published CLI. */} +{/* 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.16.0**. Run `npx stash@0.16.0 --help` to see the live command surface. +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 @@ -23,13 +23,6 @@ npx stash status [flags] | Flag | Description | | --- | --- | -| `--quest` | Force the quest-log output (emoji + progress bars) even in non-TTY contexts. Default is auto: fancy in a terminal, plain in CI / pipes / agents. | +| `--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. | - - -## Examples - -```bash -npx stash status -``` diff --git a/content/docs/reference/cli/wizard.mdx b/content/docs/reference/cli/wizard.mdx index 081dc9e..05678ed 100644 --- a/content/docs/reference/cli/wizard.mdx +++ b/content/docs/reference/cli/wizard.mdx @@ -4,13 +4,13 @@ description: "AI-guided encryption setup (reads your codebase)" type: reference components: [cli] verifiedAgainst: - cli: "0.16.0" + cli: "0.17.0" --- -{/* GENERATED — do not edit. Produced by scripts/generate-cli-docs.ts from `stash --help` (v0.16.0). Re-run `bun run generate-docs:cli` to refresh from the latest published CLI. */} +{/* 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.16.0**. Run `npx stash@0.16.0 --help` to see the live command surface. +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) @@ -18,9 +18,3 @@ AI-guided encryption setup (reads your codebase) ```bash npx stash wizard ``` - -## Examples - -```bash -npx stash wizard -``` diff --git a/scripts/fixtures/stash-help.txt b/scripts/fixtures/stash-help.txt deleted file mode 100644 index fa27406..0000000 --- a/scripts/fixtures/stash-help.txt +++ /dev/null @@ -1,98 +0,0 @@ -◇ injected env (0) from .env.local // tip: ⌁ auth for agents [www.vestauth.com] -◇ injected env (0) from .env.development.local // tip: ◈ secrets for agents [www.dotenvx.com] -◇ injected env (0) from .env.development // tip: ◈ encrypted .env [www.dotenvx.com] -◇ injected env (0) from .env // tip: ◈ encrypted .env [www.dotenvx.com] -CipherStash CLI v0.16.0 - -Usage: bunx stash [options] - -Commands: - init Initialize CipherStash for your project - plan Draft a reviewable encryption plan at .cipherstash/plan.md - impl Execute the plan with a local agent - status Displays implementation status - auth Authenticate with CipherStash - wizard AI-guided encryption setup (reads your codebase) - - db install Scaffold stash.config.ts (if missing) and install EQL extensions - db upgrade Upgrade EQL extensions to the latest version - db push Push encryption schema (writes pending if active config already exists) - db activate Promote pending → active without renames (use after additive db push) - db validate Validate encryption schema - db migrate Run pending encrypt config migrations - db status Show EQL installation status - db test-connection Test database connectivity - - schema build Build an encryption schema from your database - - encrypt status Show per-column migration status (phase, progress, drift) - encrypt plan Diff intent (.cipherstash/migrations.json) vs observed state - encrypt backfill Resumably encrypt plaintext into the encrypted column - encrypt cutover Rename swap encrypted → primary column - encrypt drop Generate a migration to drop the plaintext column - - env (experimental) Print production env vars for deployment - -Options: - --help, -h Show help - --version, -v Show version - -Init Flags: - --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) - -Plan Flags: - --complete-rollout Plan the entire encryption lifecycle (schema-add through drop) - in one document. Skips the production-deploy gate that - normally separates rollout from cutover. Only safe when this - database is not backing a deployed application (local dev, - sandbox, freshly seeded test environment). - --target Skip the agent-target picker and hand off directly to one of - claude-code | codex | agents-md | wizard. Safe to call from - non-TTY contexts (CI, pipes). Without --target in non-TTY, - the command prints a hint and exits cleanly instead of hanging. - -Status Flags: - --quest Force the quest-log output (emoji + progress bars) - even in non-TTY contexts. Default is auto: fancy - in a terminal, plain in CI / pipes / agents. - --plain Force the plain-text output even in TTY contexts. - --json Emit a structured JSON document instead. - -Impl Flags: - --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 to call from - non-TTY contexts (CI, pipes). Without --target in non-TTY, - the command prints a hint and exits cleanly instead of hanging. - -DB Flags: - --force (install) Reinstall / overwrite even if already installed - --dry-run (install, push, upgrade) Show what would happen without making changes - --supabase (install, upgrade, validate) Use Supabase-compatible mode (auto-detected from DATABASE_URL) - --drizzle (install) Generate a Drizzle migration instead of direct install (auto-detected from project) - --migration (install, requires --supabase) Write a Supabase migration file instead of running SQL directly - --direct (install, requires --supabase) Run the SQL directly against the database (mutually exclusive with --migration) - --migrations-dir (install, requires --supabase) Override the Supabase migrations directory (default: supabase/migrations) - --exclude-operator-family (install, upgrade, validate) Skip operator family creation - --latest (install, upgrade) Fetch the latest EQL from GitHub - --database-url (all db / schema commands) Override DATABASE_URL for this run only — never written to disk - -Examples: - bunx stash init - bunx stash init --supabase - bunx stash init --prisma-next - bunx stash plan - bunx stash impl - bunx stash impl --continue-without-plan - bunx stash impl --target claude-code - bunx stash status - bunx stash auth login - bunx stash wizard - bunx stash db install - bunx stash db push - bunx stash schema build 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 index 30a0427..dd0d88b 100644 --- a/scripts/generate-cli-docs.ts +++ b/scripts/generate-cli-docs.ts @@ -1,25 +1,17 @@ #!/usr/bin/env tsx /** - * CLI reference generator — PROTOTYPE (CIP-33xx). + * 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 ─────────────────────────────────────────────────────────── - * TODAY (bootstrap): we parse `stash --help`, captured to a fixture. `stash` - * is a hand-rolled TS CLI with no machine-readable output yet, and no - * per-command `--help` (every command prints the top-level help), so the - * single top-level help is the whole surface. It is thin — no args, no - * per-command examples, `auth`/`encrypt` subcommands undetailed. - * - * TARGET: add `stash manifest --json` to the CLI (it already has the command - * registry it prints `--help` from). Then replace `loadManifest()` with: - * - * JSON.parse(execSync(`npx stash@${CLI_VERSION} manifest --json`)) - * - * and delete the parser below. The renderer and page format stay identical — - * that is the point of this prototype. + * 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 @@ -27,7 +19,7 @@ * 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-help.txt`. + * `scripts/fixtures/stash-manifest.json`. */ import { execSync } from "node:child_process"; import fs from "node:fs"; @@ -37,26 +29,58 @@ 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-help.txt"); +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) stays drift-free; a supplement adds -// rich narrative + curated examples the thin `--help` can't provide. Lives -// outside content/ so it's never treated as a page or wiped by the clean step. -// Long-term, migrate these into the CLI's own long-help/examples (see the PR). +// 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"); -// ── Types (this shape is the spec for `stash manifest --json`) ────────────── -interface Flag { +// ── 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; // "" + 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; - appliesTo?: string[]; // db-flag applicability: ["install", "upgrade"] or ["all"] } interface Command { - path: string; // "db install" - base: string; // "db" + path: string; // "eql install" + base: string; // "eql" sub?: string; // "install" + group: string; // nav group title, from the manifest summary: string; flags: Flag[]; examples: string[]; @@ -64,217 +88,104 @@ interface Command { interface Manifest { name: string; version: string; - usage: string; - globalFlags: Flag[]; commands: Command[]; + groupOrder: string[]; // nav group order, as the CLI declares it } -// Which nav group each top-level command belongs to, and the group order. -const GROUPS: Record = { - init: "Setup & workflow", - plan: "Setup & workflow", - impl: "Setup & workflow", - status: "Setup & workflow", - wizard: "Setup & workflow", - auth: "Auth", - db: "Database", - schema: "Schema", - encrypt: "Encrypt", - env: "Deployment", -}; -const GROUP_ORDER = [ - "Setup & workflow", - "Auth", - "Database", - "Schema", - "Encrypt", - "Deployment", -]; -// Known db/schema subcommand names, used to resolve db-flag applicability. -const DB_SUBCOMMANDS = new Set([ - "install", - "upgrade", - "push", - "activate", - "validate", - "migrate", - "status", - "test-connection", -]); - -// EQL/Postgres commands get the `eql` component facet too (content-model rule: -// tag `eql` only for queryable-in-Postgres ciphertext). +// 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[] => - ["db", "schema", "encrypt"].includes(base) ? ["cli", "eql"] : ["cli"]; + ["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(); + return execSync(`npm view ${CLI_NAME} version`, { + encoding: "utf8", + }).trim(); } catch { const cached = fs.existsSync(FIXTURE) - ? fs.readFileSync(FIXTURE, "utf8").match(/CipherStash CLI v([0-9.]+)/)?.[1] + ? (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)."); + throw new Error( + "Cannot resolve latest stash version (offline, no fixture).", + ); } } -// Run the resolved CLI version and cache its help. (Target: `stash manifest --json`.) -function loadHelp(version: string): string { +// 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} --help`, { + const out = execSync(`npx --yes ${CLI_NAME}@${version} manifest --json`, { encoding: "utf8", cwd: os.tmpdir(), stdio: ["ignore", "pipe", "ignore"], }); + const json = out.slice(out.indexOf("{"), out.lastIndexOf("}") + 1); + const manifest = JSON.parse(json) as CliManifest; fs.mkdirSync(path.dirname(FIXTURE), { recursive: true }); - fs.writeFileSync(FIXTURE, out); - return out; + 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 fs.readFileSync(FIXTURE, "utf8"); - } - throw new Error(`Could not run stash@${version} and no cached fixture exists.`); - } -} - -// Drop dotenvx's env-injection tips and blank leading noise. -const stripNoise = (text: string): string[] => - text - .split("\n") - .filter((l) => !/^\s*◇|injected env|dotenvx|www\.(dotenvx|vestauth)/.test(l)); - -// ── Parser (delete once `stash manifest --json` exists) ───────────────────── -function parseHelp(text: string): Manifest { - const lines = stripNoise(text); - const joined = lines.join("\n"); - - const version = joined.match(/CipherStash CLI v([0-9]+\.[0-9]+\.[0-9]+)/)?.[1] ?? CLI_VERSION; - - // Section boundaries: a line like "Commands:", "Options:", "DB Flags:", "Examples:". - const sections: Record = {}; - let current = ""; - for (const line of lines) { - const header = line.match(/^([A-Za-z][A-Za-z ]*):\s*$/); - if (header && !line.startsWith(" ")) { - current = header[1].trim(); - sections[current] = []; - } else if (current) { - sections[current].push(line); - } - } - - // Commands: " db install Scaffold ..." (name is non-greedy up to 2+ spaces) - const commands: Command[] = []; - for (const line of sections.Commands ?? []) { - const m = line.match(/^ {2}(\S.*?) {2,}(.+)$/); - if (!m) continue; - const rawName = m[1].replace(/\s*\s*/, "").trim(); - const [base, ...rest] = rawName.split(/\s+/); - commands.push({ - path: rawName, - base, - sub: rest.length ? rest.join(" ") : undefined, - summary: m[2].trim(), - flags: [], - examples: [], - }); - } - - // Global options. - const globalFlags = parseFlagBlock(sections.Options ?? []); - - // Per-command flag sections: "Init Flags", "Plan Flags", "DB Flags", … - for (const [name, body] of Object.entries(sections)) { - const fm = name.match(/^(.*) Flags$/); - if (!fm) continue; - const label = fm[1].toLowerCase(); // "init", "plan", "db" - const flags = parseFlagBlock(body); - if (label === "db") { - // DB flags carry applicability annotations; resolve onto each subcommand. - for (const cmd of commands.filter((c) => c.base === "db")) { - cmd.flags = flags.filter( - (f) => - !f.appliesTo || - f.appliesTo.includes("all") || - (cmd.sub ? f.appliesTo.includes(cmd.sub) : false), - ); - } - } else { - const cmd = commands.find((c) => c.path === label); - if (cmd) cmd.flags = flags; + return JSON.parse(fs.readFileSync(FIXTURE, "utf8")) as CliManifest; } - } - - // Examples: " npx stash db install" → attach to the longest matching command. - const byLength = [...commands].sort((a, b) => b.path.length - a.path.length); - for (const line of sections.Examples ?? []) { - const inv = line.trim(); - const m = inv.match(/^(?:npx|bunx|pnpm dlx|stash)\s+(?:stash\s+)?(.+)$/); - const argPart = m ? m[1] : inv; - const cmd = byLength.find( - (c) => argPart === c.path || argPart.startsWith(`${c.path} `), + throw new Error( + `Could not run stash@${version} manifest --json and no cached fixture exists.`, ); - if (cmd) cmd.examples.push(`${RUNNER} ${CLI_NAME} ${argPart}`); } +} - return { - name: CLI_NAME, - version, - usage: `${RUNNER} ${CLI_NAME} [options]`, - globalFlags, - commands, - }; +// 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 }; } -// Parse an indented flag block, folding continuation lines into descriptions. -function parseFlagBlock(body: string[]): Flag[] { - const flags: Flag[] = []; - for (const line of body) { - if (!line.trim()) continue; - const m = line.match(/^ {2}(--[\w-]+)(?:,\s*-\w)?(?: +(<[^>]+>))? {2,}(.+)$/); - if (m) { - let description = m[3].trim(); - let appliesTo: string[] | undefined; - // Leading "(install, push, …)" on DB flags = applicability (+ conditions). - const paren = description.match(/^\(([^)]+)\)\s*(.*)$/); - if (paren) { - const inner = paren[1].trim(); - if (/^all\b/.test(inner)) { - // "(all db / schema commands)" — applies everywhere; drop the note. - appliesTo = ["all"]; - description = paren[2]; - } else { - const tokens = inner.split(/[,/]/).map((t) => t.trim()).filter(Boolean); - const applic = tokens.filter((t) => DB_SUBCOMMANDS.has(t)); - const conditions = tokens.filter((t) => !DB_SUBCOMMANDS.has(t)); - if (applic.length) appliesTo = applic; - description = - (conditions.length ? `(${conditions.join(", ")}) ` : "") + paren[2]; - } - } - flags.push({ name: m[1], value: m[2], description: description.trim(), appliesTo }); - } else { - const cont = line.trim(); - if (flags.length && cont) flags[flags.length - 1].description += ` ${cont}`; +// 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 flags; + 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} --help\` (v${CLI_VERSION}). Re-run \`bun run generate-docs:cli\` to refresh from the latest published CLI. */}`; + `{/* 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}**. Run \`${RUNNER} ${CLI_NAME}@${CLI_VERSION} --help\` to see the live command surface. +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. `; } @@ -282,7 +193,12 @@ function flagsTable(flags: Flag[]): string { if (!flags.length) return ""; const rows = flags .map((f) => { - const opt = `\`${f.name}${f.value ? ` ${f.value}` : ""}\``; + // 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, + "\\|", + ); return `| ${opt} | ${f.description.replace(/\|/g, "\\|")} |`; }) .join("\n"); @@ -303,12 +219,20 @@ function commandSection(cmd: Command, level: "##" | "###"): string { 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"), "```"); + 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 } { +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); @@ -337,9 +261,16 @@ function renderPage(base: string, cmds: Command[]): { slug: string; body: string ); } else { const c = cmds[0]; - parts.push(c.summary, "", "```bash", `${RUNNER} ${CLI_NAME} ${c.path}${c.flags.length ? " [flags]" : ""}`, "```"); + parts.push( + 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"), "```"); + if (c.examples.length) + parts.push("\n## Examples\n", "```bash", c.examples.join("\n"), "```"); } const supplement = readSupplement(base); @@ -354,7 +285,10 @@ function readSupplement(slug: string): string { return fs.existsSync(file) ? fs.readFileSync(file, "utf8").trim() : ""; } -function renderIndex(manifest: Manifest, groups: Map): string { +function renderIndex( + manifest: Manifest, + groups: Map, +): string { const frontmatter = [ "---", "title: CLI", @@ -366,7 +300,8 @@ function renderIndex(manifest: Manifest, groups: Map): string "---", ].join("\n"); - const sections = GROUP_ORDER.filter((g) => groups.has(g)) + const sections = manifest.groupOrder + .filter((g) => groups.has(g)) .map((g) => { const rows = groups .get(g)! @@ -375,7 +310,8 @@ function renderIndex(manifest: Manifest, groups: Map): string .filter((c) => c.base === base) .map((c) => { const anchor = c.sub ? `#${c.path.replace(/\s+/g, "-")}` : ""; - return `| [\`${c.path}\`](/reference/cli/${base}${anchor}) | ${c.summary} |`; + const summary = c.summary.replace(/\|/g, "\\|"); + return `| [\`${c.path}\`](/reference/cli/${base}${anchor}) | ${summary} |`; }), ) .join("\n"); @@ -395,9 +331,9 @@ ${sections} `; } -function renderMeta(groups: Map): string { +function renderMeta(manifest: Manifest, groups: Map): string { const pages: string[] = []; - for (const g of GROUP_ORDER) { + for (const g of manifest.groupOrder) { if (!groups.has(g)) continue; pages.push(`---${g}---`); pages.push(...groups.get(g)!); @@ -407,8 +343,7 @@ function renderMeta(groups: Map): string { // ── Main ───────────────────────────────────────────────────────────────────── function loadManifest(): Manifest { - // Swap point: return JSON.parse(execSync(`npx ${CLI_NAME}@${CLI_VERSION} manifest --json`)). - return parseHelp(loadHelp(CLI_VERSION)); + return toManifest(loadRawManifest(CLI_VERSION)); } function main() { @@ -417,19 +352,24 @@ function main() { // 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); + 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 = GROUPS[base] ?? "Other"; - if (!groups.has(g)) groups.set(g, []); + 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)); + if (f.endsWith(".mdx") || f === "meta.json") + fs.rmSync(path.join(OUT_DIR, f)); } let count = 0; @@ -439,8 +379,14 @@ function main() { 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(groups)); + 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)}`, From 40ee2216ae3430c364b72d532df9e76e6a87797a Mon Sep 17 00:00:00 2001 From: Dan Draper Date: Wed, 8 Jul 2026 17:30:54 +1000 Subject: [PATCH 5/7] fix(cli-docs): escape MDX braces in manifest-derived prose MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `auth regions --json` flag description is "Emit machine-readable [{ slug, label }] ...". MDX parsed `{ slug, label }` as a JS expression, so prerendering /reference/cli/auth threw `ReferenceError: slug is not defined` and failed the build. (The earlier `` failure masked this; it surfaced once #52 landed via rebase.) Escape `{`/`}` (and stray `<`) in manifest-derived prose — flag descriptions and summaries. Flag names/values stay in code spans, which are literal, so they're untouched. Verified: `next build` prerenders all 355 pages, including /reference/cli/auth. --- content/docs/reference/cli/auth.mdx | 2 +- scripts/generate-cli-docs.ts | 16 ++++++++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/content/docs/reference/cli/auth.mdx b/content/docs/reference/cli/auth.mdx index 09f0a18..482ae39 100644 --- a/content/docs/reference/cli/auth.mdx +++ b/content/docs/reference/cli/auth.mdx @@ -55,7 +55,7 @@ npx stash auth regions [flags] | Flag | Description | | --- | --- | -| `--json` | Emit machine-readable [{ slug, label }] instead of a text list. | +| `--json` | Emit machine-readable [\{ slug, label \}] instead of a text list. | #### Examples diff --git a/scripts/generate-cli-docs.ts b/scripts/generate-cli-docs.ts index dd0d88b..e80ca6d 100644 --- a/scripts/generate-cli-docs.ts +++ b/scripts/generate-cli-docs.ts @@ -189,6 +189,13 @@ Generated from **\`${CLI_NAME}\` v${CLI_VERSION}** via \`${RUNNER} ${CLI_NAME}@$ `; } +// 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 @@ -199,7 +206,8 @@ function flagsTable(flags: Flag[]): string { /\|/g, "\\|", ); - return `| ${opt} | ${f.description.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`; @@ -210,7 +218,7 @@ function commandSection(cmd: Command, level: "##" | "###"): string { const parts = [ `${level} \`${cmd.path}\``, "", - cmd.summary, + escapeMdxText(cmd.summary), "", "```bash", synopsis, @@ -262,7 +270,7 @@ function renderPage( } else { const c = cmds[0]; parts.push( - c.summary, + escapeMdxText(c.summary), "", "```bash", `${RUNNER} ${CLI_NAME} ${c.path}${c.flags.length ? " [flags]" : ""}`, @@ -310,7 +318,7 @@ function renderIndex( .filter((c) => c.base === base) .map((c) => { const anchor = c.sub ? `#${c.path.replace(/\s+/g, "-")}` : ""; - const summary = c.summary.replace(/\|/g, "\\|"); + const summary = escapeMdxText(c.summary).replace(/\|/g, "\\|"); return `| [\`${c.path}\`](/reference/cli/${base}${anchor}) | ${summary} |`; }), ) From 261b69ca1b221fce85a7d9d8a99c9a348c03ec47 Mon Sep 17 00:00:00 2001 From: Dan Draper Date: Wed, 8 Jul 2026 18:03:22 +1000 Subject: [PATCH 6/7] docs(cli): address auth-page review feedback Vercel preview feedback on /reference/cli/auth: - Remove the "--supabase / --drizzle flags only tag the referrer" paragraph. - Convert the "Good to know" blockquote into a proper . Edited the auth supplement (the page is generated from it) and recast the em-dashes in the touched prose. Verified: next build prerenders all 355 pages. --- content/docs/reference/cli/auth.mdx | 16 +++++++--------- scripts/cli-supplements/auth.md | 16 +++++++--------- 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/content/docs/reference/cli/auth.mdx b/content/docs/reference/cli/auth.mdx index 482ae39..2ba2a00 100644 --- a/content/docs/reference/cli/auth.mdx +++ b/content/docs/reference/cli/auth.mdx @@ -70,21 +70,19 @@ npx stash auth regions --json `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 +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. -The `--supabase` / `--drizzle` flags only tag the referrer for onboarding -analytics; they don't change the flow. - -> **Good to know**: login is device- and workspace-scoped. Authenticating from a -> new machine — or for a different workspace — re-runs the device flow. + +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`; +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/cli-supplements/auth.md b/scripts/cli-supplements/auth.md index 4d13348..b17fc57 100644 --- a/scripts/cli-supplements/auth.md +++ b/scripts/cli-supplements/auth.md @@ -3,21 +3,19 @@ `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 +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. -The `--supabase` / `--drizzle` flags only tag the referrer for onboarding -analytics; they don't change the flow. - -> **Good to know**: login is device- and workspace-scoped. Authenticating from a -> new machine — or for a different workspace — re-runs the device flow. + +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`; +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. */} From 7d1dcffa2f3ff8900bc79af4252bfeaed4e1a2e8 Mon Sep 17 00:00:00 2001 From: Dan Draper Date: Wed, 8 Jul 2026 18:08:41 +1000 Subject: [PATCH 7/7] fix(cli-docs): harden manifest loading (Copilot review) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - loadRawManifest: validate the JSON delimiters before slicing, throwing a clear error (which falls back to the fixture) instead of feeding garbage to JSON.parse. - Reconcile CLI_VERSION to the loaded manifest's version after loading, so pages are stamped with the version of the data actually used — including the fixture-fallback path, where npm-latest and the cached manifest can differ. --- scripts/generate-cli-docs.ts | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/scripts/generate-cli-docs.ts b/scripts/generate-cli-docs.ts index e80ca6d..07effd9 100644 --- a/scripts/generate-cli-docs.ts +++ b/scripts/generate-cli-docs.ts @@ -128,8 +128,14 @@ function loadRawManifest(version: string): CliManifest { cwd: os.tmpdir(), stdio: ["ignore", "pipe", "ignore"], }); - const json = out.slice(out.indexOf("{"), out.lastIndexOf("}") + 1); - const manifest = JSON.parse(json) as CliManifest; + 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; @@ -355,8 +361,13 @@ function loadManifest(): Manifest { } 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[] = [];