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
JavaScript
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
};