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.

202 lines (200 loc) 6.32 kB
import { parseArgs } from "./bin-1gc4zavq.js"; import { CliError, EXIT } from "./bin-t9ggwnv5.js"; import { checkOperatorSecretKey, defaultSecretsPath, deleteSecret, saveSecret, secretNames } from "./bin-q0g6xvsw.js"; import"./bin-wckvcay0.js"; // src/cli/commands/secrets.ts import { readFileSync } from "node:fs"; import { createInterface } from "node:readline"; function usage(message, hint) { return new CliError({ code: "USAGE", exit: EXIT.USAGE, message, ...hint !== undefined ? { hint } : {} }); } function readStdinSync() { try { return readFileSync(0, "utf8"); } catch { throw new CliError({ code: "SECRET_NO_INPUT", exit: EXIT.USAGE, message: "--stdin was given but stdin could not be read", hint: 'pipe the value: `printf %s "$VALUE" | kestrel secrets set KEY --stdin`' }); } } function promptSecretTty(label) { return new Promise((resolve) => { const rl = createInterface({ input: process.stdin, output: process.stderr, terminal: true }); rl._writeToOutput = (s) => { if (s.includes(label)) process.stderr.write(label); }; rl.question(label, (answer) => { rl.close(); process.stderr.write(` `); resolve(answer); }); }); } function stripOneTrailingNewline(s) { if (s.endsWith(`\r `)) return s.slice(0, -2); if (s.endsWith(` `)) return s.slice(0, -1); return s; } async function secretsCommand(argv, ctx, deps = {}) { const parsed = parseArgs(argv, new Set(["stdin"]), new Set); const env = deps.env ?? process.env; const path = deps.secretsPath ?? defaultSecretsPath(env); const [verb, ...rest] = parsed.positionals; switch (verb) { case "set": return await setSecret(rest, parsed.bools.has("stdin"), ctx, path, deps); case "list": return listSecrets(rest, parsed.bools.has("stdin"), ctx, path); case "unset": return unsetSecret(rest, ctx, path); case "path": return printPath(rest, ctx, path); case undefined: throw usage("missing secrets verb", "one of: set | list | unset | path"); default: throw usage(`unknown secrets verb ${JSON.stringify(verb)}`, "one of: set | list | unset | path"); } } async function setSecret(rest, wantStdin, ctx, path, deps) { const key = rest[0]; if (key === undefined) throw usage("missing KEY", "usage: kestrel secrets set KEY [--stdin]"); const keyViolation = checkOperatorSecretKey(key); if (keyViolation !== undefined) { throw new CliError({ code: keyViolation.code, exit: EXIT.USAGE, message: keyViolation.message, hint: keyViolation.hint }); } if (rest.length > 1) { throw new CliError({ code: "SECRET_VALUE_IN_ARGV", exit: EXIT.USAGE, message: `refusing to read a secret value from argv for ${JSON.stringify(key)} — argv leaks into shell history, \`ps\`, and process logs`, hint: `pipe it instead: printf %s "$VALUE" | kestrel secrets set ${key} --stdin (or run \`kestrel secrets set ${key}\` on a tty to be prompted)` }); } let value; if (wantStdin) { value = stripOneTrailingNewline((deps.readStdin ?? readStdinSync)()); } else if (ctx.interactive) { value = (await (deps.promptSecret ?? promptSecretTty)(`value for ${key}: `)).trim(); } else { throw new CliError({ code: "SECRET_NO_INPUT", exit: EXIT.USAGE, message: `no way to read the value for ${JSON.stringify(key)} — this session is not interactive and --stdin was not given`, hint: `printf %s "$VALUE" | kestrel secrets set ${key} --stdin` }); } if (value.length === 0) { throw new CliError({ code: "SECRET_EMPTY", exit: EXIT.USAGE, message: `refusing to store an EMPTY value for ${JSON.stringify(key)}`, hint: `to remove it instead: kestrel secrets unset ${key}` }); } saveSecret(key, value, path); if (ctx.mode === "json") { process.stdout.write(JSON.stringify({ schema: "kestrel.secrets.set/v1", key, path, stored: true }) + ` `); return 0; } if (ctx.mode === "text") { process.stdout.write(`set ${key} ${path} `); return 0; } process.stdout.write(`stored ${key} in ${path} (value redacted, file 0600) `); return 0; } function listSecrets(rest, wantStdin, ctx, path) { if (rest.length > 0) throw usage(`unexpected argument ${JSON.stringify(rest[0])}`, "usage: kestrel secrets list"); if (wantStdin) throw usage("--stdin is only valid for `kestrel secrets set`"); const names = secretNames(path); if (ctx.mode === "json") { process.stdout.write(JSON.stringify({ schema: "kestrel.secrets.list/v1", path, names }) + ` `); return 0; } if (ctx.mode === "text") { process.stdout.write(names.map((n) => `secret ${n} `).join("")); return 0; } if (names.length === 0) { process.stdout.write(`no secrets stored in ${path} `); return 0; } process.stdout.write(`${names.length} secret(s) in ${path} (names only, values never printed) `); process.stdout.write(names.map((n) => ` ${n} `).join("")); return 0; } function unsetSecret(rest, ctx, path) { const key = rest[0]; if (key === undefined) throw usage("missing KEY", "usage: kestrel secrets unset KEY"); if (rest.length > 1) throw usage(`unexpected argument ${JSON.stringify(rest[1])}`, "usage: kestrel secrets unset KEY"); const removed = deleteSecret(key, path); if (!removed) { throw new CliError({ code: "NOT_FOUND", exit: EXIT.NOT_FOUND, message: `no secret named ${JSON.stringify(key)} in ${path}`, hint: "run `kestrel secrets list` for the stored names" }); } if (ctx.mode === "json") { process.stdout.write(JSON.stringify({ schema: "kestrel.secrets.unset/v1", key, path, removed: true }) + ` `); return 0; } if (ctx.mode === "text") { process.stdout.write(`unset ${key} ${path} `); return 0; } process.stdout.write(`removed ${key} from ${path} `); return 0; } function printPath(rest, ctx, path) { if (rest.length > 0) throw usage(`unexpected argument ${JSON.stringify(rest[0])}`, "usage: kestrel secrets path"); if (ctx.mode === "json") { process.stdout.write(JSON.stringify({ schema: "kestrel.secrets.path/v1", path }) + ` `); return 0; } process.stdout.write(`${path} `); return 0; } export { secretsCommand };