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.
235 lines (233 loc) • 8.52 kB
JavaScript
import {
generateAgentKeypair
} from "./bin-z6kh7na8.js";
import {
parseArgs
} from "./bin-1gc4zavq.js";
import {
CliError,
EXIT
} from "./bin-t9ggwnv5.js";
import {
defaultCredentialsPath,
defaultKeypairPath,
saveCredential,
saveKeypair
} from "./bin-q0g6xvsw.js";
import"./bin-wckvcay0.js";
// src/cli/commands/register.ts
import { execFileSync } from "node:child_process";
var DEFAULT_API = "https://api.kestrel.markets";
function usageErr(message) {
return new CliError({ code: "USAGE", exit: EXIT.USAGE, message });
}
function readGitConfig(key) {
try {
const out = execFileSync("git", ["config", "--get", key], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
const v = out.trim();
return v.length > 0 ? v : undefined;
} catch {
return;
}
}
function defaultGitIdentity() {
const name = readGitConfig("user.name");
const email = readGitConfig("user.email");
return { ...name !== undefined ? { name } : {}, ...email !== undefined ? { email } : {} };
}
function resolveApiBase(globals, env) {
const raw = globals.api ?? env.KESTREL_API;
if (raw === undefined || raw === "" || raw === "default")
return DEFAULT_API;
return raw.replace(/\/+$/, "");
}
async function registerCommand(argv, ctx, globals, deps = {}) {
const { flags, bools } = parseArgs(argv, new Set(["no-git-identity", "keypair"]), new Set(["name", "scopes"]));
const env = deps.env ?? process.env;
const fetchImpl = deps.fetch ?? globalThis.fetch;
if (fetchImpl === undefined) {
throw new CliError({
code: "RUNTIME_UNAVAILABLE",
exit: EXIT.RUNTIME_UNAVAILABLE,
message: "global fetch unavailable — need node ≥18 or bun to register"
});
}
const base = resolveApiBase(globals, env);
const withGit = !bools.has("no-git-identity");
const identity = withGit ? (deps.gitIdentity ?? defaultGitIdentity)() : {};
const name = flags.get("name");
const scopes = flags.get("scopes")?.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
disclose(ctx, base, withGit, identity);
const body = {};
if (name !== undefined)
body.name = name;
if (withGit && (identity.name !== undefined || identity.email !== undefined)) {
body.claimed_git = {
...identity.name !== undefined ? { name: identity.name } : {},
...identity.email !== undefined ? { email: identity.email } : {}
};
}
if (scopes !== undefined && scopes.length > 0)
body.scope_request = scopes.join(",");
const res = await fetchImpl(`${base}/agents/register`, {
method: "POST",
headers: { "content-type": "application/json", accept: "application/json" },
body: JSON.stringify(body)
});
if (res.status === 403) {
const detail = await problemDetail(res);
throw usageErr(`registration refused: ${detail}`);
}
if (res.status === 429) {
throw new CliError({
code: "HTTP_429",
exit: EXIT.GENERIC,
message: "registration rate-limited — retry later or reuse the credential you already hold"
});
}
if (!res.ok) {
const detail = await problemDetail(res, `registration failed (HTTP ${res.status})`);
throw new CliError({
code: `HTTP_${res.status}`,
exit: res.status >= 500 ? EXIT.RUNTIME_UNAVAILABLE : EXIT.GENERIC,
message: `registration failed (HTTP ${res.status}): ${detail}`,
...res.status === 422 ? { hint: "--scopes is a COMMA-SEPARATED scope request (e.g. --scopes data,sim); an unknown scope literal is rejected — remove it and retry" } : {}
});
}
const reg = await res.json();
const credPath = deps.credentialsPath ?? defaultCredentialsPath(env);
const wantKeypair = bools.has("keypair");
let enrolled;
if (wantKeypair) {
enrolled = await enrollKeypair(ctx, fetchImpl, base, reg, deps, env);
}
const credential = {
version: 1,
api: base,
agent_id: reg.agent_id,
capability: reg.capability,
token_type: reg.token_type,
scopes: reg.scopes,
expiry: reg.expiry,
...reg.subject !== undefined ? { subject: reg.subject } : {},
...enrolled !== undefined ? { keypair_enrolled: true } : {},
...withGit && (identity.name !== undefined || identity.email !== undefined) ? { claimed: { ...identity.name !== undefined ? { name: identity.name } : {}, ...identity.email !== undefined ? { email: identity.email } : {} } } : {}
};
saveCredential(credential, credPath);
emitResult(ctx, reg, credPath, enrolled);
return 0;
}
async function enrollKeypair(ctx, fetchImpl, base, reg, deps, env) {
if (reg.subject === undefined) {
process.stderr.write(`disclose: --keypair skipped — the platform did not return a subject to bind the key to
`);
return;
}
const { publicJwk, privateJwk } = await generateAgentKeypair();
process.stderr.write(`disclose: generated a local Ed25519 keypair; enrolling the PUBLIC half (the private key never leaves this machine)
`);
const enrollRes = await fetchImpl(`${base}/agents/enroll-key`, {
method: "POST",
headers: { "content-type": "application/json", accept: "application/json", authorization: `Bearer ${reg.capability}` },
body: JSON.stringify({ public_key: publicJwk })
});
if (!enrollRes.ok) {
const detail = await problemDetail(enrollRes);
process.stderr.write(`disclose: --keypair enrollment failed (HTTP ${enrollRes.status}: ${detail}); the durable capability still works
`);
return;
}
const body = await enrollRes.json();
const keyPath = deps.keypairPath ?? defaultKeypairPath(env);
const keypair = {
version: 1,
api: base,
subject: reg.subject,
public_jwk: publicJwk,
private_jwk: privateJwk,
audience: body.jwt.audience,
max_ttl_seconds: body.jwt.max_ttl_seconds,
enrolled_at: new Date().toISOString()
};
saveKeypair(keypair, keyPath);
return { keyPath, thumbprint: body.public_key_thumbprint };
}
function disclose(ctx, base, withGit, identity) {
if (ctx.mode === "json") {
process.stderr.write(JSON.stringify({
disclosure: {
endpoint: `${base}/agents/register`,
sends_claimed_git_identity: withGit,
claimed: withGit ? identity : null,
note: "claimed git identity is UNVERIFIED (freely spoofable); pass --no-git-identity to omit it"
}
}) + `
`);
return;
}
const lines = [];
lines.push(`registering with ${base}/agents/register`);
if (withGit && (identity.name !== undefined || identity.email !== undefined)) {
lines.push(`will send CLAIMED git identity (UNVERIFIED): ${identity.name ?? "(no name)"} <${identity.email ?? "no-email"}>`);
lines.push("this identity is freely spoofable and is NOT trusted as authentication; pass --no-git-identity to omit it");
} else if (withGit) {
lines.push("no git user.name/user.email found — sending no claimed identity");
} else {
lines.push("--no-git-identity: sending NO claimed git identity");
}
for (const l of lines)
process.stderr.write(`disclose: ${l}
`);
}
function emitResult(ctx, reg, credPath, enrolled) {
if (ctx.mode === "json") {
process.stdout.write(JSON.stringify({
schema: "kestrel.register/v1",
agent_id: reg.agent_id,
token_type: reg.token_type,
scopes: reg.scopes,
expiry: reg.expiry,
credential_path: credPath,
keypair: enrolled ? { enrolled: true, keyfile_path: enrolled.keyPath, thumbprint: enrolled.thumbprint } : null,
upgrade: reg.upgrade ?? null
}) + `
`);
return;
}
if (ctx.mode === "text") {
const kp = enrolled ? ` keyfile=${enrolled.keyPath}` : "";
process.stdout.write(`registered agent_id=${reg.agent_id} scopes=${reg.scopes.join(",")} expiry=${reg.expiry} credential=${credPath}${kp}
`);
return;
}
process.stdout.write(`registered as ${reg.agent_id}
`);
process.stdout.write(` scopes: ${reg.scopes.join(", ")}
`);
process.stdout.write(` expiry: ${reg.expiry}
`);
process.stdout.write(` credential: ${credPath}
`);
if (enrolled) {
process.stdout.write(` keyfile: ${enrolled.keyPath} (0600 — Ed25519 private key; JWT auth)
`);
process.stdout.write(` key id: ${enrolled.thumbprint}
`);
}
if (reg.upgrade?.verify_email_hint)
process.stdout.write(` upgrade: ${reg.upgrade.verify_email_hint}
`);
}
async function problemDetail(res, fallback = "scope not mintable at registration") {
try {
const b = await res.json();
return b.detail ?? b.title ?? fallback;
} catch {
return fallback;
}
}
export {
registerCommand,
defaultGitIdentity,
DEFAULT_API
};