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.
168 lines (166 loc) • 5.58 kB
JavaScript
// src/cli/credentials.ts
import { homedir } from "node:os";
import { join, dirname } from "node:path";
import { mkdirSync, writeFileSync, readFileSync, existsSync, chmodSync, renameSync } from "node:fs";
function defaultCredentialsPath(env = process.env) {
const home = env.KESTREL_HOME ?? homedir();
return join(home, ".kestrel", "credentials.json");
}
function defaultKeypairPath(env = process.env) {
const home = env.KESTREL_HOME ?? homedir();
return join(home, ".kestrel", "agent-key.json");
}
function saveCredential(cred, path = defaultCredentialsPath()) {
const dir = dirname(path);
mkdirSync(dir, { recursive: true, mode: 448 });
writeFileSync(path, JSON.stringify(cred, null, 2) + `
`, { mode: 384 });
chmodSync(path, 384);
}
function saveKeypair(keypair, path = defaultKeypairPath()) {
const dir = dirname(path);
mkdirSync(dir, { recursive: true, mode: 448 });
writeFileSync(path, JSON.stringify(keypair, null, 2) + `
`, { mode: 384 });
chmodSync(path, 384);
}
function loadKeypair(path = defaultKeypairPath()) {
try {
if (!existsSync(path))
return;
const parsed = JSON.parse(readFileSync(path, "utf8"));
if (typeof parsed?.subject === "string" && typeof parsed.api === "string" && typeof parsed.audience === "string" && parsed.private_jwk !== undefined && typeof parsed.private_jwk.d === "string") {
return parsed;
}
return;
} catch {
return;
}
}
function loadCredential(path = defaultCredentialsPath()) {
try {
if (!existsSync(path))
return;
const parsed = JSON.parse(readFileSync(path, "utf8"));
if (typeof parsed?.capability === "string" && parsed.capability.length > 0 && typeof parsed.api === "string") {
return parsed;
}
return;
} catch {
return;
}
}
var SECRET_KEY_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
function defaultSecretsPath(env = process.env) {
const home = env.KESTREL_HOME ?? homedir();
return join(home, ".kestrel", ".env");
}
function assertValidSecretKey(key) {
if (!SECRET_KEY_RE.test(key)) {
throw new Error(`kestrel: invalid secret key ${JSON.stringify(key)} — must match ${SECRET_KEY_RE}`);
}
}
function checkOperatorSecretKey(key) {
if (/(^|_)LIVE(_|$)/i.test(key)) {
return {
code: "SECRET_LIVE_KEY_REFUSED",
message: `refusing to store live-broker key ${JSON.stringify(key)} in the paper store — ~/.kestrel/.env is paper-only (OSS-ADR-0054 §5)`,
hint: `a live key belongs in wrangler, never the paper store: kestrel env -- wrangler secret put ${key} (behind ALPACA_LIVE_ENABLED + allowlist)`
};
}
if (!/^[A-Z_][A-Z0-9_]*$/.test(key)) {
return {
code: "SECRET_INVALID_KEY_NAME",
message: `invalid secret key name ${JSON.stringify(key)} — must match [A-Z_][A-Z0-9_]* (a canonical uppercase env-var name)`,
hint: "use the canonical uppercase name, e.g. `kestrel secrets set DATABENTO_API_KEY`"
};
}
return;
}
function needsQuoting(value) {
return value.length === 0 || /[\s#"'`$\\]/.test(value);
}
function serializeSecretLine(key, value) {
if (!needsQuoting(value))
return `${key}=${value}`;
const escaped = value.replace(/([\\"$`])/g, "\\$1");
return `${key}="${escaped}"`;
}
function serializeSecrets(store) {
let out = "";
for (const [k, v] of store)
out += serializeSecretLine(k, v) + `
`;
return out;
}
function parseSecrets(text) {
const out = new Map;
for (const raw of text.split(`
`)) {
const line = raw.trim();
if (line.length === 0 || line.startsWith("#"))
continue;
const eq = line.indexOf("=");
if (eq <= 0)
continue;
const key = line.slice(0, eq).trim();
if (!SECRET_KEY_RE.test(key))
continue;
let val = line.slice(eq + 1).trim();
if (val.length >= 2 && val[0] === '"' && val[val.length - 1] === '"') {
val = val.slice(1, -1).replace(/\\([\\"$`])/g, "$1");
} else if (val.length >= 2 && val[0] === "'" && val[val.length - 1] === "'") {
val = val.slice(1, -1);
}
out.set(key, val);
}
return out;
}
function readSecretStore(path) {
try {
if (!existsSync(path))
return new Map;
return parseSecrets(readFileSync(path, "utf8"));
} catch {
return new Map;
}
}
function writeSecretStore(path, store) {
const dir = dirname(path);
mkdirSync(dir, { recursive: true, mode: 448 });
const tmp = `${path}.${process.pid}.tmp`;
writeFileSync(tmp, serializeSecrets(store), { mode: 384 });
chmodSync(tmp, 384);
renameSync(tmp, path);
chmodSync(path, 384);
}
function saveSecret(key, value, path = defaultSecretsPath()) {
assertValidSecretKey(key);
const store = readSecretStore(path);
store.set(key, value);
writeSecretStore(path, store);
}
function secretNames(path = defaultSecretsPath()) {
return [...readSecretStore(path).keys()].sort();
}
function deleteSecret(key, path = defaultSecretsPath()) {
assertValidSecretKey(key);
const store = readSecretStore(path);
if (!store.delete(key))
return false;
writeSecretStore(path, store);
return true;
}
function loadEnvFallback(opts = {}) {
const env = opts.env ?? process.env;
const path = opts.path ?? defaultSecretsPath(env);
const populated = [];
for (const [key, value] of readSecretStore(path)) {
if (env[key] === undefined) {
env[key] = value;
populated.push(key);
}
}
return populated.sort();
}
export { defaultCredentialsPath, defaultKeypairPath, saveCredential, saveKeypair, loadKeypair, loadCredential, defaultSecretsPath, checkOperatorSecretKey, saveSecret, secretNames, deleteSecret, loadEnvFallback };