feat(M3): Async CLI Integration

This commit is contained in:
2026-05-19 22:22:54 -05:00
parent a2c2b8bf6d
commit 591829369c
5 changed files with 440 additions and 271 deletions
+43 -219
View File
@@ -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: <number>[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: <number>[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 =