framework
Version:
The (AI) Framework: turnkey, zero-config AI orchestration that wraps a coding-agent CLI (Claude Code) as a black box and takes you from an idea to a running app. Vite for AI.
53 lines • 2.44 kB
JavaScript
/**
* One `execFile`-backed CLI runner, configured per binary.
*
* `git` and `gh` were each wrapped by hand — the same dynamic import, the same promise
* around `execFile`, the same `String(stdout)` — differing only in the binary, the timeout
* and how a failure is reported. Those are the parameters; the wrapper is not worth writing
* twice, and it was written five times.
*/
/**
* A CLI killed for outrunning its timeout, as opposed to one the tool itself rejected (#997).
*
* `execFile` SIGTERMs on timeout, and a killed `git push` usually writes nothing to stderr, so
* without this the failure surfaces as a bare "Command failed: git push ..." that reads like a
* rejected push.
*/
export class CliTimeoutError extends Error {
bin;
args;
timeoutMs;
/** Brand, so a value that crossed a module boundary is still recognisable. */
timedOut = true;
constructor(bin, args, timeoutMs) {
super(`${bin} ${args.join(' ')} timed out after ${timeoutMs}ms`);
this.bin = bin;
this.args = args;
this.timeoutMs = timeoutMs;
this.name = 'CliTimeoutError';
}
}
/** True when a {@link CliRunner} rejection is a timeout kill rather than a non-zero exit (#997). */
export function isCliTimeout(err) {
return err instanceof Error && err.timedOut === true;
}
/** Build a {@link CliRunner} for one binary. */
export function cliRunner(opts) {
return async (args, cwd) => {
const { execFile } = await import('node:child_process');
const timeoutMs = typeof opts.timeoutMs === 'function' ? opts.timeoutMs(args) : opts.timeoutMs;
return new Promise((resolvePromise, rejectPromise) => {
execFile(opts.bin, args, { cwd, timeout: timeoutMs, ...(opts.maxBuffer !== undefined ? { maxBuffer: opts.maxBuffer } : {}) }, (err, stdout, stderr) => {
if (!err)
return resolvePromise(String(stdout));
// execFile kills on both timeout and a maxBuffer overrun; only the latter carries ENOBUFS.
const killed = err.killed === true;
if (killed && err.code !== 'ENOBUFS') {
return rejectPromise(new CliTimeoutError(opts.bin, args, timeoutMs));
}
rejectPromise(opts.preferStderr ? new Error(String(stderr).trim() || err.message) : err);
});
});
};
}
//# sourceMappingURL=cli-exec.js.map