/** * Headless harness calling — run a one-shot prompt (optionally with images) * through a locally installed AI coding harness CLI and return its reply. * * Backends, in default preference order (subscription seats an embedding * host already pays for, before any metered API a host might add on top): * * 1. claude-code `claude -p` JSON envelope on stdout * 2. codex `codex exec` last message written to a file * 3. cursor `cursor-agent -p` plain text on stdout * * Design rules, each learned from a live failure: * * - Every call runs from a NEUTRAL temp cwd holding only the staged images, * so the nested session loads no repo instructions, hooks, or coordination * rituals from the caller's checkout. * - The child's stdin is CLOSED immediately. execFile leaves it open as a * pipe, and codex interprets that as "more prompt coming" and blocks on * it, then exits with no output. * - An empty reply THROWS instead of returning "": a one-shot call that * produced nothing was inconclusive, and callers gating on the result must * fail closed rather than read silence as success. * - `runHeadless` walks the backend chain PER CALL: a backend that is * installed but errors (rate limit, timeout, transient exec failure) hands * the same request to the next backend instead of failing the call. "On * PATH" is not "working", and under parallel load the first backend can * flake on a fraction of calls while the second runs clean. * * Env knobs (read through coordEnv, so the HARNERY_ prefix stays in one * place): HARNERY_HEADLESS_BACKEND forces one backend and disables the * fallback walk; HARNERY_HEADLESS_MODEL overrides the model passed to * whichever backend runs. */ /** Names accepted by `runHeadlessOn`, `runHeadless({backends})`, and the * HARNERY_HEADLESS_BACKEND env knob. */ export type HeadlessBackendName = "claude-code" | "codex" | "cursor"; /** An image to stage into the call's neutral cwd. `data` is the PNG bytes * (Buffer) or their base64 encoding (string). */ export interface HeadlessImage { data: Buffer | string; /** Staged filename inside the neutral dir (default `image-.png`). */ name?: string; } export interface HeadlessRequest { /** * The prompt, or a builder receiving the staged image paths. Backends that * cannot attach an image natively (claude-code, cursor) rely on the prompt * naming the staged paths so the harness reads them with its own tools; * codex additionally attaches every image via `-i`. */ prompt: string | ((ctx: { imagePaths: string[]; }) => string); images?: HeadlessImage[]; /** Model override for this call; HARNERY_HEADLESS_MODEL wins over it. */ model?: string; /** Kill the child after this long (default 240_000 ms). */ timeoutMs?: number; /** Agentic turn budget where the backend supports one (default 4). */ maxTurns?: number; } export interface HeadlessResult { /** The harness's final reply text, never empty (empty replies throw). */ text: string; /** Which backend produced it. */ backend: HeadlessBackendName; } export interface RunHeadlessOptions { /** Preference order (default: HEADLESS_BACKENDS order). */ backends?: HeadlessBackendName[]; /** Walk the chain past a failing backend (default true). Forcing a backend * via HARNERY_HEADLESS_BACKEND implies false. */ fallback?: boolean; } /** Cross-runtime `which`: walk PATH (and PATHEXT on Windows) for an * executable. Node has no built-in and Bun.which only exists under Bun. */ export declare function whichBin(bin: string): string | undefined; /** The backend registry in default preference order. */ export declare const HEADLESS_BACKENDS: ReadonlyArray<{ name: HeadlessBackendName; bin: string; }>; /** Backends whose binary is on PATH right now, in preference order. Being * listed means installed, not proven working — that is what the per-call * fallback in `runHeadless` is for. */ export declare function availableHeadlessBackends(): HeadlessBackendName[]; /** Run the request on exactly one named backend. Throws when the backend is * not installed or the call fails. */ export declare function runHeadlessOn(name: HeadlessBackendName, req: HeadlessRequest): Promise; /** * Run the request on the first backend that succeeds, walking the preference * order past installed-but-failing backends (unless fallback is disabled or a * backend is forced via HARNERY_HEADLESS_BACKEND). Throws when no backend is * installed, or every attempted backend failed — the error message carries * each backend's failure so a flaky chain is diagnosable. */ export declare function runHeadless(req: HeadlessRequest, opts?: RunHeadlessOptions): Promise; //# sourceMappingURL=index.d.ts.map