merge M5 into implement/2026-05-18-stef-openclaw-skills
This commit is contained in:
@@ -1,5 +1,9 @@
|
||||
import minimist from "minimist";
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { detectClients as realDetectClients } from "./detect.js";
|
||||
import { executePrompt as realExecutePrompt } from "./execute.js";
|
||||
import { resolveClient as realResolveClient } from "./dispatch.js";
|
||||
import { resolveConfig as realResolveConfig } from "./config.js";
|
||||
import { CLIENT_NAMES } from "./constants.js";
|
||||
import {
|
||||
type ClientName,
|
||||
type ClientInfo,
|
||||
@@ -7,83 +11,56 @@ import {
|
||||
ClientNotFoundError,
|
||||
} from "./types.js";
|
||||
|
||||
const CLIENT_NAMES: ClientName[] = ["codex", "claude", "opencode"];
|
||||
const VERSION_FLAGS: Record<ClientName, string> = {
|
||||
codex: "--version",
|
||||
claude: "--version",
|
||||
opencode: "--version",
|
||||
};
|
||||
|
||||
function whichSync(cmd: string): string | undefined {
|
||||
const isWin = process.platform === "win32";
|
||||
const result = spawnSync(isWin ? "where" : "which", [cmd], {
|
||||
encoding: "utf-8",
|
||||
});
|
||||
if (result.status === 0) {
|
||||
return result.stdout.trim().split("\n")[0];
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function discoverClients(): ClientInfo[] {
|
||||
return CLIENT_NAMES.map((name) => {
|
||||
const path = whichSync(name);
|
||||
if (!path) {
|
||||
return { name, found: false };
|
||||
}
|
||||
const versionResult = spawnSync(name, [VERSION_FLAGS[name]], {
|
||||
encoding: "utf-8",
|
||||
});
|
||||
const version =
|
||||
versionResult.status === 0 ? versionResult.stdout.trim() : undefined;
|
||||
return { name, path, version, found: true };
|
||||
});
|
||||
}
|
||||
|
||||
export function execClient(
|
||||
export interface CliDeps {
|
||||
detectClients?: () => ClientInfo[];
|
||||
executePrompt?: (
|
||||
client: ClientName,
|
||||
prompt: string
|
||||
) => Promise<ExecResult>;
|
||||
resolveClient?: (
|
||||
prompt: string,
|
||||
clientPath?: string
|
||||
): ExecResult {
|
||||
const path =
|
||||
clientPath ?? discoverClients().find((c) => c.name === client)?.path;
|
||||
if (!path) {
|
||||
throw new ClientNotFoundError(client);
|
||||
}
|
||||
const result = spawnSync(client, [prompt], {
|
||||
encoding: "utf-8",
|
||||
stdio: ["inherit", "pipe", "pipe"],
|
||||
});
|
||||
return {
|
||||
stdout: result.stdout ?? "",
|
||||
stderr: result.stderr ?? "",
|
||||
exitCode: typeof result.status === "number" ? result.status : 1,
|
||||
};
|
||||
config?: { client?: ClientName; defaultClient?: ClientName }
|
||||
) => ClientName | null;
|
||||
resolveConfig?: () => { paths: Partial<Record<ClientName, string>>; defaultClient?: ClientName };
|
||||
stdoutWrite?: (chunk: string) => void;
|
||||
stderrWrite?: (chunk: string) => void;
|
||||
}
|
||||
|
||||
function printHelp(): void {
|
||||
console.log(`AI CLI Dispatch
|
||||
|
||||
Usage:
|
||||
ai-cli-dispatch list [--json]
|
||||
ai-cli-dispatch exec --client <client> --prompt <prompt>
|
||||
ai-cli-dispatch list [--json|--text]
|
||||
ai-cli-dispatch run --client <client> --prompt <prompt> [--json|--text]
|
||||
ai-cli-dispatch dispatch <prompt> [--client <client>] [--json|--text]
|
||||
ai-cli-dispatch --help
|
||||
|
||||
Clients: codex, claude, opencode`);
|
||||
}
|
||||
|
||||
export async function main(argv: string[]): Promise<number> {
|
||||
export async function main(
|
||||
argv: string[],
|
||||
deps: CliDeps = {}
|
||||
): Promise<number> {
|
||||
const detectClients = deps.detectClients ?? realDetectClients;
|
||||
const executePrompt = deps.executePrompt ?? realExecutePrompt;
|
||||
const resolveClient = deps.resolveClient ?? realResolveClient;
|
||||
const resolveConfig = deps.resolveConfig ?? realResolveConfig;
|
||||
const stdoutWrite = deps.stdoutWrite ?? ((c: string) => process.stdout.write(c));
|
||||
const stderrWrite = deps.stderrWrite ?? ((c: string) => process.stderr.write(c));
|
||||
|
||||
const rawArgs = argv.slice(2);
|
||||
// When run via tsx, argv[2] is the script path (e.g. src/cli.ts); skip it.
|
||||
const parseArgs =
|
||||
rawArgs[0]?.includes("cli.ts") ? rawArgs.slice(1) : rawArgs;
|
||||
|
||||
const args = minimist(parseArgs, {
|
||||
string: ["client", "prompt"],
|
||||
boolean: ["json", "help", "debug"],
|
||||
boolean: ["json", "text", "help", "debug"],
|
||||
alias: { h: "help" },
|
||||
});
|
||||
|
||||
const jsonMode = !args.text;
|
||||
|
||||
if (args.help) {
|
||||
printHelp();
|
||||
return 0;
|
||||
@@ -91,9 +68,14 @@ export async function main(argv: string[]): Promise<number> {
|
||||
|
||||
const command = args._[0];
|
||||
|
||||
if (!command) {
|
||||
printHelp();
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (command === "list") {
|
||||
const clients = discoverClients();
|
||||
if (args.json) {
|
||||
const clients = detectClients();
|
||||
if (jsonMode) {
|
||||
console.log(JSON.stringify(clients, null, 2));
|
||||
} else {
|
||||
for (const c of clients) {
|
||||
@@ -106,39 +88,111 @@ export async function main(argv: string[]): Promise<number> {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (command === "exec") {
|
||||
if (command === "run") {
|
||||
const client = args.client as ClientName | undefined;
|
||||
const prompt = args.prompt as string | undefined;
|
||||
|
||||
if (!client || !CLIENT_NAMES.includes(client)) {
|
||||
console.error(
|
||||
`Error: --client must be one of ${CLIENT_NAMES.join(", ")}`
|
||||
);
|
||||
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;
|
||||
}
|
||||
|
||||
if (!prompt) {
|
||||
console.error("Error: --prompt is required");
|
||||
const message = "--prompt is required";
|
||||
if (jsonMode) {
|
||||
console.error(JSON.stringify({ error: message }, null, 2));
|
||||
} else {
|
||||
console.error(`Error: ${message}`);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
try {
|
||||
const result = execClient(client, prompt);
|
||||
if (result.stdout) process.stdout.write(result.stdout);
|
||||
if (result.stderr) process.stderr.write(result.stderr);
|
||||
return result.exitCode;
|
||||
} catch (err) {
|
||||
if (err instanceof ClientNotFoundError) {
|
||||
console.error(err.message);
|
||||
return 1;
|
||||
const result = await executePrompt(client, prompt);
|
||||
if (jsonMode) {
|
||||
console.log(JSON.stringify(result, null, 2));
|
||||
} else {
|
||||
if (result.stdout) stdoutWrite(result.stdout);
|
||||
if (result.stderr) stderrWrite(result.stderr);
|
||||
}
|
||||
throw err;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
if (command) {
|
||||
console.error(`Unknown command: ${command}`);
|
||||
} else {
|
||||
console.error("Error: no command given");
|
||||
if (command === "dispatch") {
|
||||
let prompt = args.prompt as string | undefined;
|
||||
if (!prompt && args._.length > 1) {
|
||||
prompt = args._.slice(1).join(" ");
|
||||
}
|
||||
|
||||
if (!prompt) {
|
||||
const message = "prompt is required";
|
||||
if (jsonMode) {
|
||||
console.error(JSON.stringify({ error: message }, null, 2));
|
||||
} else {
|
||||
console.error(`Error: ${message}`);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
const config = resolveConfig();
|
||||
const explicitClient = args.client as ClientName | undefined;
|
||||
const client = resolveClient(prompt, {
|
||||
client: explicitClient,
|
||||
defaultClient: config.defaultClient,
|
||||
});
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await executePrompt(client, prompt);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
const message = `Unknown command: ${command}`;
|
||||
if (jsonMode) {
|
||||
console.error(JSON.stringify({ error: message }, null, 2));
|
||||
} else {
|
||||
console.error(message);
|
||||
}
|
||||
printHelp();
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,66 +1,403 @@
|
||||
import { describe, it } from "node:test";
|
||||
import assert from "node:assert";
|
||||
import { main, discoverClients, execClient } from "../src/cli.js";
|
||||
import { main } from "../src/cli.js";
|
||||
import { ClientNotFoundError } from "../src/types.js";
|
||||
import type { ClientInfo, ExecResult, ClientName } from "../src/types.js";
|
||||
|
||||
describe("discoverClients", () => {
|
||||
it("returns all three configured clients", () => {
|
||||
const clients = discoverClients();
|
||||
assert.strictEqual(clients.length, 3);
|
||||
const names = clients.map((c) => c.name);
|
||||
assert.deepStrictEqual(names, ["codex", "claude", "opencode"]);
|
||||
});
|
||||
function captureOutput() {
|
||||
const logs: string[] = [];
|
||||
const errors: string[] = [];
|
||||
|
||||
it("returns ClientInfo with found boolean", () => {
|
||||
const clients = discoverClients();
|
||||
for (const c of clients) {
|
||||
assert.strictEqual(typeof c.found, "boolean");
|
||||
}
|
||||
});
|
||||
});
|
||||
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("main", () => {
|
||||
it("returns 0 for --help", async () => {
|
||||
const mockClients: ClientInfo[] = [
|
||||
{ name: "codex", found: true, path: "/usr/bin/codex", version: "1.0.0" },
|
||||
{ name: "claude", found: true, path: "/usr/bin/claude", version: "2.0.0" },
|
||||
{ name: "opencode", found: false },
|
||||
];
|
||||
|
||||
const mockResult: ExecResult = {
|
||||
stdout: "output",
|
||||
stderr: "",
|
||||
exitCode: 0,
|
||||
};
|
||||
|
||||
it("returns 0 for --help and prints usage", async () => {
|
||||
const out = captureOutput();
|
||||
try {
|
||||
const code = await main(["node", "cli.ts", "--help"]);
|
||||
assert.strictEqual(code, 0);
|
||||
assert.ok(out.logs.some((l) => l.includes("Usage:")));
|
||||
} finally {
|
||||
out.restore();
|
||||
}
|
||||
});
|
||||
|
||||
it("returns 0 for list", async () => {
|
||||
const code = await main(["node", "cli.ts", "list"]);
|
||||
assert.strictEqual(code, 0);
|
||||
});
|
||||
|
||||
it("returns 0 for list --json", async () => {
|
||||
const code = await main(["node", "cli.ts", "list", "--json"]);
|
||||
assert.strictEqual(code, 0);
|
||||
});
|
||||
|
||||
it("returns 1 for exec without --client", async () => {
|
||||
const code = await main(["node", "cli.ts", "exec", "--prompt", "hello"]);
|
||||
assert.strictEqual(code, 1);
|
||||
});
|
||||
|
||||
it("returns 1 for exec without --prompt", async () => {
|
||||
const code = await main(["node", "cli.ts", "exec", "--client", "codex"]);
|
||||
it("prints usage and returns 1 for bare invocation", async () => {
|
||||
const out = captureOutput();
|
||||
try {
|
||||
const code = await main(["node", "cli.ts"]);
|
||||
assert.strictEqual(code, 1);
|
||||
assert.ok(out.logs.some((l) => l.includes("Usage:")));
|
||||
} finally {
|
||||
out.restore();
|
||||
}
|
||||
});
|
||||
|
||||
it("returns 1 for unknown command", async () => {
|
||||
const out = captureOutput();
|
||||
try {
|
||||
const code = await main(["node", "cli.ts", "bogus"]);
|
||||
assert.strictEqual(code, 1);
|
||||
const err = out.errors[0] ?? out.logs[0];
|
||||
assert.ok(String(err).includes("Unknown command"));
|
||||
} finally {
|
||||
out.restore();
|
||||
}
|
||||
});
|
||||
|
||||
it("returns 1 when no command is given", async () => {
|
||||
const code = await main(["node", "cli.ts"]);
|
||||
assert.strictEqual(code, 1);
|
||||
it("list prints JSON array of clients by default", async () => {
|
||||
const out = captureOutput();
|
||||
try {
|
||||
const code = await main(["node", "cli.ts", "list"], {
|
||||
detectClients: () => mockClients,
|
||||
});
|
||||
assert.strictEqual(code, 0);
|
||||
assert.strictEqual(out.logs.length, 1);
|
||||
const parsed = JSON.parse(out.logs[0]);
|
||||
assert.deepStrictEqual(parsed, mockClients);
|
||||
} finally {
|
||||
out.restore();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("execClient", () => {
|
||||
it("throws ClientNotFoundError when client path is empty", () => {
|
||||
assert.throws(
|
||||
() => execClient("codex", "test prompt", ""),
|
||||
(err: unknown) => err instanceof ClientNotFoundError
|
||||
it("list prints human-readable output with --text", async () => {
|
||||
const out = captureOutput();
|
||||
try {
|
||||
const code = await main(["node", "cli.ts", "list", "--text"], {
|
||||
detectClients: () => mockClients,
|
||||
});
|
||||
assert.strictEqual(code, 0);
|
||||
assert.ok(out.logs.some((l) => l.includes("codex:")));
|
||||
assert.ok(out.logs.some((l) => l.includes("claude:")));
|
||||
assert.ok(out.logs.some((l) => l.includes("opencode:")));
|
||||
} finally {
|
||||
out.restore();
|
||||
}
|
||||
});
|
||||
|
||||
it("run executes client with prompt and prints JSON result", async () => {
|
||||
const out = captureOutput();
|
||||
try {
|
||||
const executed: { client: ClientName; prompt: string }[] = [];
|
||||
const code = await main(
|
||||
["node", "cli.ts", "run", "--client", "codex", "--prompt", "hello"],
|
||||
{
|
||||
detectClients: () => mockClients,
|
||||
executePrompt: async (client, prompt) => {
|
||||
executed.push({ client, prompt });
|
||||
return mockResult;
|
||||
},
|
||||
}
|
||||
);
|
||||
assert.strictEqual(code, 0);
|
||||
assert.strictEqual(executed.length, 1);
|
||||
assert.strictEqual(executed[0].client, "codex");
|
||||
assert.strictEqual(executed[0].prompt, "hello");
|
||||
const parsed = JSON.parse(out.logs[0]);
|
||||
assert.deepStrictEqual(parsed, mockResult);
|
||||
} finally {
|
||||
out.restore();
|
||||
}
|
||||
});
|
||||
|
||||
it("run prints text output with --text", async () => {
|
||||
const out = captureOutput();
|
||||
const stdoutChunks: string[] = [];
|
||||
const stderrChunks: string[] = [];
|
||||
try {
|
||||
const code = await main(
|
||||
[
|
||||
"node",
|
||||
"cli.ts",
|
||||
"run",
|
||||
"--client",
|
||||
"codex",
|
||||
"--prompt",
|
||||
"hello",
|
||||
"--text",
|
||||
],
|
||||
{
|
||||
detectClients: () => mockClients,
|
||||
executePrompt: async () => ({
|
||||
stdout: "hello-out",
|
||||
stderr: "hello-err",
|
||||
exitCode: 0,
|
||||
}),
|
||||
stdoutWrite: (chunk) => stdoutChunks.push(chunk),
|
||||
stderrWrite: (chunk) => stderrChunks.push(chunk),
|
||||
}
|
||||
);
|
||||
assert.strictEqual(code, 0);
|
||||
assert.strictEqual(stdoutChunks.join(""), "hello-out");
|
||||
assert.strictEqual(stderrChunks.join(""), "hello-err");
|
||||
} finally {
|
||||
out.restore();
|
||||
}
|
||||
});
|
||||
|
||||
it("run returns 1 and prints error JSON when client is missing", async () => {
|
||||
const out = captureOutput();
|
||||
try {
|
||||
const code = await main(["node", "cli.ts", "run", "--prompt", "hello"], {
|
||||
detectClients: () => mockClients,
|
||||
});
|
||||
assert.strictEqual(code, 1);
|
||||
const parsed = JSON.parse(out.errors[0]);
|
||||
assert.ok(parsed.error.includes("client"));
|
||||
} finally {
|
||||
out.restore();
|
||||
}
|
||||
});
|
||||
|
||||
it("run returns 1 and prints error text when client is missing in text mode", async () => {
|
||||
const out = captureOutput();
|
||||
try {
|
||||
const code = await main(
|
||||
["node", "cli.ts", "run", "--prompt", "hello", "--text"],
|
||||
{
|
||||
detectClients: () => mockClients,
|
||||
}
|
||||
);
|
||||
assert.strictEqual(code, 1);
|
||||
assert.ok(out.errors[0].includes("client"));
|
||||
} finally {
|
||||
out.restore();
|
||||
}
|
||||
});
|
||||
|
||||
it("run returns 1 and prints error JSON when prompt is missing", async () => {
|
||||
const out = captureOutput();
|
||||
try {
|
||||
const code = await main(["node", "cli.ts", "run", "--client", "codex"], {
|
||||
detectClients: () => mockClients,
|
||||
});
|
||||
assert.strictEqual(code, 1);
|
||||
const parsed = JSON.parse(out.errors[0]);
|
||||
assert.ok(parsed.error.includes("prompt"));
|
||||
} finally {
|
||||
out.restore();
|
||||
}
|
||||
});
|
||||
|
||||
it("run returns 1 for unknown client", async () => {
|
||||
const out = captureOutput();
|
||||
try {
|
||||
const code = await main(
|
||||
["node", "cli.ts", "run", "--client", "bogus", "--prompt", "hello"],
|
||||
{
|
||||
detectClients: () => mockClients,
|
||||
}
|
||||
);
|
||||
assert.strictEqual(code, 1);
|
||||
const parsed = JSON.parse(out.errors[0]);
|
||||
assert.ok(parsed.error.includes("client"));
|
||||
} finally {
|
||||
out.restore();
|
||||
}
|
||||
});
|
||||
|
||||
it("run returns 1 and prints error JSON when client not found", async () => {
|
||||
const out = captureOutput();
|
||||
try {
|
||||
const code = await main(
|
||||
["node", "cli.ts", "run", "--client", "codex", "--prompt", "hello"],
|
||||
{
|
||||
detectClients: () => mockClients,
|
||||
executePrompt: async () => {
|
||||
throw new ClientNotFoundError("codex");
|
||||
},
|
||||
}
|
||||
);
|
||||
assert.strictEqual(code, 1);
|
||||
const parsed = JSON.parse(out.errors[0]);
|
||||
assert.ok(parsed.error.includes("not found"));
|
||||
} finally {
|
||||
out.restore();
|
||||
}
|
||||
});
|
||||
|
||||
it("dispatch routes positional prompt and prints JSON result", async () => {
|
||||
const out = captureOutput();
|
||||
try {
|
||||
const executed: { client: ClientName; prompt: string }[] = [];
|
||||
const code = await main(
|
||||
["node", "cli.ts", "dispatch", "use claude to write tests"],
|
||||
{
|
||||
detectClients: () => mockClients,
|
||||
executePrompt: async (client, prompt) => {
|
||||
executed.push({ client, prompt });
|
||||
return mockResult;
|
||||
},
|
||||
resolveClient: () => "claude",
|
||||
resolveConfig: () => ({ paths: {} }),
|
||||
}
|
||||
);
|
||||
assert.strictEqual(code, 0);
|
||||
assert.strictEqual(executed.length, 1);
|
||||
assert.strictEqual(executed[0].client, "claude");
|
||||
assert.strictEqual(executed[0].prompt, "use claude to write tests");
|
||||
const parsed = JSON.parse(out.logs[0]);
|
||||
assert.deepStrictEqual(parsed, mockResult);
|
||||
} finally {
|
||||
out.restore();
|
||||
}
|
||||
});
|
||||
|
||||
it("dispatch routes --prompt flag and prints JSON result", async () => {
|
||||
const out = captureOutput();
|
||||
try {
|
||||
const executed: { client: ClientName; prompt: string }[] = [];
|
||||
const code = await main(
|
||||
["node", "cli.ts", "dispatch", "--prompt", "use codex to refactor"],
|
||||
{
|
||||
detectClients: () => mockClients,
|
||||
executePrompt: async (client, prompt) => {
|
||||
executed.push({ client, prompt });
|
||||
return mockResult;
|
||||
},
|
||||
resolveClient: () => "codex",
|
||||
resolveConfig: () => ({ paths: {} }),
|
||||
}
|
||||
);
|
||||
assert.strictEqual(code, 0);
|
||||
assert.strictEqual(executed.length, 1);
|
||||
assert.strictEqual(executed[0].prompt, "use codex to refactor");
|
||||
} finally {
|
||||
out.restore();
|
||||
}
|
||||
});
|
||||
|
||||
it("dispatch respects --client override", async () => {
|
||||
const out = captureOutput();
|
||||
try {
|
||||
const executed: { client: ClientName; prompt: string }[] = [];
|
||||
const code = await main(
|
||||
["node", "cli.ts", "dispatch", "do something", "--client", "opencode"],
|
||||
{
|
||||
detectClients: () => mockClients,
|
||||
executePrompt: async (client, prompt) => {
|
||||
executed.push({ client, prompt });
|
||||
return mockResult;
|
||||
},
|
||||
resolveClient: (_p, cfg) => cfg?.client ?? null,
|
||||
resolveConfig: () => ({ paths: {}, defaultClient: "claude" }),
|
||||
}
|
||||
);
|
||||
assert.strictEqual(code, 0);
|
||||
assert.strictEqual(executed[0].client, "opencode");
|
||||
} finally {
|
||||
out.restore();
|
||||
}
|
||||
});
|
||||
|
||||
it("dispatch returns 1 when prompt cannot be resolved", async () => {
|
||||
const out = captureOutput();
|
||||
try {
|
||||
const code = await main(
|
||||
["node", "cli.ts", "dispatch", "do something"],
|
||||
{
|
||||
detectClients: () => mockClients,
|
||||
executePrompt: async () => mockResult,
|
||||
resolveClient: () => null,
|
||||
resolveConfig: () => ({ paths: {} }),
|
||||
}
|
||||
);
|
||||
assert.strictEqual(code, 1);
|
||||
const parsed = JSON.parse(out.errors[0]);
|
||||
assert.ok(parsed.error.includes("resolve"));
|
||||
} finally {
|
||||
out.restore();
|
||||
}
|
||||
});
|
||||
|
||||
it("dispatch returns 1 when resolved client is not found", async () => {
|
||||
const out = captureOutput();
|
||||
try {
|
||||
const code = await main(
|
||||
["node", "cli.ts", "dispatch", "do something"],
|
||||
{
|
||||
detectClients: () => mockClients,
|
||||
executePrompt: async () => {
|
||||
throw new ClientNotFoundError("claude");
|
||||
},
|
||||
resolveClient: () => "claude",
|
||||
resolveConfig: () => ({ paths: {} }),
|
||||
}
|
||||
);
|
||||
assert.strictEqual(code, 1);
|
||||
const parsed = JSON.parse(out.errors[0]);
|
||||
assert.ok(parsed.error.includes("not found"));
|
||||
} finally {
|
||||
out.restore();
|
||||
}
|
||||
});
|
||||
|
||||
it("dispatch returns 1 when no prompt is given", async () => {
|
||||
const out = captureOutput();
|
||||
try {
|
||||
const code = await main(["node", "cli.ts", "dispatch"], {
|
||||
detectClients: () => mockClients,
|
||||
});
|
||||
assert.strictEqual(code, 1);
|
||||
const parsed = JSON.parse(out.errors[0]);
|
||||
assert.ok(parsed.error.includes("prompt"));
|
||||
} finally {
|
||||
out.restore();
|
||||
}
|
||||
});
|
||||
|
||||
it("dispatch prints text output with --text", async () => {
|
||||
const out = captureOutput();
|
||||
const stdoutChunks: string[] = [];
|
||||
try {
|
||||
const code = await main(
|
||||
["node", "cli.ts", "dispatch", "do something", "--text"],
|
||||
{
|
||||
detectClients: () => mockClients,
|
||||
executePrompt: async () => ({
|
||||
stdout: "done",
|
||||
stderr: "",
|
||||
exitCode: 0,
|
||||
}),
|
||||
resolveClient: () => "codex",
|
||||
resolveConfig: () => ({ paths: {} }),
|
||||
stdoutWrite: (chunk) => stdoutChunks.push(chunk),
|
||||
}
|
||||
);
|
||||
assert.strictEqual(code, 0);
|
||||
assert.strictEqual(stdoutChunks.join(""), "done");
|
||||
} finally {
|
||||
out.restore();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user