import { existsSync } from 'node:fs'; type Env = NodeJS.ProcessEnv; type Check = (env: Env) => boolean; const set = (...names: string[]): Check => (env) => names.some((name) => Boolean(env[name])); const matches = (name: string, pattern: RegExp): Check => (env) => pattern.test(env[name] ?? ''); const noTTY = (check: Check): Check => (env) => !process.stdout.isTTY && check(env); const AGENTS: Array<[name: string, check: Check]> = [ [ 'cursor-cli', (env) => set('CURSOR_AGENT')(env) || env.CURSOR_EXTENSION_HOST_ROLE === 'agent-exec', ], ['kimi', set('KIMI_PLUGIN_ROOT')], ['grok', set('GROK_PLUGIN_ROOT', 'GROK_PLUGIN_DATA')], ['gemini_cli', set('GEMINI_CLI')], ['cline', set('CLINE_ACTIVE')], [ 'codex_cli', set('CODEX_SANDBOX', 'CODEX_CI', 'CODEX_THREAD_ID', 'CODEX_SANDBOX_NETWORK_DISABLED'), ], ['antigravity', set('ANTIGRAVITY_AGENT', 'ANTIGRAVITY_CLI_ALIAS')], ['augment-cli', set('AUGMENT_AGENT')], ['open_code', (env) => set('OPENCODE')(env) || noTTY(set('OPENCODE_CLIENT'))(env)], ['goose', noTTY(set('GOOSE_PROVIDER'))], ['junie', set('JUNIE_DATA', 'JUNIE_SHIM_PATH')], ['pi', matches('PATH', /\.pi[\\/]agent/)], ['cowork', (env) => set('CLAUDE_CODE_IS_COWORK')(env) && set('CLAUDECODE', 'CLAUDE_CODE')(env)], ['claude_code', (env) => set('CLAUDECODE', 'CLAUDE_CODE')(env) || env.TERM_PROGRAM === 'claude'], ['github-copilot', noTTY(set('COPILOT_MODEL', 'COPILOT_ALLOW_ALL'))], ['openclaw', set('OPENCLAW_SHELL')], ['devin', (env) => matches('EDITOR', /devin/)(env) || existsSync('/opt/.devin')], ['replit', noTTY(set('REPL_ID'))], ['kiro', noTTY(matches('TERM_PROGRAM', /kiro/))], ['cursor', noTTY(set('CURSOR_TRACE_ID'))], ]; export function detectAgent(env: Env = process.env): string | undefined { const explicit = (env.AI_AGENT || env.AGENT)?.trim(); if (explicit) return explicit.toLowerCase(); return AGENTS.find(([, check]) => check(env))?.[0]; } export function isCI(env: Env = process.env): boolean { return Boolean(env.CI && env.CI !== 'false' && env.CI !== '0'); }