/** Supported AI CLI provider names. */ type AiProviderName = "amp" | "claude" | "codex" | "copilot" | "crush" | "cursor" | "droid" | "gemini" | "kimi" | "opencode" | "qwen"; /** * Per-invocation flags passed to a provider's `buildArgs`. * * `dangerous` controls whether permission-bypass / auto-approval flags * (e.g. `--dangerously-skip-permissions`, `--yolo`, `--approval-mode full-auto`) * are included. It defaults to `false` so that, by default, a provider runs with * its normal safety prompts intact. */ interface AiBuildArgsOptions { /** * When `true`, append the provider's permission-bypass / auto-approval flag * so the agent can act on the host without interactive confirmation. * * SECURITY: enabling this grants the agent unattended tool/file/shell access. * Any untrusted content embedded in the prompt that prompt-injects the agent * then executes with all safety rails disabled. Defaults to `false`. */ dangerous?: boolean; /** Maximum tokens in the response (only honored by providers that expose the flag). */ maxTokens: number; /** Model identifier. Empty string means provider-default. */ model: string; } /** * How strongly a session marker implies an agent is driving the process. * * `definite` markers are set only in agent-spawned shells. `ambient` markers * prove the surrounding platform (editor, cloud workspace) but not that an * agent — rather than a human — is issuing commands. */ type AiSessionConfidence = "ambient" | "definite"; /** * What kind of AI session was detected. * * - `agent` — an AI agent is autonomously driving the process (an agent-spawned shell). * - `interactive` — a human is working inside an AI environment (an editor's integrated terminal); the tooling is present but a person is at the keyboard. * * Defaults are derived from {@link AiSessionConfidence} when a marker omits it: * `definite` -> `agent`, `ambient` -> `interactive`. */ type AiSessionType = "agent" | "interactive"; /** * A single environment condition. * * - A bare string matches when that variable is set to a non-empty, non-disabled (`0`/`false`) value. * - A `[name, value]` tuple matches when that variable is exactly `value`. */ type EnvAtom = [string, string] | string; /** * A composite environment condition. When an object, every clause present must * hold: every `all` entry matches, at least one `any` entry matches, and no * `none` entry matches. */ interface EnvConditionObject { /** Every entry must match. */ all?: EnvCondition[]; /** At least one entry must match. */ any?: EnvCondition[]; /** No entry may match. */ none?: EnvCondition[]; } /** An environment condition: a single {@link EnvAtom} or a composite {@link EnvConditionObject}. */ type EnvCondition = EnvAtom | EnvConditionObject; /** Fields shared by every {@link AiSessionMarkerConfig} variant. */ interface AiSessionMarkerBase { /** Display-name override for this marker (e.g. `"Cursor editor"` for the ambient Cursor marker); defaults to the provider's `displayName`. */ agent?: string; /** See {@link AiSessionConfidence}. */ confidence: AiSessionConfidence; /** Session type override; defaults from `confidence` (see {@link AiSessionType}). */ type?: AiSessionType; } /** A composite marker: a multi-variable `match` condition with an explicit reporting `label`. */ interface AiSessionCompositeMarker extends AiSessionMarkerBase { /** Reporting label surfaced as the matched `signal` (e.g. `"TERM_PROGRAM+PAGER"`). */ label: string; /** Composite env condition. */ match: EnvCondition; } /** A simple marker: a single environment variable, optionally scoped to an exact value. */ interface AiSessionVariableMarker extends AiSessionMarkerBase { /** Require this exact value (for shared variables like `AGENT`). */ equals?: string; /** The environment variable to check. Also the reported `signal`. */ variable: string; } /** * One environment marker a provider's harness sets in the shells it spawns, * declared on the provider's config and consumed by `detectAiSession`. A marker * is EITHER a single-variable check OR a composite `match` condition — the * discriminated union makes a marker with neither (or both) unrepresentable. * Only add markers that ship in a production implementation — an unverified * marker is a behavior bug waiting for a human to hit it. */ type AiSessionMarkerConfig = AiSessionCompositeMarker | AiSessionVariableMarker; /** Configuration for an AI CLI provider, including how to build CLI arguments. */ interface AiProviderConfig { /** Alternate CLI command names to try (e.g., `gemini-cli` for `gemini`). */ alternateCommands: string[]; /** * Builds the CLI arguments array for a given prompt and options. * * Permission-bypass flags are only added when `options.dangerous` is `true`. */ buildArgs: (prompt: string, options: AiBuildArgsOptions) => string[]; /** Primary CLI command name. */ command: string; /** Default model to use if none specified. Empty string means provider-default. */ defaultModel: string; /** Human-readable provider name (e.g. `"Claude Code"`), used in session-detection results. */ displayName: string; /** Environment variable that can override the CLI path (e.g., `CLAUDE_PATH`). */ envVariable: string; /** Env markers this provider's harness sets in agent-spawned shells (see {@link AiSessionMarkerConfig}); empty when none are verified. */ sessionMarkers: AiSessionMarkerConfig[]; /** Whether the provider honors the `maxTokens` option in its CLI invocation. */ supportsMaxTokens: boolean; /** Whether the provider honors the `model` option in its CLI invocation. */ supportsModel: boolean; } /** Information about a detected AI CLI provider. */ interface AiProviderInfo { /** Whether the provider was found on the system. */ available: boolean; /** How the provider was detected. */ detectionMethod?: "envvar" | "known-path" | "which"; /** Provider name. */ name: AiProviderName; /** Absolute path to the CLI binary. */ path?: string; /** Detected version string (e.g., `"1.2.3"`). */ version?: string; } /** Options controlling provider detection. */ interface AiDetectOptions { /** * Whether to probe the detected binary for its version by spawning * `<cli> --version`. This is the slow part of detection (a cold CLI * start per provider). Set to `false` to skip it when you only need * availability + path. Defaults to `true`. */ version?: boolean; } /** * Options controlling the async, parallel detection in * `detectAllProvidersAsync`. * * Unlike the synchronous {@link AiDetectOptions} (where version probing is on * by default), the async path makes the `--version` cold-start probe **opt-in**: * `list`-style callers and startup detection usually only need availability + * path, so the slow per-provider probe is skipped unless explicitly requested. */ interface AiDetectAsyncOptions { /** * Whether to probe each detected binary for its version by spawning * `<cli> --version`. This adds a cold CLI start per available provider and * is the slowest part of detection. * * Defaults to `false` — opt in by passing `{ probeVersions: true }` when you * actually need version strings. */ probeVersions?: boolean; } /** Options for running a prompt against an AI provider. */ interface AiRunOptions { /** Working directory for the spawned CLI. Defaults to the parent process cwd. */ cwd?: string; /** * When `true`, the provider is invoked with its permission-bypass / * auto-approval flag, granting unattended tool/file/shell access. * * SECURITY: only enable for fully trusted prompts. Defaults to `false`. */ dangerous?: boolean; /** * Additional environment variables merged over the parent process env. * Useful for passing provider API keys per run. */ env?: Record; /** Maximum tokens in the response. Defaults to `4096`. */ maxTokens?: number; /** Model override (e.g., `"claude-opus-4-20250514"`). */ model?: string; /** Called with each chunk of stderr as it arrives. */ onStderr?: (chunk: string) => void; /** Called with each chunk of stdout as it arrives. */ onStdout?: (chunk: string) => void; /** Abort signal to cancel the run programmatically. */ signal?: AbortSignal; /** Timeout in milliseconds. Defaults to `300000` (5 minutes). */ timeoutMs?: number; } /** Result from running a prompt against an AI provider. */ interface AiRunResult { /** Wall-clock duration of the run in milliseconds. */ durationMs: number; /** Process exit code (`null` if the process was killed by a signal). */ exitCode: number | null; /** Which provider was used. */ provider: AiProviderName; /** Standard error output from the CLI. */ stderr: string; /** Standard output from the CLI (the AI response). */ stdout: string; } /** * Error thrown by `runProvider` when a run fails (non-zero exit, timeout, * abort, or spawn failure). Carries any partial output captured so far, * which is exactly what is needed to debug a hung or crashing agent. */ declare class AiRunError extends Error { /** Process exit code, if the process exited (`null` if killed by signal/timeout). */ readonly exitCode: number | null; /** Wall-clock duration of the run in milliseconds. */ readonly durationMs: number; /** Provider that was run. */ readonly provider: AiProviderName; /** Partial stderr captured before the failure. */ readonly stderr: string; /** Partial stdout captured before the failure. */ readonly stdout: string; /** Whether the failure was caused by the run timing out. */ readonly timedOut: boolean; /** Whether the failure was caused by an abort signal. */ readonly aborted: boolean; constructor(message: string, details: { aborted?: boolean; durationMs: number; exitCode?: number | null; provider: AiProviderName; stderr?: string; stdout?: string; timedOut?: boolean; }); } /** All supported provider names in alphabetical order. */ declare const PROVIDER_NAMES: AiProviderName[]; /** * Return the command names of the current process and its ancestors, nearest * first, lowercased and stripped of any `.exe`/`.cmd`/`.bat` suffix. Resolves * to `[]` on any failure (missing `/proc`, `ps`/`wmic` absent or timed out) so * callers can treat it as "no ancestry signal" rather than an error. * @param startPid PID to start from; defaults to the current process. */ declare const getProcessAncestry: (startPid?: number) => Promise; /** * All supported AI CLI provider configurations, keyed by name. * * Session-marker precedence does NOT depend on this key order: Qwen Code is a * gemini-cli fork that sets both `QWEN_CODE` and `GEMINI_CLI`, but the Gemini * marker explicitly excludes `QWEN_CODE` (see `gemini.ts`), so a Qwen session * is attributed to Qwen regardless of iteration order. */ declare const PROVIDERS: Record; /** Minimal env shape accepted by {@link detectAiSession} / {@link isAiSession} — `process.env` structurally, injectable for tests. */ type EnvLike = Readonly>; /** One detected agent session: which agent, how sure, and what gave it away. */ interface AiSessionInfo { /** Human-readable agent name (e.g. `"Claude Code"`). */ agent: string; /** See {@link AiSessionConfidence}. */ confidence: AiSessionConfidence; /** The matching invokable provider from `providers/`, when the agent is one. */ provider?: AiProviderName; /** * What gave the agent away: an environment variable name (`"CLAUDECODE"`), * a composite marker's label (`"TERM_PROGRAM+PAGER"`), or a process-ancestry * match (`"process:octofriend"`). Not necessarily a bare variable name. */ signal: string; /** See {@link AiSessionType}. */ type: AiSessionType; } /** Options for {@link detectAiSession} / {@link isAiSession} and their async variants. */ interface AiSessionOptions { /** * Explicit process ancestry (nearest command name first) for * {@link detectAiSessionAsync}. When provided, the ancestry is NOT spawned — * used for testing and for callers that already have the process tree. */ ancestry?: ReadonlyArray; /** * Also walk the process ancestry to detect agents that set no env marker * (Octofriend, Devin, Factory Droid). Only honored by the async variants. * Off by default: reading the process tree spawns a subprocess and is * meaningfully slow, especially on Windows. */ checkProcesses?: boolean; /** * Also report `ambient`/`interactive` markers (Cursor editor terminals, * Replit workspaces, …). Off by default: ambient environments host humans * too, and behavior switches (JSON output, auto-backgrounding) keyed on * them surprise those humans. Turn on for telemetry-style consumers where a * false positive is harmless. */ includeAmbient?: boolean; } /** A fully resolved marker table entry: a config marker with display name, provider, type, and a normalized condition attached. */ interface AiSessionMarker { agent: string; confidence: AiSessionConfidence; /** Reporting label surfaced as the matched `variable`. */ label: string; /** Normalized env condition to evaluate. */ match: EnvCondition; provider?: AiProviderName; type: AiSessionType; } /** An agent detectable only via process ancestry (it sets no env marker on the shells it spawns). */ interface ProcessAgent { agent: string; /** Lowercased process name to look for in the ancestry. */ process: string; provider?: AiProviderName; } /** * Generic marker some harnesses (and the Vercel CLI convention) set with the * agent's name as the value — checked first, since it is self-describing. */ declare const AI_AGENT_ENV = "AI_AGENT"; /** * Agents detectable only by walking the process ancestry — their harness marks * spawned shells with no env variable, so `checkProcesses` is the only signal. */ declare const PROCESS_AGENTS: ReadonlyArray; /** * The full marker table, assembled from every provider's `sessionMarkers` * plus {@link EXTRA_AGENT_MARKERS}, ordered `definite` before `ambient` so a * definite signal always shadows an ambient one. First match wins. */ declare const SESSION_MARKERS: ReadonlyArray; /** * Detect the AI agent session the current process runs inside from environment * markers alone, or `undefined` when none is detected. Pure and synchronous — * pass a custom `env` in tests. * * The self-describing `AI_AGENT` variable wins over the marker table; ambient * markers are only consulted with `includeAmbient: true`. Process-ancestry-only * agents are NOT checked here — use {@link detectAiSessionAsync} with * `checkProcesses: true` for those. */ declare const detectAiSession: (env?: EnvLike, options?: AiSessionOptions) => AiSessionInfo | undefined; /** Match the process ancestry (nearest command name first) against {@link PROCESS_AGENTS}. Pure. */ declare const detectAiSessionByProcess: (ancestry: ReadonlyArray) => AiSessionInfo | undefined; /** * Async superset of {@link detectAiSession}. Checks env markers first; when * `checkProcesses` is set and no env marker matched, walks the process ancestry * to catch agents that set no env variable (Octofriend, Devin, Factory Droid). * Pass `options.ancestry` to supply the process tree yourself (and skip the * subprocess spawn), e.g. in tests. */ declare const detectAiSessionAsync: (env?: EnvLike, options?: AiSessionOptions) => Promise; /** Convenience predicate over {@link detectAiSession}. */ declare const isAiSession: (env?: EnvLike, options?: AiSessionOptions) => boolean; /** Convenience predicate over {@link detectAiSessionAsync}. */ declare const isAiSessionAsync: (env?: EnvLike, options?: AiSessionOptions) => Promise; /** * How to spawn a resolved provider path. * * `direct` passes an argv array that nothing re-parses. `shell` is the fallback for a `.cmd` shim * we could not resolve; it keeps its command path raw and defers quoting to `toSpawnArguments`, so * no caller has to remember which of the two a `file` field was holding. */ type Invocation = { commandPath: string; mode: "shell"; } | { file: string; mode: "direct"; prefixArguments: string[]; }; /** * Decides how to spawn a provider executable. * * On Windows the provider path is usually an npm `.cmd` shim, which only `cmd.exe` can run — and a * `cmd.exe` command line has no escape for `%`, so `%VAR%` anywhere in an argument is replaced with * an environment value before the CLI sees it. Quoting cannot prevent this (see `windows-shim.ts`). * Resolving the shim to the script it wraps lets us spawn the interpreter with a plain argv array * instead, so nothing parses the prompt. * * Shims that do not follow the `cmd-shim` layout still fall back to the shell, since the * alternative is not running at all — and that fallback still carries the `%` hole. * @param commandPath The resolved provider executable. * @param isWindows Whether to apply the Windows shim rules. Injected so the decision is testable * off Windows, where it would otherwise be pinned by a module-level constant. * @returns How to spawn it. */ declare const planInvocation: (commandPath: string, isWindows?: boolean) => Invocation; /** * Detect whether a specific AI CLI provider is installed on the system. * * Detection strategies (tried in order): * * 1. Environment variable (e.g., `CLAUDE_PATH`). * 2. `which`/`where` command lookup on PATH. * 3. Known installation paths (`/opt/homebrew/bin/`, `~/.local/bin/`, etc.). * @param name The provider to detect (e.g., `"claude"`, `"gemini"`). * @param options Detection options; set `{ version: false }` to skip the (slow) version probe. * @returns Provider info including availability, path, and version. */ declare const detectProvider: (name: AiProviderName, options?: AiDetectOptions) => AiProviderInfo; /** * Detect all supported AI CLI providers (installed or not). * @param options Detection options; set `{ version: false }` to skip the version probe. * @returns An array of provider info for all 11 supported providers. */ declare const detectAllProviders: (options?: AiDetectOptions) => AiProviderInfo[]; /** * Detect all supported AI CLI providers concurrently (async, parallel). * * Unlike the synchronous {@link detectAllProviders} — which spawns its * `which`/`where` (and per-hit `--version`) probes one provider at a time, * blocking the event loop — this runs every provider's detection in parallel * via `execFile`, so total latency is roughly that of the slowest single * provider rather than the sum of all of them. * * The `--version` cold-start probe is **opt-in** here (off by default): `list` * and startup-detection callers usually only need availability + path. Pass * `{ probeVersions: true }` when you actually need version strings. * @param options Async detection options; set `{ probeVersions: true }` to also probe versions. * @returns A promise resolving to provider info for all 11 supported providers (in registration order). */ declare const detectAllProvidersAsync: (options?: AiDetectAsyncOptions) => Promise; /** * Detect only the AI CLI providers that are installed on the system. * @param options Detection options; set `{ version: false }` to skip the version probe. * @returns An array of provider info for available providers only. */ declare const detectAvailableProviders: (options?: AiDetectOptions) => AiProviderInfo[]; /** * Find the first available AI CLI provider, honoring a preference order. * * Stops at the first hit, so it is faster than detecting all 11 providers when * you just want "whatever AI CLI this machine has". Version probing is opt-in * via `options.version` (defaults to `false` here, since callers usually only * need the path). * @param preference Ordered list of providers to try. Defaults to {@link PROVIDER_NAMES}. * @param options Detection options; set `{ version: true }` to also probe the version. * @returns The first available provider, or `undefined` if none is installed. */ declare const findRunner: (preference?: AiProviderName[], options?: AiDetectOptions) => AiProviderInfo | undefined; /** * Build the CLI arguments array for a provider without executing. * Useful for previewing or logging what command would be run. * @param name The provider name. * @param prompt The prompt text to send. * @param options Optional model, maxTokens, timeout, and `dangerous` overrides. * @returns The arguments array to pass to the CLI binary. */ declare const buildCliArgs: (name: AiProviderName, prompt: string, options?: AiRunOptions) => string[]; /** * Execute a prompt against a detected AI CLI provider. * * Uses Node.js `spawn` with stdin closed immediately for non-interactive execution. * The process environment is sanitized with `NO_COLOR=1` and `FORCE_COLOR=0` for clean output. * * By default the provider runs with its normal safety prompts; pass `{ dangerous: true }` * to enable permission-bypass mode (unattended tool/file/shell access — only for trusted prompts). * @param provider A detected provider (from `detectProvider` or `detectAvailableProviders`). * @param prompt The prompt text to send. * @param options Optional model, maxTokens, timeout, cwd, env, signal, streaming, and `dangerous` overrides. * @returns The stdout/stderr output plus exit metadata from the CLI. * @throws {AiRunError} If the provider is unavailable, times out, is aborted, or exits non-zero. Carries partial output. */ declare const runProvider: (provider: AiProviderInfo, prompt: string, options?: AiRunOptions) => Promise; export { AI_AGENT_ENV, type AiBuildArgsOptions, type AiDetectAsyncOptions, type AiDetectOptions, type AiProviderConfig, type AiProviderInfo, type AiProviderName, AiRunError, type AiRunOptions, type AiRunResult, type AiSessionConfidence, type AiSessionInfo, type AiSessionMarker, type AiSessionMarkerConfig, type AiSessionOptions, type AiSessionType, type EnvAtom, type EnvCondition, type EnvConditionObject, type EnvLike, PROCESS_AGENTS, PROVIDERS, PROVIDER_NAMES, type ProcessAgent, SESSION_MARKERS, buildCliArgs, detectAiSession, detectAiSessionAsync, detectAiSessionByProcess, detectAllProviders, detectAllProvidersAsync, detectAvailableProviders, detectProvider, findRunner, getProcessAncestry, isAiSession, isAiSessionAsync, planInvocation, runProvider };