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.
120 lines (110 loc) • 5.78 kB
text/typescript
/**
* # cli/commands/card — `card` (LIGHT, node-runnable, zero-network)
*
* The authoring on-ramp (kestrel-nde6). The published package SHIPS the complete agent reference —
* `docs/public/card.md` (the compact, version-coupled agent language card), its machine twin
* `card.json`, and the first-steps walkthroughs (`first-plan.md`, `first-wake.md`, …) — but nothing
* in the CLI surfaced them: `kestrel card` was an unknown command and no help pointed at the plan
* syntax. An agent with the teaching material ALREADY on disk could not find it. This verb prints
* that shipped material from the INSTALLED package, offline, from any cwd:
*
* - `kestrel card` → `docs/public/card.md` (the agent language card).
* - `kestrel card --json` → `docs/public/card.json` (the machine twin) verbatim.
* - `kestrel card <topic>` → `docs/public/<topic>.md` (a shipped walkthrough / reference).
* - `kestrel card <topic> --json` → a JSON envelope carrying that markdown.
* - an unknown topic → a typed USAGE refusal (exit 2) that LISTS the valid topics.
*
* Imports only node built-ins (`fs`/`path`/`url`) — no `bun:*`, no network — so it runs under plain
* node and works with zero connectivity in any directory. The valid-topic set is ENUMERATED from the
* shipped `docs/public/` directory (every `*.md`), never a hand-kept list that could drift from what
* the package actually ships; the refusal names exactly what a real install carries.
*
* PACKAGING RESOLUTION — two layouts, one ascending search. Running from a repo checkout the module
* lives at `src/cli/commands/card.ts` (root three levels up); in the published package the whole CLI
* is bundled into `dist/cli.js` (root one level up). `resolveDocsPublicDir` ascends from this
* module's own directory until it finds a `docs/public/card.md`, so BOTH layouts resolve without a
* hard-coded depth. `docs/public` is in package.json `files`, so the directory is present on a real
* install (a `bun test` guard asserts that, so this verb can never 404 on a shipped tree).
*/
import { existsSync, readFileSync, readdirSync } from "node:fs";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import type { OutputCtx } from "../context.ts";
import { parseArgs } from "../args.ts";
import { CliError, EXIT } from "../errors.ts";
/** The default document `kestrel card` (no topic) prints — the compact agent language card. */
const CARD_MD = "card.md";
/** The machine twin `kestrel card --json` (no topic) emits verbatim. */
const CARD_JSON = "card.json";
/**
* Resolve the shipped `docs/public/` directory by ascending from THIS module's own location until a
* `docs/public/card.md` is found. Handles both the repo checkout (`src/cli/commands/`, three up) and
* the bundled install (`dist/cli.js`, one up) without a hard-coded depth. Fails closed (a loud
* NOT_FOUND, never a raw ENOENT stack) if the shipped docs are somehow absent.
*/
function resolveDocsPublicDir(): string {
let dir = dirname(fileURLToPath(import.meta.url));
// Six levels is comfortably deeper than either real layout (3 for source, 1 for the bundle).
for (let i = 0; i < 6; i += 1) {
const candidate = join(dir, "docs", "public");
if (existsSync(join(candidate, CARD_MD))) return candidate;
const parent = dirname(dir);
if (parent === dir) break; // reached the filesystem root
dir = parent;
}
throw new CliError({
code: "NOT_FOUND",
exit: EXIT.NOT_FOUND,
message: "the shipped agent card (docs/public/card.md) was not found next to the installed package",
hint: "reinstall kestrel.markets — `docs/public` is part of the published files allowlist",
});
}
/**
* The valid topic set: every shipped `docs/public/*.md`, basename without the extension, sorted. The
* default `card` is one of them (`kestrel card card` === `kestrel card`). Enumerated from disk so the
* unknown-topic refusal names exactly what the package ships — the list cannot drift from reality.
*/
function shippedTopics(dir: string): string[] {
return readdirSync(dir)
.filter((f) => f.endsWith(".md"))
.map((f) => f.slice(0, -".md".length))
.sort();
}
/**
* `card` — print the shipped agent reference from the installed package. Exit 0 on success; a USAGE
* refusal (exit 2) that lists the valid topics on an unknown topic; a NOT_FOUND (exit 3) only if the
* shipped docs are missing entirely.
*/
export function cardCommand(argv: readonly string[], ctx: OutputCtx): number {
const { positionals } = parseArgs(argv);
const topic = positionals[0];
const dir = resolveDocsPublicDir();
// Bare `card`: the compact agent language card, or its machine twin under --json.
if (topic === undefined) {
if (ctx.mode === "json") {
// Emit card.json verbatim — it is already canonical machine JSON (the version-coupling guard
// owns its content; this verb only SURFACES it, never regenerates it).
process.stdout.write(readFileSync(join(dir, CARD_JSON), "utf8"));
} else {
process.stdout.write(readFileSync(join(dir, CARD_MD), "utf8"));
}
return 0;
}
// A named topic: validate against the shipped set, then print it.
const topics = shippedTopics(dir);
if (!topics.includes(topic)) {
throw new CliError({
code: "USAGE",
exit: EXIT.USAGE,
message: `unknown card topic ${JSON.stringify(topic)}`,
hint: `valid topics: ${topics.join(", ")}`,
});
}
const markdown = readFileSync(join(dir, `${topic}.md`), "utf8");
if (ctx.mode === "json") {
process.stdout.write(JSON.stringify({ schema: "kestrel.card.topic/v1", topic, markdown }) + "\n");
} else {
process.stdout.write(markdown);
}
return 0;
}