UNPKG

kestrel.markets

Version:

A typed, token-efficient language + runtime for agentic trading: agents author bounded plans, the runtime fires them at the tick. CLI + typed library + MCP server.

226 lines (191 loc) 14.7 kB
# CLI reference — the `kestrel` command The package ships a `kestrel` binary: a router over a small set of verbs. Every verb renders to `stdout` as a pure payload channel and reports failures on `stderr` with a stable machine-parseable `code` and a nonzero exit — agents match on `code`, not prose. This page consolidates the verbs, their key flags, the runtime each requires, and the exit-code taxonomy. For each verb's full flag list, run `kestrel <verb> --help`, which stays on the light runtime (it never loads the heavy runtime just to print usage). For the programmatic surface, see the [API reference](./api-reference.md). ```bash kestrel <command> [args] [global flags] kestrel <command> --help # that one command's usage + its own flags ``` ## Light vs heavy verbs (the runtime requirement) The verbs split on the runtime they need: - **Light verbs run on Node** (or Bun, or a Worker). They parse, validate, print, render a Frame, report the version, or print help — no native dependency. These are `parse`, `validate`, `print`, `frame`, `percept`, `version`, and `help`. - **Heavy verbs execute on Bun** (and, for the registry, the native store). They run graded sessions and query the run registry. These are `run`, `day`, `runs`, `lineage`, `leaderboard`, and `agent`. You do **not** need to install Bun: the package bundles it, so `npx kestrel.markets run …` works on a Node-only host. The bundled binary is structured so every light verb loads without the heavy runtime on the import graph; the heavy runtime is pulled in only when a heavy verb is actually invoked. ### How the runtime is found The published `kestrel` bin is a Node launcher. A light verb is executed in-process under whatever runtime you invoked it with. A heavy verb, when you are already on Bun, likewise runs in-process; under Node the launcher **re-execs** the CLI onto a Bun binary with your argv unchanged and propagates the child's exit code (a signalled child surfaces as `128 + signo`, so Ctrl-C is still 130). Grading therefore always runs on Bun by construction. Every candidate is **probed** (`bun --version` must exit 0 with a version at or above the `engines.bun` floor) before it is used, so a broken shim on `PATH` — or a Bun older than the floor, which would otherwise die deep in the registry with a misleading SQLite error — is skipped rather than exec'd. Bun is looked up in this order: 1. `$KESTREL_BUN` — an explicit path to a Bun binary. This is a **pin**: if it does not probe clean, the CLI refuses (exit 4). It never falls back to a Bun you did not name. 2. Any `bun` on your `$PATH`, in order. 3. The shared runtime cache: `$KESTREL_HOME/.kestrel/runtime/bin/bun` (else `~/.kestrel/runtime/…`). The first working Bun the launcher resolves is **materialized** here once — its bytes are copied (a hardlink when same-filesystem, a real copy on a cross-device home), so every other checkout / install on the same machine reuses it instead of probing (or installing) its own copy. The cache holds independent bytes, **never a symlink** into an install's `node_modules`, so it keeps working after that install is deleted (worktree cleanup) or was skipped (`--omit=optional`) — the exact cases the shared cache exists to serve. Populating it is best-effort and atomic (a temp file published by rename), so concurrent installs never see a half-written binary. 4. The bundled `bun` optional dependency. Only when **none** of these yields a working Bun does a heavy verb fault **loud** with `RUNTIME_UNAVAILABLE` (exit 4) — never a raw resolution stack. | Variable | Effect | | --- | --- | | `KESTREL_BUN` | absolute path to the Bun binary the launcher must re-exec onto (a pin: no fallback) | | `KESTREL_HOME` | base dir for the shared `.kestrel/runtime` runtime cache (and the credential store); defaults to `~` | | `KESTREL_NO_BUNDLED_BUN=1` | ignore the bundled dependency (use only `$KESTREL_BUN` / `PATH` / the shared runtime cache) | **Install weight.** The bundled runtime is not small, and the cost is platform-dependent — npm keeps every platform variant it downloaded, and only Bun's postinstall picks the right one. Measured (`bun` + `@oven` on disk): | Host | Installed | | --- | --- | | macOS arm64 | ≈ 61 MB | | Linux arm64 (glibc) | ≈ 172 MB | | Linux arm64 (musl / Alpine) | ≈ 259 MB | | Linux x64 | ≈ 347 MB (npm retains all four x64 variants) | The published tarball stays ~2 MB; this is install weight, not download weight. `npm i kestrel.markets --omit=optional` skips the runtime in a **project** install: light verbs are unaffected, and heavy verbs then require a Bun on `PATH`, in `KESTREL_BUN`, or in the shared `~/.kestrel/runtime` cache (and refuse with exit 4 if none exists). On a shared machine this is the recommended layout — install once with the runtime, `--omit=optional` everywhere else, and every checkout reuses the one cached Bun. npm ignores `--omit=optional` for **global** (`-g`) installs and fetches the runtime regardless — there, set `KESTREL_NO_BUNDLED_BUN=1` to ignore the bundled copy at run time. ## Verbs | Verb | Runtime | Purpose | Key flags | | --- | --- | --- | --- | | `version` | Node | print the version and exit | — | | `help [command]` | Node | print usage, or one command's usage | — | | `parse <file>` | Node | parse + validate a plan document | `--arm`, `--bus`, `--instruments` | | `validate <file>` | Node | parse + validate (alias of `parse`) | `--arm`, `--bus`, `--instruments` | | `print <file>` | Node | canonical re-print of a plan document | — | | `frame <input.json>` | Node | render a Frame from a fixture/snapshot JSON | `--input`, `--kind`, `--view`, `--seat`, `--seat-views` | | `percept <input.json>` | Node | render a Frame (alias of `frame`) | `--input`, `--kind`, `--view`, `--seat`, `--seat-views` | | `run` | Bun | grade a session against a recorded bus, auto-record | `--bus`, `--plans`, `--fill`, `--r-usd` | | `day` | Bun | stepped / wake session (wake handshake + supersession), auto-record | `--bus`, `--dir`, `--fill`, `--r-usd` | | `runs list` | Bun | query recorded runs | `--session-date`, `--fill`, `--lineage`, `--db` | | `runs show <run_id>` | Bun | show one recorded run + its plans | `--db` | | `lineage <name>` | Bun | show a plan lineage | `--db` | | `leaderboard` | Bun | ranked leaderboard over recorded runs | `--since`, `--mode`, `--db` | | `agent` | Bun | machine mode: a JSONL request/response stream over the SDK | `--api` (transport) | There is **no `grade` verb.** Grading is not a standalone step: it happens inside `run` and `day`, which grade the session as they run it and record the result. ### Light verbs - **`version`** — prints `kestrel <version>`; in `--json`, `{ schema, version }`. Exit 0. - **`help [command]`** — with no command word, prints the top-level usage (commands, global flags, and the exit-code taxonomy). With a command word (`kestrel <cmd> --help` or `kestrel help <cmd>`), prints that command's own usage and flags. Exit 0. - **`parse` / `validate <file>`** — parse and validate a plan document. A parse failure exits in the `USAGE` bucket (exit 2) carrying the `PARSE` code from the language layer. Parse-green is **not** arm-green: `--arm` (or `--bus <tape>` / `--instruments <SYM[,SYM…]>`, which imply it) additionally runs the engine's arm tier — the same `armDocument` a real `run` raises, so the refusal shown is byte-identical. `--bus` derives the session instruments from the tape's META exactly as `run` resolves them; `--instruments` names them directly, validated as option-underliers; bare `--arm` with no context source refuses fail-closed. An arm-tier refusal also exits in the `USAGE` bucket (exit 2), with the distinct codes `ARM` (the document refuses to arm), `ARM_CONTEXT` (no derivable session context), or `BUS` (the `--bus` tape is unusable). - **`print <file>`** — the canonical re-print; `print(parse(text))` is byte-stable. - **`frame` / `percept [<input.json>|-]`** — render a Frame from a fixture/snapshot JSON, read from a path argument, `--input <p>`, or `-` / stdin. `--kind <briefing|wake>` overrides the inference (the presence of a wake index implies a wake Frame). **View addressing** (local backend): `--view <name[:panes]>` selects the View the Frame renders under — a bare `name` resolves against the shipped named-View registry (the founder Views, e.g. `strategist-open`, `watcher-wake`, `scan-fire`), while `name:pane[ arg…][,pane…]` defines an inline View in the language's own pane syntax (e.g. `--view "custom:delta,tape 5m"`). `--seat <pm|strategist|watcher>` binds the acting pod seat, so a seat with a founder seed for the frame kind reads its founder View; `--seat-views founder` is the explicit opt-in assertion of that same behaviour (requires `--seat`). Resolution precedence is strict: explicit `--view` > seat founder View > phase default. **Fail-closed:** an unknown View name, unknown seat, malformed pane/arg token, or a pane the catalog refuses is a loud typed `USAGE` error (exit 2) — never a silent fall-through to the phase-default panes. The remote backend has no percept route and fails closed as before. ### Heavy verbs - **`run`** — grade a plan document against a recorded market bus, print the report, and auto-record the run. Required: `--bus <p>` (the recorded bus), `--plans <p>` (the plan document — a `.kestrel` file, or a Markdown file with fenced kestrel blocks), `--fill <m>` (`strict-cross-v1` or `maker-fair-v1`), and `--r-usd <n>` (the dollar value of 1R, positive). Optional: `--tau-hours`, `--hazard-params` (maker-fair only), `--out` (also write the report JSON), `--db` (registry path), `--no-record` (grade only). - **`day`** — run a stepped / wake session with a wake handshake and document supersession, and auto-record it. Required: `--bus`, `--dir <d>` (the handshake directory the session authors plans into), `--fill`, `--r-usd`. **The default terminates:** with no author wait declared, `day` runs from whatever handshake documents are staged in `--dir`, and a missing document refuses promptly with `NO_AUTHOR`/exit 6, naming the file it wanted — it never rides a 900s poll for a reply the caller never declared. Optional: `--wakes <HH:MM,…>` (explicit wake clock times, ET), `--await-author` (declare an out-of-band author WILL reply: block at each handshake up to `--max-wait`, then abandon loudly with `HANDSHAKE_ABANDONED`/exit 6), `--max-wait <s>` (seconds to wait for each handshake reply under `--await-author`, default 900; supplying it implies `--await-author`), `--no-author` (the explicit form of the default: documents are pre-staged in `--dir`, a miss is the same prompt `NO_AUTHOR`/exit 6 refusal), `--structural` (structural-cadence wakes), plus the same `--tau-hours` / `--hazard-params` / `--out` / `--db` / `--no-record` as `run`. `--await-author` and `--no-author` together are a usage error (contradictory declarations). Every handshake block is announced on stderr before it is entered and heartbeats while it waits, so a blocked day is never mistaken for a hung one. - **`runs list`** — query recorded runs, optionally filtered by `--session-date`, `--fill`, or `--lineage`; `--db <p>` selects the registry path. - **`runs show <run_id>`** — show one recorded run and its plans; `<run_id>` may be a 12-hex prefix. A missing run exits `NOT_FOUND` (exit 3). - **`lineage <name>`** — show a plan lineage (the graded runs sharing a plan name). A missing lineage exits `NOT_FOUND` (exit 3). - **`leaderboard`** — the ranked leaderboard over recorded runs; `--since <ms>` and `--mode <m>` narrow it. - **`agent`** — the machine/agent mode: reads a JSONL request stream on stdin (one verb envelope per line) and writes versioned protocol JSONL on stdout, forwarding each verb to the SDK. It is a thin projection of the SDK surface (catalog / validate / the Session lifecycle / grade / artifact / operation continuation). `--api` selects the transport only: bare or absent uses the local transport; `--api <url>` (or `KESTREL_API`) uses the remote transport. ## Global flags These apply to every verb (they are peeled before the command runs): | Flag | Effect | | --- | --- | | `--json` | machine JSON output, even on a TTY | | `--format <json\|text\|human>` | force a render mode | | `--agent` | agent mode: text, no color, non-interactive | | `--color <always\|never\|auto>` | colorize (human mode only) | | `--no-color` | disable color (`NO_COLOR`-equivalent) | | `-h`, `--help` | print usage and exit 0 (per command: `kestrel <cmd> --help`) | | `-V`, `--version` | print version and exit 0 | `stdout` stays a pure payload channel: an error is written to `stderr` in the active mode, so an agent's `JSON.parse(stdout)` never sees a half-written object followed by an error. ## Exit-code taxonomy Exit `0` iff the command truly succeeded; every error path is nonzero. The `code` strings are stable across releases — match on `code`, not on the message. | Code | Exit | When | | --- | --- | --- | | `OK` | 0 | the command succeeded | | `GENERIC` | 1 | an unexpected / uncaught error (`KESTREL_DEBUG=1` dumps the stack) | | `USAGE` | 2 | bad/unknown flag, missing required flag, bad `--format`, a parse failure (code `PARSE`), or a `parse`/`validate --arm` refusal (codes `ARM` / `ARM_CONTEXT` / `BUS`) | | `NOT_FOUND` | 3 | `runs show <id>` / `lineage <name>` / an input file is absent | | `RUNTIME_UNAVAILABLE` | 4 | no working Bun could be found (or the native store failed to load) | | `PAYMENT_REQUIRED` | 5 | a remote verb returned a 402/Offer (surfaced as data) | | `SIGINT` | 130 | interrupted (Ctrl-C) | A parse failure is a `USAGE` exit (2) carrying the more specific `PARSE` code raised by the language layer; a `parse`/`validate --arm` refusal shares that exit bucket with its own specific codes (`ARM`, `ARM_CONTEXT`, `BUS`). `RUNTIME_UNAVAILABLE` is reserved for a host where the launcher found **no** working Bun in any tier — `$KESTREL_BUN`, `PATH`, or the bundled dependency; it fails closed with a hint naming those escape hatches, never a raw stack. A heavy verb re-exec'd onto Bun returns the child's own exit code, and a signalled child surfaces as `128 + signo`. ## See also - **[API reference](./api-reference.md)** — the eight published entry points and their types. - **[Capability truth](./capability-truth.md)** / **[status page](./status.md)** — what each verb's runtime actually does today, graded honestly. - **[Overview](./overview.md)** — what Kestrel is and the four statement kinds.