import { execSync } from 'node:child_process'; import { platform } from 'node:os'; import type { RuntimeName } from '../types.js'; export interface RuntimeInfo { name: RuntimeName; label: string; installed: boolean; path?: string; version?: string; installHint: string; } const RUNTIME_DEFS: Record< RuntimeName, { label: string; command: string; versionFlag: string; installHint: string } > = { claude: { label: 'Claude Code', command: 'claude', versionFlag: '--version', installHint: 'npm install -g @anthropic-ai/claude-code', }, codex: { label: 'Codex', command: 'codex', versionFlag: '--version', installHint: 'npm install -g @openai/codex', }, opencode: { label: 'OpenCode', command: 'opencode', versionFlag: 'version', installHint: 'See https://opencode.ai for install instructions', }, }; export function detectRuntime(name: RuntimeName): RuntimeInfo { const def = RUNTIME_DEFS[name]; const isWindows = platform() === 'win32'; const whichCmd = isWindows ? `where ${def.command} 2>nul` : `which ${def.command} 2>/dev/null`; try { const pathOutput = execSync(whichCmd, { encoding: 'utf-8', timeout: 5000, }) .trim() .split('\n')[0] ?? ''; let version: string | undefined; try { version = execSync(`${def.command} ${def.versionFlag} 2>/dev/null`, { encoding: 'utf-8', timeout: 5000, }).trim(); } catch { // Version detection is optional } return { name, label: def.label, installed: true, path: pathOutput, version, installHint: def.installHint, }; } catch { return { name, label: def.label, installed: false, installHint: def.installHint, }; } } export function getInstallInstructions(name: RuntimeName): string { return RUNTIME_DEFS[name].installHint; }