/** * CLI subprocess capture. * * Implements the bounded-capture wrapper used by `peac observe command` * and `peac record command`. Hard implementation invariants: * * - Spawn discipline: `spawn(prog, args, { shell: false })` * exclusively. NEVER `exec()`. The wrapper does NOT synthesize * shell syntax or rewrite the command. `--shell-mode` is * acknowledgement only. * - Streaming capture: `node:crypto.createHash` updated chunk-by-chunk. * Byte counter incremented chunk-by-chunk. Bounded sample buffer * is the ONLY buffer retained, and ONLY when raw capture is * double-opted-in. Full streams NEVER held in memory. * - Stdin: `--capture-stdin-mode` controls BOTH whether the wrapper * pipes parent stdin to the child AND what the wrapper records. * `none` (default) closes child stdin and does NOT read parent stdin. * The stdin pump must terminate when the child exits even if the * parent stream is still open. * - Timeout: `--timeout-ms` / `--kill-grace-ms` cascade SIGTERM then * SIGKILL; the record IS still emitted. The nested SIGKILL timer * is cleared on child close so it cannot keep the event loop alive. * - Signal exit codes follow POSIX `128 + signal-num`: SIGINT=130, * SIGTERM=143, SIGKILL=137; unknown signals fall back to 128. * - Stdin pumping is listener-based (event handlers attached to the * parent stream and child stdin). Backpressure pauses the parent * until child stdin emits 'drain'. The pump is reliably abortable * via an external AbortSignal even if the parent stream stays * open and idle, and removes ONLY pump-owned listeners on cleanup * (the parent is caller-owned). */ import type { Readable } from 'node:stream'; /** * Thrown by `captureCommand` when the child process fails to spawn * (missing binary, ENOENT, EACCES, etc.). Node emits the `'error'` * event in this case and never emits `'spawn'`/`'close'` -- without * explicit handling the wrapper would hang waiting for `'close'`. */ export declare class CliSpawnFailedError extends Error { readonly cause?: unknown | undefined; readonly code = "cli.spawn_failed"; constructor(message: string, cause?: unknown | undefined); } export type StdinMode = 'none' | 'length-only' | 'hashed'; export type CaptureMode = 'hashed' | 'redacted' | 'raw'; export interface CaptureOptions { /** Resolved program path (passed verbatim to `spawn`). */ program: string; /** Argv tail (passed verbatim to `spawn`; never wrapped in a shell). */ args: string[]; /** Working directory for the child (passed to `spawn`); not the recorded cwd. */ cwd: string; /** * Environment passed to the child. REQUIRED. Callers (the * pipeline / pure handler) must construct the env explicitly so * the env source is auditable; this function does NOT silently * fall back to `process.env`. */ env: NodeJS.ProcessEnv; stdinMode: StdinMode; /** * Whether raw sample emission is enabled (requires `--capture-mode raw` * AND `--unsafe-allow-raw-capture`). When false, no `sample_base64` is * retained for stdout / stderr and the bounded sample buffer stays * empty even though the streams are still hashed and counted. */ rawCaptureEnabled: boolean; /** Bounded sample cap for stdout (raw mode only). */ stdoutSampleBytes: number; /** Bounded sample cap for stderr (raw mode only). */ stderrSampleBytes: number; /** Wrapper timeout (ms). After this, the wrapper sends SIGTERM. */ timeoutMs: number; /** SIGTERM-to-SIGKILL grace (ms). */ killGraceMs: number; /** * Optional readable from which to source parent stdin. Defaults to * `process.stdin`. Tests inject a custom stream. */ parentStdin?: Readable; } export interface StreamCaptureRef { length: number; /** sha256 hex digest with the canonical `sha256:` prefix. */ sha256: string; truncated: boolean; /** Present only when raw capture is enabled and the secret-scan does not suppress it. */ sample_base64?: string; } export interface StdinCaptureRef { mode: StdinMode; length?: number; sha256?: string; truncated?: boolean; } export interface CaptureResult { exitCode: number; /** OS-reported child exit signal. Distinct from `terminationSignal`. */ signal: string | null; /** True when the wrapper sent termination signals due to `--timeout-ms` elapsing. */ timedOut: boolean; /** Signal sent BY THE WRAPPER after timeout. */ terminationSignal: string | null; startedAt: string; finishedAt: string; durationMs: number; stdout: StreamCaptureRef; stderr: StreamCaptureRef; stdin: StdinCaptureRef; } export declare function exitCodeForSignal(signal: string | null | undefined): number; /** * Run the bounded-capture wrapper. The promise resolves when the child * exits OR is killed by the wrapper after timeout; it does NOT reject * on non-zero exit; the record IS still emitted regardless of child exit code. */ export declare function captureCommand(opts: CaptureOptions): Promise; //# sourceMappingURL=capture.d.ts.map