refactor(cli): share exec approvals save flow

This commit is contained in:
Peter Steinberger
2026-02-15 01:40:47 +00:00
parent bcecf05292
commit 0024ea49d3

View File

@@ -89,6 +89,51 @@ async function loadSnapshotTarget(opts: ExecApprovalsCliOpts): Promise<{
return { snapshot, nodeId, source: nodeId ? "node" : "gateway" };
}
function exitWithError(message: string): never {
defaultRuntime.error(message);
defaultRuntime.exit(1);
throw new Error(message);
}
async function loadWritableSnapshotTarget(opts: ExecApprovalsCliOpts): Promise<{
snapshot: ExecApprovalsSnapshot;
nodeId: string | null;
source: "gateway" | "node" | "local";
targetLabel: string;
baseHash: string;
}> {
const { snapshot, nodeId, source } = await loadSnapshotTarget(opts);
if (source === "local") {
defaultRuntime.log(theme.muted("Writing local approvals."));
}
const targetLabel = source === "local" ? "local" : nodeId ? `node:${nodeId}` : "gateway";
const baseHash = snapshot.hash;
if (!baseHash) {
exitWithError("Exec approvals hash missing; reload and retry.");
}
return { snapshot, nodeId, source, targetLabel, baseHash };
}
async function saveSnapshotTargeted(params: {
opts: ExecApprovalsCliOpts;
source: "gateway" | "node" | "local";
nodeId: string | null;
file: ExecApprovalsFile;
baseHash: string;
targetLabel: string;
}): Promise<void> {
const next =
params.source === "local"
? saveSnapshotLocal(params.file)
: await saveSnapshot(params.opts, params.nodeId, params.file, params.baseHash);
if (params.opts.json) {
defaultRuntime.log(JSON.stringify(next));
return;
}
defaultRuntime.log(theme.muted(`Target: ${params.targetLabel}`));
renderApprovalsSnapshot(next, params.targetLabel);
}
function formatCliError(err: unknown): string {
const msg = describeUnknownError(err);
return msg.includes("\n") ? msg.split("\n")[0] : msg;
@@ -268,45 +313,21 @@ export function registerExecApprovalsCli(program: Command) {
.action(async (opts: ExecApprovalsCliOpts) => {
try {
if (!opts.file && !opts.stdin) {
defaultRuntime.error("Provide --file or --stdin.");
defaultRuntime.exit(1);
return;
exitWithError("Provide --file or --stdin.");
}
if (opts.file && opts.stdin) {
defaultRuntime.error("Use either --file or --stdin (not both).");
defaultRuntime.exit(1);
return;
}
const { snapshot, nodeId, source } = await loadSnapshotTarget(opts);
if (source === "local") {
defaultRuntime.log(theme.muted("Writing local approvals."));
}
const targetLabel = source === "local" ? "local" : nodeId ? `node:${nodeId}` : "gateway";
if (!snapshot.hash) {
defaultRuntime.error("Exec approvals hash missing; reload and retry.");
defaultRuntime.exit(1);
return;
exitWithError("Use either --file or --stdin (not both).");
}
const { source, nodeId, targetLabel, baseHash } = await loadWritableSnapshotTarget(opts);
const raw = opts.stdin ? await readStdin() : await fs.readFile(String(opts.file), "utf8");
let file: ExecApprovalsFile;
try {
file = JSON5.parse(raw);
} catch (err) {
defaultRuntime.error(`Failed to parse approvals JSON: ${String(err)}`);
defaultRuntime.exit(1);
return;
exitWithError(`Failed to parse approvals JSON: ${String(err)}`);
}
file.version = 1;
const next =
source === "local"
? saveSnapshotLocal(file)
: await saveSnapshot(opts, nodeId, file, snapshot.hash);
if (opts.json) {
defaultRuntime.log(JSON.stringify(next));
return;
}
defaultRuntime.log(theme.muted(`Target: ${targetLabel}`));
renderApprovalsSnapshot(next, targetLabel);
await saveSnapshotTargeted({ opts, source, nodeId, file, baseHash, targetLabel });
} catch (err) {
defaultRuntime.error(formatCliError(err));
defaultRuntime.exit(1);
@@ -345,20 +366,10 @@ export function registerExecApprovalsCli(program: Command) {
try {
const trimmed = pattern.trim();
if (!trimmed) {
defaultRuntime.error("Pattern required.");
defaultRuntime.exit(1);
return;
}
const { snapshot, nodeId, source } = await loadSnapshotTarget(opts);
if (source === "local") {
defaultRuntime.log(theme.muted("Writing local approvals."));
}
const targetLabel = source === "local" ? "local" : nodeId ? `node:${nodeId}` : "gateway";
if (!snapshot.hash) {
defaultRuntime.error("Exec approvals hash missing; reload and retry.");
defaultRuntime.exit(1);
return;
exitWithError("Pattern required.");
}
const { snapshot, nodeId, source, targetLabel, baseHash } =
await loadWritableSnapshotTarget(opts);
const file = snapshot.file ?? { version: 1 };
file.version = 1;
const agentKey = resolveAgentKey(opts.agent);
@@ -371,16 +382,7 @@ export function registerExecApprovalsCli(program: Command) {
allowlistEntries.push({ pattern: trimmed, lastUsedAt: Date.now() });
agent.allowlist = allowlistEntries;
file.agents = { ...file.agents, [agentKey]: agent };
const next =
source === "local"
? saveSnapshotLocal(file)
: await saveSnapshot(opts, nodeId, file, snapshot.hash);
if (opts.json) {
defaultRuntime.log(JSON.stringify(next));
return;
}
defaultRuntime.log(theme.muted(`Target: ${targetLabel}`));
renderApprovalsSnapshot(next, targetLabel);
await saveSnapshotTargeted({ opts, source, nodeId, file, baseHash, targetLabel });
} catch (err) {
defaultRuntime.error(formatCliError(err));
defaultRuntime.exit(1);
@@ -398,20 +400,10 @@ export function registerExecApprovalsCli(program: Command) {
try {
const trimmed = pattern.trim();
if (!trimmed) {
defaultRuntime.error("Pattern required.");
defaultRuntime.exit(1);
return;
}
const { snapshot, nodeId, source } = await loadSnapshotTarget(opts);
if (source === "local") {
defaultRuntime.log(theme.muted("Writing local approvals."));
}
const targetLabel = source === "local" ? "local" : nodeId ? `node:${nodeId}` : "gateway";
if (!snapshot.hash) {
defaultRuntime.error("Exec approvals hash missing; reload and retry.");
defaultRuntime.exit(1);
return;
exitWithError("Pattern required.");
}
const { snapshot, nodeId, source, targetLabel, baseHash } =
await loadWritableSnapshotTarget(opts);
const file = snapshot.file ?? { version: 1 };
file.version = 1;
const agentKey = resolveAgentKey(opts.agent);
@@ -436,16 +428,7 @@ export function registerExecApprovalsCli(program: Command) {
} else {
file.agents = { ...file.agents, [agentKey]: agent };
}
const next =
source === "local"
? saveSnapshotLocal(file)
: await saveSnapshot(opts, nodeId, file, snapshot.hash);
if (opts.json) {
defaultRuntime.log(JSON.stringify(next));
return;
}
defaultRuntime.log(theme.muted(`Target: ${targetLabel}`));
renderApprovalsSnapshot(next, targetLabel);
await saveSnapshotTargeted({ opts, source, nodeId, file, baseHash, targetLabel });
} catch (err) {
defaultRuntime.error(formatCliError(err));
defaultRuntime.exit(1);