diff --git a/tools/ai-cli-dispatch/SKILL.md b/tools/ai-cli-dispatch/SKILL.md index db86cee..fa4db68 100644 --- a/tools/ai-cli-dispatch/SKILL.md +++ b/tools/ai-cli-dispatch/SKILL.md @@ -28,12 +28,13 @@ npm install ```bash scripts/ai-cli-dispatch list --json -scripts/ai-cli-dispatch exec --client codex --prompt "refactor this function" -scripts/ai-cli-dispatch exec --client claude --prompt "add tests for auth middleware" -scripts/ai-cli-dispatch exec --client opencode --prompt "migrate to ESM" +scripts/ai-cli-dispatch run --client codex --prompt "refactor this function" +scripts/ai-cli-dispatch run --client claude --prompt "add tests for auth middleware" +scripts/ai-cli-dispatch run --client opencode --prompt "migrate to ESM" +scripts/ai-cli-dispatch dispatch "use codex to write tests" ``` -Use `--json` for machine-readable command output. +Use `--json` for machine-readable command output. Use `--sync` with `run` or `dispatch` to block until the client returns; the default is async (starts a background job and returns the job ID immediately). ## Client Discovery @@ -47,7 +48,7 @@ Run `list` to see which clients are installed and their resolved versions. ## Background Jobs -For long-running or fire-and-forget tasks, use the programmatic job API instead of `exec`: +For long-running or fire-and-forget tasks, use the programmatic job API or invoke `run` / `dispatch` without `--sync`: ```typescript import { startJob, getJob, cancelJob, listJobs, cleanupJobs } from "./src/jobs.js"; diff --git a/tools/ai-cli-dispatch/src/cli-helpers.ts b/tools/ai-cli-dispatch/src/cli-helpers.ts new file mode 100644 index 0000000..e04da2c --- /dev/null +++ b/tools/ai-cli-dispatch/src/cli-helpers.ts @@ -0,0 +1,85 @@ +import type { ClientName, ExecResult, DebugInfo, Job } from "./types.js"; + +export interface RunContext { + jsonMode: boolean; + stdoutWrite: (chunk: string) => void; + stderrWrite: (chunk: string) => void; +} + +export function reportError(err: unknown, jsonMode: boolean): number { + const message = err instanceof Error ? err.message : String(err); + if (jsonMode) { + console.error(JSON.stringify({ error: message }, null, 2)); + } else { + console.error(message); + } + return 1; +} + +export function reportCliError(message: string, jsonMode: boolean): number { + if (jsonMode) { + console.error(JSON.stringify({ error: message }, null, 2)); + } else { + console.error(`Error: ${message}`); + } + return 1; +} + +export async function handleSyncRun( + executePrompt: ( + client: ClientName, + prompt: string, + options?: { timeoutMs?: number; debug?: boolean; onDebug?: (info: DebugInfo) => void } + ) => Promise, + client: ClientName, + prompt: string, + timeoutMs: number | undefined, + debug: boolean, + ctx: RunContext +): Promise { + try { + const result = await executePrompt(client, prompt, { + timeoutMs, + debug, + onDebug: debug ? (info) => ctx.stderrWrite(JSON.stringify(info) + "\n") : undefined, + }); + if (ctx.jsonMode) { + console.log(JSON.stringify(result, null, 2)); + } else { + if (result.stdout) ctx.stdoutWrite(result.stdout); + if (result.stderr) ctx.stderrWrite(result.stderr); + } + return 0; + } catch (err) { + return reportError(err, ctx.jsonMode); + } +} + +export async function handleAsyncRun( + startJob: ( + client: ClientName, + prompt: string, + options?: { timeoutMs?: number; debug?: boolean; onDebug?: (info: DebugInfo) => void } + ) => Promise, + client: ClientName, + prompt: string, + timeoutMs: number | undefined, + debug: boolean, + ctx: RunContext +): Promise { + try { + const job = await startJob(client, prompt, { + timeoutMs, + debug, + onDebug: debug ? (info) => ctx.stderrWrite(JSON.stringify(info) + "\n") : undefined, + }); + if (ctx.jsonMode) { + console.log(JSON.stringify({ jobId: job.id, client: job.client, status: job.status }, null, 2)); + } else { + console.log(`Job ${job.id} started (${job.client}): ${job.status}`); + } + return 0; + } catch (err) { + return reportError(err, ctx.jsonMode); + } +} diff --git a/tools/ai-cli-dispatch/src/cli.ts b/tools/ai-cli-dispatch/src/cli.ts index b04dae2..b3ddcb5 100644 --- a/tools/ai-cli-dispatch/src/cli.ts +++ b/tools/ai-cli-dispatch/src/cli.ts @@ -21,6 +21,12 @@ import { type JobStatus, ClientNotFoundError, } from "./types.js"; +import { + reportError, + reportCliError, + handleSyncRun, + handleAsyncRun, +} from "./cli-helpers.js"; export interface CliDeps { detectClients?: () => ClientInfo[]; @@ -85,7 +91,7 @@ Flags: --sync Run synchronously and block until the client returns (default is async) --timeout Timeout in milliseconds (or override via config) --debug Emit diagnostic JSON to stderr - --max-age Maximum age for cleanup (default unit: hours) + --max-age Maximum age for cleanup (default unit: hours, e.g. 24h or 30m) --status Filter jobs by status (running, completed, failed) --json Output JSON (default) --text Output plain text @@ -155,25 +161,14 @@ export async function main( const prompt = args.prompt as string | undefined; if (!client || !CLIENT_NAMES.includes(client)) { - const message = !client - ? "--client is required" - : `Unknown client: ${client}`; - if (jsonMode) { - console.error(JSON.stringify({ error: message }, null, 2)); - } else { - console.error(`Error: ${message}`); - } - return 1; + return reportCliError( + !client ? "--client is required" : `Unknown client: ${client}`, + jsonMode + ); } if (!prompt) { - const message = "--prompt is required"; - if (jsonMode) { - console.error(JSON.stringify({ error: message }, null, 2)); - } else { - console.error(`Error: ${message}`); - } - return 1; + return reportCliError("--prompt is required", jsonMode); } const config = resolveConfig(); @@ -182,52 +177,11 @@ export async function main( const timeoutMs = Number.isFinite(parsedTimeout) ? parsedTimeout : config.timeout; + const ctx = { jsonMode, stdoutWrite, stderrWrite }; if (args.sync) { - try { - const result = await executePrompt(client, prompt, { - timeoutMs, - debug, - onDebug: debug ? (info) => stderrWrite(JSON.stringify(info) + "\n") : undefined, - }); - if (jsonMode) { - console.log(JSON.stringify(result, null, 2)); - } else { - if (result.stdout) stdoutWrite(result.stdout); - if (result.stderr) stderrWrite(result.stderr); - } - return 0; - } catch (err) { - const message = err instanceof Error ? err.message : String(err); - if (jsonMode) { - console.error(JSON.stringify({ error: message }, null, 2)); - } else { - console.error(message); - } - return 1; - } - } else { - try { - const job = await startJob(client, prompt, { - timeoutMs, - debug, - onDebug: debug ? (info) => stderrWrite(JSON.stringify(info) + "\n") : undefined, - }); - if (jsonMode) { - console.log(JSON.stringify({ jobId: job.id, client: job.client, status: job.status }, null, 2)); - } else { - console.log(`Job ${job.id} started (${job.client}): ${job.status}`); - } - return 0; - } catch (err) { - const message = err instanceof Error ? err.message : String(err); - if (jsonMode) { - console.error(JSON.stringify({ error: message }, null, 2)); - } else { - console.error(message); - } - return 1; - } + return await handleSyncRun(executePrompt, client, prompt, timeoutMs, debug, ctx); } + return await handleAsyncRun(startJob, client, prompt, timeoutMs, debug, ctx); } if (command === "dispatch") { @@ -237,13 +191,7 @@ export async function main( } if (!prompt) { - const message = "prompt is required"; - if (jsonMode) { - console.error(JSON.stringify({ error: message }, null, 2)); - } else { - console.error(`Error: ${message}`); - } - return 1; + return reportCliError("prompt is required", jsonMode); } const config = resolveConfig(); @@ -254,13 +202,7 @@ export async function main( }); if (!client) { - const message = "Could not resolve client from prompt"; - if (jsonMode) { - console.error(JSON.stringify({ error: message }, null, 2)); - } else { - console.error(`Error: ${message}`); - } - return 1; + return reportCliError("Could not resolve client from prompt", jsonMode); } const parsedTimeout = @@ -268,52 +210,11 @@ export async function main( const timeoutMs = Number.isFinite(parsedTimeout) ? parsedTimeout : config.timeout; + const ctx = { jsonMode, stdoutWrite, stderrWrite }; if (args.sync) { - try { - const result = await executePrompt(client, prompt, { - timeoutMs, - debug, - onDebug: debug ? (info) => stderrWrite(JSON.stringify(info) + "\n") : undefined, - }); - if (jsonMode) { - console.log(JSON.stringify(result, null, 2)); - } else { - if (result.stdout) stdoutWrite(result.stdout); - if (result.stderr) stderrWrite(result.stderr); - } - return 0; - } catch (err) { - const message = err instanceof Error ? err.message : String(err); - if (jsonMode) { - console.error(JSON.stringify({ error: message }, null, 2)); - } else { - console.error(message); - } - return 1; - } - } else { - try { - const job = await startJob(client, prompt, { - timeoutMs, - debug, - onDebug: debug ? (info) => stderrWrite(JSON.stringify(info) + "\n") : undefined, - }); - if (jsonMode) { - console.log(JSON.stringify({ jobId: job.id, client: job.client, status: job.status }, null, 2)); - } else { - console.log(`Job ${job.id} started (${job.client}): ${job.status}`); - } - return 0; - } catch (err) { - const message = err instanceof Error ? err.message : String(err); - if (jsonMode) { - console.error(JSON.stringify({ error: message }, null, 2)); - } else { - console.error(message); - } - return 1; - } + return await handleSyncRun(executePrompt, client, prompt, timeoutMs, debug, ctx); } + return await handleAsyncRun(startJob, client, prompt, timeoutMs, debug, ctx); } if (command === "start") { @@ -321,25 +222,14 @@ export async function main( const prompt = args.prompt as string | undefined; if (!client || !CLIENT_NAMES.includes(client)) { - const message = !client - ? "--client is required" - : `Unknown client: ${client}`; - if (jsonMode) { - console.error(JSON.stringify({ error: message }, null, 2)); - } else { - console.error(`Error: ${message}`); - } - return 1; + return reportCliError( + !client ? "--client is required" : `Unknown client: ${client}`, + jsonMode + ); } if (!prompt) { - const message = "--prompt is required"; - if (jsonMode) { - console.error(JSON.stringify({ error: message }, null, 2)); - } else { - console.error(`Error: ${message}`); - } - return 1; + return reportCliError("--prompt is required", jsonMode); } const config = resolveConfig(); @@ -361,26 +251,14 @@ export async function main( } return 0; } catch (err) { - const message = err instanceof Error ? err.message : String(err); - if (jsonMode) { - console.error(JSON.stringify({ error: message }, null, 2)); - } else { - console.error(message); - } - return 1; + return reportError(err, jsonMode); } } if (command === "status") { const jobId = args._[1]; if (!jobId) { - const message = "job-id is required"; - if (jsonMode) { - console.error(JSON.stringify({ error: message }, null, 2)); - } else { - console.error(`Error: ${message}`); - } - return 1; + return reportCliError("job-id is required", jsonMode); } try { @@ -392,26 +270,14 @@ export async function main( } return 0; } catch (err) { - const message = err instanceof Error ? err.message : String(err); - if (jsonMode) { - console.error(JSON.stringify({ error: message }, null, 2)); - } else { - console.error(message); - } - return 1; + return reportError(err, jsonMode); } } if (command === "results") { const jobId = args._[1]; if (!jobId) { - const message = "job-id is required"; - if (jsonMode) { - console.error(JSON.stringify({ error: message }, null, 2)); - } else { - console.error(`Error: ${message}`); - } - return 1; + return reportCliError("job-id is required", jsonMode); } try { @@ -424,38 +290,23 @@ export async function main( } return 0; } catch (err) { - const message = err instanceof Error ? err.message : String(err); - if (jsonMode) { - console.error(JSON.stringify({ error: message }, null, 2)); - } else { - console.error(message); - } - return 1; + return reportError(err, jsonMode); } } if (command === "cancel") { const jobId = args._[1]; if (!jobId) { - const message = "job-id is required"; - if (jsonMode) { - console.error(JSON.stringify({ error: message }, null, 2)); - } else { - console.error(`Error: ${message}`); - } - return 1; + return reportCliError("job-id is required", jsonMode); } try { const job = getJob(jobId); if (job.status !== "running") { - const message = `Job is not running (status: ${job.status})`; - if (jsonMode) { - console.error(JSON.stringify({ error: message }, null, 2)); - } else { - console.error(`Error: ${message}`); - } - return 1; + return reportCliError( + `Job is not running (status: ${job.status})`, + jsonMode + ); } cancelJob(jobId); if (jsonMode) { @@ -465,13 +316,7 @@ export async function main( } return 0; } catch (err) { - const message = err instanceof Error ? err.message : String(err); - if (jsonMode) { - console.error(JSON.stringify({ error: message }, null, 2)); - } else { - console.error(message); - } - return 1; + return reportError(err, jsonMode); } } @@ -488,13 +333,7 @@ export async function main( } return 0; } catch (err) { - const message = err instanceof Error ? err.message : String(err); - if (jsonMode) { - console.error(JSON.stringify({ error: message }, null, 2)); - } else { - console.error(message); - } - return 1; + return reportError(err, jsonMode); } } @@ -504,13 +343,10 @@ export async function main( if (maxAgeRaw !== undefined) { const parsed = parseMaxAge(maxAgeRaw); if (parsed === null) { - const message = "Invalid --max-age format. Use: [h|m|s|d], e.g. 24h"; - if (jsonMode) { - console.error(JSON.stringify({ error: message }, null, 2)); - } else { - console.error(`Error: ${message}`); - } - return 1; + return reportCliError( + "Invalid --max-age format. Use: [h|m|s|d], e.g. 24h", + jsonMode + ); } maxAgeMs = parsed; } @@ -527,23 +363,11 @@ export async function main( } return 0; } catch (err) { - const message = err instanceof Error ? err.message : String(err); - if (jsonMode) { - console.error(JSON.stringify({ error: message }, null, 2)); - } else { - console.error(message); - } - return 1; + return reportError(err, jsonMode); } } - const message = `Unknown command: ${command}`; - if (jsonMode) { - console.error(JSON.stringify({ error: message }, null, 2)); - } else { - console.error(message); - } - return 1; + return reportError(`Unknown command: ${command}`, jsonMode); } const isMain = diff --git a/tools/ai-cli-dispatch/tests/cli-helpers.test.ts b/tools/ai-cli-dispatch/tests/cli-helpers.test.ts new file mode 100644 index 0000000..5272c1a --- /dev/null +++ b/tools/ai-cli-dispatch/tests/cli-helpers.test.ts @@ -0,0 +1,278 @@ +import { describe, it } from "node:test"; +import assert from "node:assert"; +import { + reportError, + reportCliError, + handleSyncRun, + handleAsyncRun, +} from "../src/cli-helpers.js"; +import type { ExecResult, Job } from "../src/types.js"; + +function captureConsole() { + const logs: string[] = []; + const errors: string[] = []; + const origLog = console.log; + const origError = console.error; + console.log = (...args: unknown[]) => logs.push(args.map(String).join(" ")); + console.error = (...args: unknown[]) => errors.push(args.map(String).join(" ")); + return { + logs, + errors, + restore() { + console.log = origLog; + console.error = origError; + }, + }; +} + +describe("reportError", () => { + it("prints JSON error for Error instance when jsonMode=true", () => { + const out = captureConsole(); + try { + const code = reportError(new Error("boom"), true); + assert.strictEqual(code, 1); + assert.strictEqual(out.errors.length, 1); + const parsed = JSON.parse(out.errors[0]); + assert.strictEqual(parsed.error, "boom"); + } finally { + out.restore(); + } + }); + + it("prints plain text error for Error instance when jsonMode=false", () => { + const out = captureConsole(); + try { + const code = reportError(new Error("boom"), false); + assert.strictEqual(code, 1); + assert.strictEqual(out.errors.length, 1); + assert.strictEqual(out.errors[0], "boom"); + } finally { + out.restore(); + } + }); + + it("prints JSON error for non-Error value when jsonMode=true", () => { + const out = captureConsole(); + try { + const code = reportError("plain string", true); + assert.strictEqual(code, 1); + const parsed = JSON.parse(out.errors[0]); + assert.strictEqual(parsed.error, "plain string"); + } finally { + out.restore(); + } + }); +}); + +describe("reportCliError", () => { + it("prints JSON error with jsonMode=true", () => { + const out = captureConsole(); + try { + const code = reportCliError("missing arg", true); + assert.strictEqual(code, 1); + const parsed = JSON.parse(out.errors[0]); + assert.strictEqual(parsed.error, "missing arg"); + } finally { + out.restore(); + } + }); + + it("prints prefixed text error with jsonMode=false", () => { + const out = captureConsole(); + try { + const code = reportCliError("missing arg", false); + assert.strictEqual(code, 1); + assert.strictEqual(out.errors[0], "Error: missing arg"); + } finally { + out.restore(); + } + }); +}); + +describe("handleSyncRun", () => { + it("returns 0 and prints JSON result in jsonMode", async () => { + const out = captureConsole(); + try { + const result: ExecResult = { + stdout: "out", + stderr: "err", + exitCode: 0, + client: "codex", + durationMs: 10, + }; + const code = await handleSyncRun( + async () => result, + "codex", + "hello", + 5000, + false, + { jsonMode: true, stdoutWrite: () => {}, stderrWrite: () => {} } + ); + assert.strictEqual(code, 0); + assert.strictEqual(out.logs.length, 1); + const parsed = JSON.parse(out.logs[0]); + assert.strictEqual(parsed.stdout, "out"); + } finally { + out.restore(); + } + }); + + it("returns 0 and writes stdout/stderr in text mode", async () => { + const out = captureConsole(); + const stdout: string[] = []; + const stderr: string[] = []; + try { + const result: ExecResult = { + stdout: "out", + stderr: "err", + exitCode: 0, + client: "codex", + durationMs: 10, + }; + const code = await handleSyncRun( + async () => result, + "codex", + "hello", + 5000, + false, + { + jsonMode: false, + stdoutWrite: (c) => stdout.push(c), + stderrWrite: (c) => stderr.push(c), + } + ); + assert.strictEqual(code, 0); + assert.strictEqual(stdout.join(""), "out"); + assert.strictEqual(stderr.join(""), "err"); + assert.strictEqual(out.logs.length, 0); + } finally { + out.restore(); + } + }); + + it("passes timeoutMs and debug to executePrompt", async () => { + const out = captureConsole(); + try { + let received: any; + const code = await handleSyncRun( + async (_c, _p, opts) => { + received = opts; + return { + stdout: "", + stderr: "", + exitCode: 0, + client: "codex", + durationMs: 1, + }; + }, + "codex", + "hello", + 12345, + true, + { jsonMode: true, stdoutWrite: () => {}, stderrWrite: () => {} } + ); + assert.strictEqual(code, 0); + assert.strictEqual(received?.timeoutMs, 12345); + assert.strictEqual(received?.debug, true); + } finally { + out.restore(); + } + }); + + it("returns 1 and prints error JSON when executePrompt throws", async () => { + const out = captureConsole(); + try { + const code = await handleSyncRun( + async () => { + throw new Error("fail"); + }, + "codex", + "hello", + undefined, + false, + { jsonMode: true, stdoutWrite: () => {}, stderrWrite: () => {} } + ); + assert.strictEqual(code, 1); + const parsed = JSON.parse(out.errors[0]); + assert.strictEqual(parsed.error, "fail"); + } finally { + out.restore(); + } + }); +}); + +describe("handleAsyncRun", () => { + it("returns 0 and prints JSON job in jsonMode", async () => { + const out = captureConsole(); + try { + const job: Job = { + id: "j1", + client: "codex", + prompt: "hi", + status: "running", + startedAt: "2024-01-01T00:00:00Z", + }; + const code = await handleAsyncRun( + async () => job, + "codex", + "hi", + undefined, + false, + { jsonMode: true, stdoutWrite: () => {}, stderrWrite: () => {} } + ); + assert.strictEqual(code, 0); + const parsed = JSON.parse(out.logs[0]); + assert.strictEqual(parsed.jobId, "j1"); + assert.strictEqual(parsed.status, "running"); + } finally { + out.restore(); + } + }); + + it("returns 0 and prints text job info", async () => { + const out = captureConsole(); + try { + const job: Job = { + id: "j1", + client: "codex", + prompt: "hi", + status: "running", + startedAt: "2024-01-01T00:00:00Z", + }; + const code = await handleAsyncRun( + async () => job, + "codex", + "hi", + undefined, + false, + { jsonMode: false, stdoutWrite: () => {}, stderrWrite: () => {} } + ); + assert.strictEqual(code, 0); + assert.ok(out.logs[0].includes("j1")); + assert.ok(out.logs[0].includes("running")); + } finally { + out.restore(); + } + }); + + it("returns 1 and prints error JSON when startJob throws", async () => { + const out = captureConsole(); + try { + const code = await handleAsyncRun( + async () => { + throw new Error("fail"); + }, + "codex", + "hi", + undefined, + false, + { jsonMode: true, stdoutWrite: () => {}, stderrWrite: () => {} } + ); + assert.strictEqual(code, 1); + const parsed = JSON.parse(out.errors[0]); + assert.strictEqual(parsed.error, "fail"); + } finally { + out.restore(); + } + }); +}); diff --git a/tools/ai-cli-dispatch/tests/cli.test.ts b/tools/ai-cli-dispatch/tests/cli.test.ts index 107613c..f4ce386 100644 --- a/tools/ai-cli-dispatch/tests/cli.test.ts +++ b/tools/ai-cli-dispatch/tests/cli.test.ts @@ -2,7 +2,16 @@ import { describe, it } from "node:test"; import assert from "node:assert"; import { main } from "../src/cli.js"; import { ClientNotFoundError } from "../src/types.js"; -import type { ClientInfo, ExecResult, ClientName } from "../src/types.js"; +import type { ClientInfo, ExecResult, ClientName, Job, JobStatus, DebugInfo } from "../src/types.js"; + +function mockJob(overrides: Partial & { id: string; status: JobStatus }): Job { + return { + client: "codex", + prompt: "hello", + startedAt: "2024-01-01T00:00:00Z", + ...overrides, + } as Job; +} function captureOutput() { const logs: string[] = []; @@ -426,7 +435,8 @@ describe("main", () => { durationMs: 42, stderrLength: 0, stdoutLength: 6, - } as any); + noisySuccess: false, + } satisfies DebugInfo); return { stdout: "output", stderr: "", @@ -516,7 +526,8 @@ describe("main", () => { durationMs: 42, stderrLength: 0, stdoutLength: 6, - } as any); + noisySuccess: false, + } satisfies DebugInfo); return { stdout: "output", stderr: "", @@ -705,7 +716,8 @@ describe("main", () => { durationMs: 42, stderrLength: 0, stdoutLength: 6, - } as any); + noisySuccess: false, + } satisfies DebugInfo); return { id: "job-def", client: "codex", prompt: "hello", status: "running", startedAt: new Date().toISOString() }; }, stderrWrite: (chunk) => stderrChunks.push(chunk), @@ -871,7 +883,8 @@ describe("main", () => { durationMs: 42, stderrLength: 0, stdoutLength: 6, - } as any); + noisySuccess: false, + } satisfies DebugInfo); return { id: "job-ghi", client: "codex", prompt: "do something", status: "running", startedAt: new Date().toISOString() }; }, resolveClient: () => "codex", @@ -953,13 +966,7 @@ describe("main", () => { ["node", "cli.ts", "status", "job-123"], { detectClients: () => mockClients, - getJob: (jobId) => ({ - id: jobId, - client: "codex", - prompt: "hello", - status: "running", - startedAt: "2024-01-01T00:00:00Z", - } as any), + getJob: (jobId) => mockJob({ id: jobId, status: "running" }), } ); assert.strictEqual(code, 0); @@ -979,15 +986,14 @@ describe("main", () => { ["node", "cli.ts", "status", "job-456"], { detectClients: () => mockClients, - getJob: (jobId) => ({ + getJob: (jobId) => mockJob({ id: jobId, client: "claude", prompt: "write tests", status: "completed", - startedAt: "2024-01-01T00:00:00Z", completedAt: "2024-01-01T00:01:00Z", result: { stdout: "ok", stderr: "", exitCode: 0, client: "claude", durationMs: 100 }, - } as any), + }), } ); assert.strictEqual(code, 0); @@ -1040,13 +1046,7 @@ describe("main", () => { ["node", "cli.ts", "status", "job-123", "--text"], { detectClients: () => mockClients, - getJob: (jobId) => ({ - id: jobId, - client: "codex", - prompt: "hello", - status: "running", - startedAt: "2024-01-01T00:00:00Z", - } as any), + getJob: (jobId) => mockJob({ id: jobId, status: "running" }), } ); assert.strictEqual(code, 0); @@ -1169,13 +1169,7 @@ describe("main", () => { ["node", "cli.ts", "cancel", "job-abc"], { detectClients: () => mockClients, - getJob: (jobId) => ({ - id: jobId, - client: "codex", - prompt: "hello", - status: "running", - startedAt: "2024-01-01T00:00:00Z", - } as any), + getJob: (jobId) => mockJob({ id: jobId, status: "running" }), cancelJob: (jobId) => { cancelledJobId = jobId; }, @@ -1198,14 +1192,7 @@ describe("main", () => { ["node", "cli.ts", "cancel", "job-done"], { detectClients: () => mockClients, - getJob: (jobId) => ({ - id: jobId, - client: "codex", - prompt: "hello", - status: "completed", - startedAt: "2024-01-01T00:00:00Z", - completedAt: "2024-01-01T00:01:00Z", - } as any), + getJob: (jobId) => mockJob({ id: jobId, status: "completed", completedAt: "2024-01-01T00:01:00Z" }), } ); assert.strictEqual(code, 1); @@ -1257,13 +1244,7 @@ describe("main", () => { ["node", "cli.ts", "cancel", "job-abc", "--text"], { detectClients: () => mockClients, - getJob: (jobId) => ({ - id: jobId, - client: "codex", - prompt: "hello", - status: "running", - startedAt: "2024-01-01T00:00:00Z", - } as any), + getJob: (jobId) => mockJob({ id: jobId, status: "running" }), cancelJob: () => {}, } ); @@ -1285,7 +1266,7 @@ describe("main", () => { listJobs: () => [ { id: "job-1", client: "codex", prompt: "p1", status: "running", startedAt: "2024-01-01T00:00:00Z" }, { id: "job-2", client: "claude", prompt: "p2", status: "completed", startedAt: "2024-01-01T00:01:00Z" }, - ] as any[], + ], } ); assert.strictEqual(code, 0); @@ -1310,7 +1291,7 @@ describe("main", () => { receivedFilter = options?.filter; return [ { id: "job-1", client: "codex", prompt: "p1", status: "running", startedAt: "2024-01-01T00:00:00Z" }, - ] as any[]; + ]; }, } ); @@ -1333,7 +1314,7 @@ describe("main", () => { detectClients: () => mockClients, listJobs: () => [ { id: "job-1", client: "codex", prompt: "p1", status: "running", startedAt: "2024-01-01T00:00:00Z" }, - ] as any[], + ], } ); assert.strictEqual(code, 0);