RunCliTool.ts•2.32 kB
import { z } from 'zod';
import { spawn, ChildProcess } from 'child_process';
import { createOutputCollector, formatTruncationSuffix } from './outputCollector';
export const RunCliToolSchema = z.object({
args: z
.array(z.string())
.describe('Array of arguments to pass to the CLI tool (e.g., ["status"] for "git status")'),
});
export type RunCliInput = z.infer<typeof RunCliToolSchema>;
/**
* Executes the CLI with provided arguments using async spawn for safe argument passing.
* Returns a Promise to avoid blocking the event loop.
*/
export function executeRunCli(baseCli: string, input: RunCliInput): Promise<string> {
return new Promise((resolve) => {
const child: ChildProcess = spawn(baseCli, input.args);
const stdoutCollector = createOutputCollector(child.stdout);
const stderrCollector = createOutputCollector(child.stderr);
child.on('error', (error: Error) => {
resolve(formatSpawnError(error));
});
child.on('close', (code: number | null, signal: NodeJS.Signals | null) => {
resolve(
formatResult(
stdoutCollector.getText(),
stderrCollector.getText(),
stdoutCollector.isTruncated(),
stderrCollector.isTruncated(),
code,
signal
)
);
});
});
}
function formatResult(
stdout: string,
stderr: string,
stdoutTruncated: boolean,
stderrTruncated: boolean,
exitCode: number | null,
signal: NodeJS.Signals | null
): string {
// If killed by signal, treat as error
if (signal) {
return [
'Error:',
`Stdout: ${stdout || '(empty)'}${formatTruncationSuffix(stdoutTruncated)}`,
`Stderr: ${stderr || '(empty)'}${formatTruncationSuffix(stderrTruncated)}`,
`Exit code: (killed by signal)`,
`Signal: ${signal}`,
].join('\n');
}
const code = exitCode ?? 0;
const status = code === 0 ? 'Success' : 'Error';
return [
`${status}:`,
`Stdout: ${stdout || '(empty)'}${formatTruncationSuffix(stdoutTruncated)}`,
`Stderr: ${stderr || '(empty)'}${formatTruncationSuffix(stderrTruncated)}`,
`Exit code: ${code}`,
].join('\n');
}
function formatSpawnError(error: Error): string {
return [
'Error:',
`Stdout: `,
`Stderr: `,
`Exit code: unknown`,
`Message: ${error.message}`,
].join('\n');
}