/**
 * # cli/commands/meta — `version` + `help` (LIGHT, dependency-free)
 *
 * Zero dynamic imports. The version string comes from a top-level JSON import of `package.json`
 * (a compile-time constant once bundled — no runtime dependency, no `.ts` on the graph). These
 * handlers are fast-pathed in {@link ../index.ts} before the router ever touches a heavy handler.
 *
 * `help` is a DISPATCHER (kestrel-1qc): with no command word it renders the top-level usage, but
 * `kestrel <cmd> --help` (or `kestrel help <cmd>`) renders that ONE command's own usage + its own
 * flags. The per-command help tables ({@link SUBHELP}) are static data authored here — they carry
 * NO import of the heavy command modules, so `<cmd> --help` stays LIGHT (node-runnable, no bun/chdb)
 * exactly like the top-level short-circuit. The exit-code taxonomy ({@link EXIT_CODES}) mirrors
 * `src/cli/errors.ts` `EXIT` and is surfaced in the top-level `help` so agents can discover it.
 */

import type { OutputCtx } from "../context.ts";
import pkg from "../../../package.json" with { type: "json" };
// The signing-input prefix is a WIRE constant homed in the dependency-free protocol package
// (OSS-ADR-0046 §2, kestrel-s67u); interpolated into help text so the docs can't drift from the
// prefix the verifier actually rebuilds the signed message with.
import { GRADE_SIGN_PREFIX } from "../../protocol/attestation.ts";

/** The published version, injected at bundle time from `package.json`. */
export const VERSION: string = pkg.version;

/** The command inventory, rendered by `help`. */
const COMMANDS: readonly { name: string; args: string; purpose: string }[] = [
  { name: "version", args: "", purpose: "print the version and exit" },
  { name: "help", args: "[command]", purpose: "print this usage (or one command's usage) and exit" },
  { name: "parse", args: "<file>", purpose: "parse + validate a plan document" },
  { name: "validate", args: "<file>", purpose: "parse + validate a plan document (alias of parse)" },
  { name: "print", args: "<file>", purpose: "canonical re-print of a plan document" },
  { name: "card", args: "[<topic>]", purpose: "print the shipped agent language card (offline); a topic prints a walkthrough (see `kestrel card --help`)" },
  { name: "frame", args: "<input.json>", purpose: "render a Frame from a fixture/snapshot JSON" },
  { name: "percept", args: "<input.json>", purpose: "render a Frame (alias of frame)" },
  { name: "register", args: "[--name <n>] [--scopes <s,s>] [--no-git-identity]", purpose: "self-register as an autonomous agent; store the credential" },
  { name: "whoami", args: "", purpose: "inspect the stored credential: identity, scopes, expiry + status, API host, file path (no network)" },
  { name: "refresh", args: "", purpose: "renew the stored durable capability before it expires (server-minted; no client RNG)" },
  { name: "secrets", args: "set <KEY> [--stdin] | list | unset <KEY> | path", purpose: "manage operator/BYOK secrets in ~/.kestrel/.env (value via prompt or --stdin, NEVER argv; list is names-only)" },
  { name: "sim", args: "[<scenario-slug>] [--plans <p>] [--budget <amount>]", purpose: "run a curated hosted scenario, free (bare: the menu); --budget opts into the paid boundary" },
  { name: "prove", args: "[<scenario-slug>] [--plans <p>] [--copy-token <t>]", purpose: "the zero-credential front door: bare runs a default scenario + a proof URL, no key/config/prompt" },
  { name: "replay", args: "<proofId>", purpose: "reproduce a local proof (fresh trial), or verify a published one" },
  { name: "verify", args: "<url|proofId>", purpose: "zero-trust re-verify of a published proof's signature (never trusts the body's own verdict)" },
  { name: "certify", args: "<url|proofId>", purpose: "open recomputation: re-project the Blotter LOCALLY and reproduce the hosted result byte-identically (gate G10)" },
  { name: "agent", args: "[--api <url>]", purpose: "machine mode: a JSONL request/response protocol over stdin/stdout (see `kestrel agent --help`)" },
  { name: "mcp", args: "[--local]", purpose: "serve the MCP face over stdio (JSON-RPC 2.0) for an MCP client; remote by default, --local for the in-process engine (see `kestrel mcp --help`)" },
  { name: "run", args: "--bus <p> --plans <p> --fill <m> --r-usd <n>", purpose: "grade a session, auto-record" },
  { name: "day", args: "--bus <p> --dir <d> --fill <m> --r-usd <n>", purpose: "stepped/wake session, auto-record" },
  {
    name: "paper",
    args: "--instrument <s> --plans <p> --session-date <d> --r-usd <n> --max-order-qty <n> --max-position-qty <n> --max-notional-usd <n>",
    purpose: "run a PAPER session on a live IB Gateway feed (simulated fills, never live money)",
  },
  { name: "runs list", args: "[--session-date <d>] [--fill <m>] [--lineage <n>]", purpose: "query recorded runs (hosted rows show the plan/strategy each used)" },
  { name: "runs show", args: "<id>", purpose: "show one run: a local run + plans, or a hosted `sim` receipt + proof URL" },
  { name: "runs compare", args: "<runA> <runB>", purpose: "diff two hosted `sim` receipts — which plan each used + the orders/fills/P&L deltas (\"did my tweak help?\")" },
  { name: "lineage", args: "<name>", purpose: "show a plan lineage" },
  { name: "leaderboard", args: "[--since <ms>] [--mode <m>]", purpose: "ranked leaderboard" },
];

const GLOBALS: readonly { flag: string; purpose: string }[] = [
  { flag: "--json", purpose: "machine JSON output (even on a TTY)" },
  { flag: "--api <url>", purpose: "select the managed remote transport (`--api default` ⇒ api.kestrel.markets; also KESTREL_API); default is local" },
  { flag: "--format <json|text|human>", purpose: "force a render mode" },
  { flag: "--agent", purpose: "agent mode: text + no color + non-interactive" },
  { flag: "--color <always|never|auto>", purpose: "colorize (human mode only)" },
  { flag: "--no-color", purpose: "disable color (NO_COLOR-equivalent)" },
  { flag: "-h, --help", purpose: "print usage and exit 0 (per-command: `kestrel <cmd> --help`)" },
  { flag: "-V, --version", purpose: "print version and exit 0" },
];

/**
 * The exit-code taxonomy — a VERBATIM mirror of the `EXIT` map in `src/cli/errors.ts`. Every error
 * path is nonzero; `code` strings are stable across releases (agents match on `code`, not prose).
 * Surfaced in the top-level `help` so the taxonomy is discoverable from the CLI itself. NOTE: a
 * parse failure carries the `code: "PARSE"` (raised by the lang layer) inside the USAGE exit bucket.
 */
const EXIT_CODES: readonly { code: string; exit: number; when: string }[] = [
  { code: "OK", exit: 0, when: "the command succeeded" },
  { code: "GENERIC", exit: 1, when: "an unexpected/uncaught error (KESTREL_DEBUG=1 dumps the stack)" },
  { code: "USAGE", exit: 2, when: "bad/unknown flag, missing required flag, bad --format, or a parse/arm failure (code PARSE, or — under validate/parse --arm/--bus/--instruments — ARM / ARM_CONTEXT / BUS)" },
  { code: "NOT_FOUND", exit: 3, when: "`runs show <id>` / `lineage <name>` / an input file is absent" },
  { code: "RUNTIME_UNAVAILABLE", exit: 4, when: "bun/chdb is needed but could not be loaded" },
  { code: "PAYMENT_REQUIRED", exit: 5, when: "a remote verb returned a 402/Offer (surfaced as data)" },
  { code: "NO_AUTHOR", exit: 6, when: "`day` (default or `--no-author`): a handshake document was not staged in --dir and no author wait was declared" },
  { code: "HANDSHAKE_ABANDONED", exit: 6, when: "`day --await-author`: no author reply (plans-0/revision/pass) within --max-wait" },
  {
    code: "PAPER_REFUSED",
    exit: 7,
    when: "`paper`: the session refused fail-closed (unreachable/degraded IB Gateway, unresolved contract, absent/stale feed, tripped kill-switch, unbounded limits, a non-paper mode, a real-money port, or a gateway reporting a non-PAPER account)",
  },
  { code: "SIGINT", exit: 130, when: "interrupted (Ctrl-C)" },
];

/** One command's own usage + flag inventory — static help data (no heavy import). */
interface SubHelp {
  readonly name: string;
  readonly usage: string;
  readonly purpose: string;
  readonly flags: readonly { flag: string; purpose: string }[];
  /** Nested subcommands (only `runs`), listed when the bare parent is asked for help. */
  readonly subcommands?: readonly { name: string; purpose: string }[];
  /** Free-form explanatory paragraphs rendered under a `notes:` header (e.g. `register`'s disclosure/storage/re-register behavior). */
  readonly notes?: readonly string[];
  /** Free-form protocol description paragraphs (`agent` / `mcp`) — what the verb is and how to drive it. */
  readonly protocol?: readonly string[];
  /**
   * The verb's request operations (only `agent`) — enumerated from the actual handler switch
   * (`src/cli/commands/agent.ts`). The discovery op (`catalog`) is authored first. `args` names the
   * request-envelope fields each op reads beyond `op`.
   */
  readonly ops?: readonly { op: string; args: string; purpose: string }[];
  /** A copy-pasteable example (only `agent`): the command line, the piped request, and the response shape. */
  readonly example?: { readonly title: string; readonly command: string; readonly output: string };
}

/**
 * Per-command help tables. The flag lists are authored to match each command's own parser
 * (`src/cli/commands/{grade,registry,frame,lang}.ts`) — this is the discoverability surface the
 * top-level usage cannot carry. Required flags are marked `(required)`.
 */
const SUBHELP: Readonly<Record<string, SubHelp>> = {
  run: {
    name: "run",
    usage: "kestrel run --bus <p> --plans <p> --fill <m> --r-usd <n> [flags]",
    purpose: "grade a plan document against a recorded bus, print the report, and auto-record it",
    flags: [
      { flag: "--bus <p>", purpose: "the recorded market bus to grade against (required)" },
      { flag: "--plans <p>", purpose: "the plan document to arm — .kestrel, or .md with ```kestrel blocks (required)" },
      { flag: "--fill <m>", purpose: "fill model: strict-cross-v1 | maker-fair-v1 (required)" },
      { flag: "--r-usd <n>", purpose: "dollar value of 1R, a positive number (required)" },
      { flag: "--tau-hours <n>", purpose: "fair time-to-expiry in hours (maker-fair calibration)" },
      { flag: "--hazard-params <p>", purpose: "episode-sigmoid params JSON (only with --fill maker-fair-v1)" },
      { flag: "--out <p>", purpose: "also write the EpisodeReport JSON to this path" },
      { flag: "--db <p>", purpose: "registry path to record into (default data/kestrel.db)" },
      { flag: "--no-record", purpose: "grade only — do NOT write the run to the registry" },
    ],
  },
  day: {
    name: "day",
    usage: "kestrel day --bus <p> --dir <d> --fill <m> --r-usd <n> [flags]",
    purpose: "run a stepped/wake session (wake handshake + document supersession), and auto-record it",
    flags: [
      { flag: "--bus <p>", purpose: "the recorded market bus to grade against (required)" },
      { flag: "--dir <d>", purpose: "handshake dir the stepped session authors plans into (plans-N.kestrel) (required)" },
      { flag: "--fill <m>", purpose: "fill model: strict-cross-v1 | maker-fair-v1 (required)" },
      { flag: "--r-usd <n>", purpose: "dollar value of 1R, a positive number (required)" },
      { flag: "--wakes <HH:MM,…>", purpose: "explicit wake clock times (ET) to step the session at" },
      { flag: "--await-author", purpose: "block for an out-of-band author at each handshake, up to --max-wait (default: refuse promptly on a missing document)" },
      { flag: "--max-wait <s>", purpose: "seconds to wait for a handshake reply under --await-author (positive number, default 900); supplying it implies --await-author" },
      { flag: "--no-author", purpose: "explicit: no author will reply — documents must be pre-staged in --dir (same prompt exit-6 refusal as the default)" },
      { flag: "--structural", purpose: "use structural-cadence wakes" },
      { flag: "--tau-hours <n>", purpose: "fair time-to-expiry in hours (maker-fair calibration)" },
      { flag: "--hazard-params <p>", purpose: "episode-sigmoid params JSON (only with --fill maker-fair-v1)" },
      { flag: "--out <p>", purpose: "also write the EpisodeReport JSON to this path" },
      { flag: "--db <p>", purpose: "registry path to record into (default data/kestrel.db)" },
      { flag: "--no-record", purpose: "grade only — do NOT write the run to the registry" },
    ],
  },
  paper: {
    name: "paper",
    usage:
      "kestrel paper --instrument <s> --plans <p> --session-date <d> --r-usd <n> --max-order-qty <n> --max-position-qty <n> --max-notional-usd <n> [flags]",
    purpose:
      "run a PAPER session: a LIVE IB Gateway feed + the venue's paper gate under the same engine `run`/`day` drive over recorded tape. Simulated fills; NEVER live money (exit 7 refuses, loudly): there is no --live flag, KESTREL_IBKR_MODE=live is REFUSED not honoured, IB's real-money ports (4001/7496) are refused, and the account barrier classifies the account the gateway ITSELF reports (never the pinned claim) — a gateway that reports a non-PAPER account (live U…, or one that cannot be classified), OR a pinned --account that does not match the gateway's report, is refused and its socket closed BEFORE any tape, gate, or order exists, on any port",
    flags: [
      { flag: "--instrument <s>", purpose: "the underlier symbol to trade, e.g. SPY (required)" },
      { flag: "--plans <p>", purpose: "the standing document to arm — .kestrel, or .md with ```kestrel blocks (required)" },
      { flag: "--session-date <d>", purpose: "the session's calendar token, YYYY-MM-DD (required)" },
      { flag: "--r-usd <n>", purpose: "dollar value of 1R, a positive number (required)" },
      { flag: "--max-order-qty <n>", purpose: "L0 ceiling: max contracts/shares per order (required — no default; an absent ceiling is not a ceiling)" },
      { flag: "--max-position-qty <n>", purpose: "L0 ceiling: max absolute net position per leg (required)" },
      { flag: "--max-notional-usd <n>", purpose: "L0 ceiling: max px×qty×multiplier per order, in dollars (required)" },
      { flag: "--budget-usd <n>", purpose: "bounded-risk budget: size × max_loss ≤ budget (default: --max-notional-usd)" },
      { flag: "--expiry <YYYYMMDD>", purpose: "the option chain expiry to subscribe to (needs --spot). Absent ⇒ an equity-only tape" },
      { flag: "--spot <n>", purpose: "the OBSERVED underlier price the strike window centres on — required with --expiry; a centre is never guessed" },
      { flag: "--half-width <n>", purpose: "LISTED strikes each side of the ATM one" },
      { flag: "--tau-hours <n>", purpose: "fair time-to-expiry in hours (injected — never read off a clock)" },
      { flag: "--live-market-data", purpose: "DECLARE that this gateway serves REAL-TIME data (asks IB for type 1). Absent ⇒ every price anchor is stamped `unknown` and every order is REFUSED — the session perceives but cannot price" },
      { flag: "--tolerance <n>", purpose: "reconciliation tolerance, absolute qty per leg (default 0 — exact match)" },
      { flag: "--cycles <n>", purpose: "how many live pump cycles to run (default 60)" },
      { flag: "--pump-ms <n>", purpose: "host park between pump cycles in ms (default 1000)" },
      { flag: "--host <h>", purpose: "IB Gateway host (default 127.0.0.1 / $KESTREL_IBKR_HOST) — a locally-running gateway, not a credential" },
      { flag: "--port <n>", purpose: "IB Gateway API port (default 4002 = IB Gateway PAPER / $KESTREL_IBKR_PORT). IB's REAL-MONEY ports (4001 IB Gateway live, 7496 TWS live) are REFUSED — there is no proceed-anyway flag. A live gateway on any OTHER port is caught by the account barrier, which classifies the account the gateway itself reports (never the port, never a pinned claim)" },
      { flag: "--client-id <n>", purpose: "unique API client id per gateway instance (default 0 / $KESTREL_IBKR_CLIENT_ID)" },
      { flag: "--account <a>", purpose: "pin an account id (default: discovered from the gateway handshake). The pin is a CLAIM, never the barrier: the gateway's OWN reported account must be a PAPER account (DU/DF) AND the pin must EQUAL it — a reported live U… account, an unclassifiable one, or a pin that does not match the gateway's report is REFUSED and the socket closed. Redacted in every diagnostic" },
      { flag: "--out <p>", purpose: "also write the session Bus + order ledger JSON to this path" },
    ],
  },
  runs: {
    name: "runs",
    usage: "kestrel runs <list|show|compare> [flags]",
    purpose: "query the run registry",
    flags: [],
    subcommands: [
      { name: "list", purpose: "query recorded runs (see `kestrel runs list --help`)" },
      { name: "show", purpose: "show one recorded run + its plans (see `kestrel runs show --help`)" },
      { name: "compare", purpose: "diff two hosted `sim` receipts (see `kestrel runs compare --help`)" },
    ],
  },
  "runs list": {
    name: "runs list",
    usage: "kestrel runs list [flags]",
    purpose: "query recorded runs, optionally filtered",
    flags: [
      { flag: "--session-date <d>", purpose: "filter to one session date" },
      { flag: "--fill <m>", purpose: "filter to one fill model" },
      { flag: "--lineage <n>", purpose: "filter to one plan lineage" },
      { flag: "--db <p>", purpose: "registry path to read (default data/kestrel.db)" },
    ],
  },
  "runs show": {
    name: "runs show",
    usage: "kestrel runs show <id> [flags]",
    purpose:
      "show one run; <id> may be a 12-char prefix — a local `run`/`day` run (+ its plans), or a hosted `sim` receipt (+ its shareable proof URL). The hosted lineage resolves node-light (no Bun needed).",
    flags: [{ flag: "--db <p>", purpose: "registry path to read; the hosted receipts sit beside it (default data/kestrel.db)" }],
  },
  "runs compare": {
    name: "runs compare",
    usage: "kestrel runs compare <runA> <runB> [flags]",
    purpose:
      "diff two hosted `sim` receipts (by operation-id or 12-char prefix): which plan/strategy each used + the graded activity deltas (orders, fills, realized P&L), computed B − A. Node-light (no Bun) — the returning user's \"did my tweak help?\" verb.",
    flags: [{ flag: "--db <p>", purpose: "registry path; the hosted receipts sit beside it (default data/kestrel.db)" }],
  },
  lineage: {
    name: "lineage",
    usage: "kestrel lineage <name> [flags]",
    purpose: "show a plan lineage (the graded runs sharing a plan name)",
    flags: [{ flag: "--db <p>", purpose: "registry path to read (default data/kestrel.db)" }],
  },
  leaderboard: {
    name: "leaderboard",
    usage: "kestrel leaderboard [flags]",
    purpose: "the ranked leaderboard over recorded runs",
    flags: [
      { flag: "--since <ms>", purpose: "only runs at/after this epoch-ms" },
      { flag: "--mode <m>", purpose: "leaderboard mode" },
      { flag: "--db <p>", purpose: "registry path to read (default data/kestrel.db)" },
    ],
  },
  frame: {
    name: "frame",
    usage: "kestrel frame [<input.json>|-] [flags]",
    purpose: "render a Frame from a fixture/snapshot JSON (path arg, --input, or - / stdin)",
    flags: [
      { flag: "--input <p>", purpose: "the fixture JSON path (alternative to the positional)" },
      { flag: "--kind <briefing|wake>", purpose: "override the kind inference (presence of wakeIndex ⇒ wake)" },
      {
        flag: "--view <name[:panes]>",
        purpose:
          "render under a View: a shipped named View, or inline panes `name:pane[ arg…][,pane…]` (unknown/malformed ⇒ loud refusal, exit 2)",
      },
      {
        flag: "--seat <pm|strategist|watcher>",
        purpose: "acting pod seat — reads its founder View when one exists (precedence: --view > seat founder View > phase default)",
      },
      { flag: "--seat-views founder", purpose: "explicit founder-View opt-in assertion (requires --seat)" },
    ],
  },
  percept: {
    name: "percept",
    usage: "kestrel percept [<input.json>|-] [flags]",
    purpose: "render a Frame (alias of frame)",
    flags: [
      { flag: "--input <p>", purpose: "the fixture JSON path (alternative to the positional)" },
      { flag: "--kind <briefing|wake>", purpose: "override the kind inference (presence of wakeIndex ⇒ wake)" },
      {
        flag: "--view <name[:panes]>",
        purpose:
          "render under a View: a shipped named View, or inline panes `name:pane[ arg…][,pane…]` (unknown/malformed ⇒ loud refusal, exit 2)",
      },
      {
        flag: "--seat <pm|strategist|watcher>",
        purpose: "acting pod seat — reads its founder View when one exists (precedence: --view > seat founder View > phase default)",
      },
      { flag: "--seat-views founder", purpose: "explicit founder-View opt-in assertion (requires --seat)" },
    ],
  },
  sim: {
    name: "sim",
    usage: "kestrel sim [<scenario-slug>] [--plans <file>] [--budget <amount>]",
    purpose:
      "run a curated hosted market scenario, free — bare `sim` prints the menu; a lowercase scenario slug mints an anonymous trial, runs the hosted sim, and prints the graded story + a shareable proof URL",
    notes: [
      "author a --plans document: `kestrel card first-plan` (offline walkthrough); `kestrel card` for the full language card",
      "`--budget <amount>` declares a spend ceiling (USD) you OPT INTO — a metered draw past it pauses at the paid boundary and prints the settleable Offer (price, scope, settle URL, expiry) with exit 5 PAYMENT_REQUIRED. The CLI never auto-pays; payment stays an explicit, separate step.",
    ],
    flags: [
      { flag: "--plans <file>", purpose: "the Kestrel document to arm — .kestrel, .md (```kestrel blocks), or - (stdin); default: a labeled stand-down demo" },
      { flag: "--budget <amount>", purpose: "declare a spend ceiling in USD (a HONORED CAP) — reaches the paid boundary as DATA (exit 5); the CLI never auto-pays" },
      { flag: "--copy-token <t>", purpose: "an OPAQUE pass-through nonce forwarded verbatim to the platform at mint time (also KESTREL_COPY_TOKEN); the CLI never interprets it" },
    ],
  },
  prove: {
    name: "prove",
    usage: "kestrel prove [<scenario-slug>] [--plans <file>] [--copy-token <t>] [--no-residency]",
    purpose:
      "the zero-credential front door — bare `prove` needs no key, no config, and no prompt: it runs a default free scenario against the hosted anonymous trial, prints the graded story + a shareable proof URL, and exhausts to a residency snippet (how to keep the capability)",
    notes: [
      "Bare `prove` selects a pinned default free scenario and succeeds with exit 0 + a real proof URL — it never falls through to a menu or a prompt.",
      "`prove <slug>` runs that curated scenario; `--plans <file>` authors a strategy; `--no-residency` suppresses the persist-me snippet.",
      "author a --plans document: `kestrel card first-plan` (offline walkthrough); `kestrel card` for the full language card.",
    ],
    flags: [
      { flag: "--plans <file>", purpose: "the Kestrel document to arm — .kestrel, .md (```kestrel blocks), or - (stdin); default: a labeled stand-down demo" },
      { flag: "--copy-token <t>", purpose: "an OPAQUE pass-through nonce forwarded verbatim to the platform at mint time (also KESTREL_COPY_TOKEN); the CLI never interprets it" },
      { flag: "--no-residency", purpose: "do NOT print the residency (persist-me) snippet after the proof" },
    ],
  },
  replay: {
    name: "replay",
    usage: "kestrel replay <proofId> [--copy-token <t>]",
    purpose:
      "reproduce a proof this machine produced (re-runs the recorded scenario on a fresh anonymous trial and asserts the new grade is structurally reproducible + its signature re-verifies), or — for any other proof — degrade honestly to verifying the published proof",
    notes: [
      "A proof id present in your identity-bound run ledger (~/.kestrel/hosted-runs.jsonl, the store `runs list` reads — found from any directory) is REPRODUCED; any other id degrades to `verify` with a one-line notice.",
      "No-leak: the ledger never stores the strategy source, so a stranger's proof can never be re-executed — only verified.",
    ],
    flags: [
      { flag: "--copy-token <t>", purpose: "an OPAQUE pass-through nonce forwarded verbatim to the platform (also KESTREL_COPY_TOKEN); the CLI never interprets it" },
    ],
  },
  verify: {
    name: "verify",
    usage: "kestrel verify <https://kestrel.markets/proof/<id> | <proofId>> [--copy-token <t>]",
    purpose:
      `zero-trust re-verification of a PUBLISHED proof: fetch the proof + the published verify keys independently, and re-check the Ed25519 signature over \`${GRADE_SIGN_PREFIX}.<root>\` LOCALLY — it NEVER trusts the proof body's own \`verification.verified\``,
    notes: [
      "Verdicts (data on stdout): VERIFIED (a held key verifies the signature) · UNVERIFIED (a held key does not — tampering/corruption) · KEY_RETIRED (the proof's kid is not in the published set) · NOT_FOUND. Exit 0 only for VERIFIED.",
      "Mints no trial — two GETs, no funnel touch.",
    ],
    flags: [
      { flag: "--copy-token <t>", purpose: "an OPAQUE pass-through nonce forwarded verbatim on the read (also KESTREL_COPY_TOKEN); the CLI never interprets it" },
    ],
  },
  certify: {
    name: "certify",
    usage: "kestrel certify <https://kestrel.markets/proof/<id> | <proofId>> [--copy-token <t>]",
    purpose:
      "open recomputation (\"don't trust us — run it\", gate G10): fetch the proof's evidence bundle, re-project the Blotter LOCALLY with the shipped projector, and assert byte-identical reproduction of the hosted certified result — the determinism leg (L1 minimum), never a signature check",
    notes: [
      "Verdicts (data on stdout): REPRODUCED (the local re-projection reproduces the published Blotter byte-for-byte AND the local projector is pure) · MISMATCH (the local re-projection differs — genuine divergence) · NON_DETERMINISTIC (the local projector is not a pure function of the bus, fail-closed) · NOT_FOUND (no re-projectable free-tier bundle for this id). Exit 0 only for REPRODUCED.",
      "This is the L1 determinism leg (byte-identical Blotter re-projection), clearly labeled; full judged-Grade recomputation follows by L3. Distinct from `verify` (which re-checks the Ed25519 signature); `certify` re-runs the computation.",
    ],
    flags: [
      { flag: "--copy-token <t>", purpose: "an OPAQUE pass-through nonce forwarded verbatim on the read (also KESTREL_COPY_TOKEN); the CLI never interprets it" },
    ],
  },
  register: {
    name: "register",
    usage: "kestrel register [--name <n>] [--scopes <s,s>] [--no-git-identity] [--api <url>]",
    purpose: "self-register as an autonomous agent and store the minted capability (PLAT-ADR-0021 tier 1, no human in the loop)",
    notes: [
      "Mints a durable capability from the managed platform — the middle rung between an anonymous trial and a human",
      "account; no human is in the loop. It always routes to the managed remote (registration needs the network).",
      "Before sending, it DISCLOSES to stderr exactly what it will POST (the endpoint + any claimed git identity) and",
      "that the git identity is UNVERIFIED / freely spoofable — pass --no-git-identity to omit it.",
      "On success the returned capability is stored at ~/.kestrel/credentials.json (dir 0700, file 0600; override the",
      "home dir with $KESTREL_HOME). Subsequent --api calls present it instead of self-minting an anonymous trial.",
      "--scopes is a comma-separated scope REQUEST forwarded to the platform as `scope_request`; the platform decides",
      "what to grant and the GRANTED scopes are echoed on success (the CLI enforces no scope list of its own).",
      "Re-registering POSTs again and OVERWRITES the stored credential for that API host with the freshly minted one",
      "(the credential is host-scoped by its `api` field).",
    ],
    flags: [
      { flag: "--name <n>", purpose: "an optional display name to send with the registration" },
      { flag: "--scopes <s,s>", purpose: "a comma-separated scope REQUEST (sent as `scope_request`); the platform decides what to grant" },
      { flag: "--no-git-identity", purpose: "do NOT send the claimed (unverified) git user.name/user.email" },
      { flag: "--api <url>", purpose: "the managed base to register against (`--api default` ⇒ api.kestrel.markets; also KESTREL_API)" },
    ],
  },
  whoami: {
    name: "whoami",
    usage: "kestrel whoami",
    purpose: "inspect the stored credential without hand-parsing credentials.json",
    notes: [
      "Reads ~/.kestrel/credentials.json ($KESTREL_HOME-overridable) and prints the identity (agent_id / subject),",
      "the API host the credential is bound to, the token type, the granted scopes, the expiry with a display-clock",
      "STATUS (valid / expiring / EXPIRED — display only, never a token or Session clock), whether an Ed25519 keypair",
      "is enrolled, and the on-disk file path. Pure-local: ZERO network. No stored credential → exit 3 (NOT_FOUND)",
      "naming how to `register`. When the credential is near/after expiry the status line points at `kestrel refresh`.",
    ],
    flags: [],
  },
  secrets: {
    name: "secrets",
    usage: "kestrel secrets set <KEY> [--stdin] | kestrel secrets list | kestrel secrets unset <KEY> | kestrel secrets path",
    purpose: "manage operator/BYOK secrets in the owner-only ~/.kestrel/.env store",
    notes: [
      "The store is a plain dotenv file at ~/.kestrel/.env ($KESTREL_HOME-overridable), dir 0700 / file 0600, written",
      "atomically. Resolution precedence is process.env FIRST, the file as the fallback — so CI/containers can inject",
      "a secret without touching disk. Pure-local: ZERO network.",
      "`set` reads the value from an interactive prompt (echo suppressed) or from stdin with --stdin. A value passed",
      "POSITIONALLY is REFUSED (code SECRET_VALUE_IN_ARGV, exit 2) and nothing is written: argv leaks into shell",
      "history, `ps` output, and process logs. A non-interactive session without --stdin is likewise refused",
      "(SECRET_NO_INPUT) rather than hanging or storing an empty value.",
      "A key name must be a canonical uppercase env name [A-Z_][A-Z0-9_]* or it is refused (SECRET_INVALID_KEY_NAME),",
      "and a *LIVE* broker key name — any `LIVE` name segment, matched case-insensitively and segment-bounded",
      "(/(^|_)LIVE(_|$)/i, so ALPACA_LIVE_KEY is caught but DELIVERY_URL is not) — is refused (SECRET_LIVE_KEY_REFUSED)",
      "with a wrangler pointer: the ~/.kestrel/.env store is PAPER-ONLY (OSS-ADR-0054 §5); live keys belong in",
      "`wrangler secret`. Both refuse fail-closed, BEFORE any value is read, so nothing is written.",
      "`list` prints NAMES ONLY — no output path of this command ever prints a value. `unset` on an absent key is",
      "exit 3 (NOT_FOUND), never a silent success. `path` prints the store location.",
      "The SAME store is exposed on the local MCP face (kestrel.secrets.set/unset/list) so an agent can self-install",
      "its BYOK keys once; there, too, list is names-only and NO face ever returns a stored value.",
    ],
    flags: [{ flag: "--stdin", purpose: "read the value for `set` from stdin instead of prompting (one trailing newline is stripped)" }],
  },
  refresh: {
    name: "refresh",
    usage: "kestrel refresh",
    purpose: "renew the stored durable capability before it lapses",
    notes: [
      "Projects the platform's `POST /capabilities/refresh` primitive onto the CLI face: a durable capability lives",
      "1h, and CLI-only fleets previously had no renewal path (renewal existed only over MCP/HTTP). Authenticated by",
      "the CURRENT stored capability, host-scoped to the credential's own `api`. The fresh token is minted SERVER-SIDE",
      "— the CLI adds NO client RNG and no client wall-clock into the token (determinism doctrine) — then overwrites",
      "the stored credential IN PLACE (preserving agent_id / subject / keypair enrollment / claimed attribution).",
      "No stored credential → exit 3 (NOT_FOUND) naming how to `register`; a server refusal (a capability past its",
      "refresh window, a revoked family) surfaces the server's problem+json detail, never a bare status.",
    ],
    flags: [],
  },
  agent: {
    name: "agent",
    usage: "kestrel agent [--api <url>]   (reads JSONL requests on stdin, writes JSONL responses on stdout)",
    purpose: "the machine/agent face — a JSONL request/response protocol projecting the Kestrel SDK",
    protocol: [
      "Read one JSON request envelope per line on stdin (`{ \"op\": <name>, ...args }`); each is forwarded to the",
      "Kestrel SDK and answered with one line on stdout: `{ \"v\": \"kestrel.agent/v1\", \"kind\": <op>, \"value\": <payload> }`.",
      "stdout is a PURE protocol channel (only versioned JSONL); ALL diagnostics go to stderr.",
      "A refusal is a typed line on STDERR — `{ \"v\": \"kestrel.agent/v1\", \"kind\": \"refusal\", \"code\": <stable>, \"message\": ... }`",
      "(match on `code`, not prose) — and the process exits nonzero; the payload channel never carries a fabricated ok.",
      "Transport: no --api ⇒ LOCAL (in-process, no network); `--api <url>` / `--api default` / KESTREL_API ⇒ the managed",
      "remote. The request stream and the protocol objects are byte-identical across transports.",
      "Start with `describe` (needs no session) to learn every op's ARGUMENT + response shapes — the `{kind:…}` unions a",
      "`subject` / `advance.response` must carry, including the exact accepted field name `document`; then `catalog` to discover subjects.",
      "The session verbs (start/advance/revise/submit/resume/finalize) require an `openSession` first and refuse (code `no-session`)",
      "otherwise. An unknown op refuses (code `unknown-op`) and names the valid ops; a malformed argument object refuses (code `bad-arg`) and NAMES the expected shape.",
      "Exit codes: 0 when every request line was answered; 2 (USAGE) on a malformed line, an unknown op, or an empty request",
      "stream; 1 (GENERIC) on a domain refusal from the SDK mid-stream. (Full taxonomy: `kestrel --help`.)",
    ],
    ops: [
      { op: "describe", args: "", purpose: "emit this protocol's own op/argument/response shapes (self-description) — the `{kind:…}` shapes for `subject` and `advance.response`, incl. the exact accepted field name `document`; needs no session (call it first)" },
      { op: "catalog", args: "", purpose: "list the managed capability catalog — the subject-discovery op; needs no session" },
      { op: "validate", args: "document", purpose: "validate a Kestrel document string" },
      { op: "openSession", args: "subject, document?", purpose: "open a Session over a subject; pass `document` (Kestrel plan text) as the customer strategy the managed backend requires (a bare catalog openSession is refused by the author-no-strategy fence — ADR-0012); emits { gated:false, sessionId } or { gated:true, payment } (a 402/Offer is DATA)" },
      { op: "start", args: "", purpose: "start the opened Session (requires a prior openSession)" },
      { op: "advance", args: "response", purpose: "advance the Session with an authored response" },
      { op: "revise", args: "response", purpose: "revise the current turn with an authored response" },
      { op: "submit", args: "response", purpose: "submit a bound response" },
      { op: "resume", args: "after?", purpose: "replay the Session event stream from an optional cursor" },
      { op: "finalize", args: "", purpose: "finalize the Session" },
      { op: "grade", args: "blotters[]", purpose: "grade the given blotters" },
      { op: "artifact", args: "ref", purpose: "fetch an artifact by ref" },
      { op: "resumeOperation", args: "operationId, after?", purpose: "resume a durable managed Operation from its handle" },
    ],
    example: {
      title: "discover the catalog (LOCAL — no network):",
      command: "echo '{\"op\":\"catalog\"}' | kestrel agent",
      output: '{"v":"kestrel.agent/v1","kind":"catalog","value":[ /* CatalogEntry[] */ ]}',
    },
    flags: [
      { flag: "--api <url>", purpose: "route to the managed remote transport (`--api default` ⇒ api.kestrel.markets; also KESTREL_API); omit for local" },
    ],
  },
  mcp: {
    name: "mcp",
    usage: "kestrel mcp [--local | --api <url>]   (serves MCP — newline-delimited JSON-RPC 2.0 — on stdin/stdout until stdin ends)",
    purpose: "the MCP stdio server — the same tool/resource surface the hosted MCP server projects, as a local stdio drop-in",
    protocol: [
      "Speaks MCP (JSON-RPC 2.0, one frame per line) over stdin/stdout: initialize, tools/list, tools/call,",
      "resources/list, resources/read. One tool per SDK verb (validate, the Session lifecycle, grade, operation",
      "resume); every tool result carries the SDK's protocol object VERBATIM in `structuredContent`.",
      "Configure an MCP client with: { \"command\": \"npx\", \"args\": [\"kestrel.markets\", \"mcp\"] }.",
      "Transport: DEFAULT is the managed remote (api.kestrel.markets; `--api <url>` selects another base) — pure",
      "fetch, runs under plain node. `--local` serves the in-process engine instead (requires the Bun runtime;",
      "refuses loud, exit 4, without one). `--local` with `--api` is a contradiction and refuses (USAGE).",
      "stdout is the MCP wire ONLY; the process serves until stdin ends (the MCP client owns the lifecycle).",
    ],
    flags: [
      { flag: "--local", purpose: "serve the in-process engine instead of the managed remote (requires bun; contradicts --api)" },
      { flag: "--api <url>", purpose: "the managed remote base (`--api default` ⇒ api.kestrel.markets; also KESTREL_API); this is the default transport" },
    ],
  },
  parse: {
    name: "parse",
    usage: "kestrel parse <file> [--arm] [--bus <tape> | --instruments <SYM[,SYM…]>]",
    purpose: "parse + validate a plan document",
    notes: [
      "parse-green is not arm-green: --arm also runs the engine's arm-time checks (series vocabulary, envelope) — the refusal shown is byte-identical to what a run raises",
      "learn the language: `kestrel card` (the shipped agent language card, offline; `kestrel card first-plan` for a walkthrough)",
    ],
    flags: [
      { flag: "--arm", purpose: "also validate at the ARM tier (needs a session context via --bus or --instruments; refuses fail-closed without one)" },
      { flag: "--bus <tape>", purpose: "derive the session instruments from the tape's META, exactly as `run` resolves them (implies --arm)" },
      { flag: "--instruments <SYM[,SYM…]>", purpose: "name the session instruments directly, validated as option-underliers (implies --arm)" },
    ],
  },
  validate: {
    name: "validate",
    usage: "kestrel validate <file> [--arm] [--bus <tape> | --instruments <SYM[,SYM…]>]",
    purpose: "parse + validate a plan document (alias of parse)",
    notes: [
      "parse-green is not arm-green: --arm also runs the engine's arm-time checks (series vocabulary, envelope) — the refusal shown is byte-identical to what a run raises",
      "learn the language: `kestrel card` (the shipped agent language card, offline; `kestrel card first-plan` for a walkthrough)",
    ],
    flags: [
      { flag: "--arm", purpose: "also validate at the ARM tier (needs a session context via --bus or --instruments; refuses fail-closed without one)" },
      { flag: "--bus <tape>", purpose: "derive the session instruments from the tape's META, exactly as `run` resolves them (implies --arm)" },
      { flag: "--instruments <SYM[,SYM…]>", purpose: "name the session instruments directly, validated as option-underliers (implies --arm)" },
    ],
  },
  print: {
    name: "print",
    usage: "kestrel print <file>",
    purpose: "canonical re-print of a plan document (print(parse(text)) is byte-stable)",
    notes: ["learn the language: `kestrel card` (the shipped agent language card, offline; `kestrel card first-plan` for a walkthrough)"],
    flags: [],
  },
  card: {
    name: "card",
    usage: "kestrel card [<topic>] [--json]",
    purpose: "print the shipped agent language card / first-steps walkthroughs from the INSTALLED package, offline",
    notes: [
      "Bare `kestrel card` prints the compact agent language card (docs/public/card.md) — the version-coupled",
      "reference an agent loads before authoring Kestrel. It resolves from the installed package: zero network,",
      "works offline from any directory.",
      "`--json` (bare) emits the machine twin card.json verbatim; `kestrel card <topic> --json` wraps a topic's",
      "markdown in a { schema, topic, markdown } envelope.",
      "A `<topic>` prints docs/public/<topic>.md — start with `first-plan` (author your first document), then",
      "`first-wake`, `first-frame`, `first-grade`; `adopt` walks the fill→adopt→exit escape hatch (carry a held",
      "leg across a wake with `ARM … foreach held leg`); `overview`, `lexical-core`, `grammar-fences`,",
      "`cli-reference`, `api-reference`, `agent-protocol`, `capability-truth`, and `status` go deeper. An unknown",
      "topic is refused (exit 2) and the refusal lists every valid topic.",
    ],
    flags: [
      { flag: "--json", purpose: "bare: emit card.json (the machine twin) verbatim; with a topic: a { schema, topic, markdown } envelope" },
    ],
  },
};

const BOLD = "[1m";
const RESET = "[0m";

/** `version` — prints `kestrel <version>`; in json `{schema, version}`. Exit 0. */
export function versionCommand(ctx: OutputCtx): number {
  if (ctx.mode === "json") {
    process.stdout.write(JSON.stringify({ schema: "kestrel.version/v1", version: VERSION }) + "\n");
  } else if (ctx.mode === "text") {
    process.stdout.write(`kestrel ${VERSION}\n`);
  } else {
    process.stdout.write(`kestrel ${VERSION}\n`);
  }
  return 0;
}

/**
 * Resolve the command path (`["runs","list"]`, `["run"]`, …) a `--help`/`help` request targets:
 * strip a leading `help` word, then take the leading NON-flag tokens. Returns the {@link SUBHELP}
 * key if one exists (joining `runs`+sub), else `undefined` (→ the top-level usage).
 */
function helpKeyFor(rest: readonly string[]): string | undefined {
  const words = rest[0] === "help" ? rest.slice(1) : rest;
  const path: string[] = [];
  for (const w of words) {
    if (w.startsWith("-")) break;
    path.push(w);
  }
  const [a, b] = path;
  if (a === undefined) return undefined;
  if (a === "runs") {
    if (b === "list" || b === "show" || b === "compare") return `runs ${b}`;
    return "runs";
  }
  return a in SUBHELP ? a : undefined;
}

/**
 * `help` — the usage inventory. With no command word it prints the top-level usage; a command word
 * (`kestrel <cmd> --help` or `kestrel help <cmd>`) prints that command's own usage + flags. Exit 0.
 */
export function helpCommand(ctx: OutputCtx, rest: readonly string[] = []): number {
  const key = helpKeyFor(rest);
  if (key !== undefined) return renderCommandHelp(ctx, SUBHELP[key]!);
  return renderTopHelp(ctx);
}

/** The top-level usage inventory: commands, global flags, and the exit-code taxonomy. */
function renderTopHelp(ctx: OutputCtx): number {
  if (ctx.mode === "json") {
    process.stdout.write(
      JSON.stringify({
        schema: "kestrel.help/v1",
        version: VERSION,
        commands: COMMANDS.map((c) => ({ name: c.name, args: c.args, purpose: c.purpose })),
        globalFlags: GLOBALS.map((g) => ({ flag: g.flag, purpose: g.purpose })),
        exitCodes: EXIT_CODES.map((e) => ({ code: e.code, exit: e.exit, when: e.when })),
      }) + "\n",
    );
    return 0;
  }

  const color = ctx.mode === "human" && ctx.color;
  const header = (s: string): string => (color ? `${BOLD}${s}${RESET}` : s);
  const lines: string[] = [];
  lines.push(`kestrel ${VERSION} — a typed, token-efficient language + runtime for agentic trading`);
  lines.push("");
  lines.push(header("usage:"));
  lines.push("  kestrel <command> [args] [--json|--format <m>|--agent] [--color <m>] [--no-color]");
  lines.push("  kestrel <command> --help    per-command usage + its own flags");
  lines.push("");
  lines.push(header("commands:"));
  const wName = Math.max(...COMMANDS.map((c) => `${c.name} ${c.args}`.length));
  for (const c of COMMANDS) {
    const left = `${c.name} ${c.args}`.trimEnd();
    lines.push(`  ${left.padEnd(wName)}  ${c.purpose}`);
  }
  lines.push("");
  lines.push(header("global flags:"));
  const wFlag = Math.max(...GLOBALS.map((g) => g.flag.length));
  for (const g of GLOBALS) {
    lines.push(`  ${g.flag.padEnd(wFlag)}  ${g.purpose}`);
  }
  lines.push("");
  lines.push(header("exit codes:"));
  const wCode = Math.max(...EXIT_CODES.map((e) => `${e.exit} ${e.code}`.length));
  for (const e of EXIT_CODES) {
    lines.push(`  ${`${e.exit} ${e.code}`.padEnd(wCode)}  ${e.when}`);
  }
  lines.push("");
  lines.push("learn the language (offline, shipped in this package): `kestrel card` — `kestrel card first-plan` for a first-steps walkthrough");
  process.stdout.write(lines.join("\n") + "\n");
  return 0;
}

/** One command's own usage + flags. json = structured; text/human = a printed usage. Exit 0. */
function renderCommandHelp(ctx: OutputCtx, spec: SubHelp): number {
  if (ctx.mode === "json") {
    process.stdout.write(
      JSON.stringify({
        schema: "kestrel.help.command/v1",
        version: VERSION,
        command: spec.name,
        usage: spec.usage,
        purpose: spec.purpose,
        flags: spec.flags.map((f) => ({ flag: f.flag, purpose: f.purpose })),
        ...(spec.subcommands !== undefined
          ? { subcommands: spec.subcommands.map((s) => ({ name: s.name, purpose: s.purpose })) }
          : {}),
        ...(spec.notes !== undefined ? { notes: spec.notes.join(" ") } : {}),
        ...(spec.protocol !== undefined ? { protocol: spec.protocol.join(" ") } : {}),
        ...(spec.ops !== undefined
          ? { ops: spec.ops.map((o) => ({ op: o.op, args: o.args, purpose: o.purpose })) }
          : {}),
        ...(spec.example !== undefined
          ? { example: { title: spec.example.title, command: spec.example.command, output: spec.example.output } }
          : {}),
      }) + "\n",
    );
    return 0;
  }

  const color = ctx.mode === "human" && ctx.color;
  const header = (s: string): string => (color ? `${BOLD}${s}${RESET}` : s);
  const lines: string[] = [];
  lines.push(`kestrel ${spec.name} — ${spec.purpose}`);
  lines.push("");
  lines.push(header("usage:"));
  lines.push(`  ${spec.usage}`);
  if (spec.notes !== undefined && spec.notes.length > 0) {
    lines.push("");
    lines.push(header("notes:"));
    for (const n of spec.notes) lines.push(`  ${n}`);
  }
  if (spec.protocol !== undefined && spec.protocol.length > 0) {
    lines.push("");
    lines.push(header("protocol:"));
    for (const p of spec.protocol) lines.push(`  ${p}`);
  }
  if (spec.ops !== undefined && spec.ops.length > 0) {
    lines.push("");
    lines.push(header("operations (op → payload; first is the discovery op):"));
    const wOp = Math.max(...spec.ops.map((o) => (o.args.length > 0 ? `${o.op} { ${o.args} }` : o.op).length));
    for (const o of spec.ops) {
      const left = o.args.length > 0 ? `${o.op} { ${o.args} }` : o.op;
      lines.push(`  ${left.padEnd(wOp)}  ${o.purpose}`);
    }
  }
  if (spec.example !== undefined) {
    lines.push("");
    lines.push(header("example:"));
    lines.push(`  ${spec.example.title}`);
    lines.push(`    $ ${spec.example.command}`);
    lines.push(`    ${spec.example.output}`);
  }
  if (spec.subcommands !== undefined && spec.subcommands.length > 0) {
    lines.push("");
    lines.push(header("subcommands:"));
    const wSub = Math.max(...spec.subcommands.map((s) => s.name.length));
    for (const s of spec.subcommands) {
      lines.push(`  ${s.name.padEnd(wSub)}  ${s.purpose}`);
    }
  }
  if (spec.flags.length > 0) {
    lines.push("");
    lines.push(header("flags:"));
    const wFlag = Math.max(...spec.flags.map((f) => f.flag.length));
    for (const f of spec.flags) {
      lines.push(`  ${f.flag.padEnd(wFlag)}  ${f.purpose}`);
    }
  }
  lines.push("");
  lines.push("global flags + exit codes: `kestrel --help`");
  process.stdout.write(lines.join("\n") + "\n");
  return 0;
}
