skalpel 2.0.13 → 2.0.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +588 -247
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/proxy-runner.js +592 -76
- package/dist/cli/proxy-runner.js.map +1 -1
- package/dist/index.cjs +1580 -1045
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1570 -1028
- package/dist/index.js.map +1 -1
- package/dist/proxy/index.cjs +628 -93
- package/dist/proxy/index.cjs.map +1 -1
- package/dist/proxy/index.d.cts +1 -1
- package/dist/proxy/index.d.ts +1 -1
- package/dist/proxy/index.js +621 -79
- package/dist/proxy/index.js.map +1 -1
- package/package.json +4 -2
package/dist/cli/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/cli/index.ts","../../src/cli/init.ts","../../src/cli/utils.ts","../../src/cli/doctor.ts","../../src/cli/agents/detect.ts","../../src/cli/benchmark.ts","../../src/cli/replay.ts","../../src/cli/start.ts","../../src/proxy/config.ts","../../src/proxy/pid.ts","../../src/cli/service/install.ts","../../src/cli/service/detect-os.ts","../../src/cli/service/templates.ts","../../src/proxy/server.ts","../../src/proxy/dispatcher.ts","../../src/proxy/recovery.ts","../../src/proxy/streaming.ts","../../src/proxy/logger.ts","../../src/cli/stop.ts","../../src/cli/status.ts","../../src/cli/logs.ts","../../src/cli/agents/configure.ts","../../src/cli/agents/shell.ts","../../src/cli/config-cmd.ts","../../src/cli/update.ts","../../src/cli/wizard.ts","../../src/cli/uninstall.ts"],"sourcesContent":["import { Command } from 'commander';\nimport { createRequire } from 'node:module';\nimport { runInit } from './init.js';\nimport { runDoctor } from './doctor.js';\nimport { runBenchmark } from './benchmark.js';\nimport { runReplay } from './replay.js';\nimport { runStart } from './start.js';\nimport { runStop } from './stop.js';\nimport { runStatus } from './status.js';\nimport { runLogs } from './logs.js';\nimport { runConfig } from './config-cmd.js';\nimport { runUpdate } from './update.js';\nimport { runWizard } from './wizard.js';\nimport { runUninstall } from './uninstall.js';\n\nconst require = createRequire(import.meta.url);\nconst pkg = require('../../package.json');\n\nconst program = new Command();\n\nprogram\n .name('skalpel')\n .description('Skalpel AI CLI — optimize your OpenAI and Anthropic API calls')\n .version(pkg.version)\n .option('--api-key <key>', 'Skalpel API key for non-interactive setup')\n .option('--auto', 'Run setup in non-interactive mode')\n .option('--skip-claude', 'Skip Claude Code configuration')\n .option('--skip-codex', 'Skip Codex configuration')\n .option('--skip-cursor', 'Skip Cursor configuration')\n .action((options) => runWizard(options));\n\nprogram\n .command('init')\n .description('Initialize Skalpel in your project')\n .action(runInit);\n\nprogram\n .command('doctor')\n .description('Check Skalpel configuration health')\n .action(runDoctor);\n\nprogram\n .command('benchmark')\n .description('Run performance benchmarks')\n .action(runBenchmark);\n\nprogram\n .command('replay')\n .description('Replay saved request files')\n .argument('<files...>', 'JSON request files')\n .action(runReplay);\n\nprogram\n .command('start')\n .description('Start the Skalpel proxy')\n .action(runStart);\n\nprogram\n .command('stop')\n .description('Stop the Skalpel proxy')\n .action(runStop);\n\nprogram\n .command('status')\n .description('Show proxy status')\n .action(runStatus);\n\nprogram\n .command('logs')\n .description('View proxy logs')\n .option('-n, --lines <count>', 'Number of lines to show', '50')\n .option('-f, --follow', 'Follow log output')\n .action(runLogs);\n\nprogram\n .command('config')\n .description('View or edit proxy configuration')\n .argument('[subcommand]', 'path | set')\n .argument('[args...]', 'Arguments for subcommand')\n .action(runConfig);\n\nprogram\n .command('update')\n .description('Update Skalpel to the latest version')\n .action(runUpdate);\n\nprogram\n .command('setup')\n .description('Run the Skalpel setup wizard')\n .action(runWizard);\n\nprogram\n .command('uninstall')\n .description('Remove Skalpel proxy and configurations')\n .option('--force', 'Skip confirmation prompts and remove everything')\n .action(runUninstall);\n\nprogram.parse(process.argv);\n","import * as readline from 'node:readline';\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport { detectProjectType, detectAiSdks, validateApiKey, generateCodeSample } from './utils.js';\nimport type { InitConfig, SupportedProvider } from '../types.js';\n\nfunction print(msg: string): void {\n console.log(msg);\n}\n\nexport async function runInit(): Promise<void> {\n const rl = readline.createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n\n function ask(question: string): Promise<string> {\n return new Promise((resolve) => {\n rl.question(question, (answer) => resolve(answer.trim()));\n });\n }\n\n try {\n print('');\n print(' Skalpel AI — SDK Setup');\n print(' ─────────────────────');\n print('');\n\n // Step 1: Detect project type\n const projectType = detectProjectType();\n print(` Detected project type: ${projectType}`);\n\n // Step 2: Detect existing AI SDKs\n const sdks = detectAiSdks(projectType);\n if (sdks.length > 0) {\n print(` Detected AI SDKs: ${sdks.join(', ')}`);\n } else {\n print(' No AI SDKs detected');\n }\n print('');\n\n // Step 3: API key\n let apiKey = process.env.SKALPEL_API_KEY ?? '';\n if (apiKey && validateApiKey(apiKey)) {\n print(` Using API key from SKALPEL_API_KEY env var: ${apiKey.slice(0, 14)}...`);\n } else {\n apiKey = await ask(' Enter your Skalpel API key (sk-skalpel-...): ');\n if (!validateApiKey(apiKey)) {\n print(' Error: Invalid API key. Must start with \"sk-skalpel-\" and be at least 20 characters.');\n rl.close();\n process.exit(1);\n }\n print(` API key set: ${apiKey.slice(0, 14)}${'*'.repeat(Math.max(0, apiKey.length - 14))}`);\n }\n print('');\n\n // Step 4: Choose integration method\n print(' Choose integration method:');\n print(' 1) Wrapper pattern — Wraps your existing SDK client. Adds fallback and metadata.');\n print(' 2) URL swap — Changes the base URL. Simplest, one-line change.');\n print('');\n const methodChoice = await ask(' Enter choice (1 or 2): ');\n const integrationMethod = methodChoice === '2' ? 'url_swap' : 'wrapper';\n print('');\n\n // Step 5: Generate .env\n const envPath = path.join(process.cwd(), '.env');\n const envContent = `SKALPEL_API_KEY=${apiKey}\\nSKALPEL_BASE_URL=https://api.skalpel.ai\\n`;\n\n if (fs.existsSync(envPath)) {\n fs.appendFileSync(envPath, `\\n${envContent}`);\n print(' Appended Skalpel config to existing .env file');\n } else {\n fs.writeFileSync(envPath, envContent);\n print(' Created .env file with Skalpel config');\n }\n print('');\n\n // Step 6: Show code sample\n const providers: SupportedProvider[] = sdks.length > 0 ? sdks : ['openai'];\n const config: InitConfig = {\n projectType,\n integrationMethod: integrationMethod as 'wrapper' | 'url_swap',\n providers,\n apiKey,\n };\n\n const sample = generateCodeSample(config);\n print(' Add this to your code:');\n print(' ──────────────────────');\n for (const line of sample.split('\\n')) {\n print(` ${line}`);\n }\n print(' ──────────────────────');\n print('');\n\n // Step 7: Success\n print(' Setup complete! Your API calls will now be optimized by Skalpel.');\n print('');\n\n rl.close();\n } catch (err) {\n rl.close();\n throw err;\n }\n}\n\n","import * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport type { SupportedProvider, InitConfig } from '../types.js';\n\nexport function detectProjectType(): 'node' | 'python' | 'other' {\n if (fs.existsSync(path.join(process.cwd(), 'package.json'))) {\n return 'node';\n }\n if (\n fs.existsSync(path.join(process.cwd(), 'requirements.txt')) ||\n fs.existsSync(path.join(process.cwd(), 'pyproject.toml'))\n ) {\n return 'python';\n }\n return 'other';\n}\n\nexport function detectAiSdks(projectType: string): SupportedProvider[] {\n const providers: SupportedProvider[] = [];\n\n if (projectType === 'node') {\n try {\n const pkgPath = path.join(process.cwd(), 'package.json');\n const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));\n const allDeps = {\n ...pkg.dependencies,\n ...pkg.devDependencies,\n };\n if (allDeps['openai']) providers.push('openai');\n if (allDeps['@anthropic-ai/sdk']) providers.push('anthropic');\n } catch {\n // ignore\n }\n }\n\n if (projectType === 'python') {\n try {\n const reqPath = path.join(process.cwd(), 'requirements.txt');\n if (fs.existsSync(reqPath)) {\n const content = fs.readFileSync(reqPath, 'utf-8');\n if (/^openai/m.test(content)) providers.push('openai');\n if (/^anthropic/m.test(content)) providers.push('anthropic');\n }\n } catch {\n // ignore\n }\n }\n\n return providers;\n}\n\nexport function validateApiKey(key: string): boolean {\n return key.startsWith('sk-skalpel-') && key.length >= 20;\n}\n\nexport function generateCodeSample(config: InitConfig): string {\n if (config.integrationMethod === 'wrapper') {\n if (config.providers.includes('openai')) {\n return `import OpenAI from 'openai';\nimport { createSkalpelClient } from 'skalpel';\n\nconst openai = createSkalpelClient(new OpenAI(), {\n apiKey: process.env.SKALPEL_API_KEY!,${config.workspace ? `\\n workspace: '${config.workspace}',` : ''}\n});\n\nconst response = await openai.chat.completions.create({\n model: 'gpt-4o',\n messages: [{ role: 'user', content: 'Hello' }],\n});`;\n }\n if (config.providers.includes('anthropic')) {\n return `import Anthropic from '@anthropic-ai/sdk';\nimport { createSkalpelClient } from 'skalpel';\n\nconst anthropic = createSkalpelClient(new Anthropic(), {\n apiKey: process.env.SKALPEL_API_KEY!,${config.workspace ? `\\n workspace: '${config.workspace}',` : ''}\n});\n\nconst response = await anthropic.messages.create({\n model: 'claude-sonnet-4-20250514',\n max_tokens: 1024,\n messages: [{ role: 'user', content: 'Hello' }],\n});`;\n }\n }\n\n if (config.integrationMethod === 'url_swap') {\n if (config.providers.includes('openai')) {\n return `import OpenAI from 'openai';\n\nconst openai = new OpenAI({\n baseURL: 'https://api.skalpel.ai/v1',\n apiKey: process.env.SKALPEL_API_KEY,\n});\n\nconst response = await openai.chat.completions.create({\n model: 'gpt-4o',\n messages: [{ role: 'user', content: 'Hello' }],\n});`;\n }\n if (config.providers.includes('anthropic')) {\n return `import Anthropic from '@anthropic-ai/sdk';\n\nconst anthropic = new Anthropic({\n baseURL: 'https://api.skalpel.ai/v1',\n apiKey: process.env.SKALPEL_API_KEY,\n});\n\nconst response = await anthropic.messages.create({\n model: 'claude-sonnet-4-20250514',\n max_tokens: 1024,\n messages: [{ role: 'user', content: 'Hello' }],\n});`;\n }\n }\n\n return `// Configure your Skalpel API key\n// SKALPEL_API_KEY=${config.apiKey}\n// See https://docs.skalpel.ai for integration guides`;\n}\n","import * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport * as os from 'node:os';\nimport { validateApiKey } from './utils.js';\nimport { detectAgents } from './agents/detect.js';\n\ninterface DoctorCheck {\n name: string;\n status: 'ok' | 'warn' | 'fail';\n message: string;\n}\n\nfunction print(msg: string): void {\n console.log(msg);\n}\n\nfunction loadConfigApiKey(): string | null {\n try {\n const configPath = path.join(os.homedir(), '.skalpel', 'config.json');\n const raw = JSON.parse(fs.readFileSync(configPath, 'utf-8'));\n if (typeof raw.apiKey === 'string' && raw.apiKey.length > 0) {\n return raw.apiKey;\n }\n } catch {\n // config doesn't exist or is invalid\n }\n return null;\n}\n\nexport async function runDoctor(): Promise<void> {\n print('');\n print(' Skalpel Doctor');\n print(' ──────────────');\n print('');\n\n const checks: DoctorCheck[] = [];\n\n // 1. Check API key — config file first, then env var\n const configKey = loadConfigApiKey();\n const envKey = process.env.SKALPEL_API_KEY ?? '';\n const apiKey = configKey || envKey;\n\n if (apiKey && validateApiKey(apiKey)) {\n const source = configKey ? '~/.skalpel/config.json' : 'environment';\n checks.push({\n name: 'API Key',\n status: 'ok',\n message: `Valid key from ${source}: ${apiKey.slice(0, 14)}${'*'.repeat(Math.max(0, apiKey.length - 14))}`,\n });\n } else if (apiKey) {\n checks.push({\n name: 'API Key',\n status: 'fail',\n message: `Invalid format — must start with \"sk-skalpel-\" and be >= 20 chars`,\n });\n } else {\n checks.push({\n name: 'API Key',\n status: 'fail',\n message: 'No API key found. Run \"npx skalpel\" to set up.',\n });\n }\n\n // 2. Check Skalpel config\n const skalpelConfigPath = path.join(os.homedir(), '.skalpel', 'config.json');\n if (fs.existsSync(skalpelConfigPath)) {\n checks.push({ name: 'Skalpel config', status: 'ok', message: '~/.skalpel/config.json found' });\n } else {\n checks.push({ name: 'Skalpel config', status: 'warn', message: 'No ~/.skalpel/config.json — run \"npx skalpel\" to set up' });\n }\n\n // 2b. Report active mode (proxy vs direct).\n let mode: 'proxy' | 'direct' = 'proxy';\n try {\n const raw = JSON.parse(fs.readFileSync(skalpelConfigPath, 'utf-8'));\n if (raw.mode === 'direct') mode = 'direct';\n } catch {\n // missing/invalid config — default to proxy, same as loadConfig()\n }\n const modeMessage = mode === 'direct'\n ? 'direct (agents point at api.skalpel.ai)'\n : 'proxy (local proxy on ports 18100/18101/18102)';\n checks.push({ name: 'mode', status: 'ok', message: modeMessage });\n\n // 3. Check proxy endpoint reachability\n const baseURL = 'https://api.skalpel.ai';\n try {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), 5000);\n const response = await fetch(`${baseURL}/health`, { signal: controller.signal });\n clearTimeout(timeout);\n if (response.ok) {\n checks.push({ name: 'Skalpel backend', status: 'ok', message: `${baseURL} reachable (HTTP ${response.status})` });\n } else {\n checks.push({ name: 'Skalpel backend', status: 'warn', message: `${baseURL} responded with HTTP ${response.status}` });\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n checks.push({ name: 'Skalpel backend', status: 'fail', message: `Cannot reach ${baseURL} — ${msg}` });\n }\n\n // 4. Check local proxy health\n let proxyPort = 18100;\n try {\n const raw = JSON.parse(fs.readFileSync(skalpelConfigPath, 'utf-8'));\n proxyPort = raw.anthropicPort ?? 18100;\n } catch {\n // use default\n }\n try {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), 2000);\n const res = await fetch(`http://localhost:${proxyPort}/health`, { signal: controller.signal });\n clearTimeout(timeout);\n if (res.ok) {\n checks.push({ name: 'Local proxy', status: 'ok', message: `Running on port ${proxyPort}` });\n } else {\n checks.push({ name: 'Local proxy', status: 'warn', message: `Port ${proxyPort} responded with HTTP ${res.status}` });\n }\n } catch {\n checks.push({ name: 'Local proxy', status: 'warn', message: `Not running on port ${proxyPort}. Run \"npx skalpel start\" to start.` });\n }\n\n // 4b. Check Cursor proxy health\n let cursorPort = 18102;\n try {\n const raw = JSON.parse(fs.readFileSync(skalpelConfigPath, 'utf-8'));\n cursorPort = raw.cursorPort ?? 18102;\n } catch {\n // use default\n }\n try {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), 2000);\n const res = await fetch(`http://localhost:${cursorPort}/health`, { signal: controller.signal });\n clearTimeout(timeout);\n if (res.ok) {\n checks.push({ name: 'Cursor proxy', status: 'ok', message: `Running on port ${cursorPort}` });\n } else {\n checks.push({ name: 'Cursor proxy', status: 'warn', message: `Port ${cursorPort} responded with HTTP ${res.status}` });\n }\n } catch {\n checks.push({ name: 'Cursor proxy', status: 'warn', message: `Not running on port ${cursorPort}. Run \"npx skalpel start\" to start.` });\n }\n\n // 5. Detect coding agents\n const agents = detectAgents();\n for (const agent of agents) {\n if (agent.installed) {\n const ver = agent.version ? ` v${agent.version}` : '';\n const configured = agent.configPath && fs.existsSync(agent.configPath) ? ' (configured)' : '';\n checks.push({ name: agent.name, status: 'ok', message: `Installed${ver}${configured}` });\n } else {\n checks.push({ name: agent.name, status: 'warn', message: 'Not installed' });\n }\n }\n\n // Print results\n const icons = { ok: '+', warn: '!', fail: 'x' };\n for (const check of checks) {\n const icon = icons[check.status];\n print(` [${icon}] ${check.name}: ${check.message}`);\n }\n\n const failures = checks.filter((c) => c.status === 'fail');\n const warnings = checks.filter((c) => c.status === 'warn');\n print('');\n if (failures.length > 0) {\n print(` ${failures.length} issue(s) found. Fix the above errors to use Skalpel.`);\n } else if (warnings.length > 0) {\n print(` All critical checks passed. ${warnings.length} warning(s).`);\n } else {\n print(' All checks passed. Skalpel is ready.');\n }\n print('');\n}\n","import { execSync } from 'node:child_process';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport os from 'node:os';\n\nexport interface DetectedAgent {\n name: 'claude-code' | 'codex' | 'cursor';\n installed: boolean;\n version: string | null;\n configPath: string | null;\n}\n\nfunction whichCommand(): string {\n return process.platform === 'win32' ? 'where' : 'which';\n}\n\nfunction tryExec(cmd: string): string | null {\n try {\n return execSync(cmd, { encoding: 'utf-8', timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'] }).trim();\n } catch {\n return null;\n }\n}\n\nfunction detectClaudeCode(): DetectedAgent {\n const agent: DetectedAgent = {\n name: 'claude-code',\n installed: false,\n version: null,\n configPath: null,\n };\n\n // Check binary\n const binaryPath = tryExec(`${whichCommand()} claude`);\n const hasBinary = binaryPath !== null && binaryPath.length > 0;\n\n // Check config directory\n const claudeDir = path.join(os.homedir(), '.claude');\n const hasConfigDir = fs.existsSync(claudeDir);\n\n agent.installed = hasBinary || hasConfigDir;\n\n if (hasBinary) {\n const versionOutput = tryExec('claude --version');\n if (versionOutput) {\n // Extract version number from output\n const match = versionOutput.match(/(\\d+\\.\\d+[\\w.-]*)/);\n agent.version = match ? match[1] : versionOutput;\n }\n }\n\n const settingsPath = path.join(claudeDir, 'settings.json');\n if (fs.existsSync(settingsPath)) {\n agent.configPath = settingsPath;\n } else if (hasConfigDir) {\n // Config dir exists but no settings.json yet — we'll create it during configuration\n agent.configPath = settingsPath;\n }\n\n return agent;\n}\n\nfunction detectCodex(): DetectedAgent {\n const agent: DetectedAgent = {\n name: 'codex',\n installed: false,\n version: null,\n configPath: null,\n };\n\n // Check binary\n const binaryPath = tryExec(`${whichCommand()} codex`);\n const hasBinary = binaryPath !== null && binaryPath.length > 0;\n\n // Check config directory\n const codexConfigDir = process.platform === 'win32'\n ? path.join(os.homedir(), 'AppData', 'Roaming', 'codex')\n : path.join(os.homedir(), '.codex');\n const hasConfigDir = fs.existsSync(codexConfigDir);\n\n agent.installed = hasBinary || hasConfigDir;\n\n if (hasBinary) {\n const versionOutput = tryExec('codex --version');\n if (versionOutput) {\n const match = versionOutput.match(/(\\d+\\.\\d+[\\w.-]*)/);\n agent.version = match ? match[1] : versionOutput;\n }\n }\n\n // Codex uses config.toml (not config.json)\n const configFile = path.join(codexConfigDir, 'config.toml');\n if (fs.existsSync(configFile)) {\n agent.configPath = configFile;\n } else if (hasConfigDir) {\n agent.configPath = configFile;\n }\n\n return agent;\n}\n\nfunction detectCursor(): DetectedAgent {\n const agent: DetectedAgent = {\n name: 'cursor',\n installed: false,\n version: null,\n configPath: null,\n };\n\n // Check binary\n const binaryPath = tryExec(`${whichCommand()} cursor`);\n const hasBinary = binaryPath !== null && binaryPath.length > 0;\n\n // Check config directory (VS Code-style, platform-specific)\n let cursorConfigDir: string;\n if (process.platform === 'darwin') {\n cursorConfigDir = path.join(os.homedir(), 'Library', 'Application Support', 'Cursor', 'User');\n } else if (process.platform === 'win32') {\n cursorConfigDir = path.join(process.env.APPDATA ?? path.join(os.homedir(), 'AppData', 'Roaming'), 'Cursor', 'User');\n } else {\n cursorConfigDir = path.join(os.homedir(), '.config', 'Cursor', 'User');\n }\n const hasConfigDir = fs.existsSync(cursorConfigDir);\n\n agent.installed = hasBinary || hasConfigDir;\n\n if (hasBinary) {\n const versionOutput = tryExec('cursor --version');\n if (versionOutput) {\n const match = versionOutput.match(/(\\d+\\.\\d+[\\w.-]*)/);\n agent.version = match ? match[1] : versionOutput;\n }\n }\n\n const settingsPath = path.join(cursorConfigDir, 'settings.json');\n if (fs.existsSync(settingsPath)) {\n agent.configPath = settingsPath;\n } else if (hasConfigDir) {\n agent.configPath = settingsPath;\n }\n\n return agent;\n}\n\nexport function detectAgents(): DetectedAgent[] {\n return [detectClaudeCode(), detectCodex(), detectCursor()];\n}\n","import { validateApiKey } from './utils.js';\n\ninterface BenchmarkResult {\n requestIndex: number;\n model: string;\n directLatencyMs: number;\n proxyLatencyMs: number;\n overheadMs: number;\n savingsUsd: number | null;\n cacheHit: boolean;\n}\n\nfunction print(msg: string): void {\n console.log(msg);\n}\n\nasync function timedFetch(\n url: string,\n body: object,\n headers: Record<string, string>,\n): Promise<{ latencyMs: number; status: number; headers: Headers; body: any }> {\n const start = performance.now();\n const response = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...headers },\n body: JSON.stringify(body),\n });\n const latencyMs = performance.now() - start;\n let responseBody: any = null;\n try {\n responseBody = await response.json();\n } catch {\n // ignore\n }\n return { latencyMs, status: response.status, headers: response.headers, body: responseBody };\n}\n\nexport async function runBenchmark(): Promise<void> {\n print('');\n print(' Skalpel Benchmark');\n print(' ─────────────────');\n print('');\n\n const apiKey = process.env.SKALPEL_API_KEY ?? '';\n if (!validateApiKey(apiKey)) {\n print(' Error: SKALPEL_API_KEY not set or invalid. Run \"npx skalpel doctor\" to diagnose.');\n print('');\n process.exit(1);\n }\n\n const baseURL = process.env.SKALPEL_BASE_URL ?? 'https://api.skalpel.ai';\n const testPrompts = [\n { model: 'gpt-4o-mini', messages: [{ role: 'user', content: 'Say hello in one word.' }] },\n { model: 'gpt-4o-mini', messages: [{ role: 'user', content: 'What is 2+2?' }] },\n { model: 'gpt-4o-mini', messages: [{ role: 'user', content: 'Say hello in one word.' }] },\n ];\n\n print(` Proxy: ${baseURL}`);\n print(` Running ${testPrompts.length} test requests...`);\n print('');\n\n const results: BenchmarkResult[] = [];\n\n for (let i = 0; i < testPrompts.length; i++) {\n const prompt = testPrompts[i];\n print(` Request ${i + 1}/${testPrompts.length}: ${prompt.model} — \"${prompt.messages[0].content}\"`);\n\n // Request through proxy\n let proxyLatencyMs = -1;\n let savingsUsd: number | null = null;\n let cacheHit = false;\n try {\n const proxyResult = await timedFetch(\n `${baseURL}/v1/chat/completions`,\n prompt,\n { Authorization: `Bearer ${apiKey}` },\n );\n proxyLatencyMs = Math.round(proxyResult.latencyMs);\n const savingsHeader = proxyResult.headers.get('x-skalpel-savings-usd');\n if (savingsHeader) savingsUsd = parseFloat(savingsHeader);\n cacheHit = proxyResult.headers.get('x-skalpel-cache-hit') === 'true';\n } catch (err) {\n print(` Proxy request failed: ${err instanceof Error ? err.message : String(err)}`);\n }\n\n results.push({\n requestIndex: i + 1,\n model: prompt.model,\n directLatencyMs: 0,\n proxyLatencyMs,\n overheadMs: 0,\n savingsUsd,\n cacheHit,\n });\n\n const cacheStr = cacheHit ? ' (cache hit)' : '';\n const savingsStr = savingsUsd !== null ? ` | savings: $${savingsUsd.toFixed(4)}` : '';\n print(` Proxy: ${proxyLatencyMs}ms${cacheStr}${savingsStr}`);\n }\n\n // Summary\n print('');\n print(' Summary');\n print(' ───────');\n const validResults = results.filter((r) => r.proxyLatencyMs >= 0);\n if (validResults.length === 0) {\n print(' No successful requests. Check your API key and proxy endpoint.');\n } else {\n const avgProxy = Math.round(validResults.reduce((s, r) => s + r.proxyLatencyMs, 0) / validResults.length);\n const cacheHits = validResults.filter((r) => r.cacheHit).length;\n const totalSavings = validResults.reduce((s, r) => s + (r.savingsUsd ?? 0), 0);\n\n print(` Requests: ${validResults.length}`);\n print(` Avg latency: ${avgProxy}ms (proxy)`);\n print(` Cache hits: ${cacheHits}/${validResults.length}`);\n if (totalSavings > 0) {\n print(` Savings: $${totalSavings.toFixed(4)}`);\n }\n }\n print('');\n}\n","import * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport { validateApiKey } from './utils.js';\n\nfunction print(msg: string): void {\n console.log(msg);\n}\n\nexport async function runReplay(filePaths: string[]): Promise<void> {\n print('');\n print(' Skalpel Replay');\n print(' ──────────────');\n print('');\n\n if (filePaths.length === 0) {\n print(' Usage: skalpel replay <request-file.json> [request-file2.json ...]');\n print('');\n print(' Replays saved request files through the Skalpel proxy.');\n print(' Each file should be a JSON object with \"model\" and \"messages\" fields.');\n print('');\n process.exit(1);\n }\n\n const apiKey = process.env.SKALPEL_API_KEY ?? '';\n if (!validateApiKey(apiKey)) {\n print(' Error: SKALPEL_API_KEY not set or invalid. Run \"npx skalpel doctor\" to diagnose.');\n print('');\n process.exit(1);\n }\n\n const baseURL = process.env.SKALPEL_BASE_URL ?? 'https://api.skalpel.ai';\n print(` Proxy: ${baseURL}`);\n print(` Replaying ${filePaths.length} request file(s)...`);\n print('');\n\n let successCount = 0;\n let failCount = 0;\n\n for (const filePath of filePaths) {\n const resolved = path.resolve(filePath);\n print(` File: ${resolved}`);\n\n if (!fs.existsSync(resolved)) {\n print(` Error: file not found`);\n failCount++;\n continue;\n }\n\n let requestBody: any;\n try {\n const raw = fs.readFileSync(resolved, 'utf-8');\n requestBody = JSON.parse(raw);\n } catch (err) {\n print(` Error: invalid JSON — ${err instanceof Error ? err.message : String(err)}`);\n failCount++;\n continue;\n }\n\n if (!requestBody.model || !requestBody.messages) {\n print(' Error: request file must contain \"model\" and \"messages\" fields');\n failCount++;\n continue;\n }\n\n const model = requestBody.model;\n const messageCount = Array.isArray(requestBody.messages) ? requestBody.messages.length : 0;\n print(` Model: ${model} | Messages: ${messageCount}`);\n\n try {\n const start = performance.now();\n const response = await fetch(`${baseURL}/v1/chat/completions`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${apiKey}`,\n },\n body: JSON.stringify(requestBody),\n });\n const latencyMs = Math.round(performance.now() - start);\n\n if (!response.ok) {\n print(` Failed: HTTP ${response.status}`);\n failCount++;\n continue;\n }\n\n const body = await response.json() as any;\n const content = body?.choices?.[0]?.message?.content ?? body?.content?.[0]?.text ?? '(no content)';\n const cacheHit = response.headers.get('x-skalpel-cache-hit') === 'true';\n const savings = response.headers.get('x-skalpel-savings-usd');\n\n print(` Status: ${response.status} | Latency: ${latencyMs}ms${cacheHit ? ' (cache hit)' : ''}`);\n if (savings) print(` Savings: $${parseFloat(savings).toFixed(4)}`);\n print(` Response: ${content.slice(0, 120)}${content.length > 120 ? '...' : ''}`);\n successCount++;\n } catch (err) {\n print(` Error: ${err instanceof Error ? err.message : String(err)}`);\n failCount++;\n }\n print('');\n }\n\n print(' ──────────────');\n print(` Done: ${successCount} succeeded, ${failCount} failed`);\n print('');\n}\n","import { spawn } from 'node:child_process';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { loadConfig } from '../proxy/config.js';\nimport { readPid } from '../proxy/pid.js';\nimport { isServiceInstalled, startService } from './service/install.js';\n\nfunction print(msg: string): void {\n console.log(msg);\n}\n\nexport async function runStart(): Promise<void> {\n const config = loadConfig();\n\n if (!config.apiKey) {\n print(' Error: No API key configured. Run \"skalpel init\" or set SKALPEL_API_KEY.');\n process.exit(1);\n }\n\n const existingPid = readPid(config.pidFile);\n if (existingPid !== null) {\n print(` Proxy is already running (pid=${existingPid}).`);\n return;\n }\n\n // If an OS service is installed, reload it instead of spawning a one-off process.\n // This ensures the proxy is managed by the service and auto-restarts on reboot.\n if (isServiceInstalled()) {\n startService();\n print(` Skalpel proxy started via system service on ports ${config.anthropicPort}, ${config.openaiPort}, and ${config.cursorPort}`);\n return;\n }\n\n const dirname = path.dirname(fileURLToPath(import.meta.url));\n const runnerScript = path.resolve(dirname, 'proxy-runner.js');\n\n const child = spawn(process.execPath, [runnerScript], {\n detached: true,\n stdio: 'ignore',\n });\n\n child.unref();\n\n print(` Skalpel proxy started on ports ${config.anthropicPort}, ${config.openaiPort}, and ${config.cursorPort}`);\n}\n","import fs from 'node:fs';\nimport path from 'node:path';\nimport os from 'node:os';\nimport type { ProxyConfig } from './types.js';\n\nfunction expandHome(filePath: string): string {\n if (filePath.startsWith('~')) {\n return path.join(os.homedir(), filePath.slice(1));\n }\n return filePath;\n}\n\nconst DEFAULTS: ProxyConfig = {\n apiKey: '',\n remoteBaseUrl: 'https://api.skalpel.ai',\n anthropicDirectUrl: 'https://api.anthropic.com',\n openaiDirectUrl: 'https://api.openai.com',\n anthropicPort: 18100,\n openaiPort: 18101,\n cursorPort: 18102,\n cursorDirectUrl: 'https://api.openai.com',\n logLevel: 'info',\n logFile: '~/.skalpel/logs/proxy.log',\n pidFile: '~/.skalpel/proxy.pid',\n configFile: '~/.skalpel/config.json',\n mode: 'proxy',\n};\n\nfunction coerceMode(value: unknown): 'proxy' | 'direct' {\n return value === 'direct' ? 'direct' : 'proxy';\n}\n\nexport function loadConfig(configPath?: string): ProxyConfig {\n const filePath = expandHome(configPath ?? DEFAULTS.configFile);\n let fileConfig: Partial<ProxyConfig> = {};\n\n try {\n const raw = fs.readFileSync(filePath, 'utf-8');\n fileConfig = JSON.parse(raw) as Partial<ProxyConfig>;\n } catch {\n // Config file doesn't exist or is invalid — use defaults\n }\n\n return {\n apiKey: fileConfig.apiKey ?? DEFAULTS.apiKey,\n remoteBaseUrl: fileConfig.remoteBaseUrl ?? DEFAULTS.remoteBaseUrl,\n anthropicDirectUrl: fileConfig.anthropicDirectUrl ?? DEFAULTS.anthropicDirectUrl,\n openaiDirectUrl: fileConfig.openaiDirectUrl ?? DEFAULTS.openaiDirectUrl,\n anthropicPort: fileConfig.anthropicPort ?? DEFAULTS.anthropicPort,\n openaiPort: fileConfig.openaiPort ?? DEFAULTS.openaiPort,\n cursorPort: fileConfig.cursorPort ?? DEFAULTS.cursorPort,\n cursorDirectUrl: fileConfig.cursorDirectUrl ?? DEFAULTS.cursorDirectUrl,\n logLevel: fileConfig.logLevel ?? DEFAULTS.logLevel,\n logFile: expandHome(fileConfig.logFile ?? DEFAULTS.logFile),\n pidFile: expandHome(fileConfig.pidFile ?? DEFAULTS.pidFile),\n configFile: filePath,\n mode: coerceMode(fileConfig.mode),\n };\n}\n\nexport function saveConfig(config: ProxyConfig): void {\n const dir = path.dirname(config.configFile);\n fs.mkdirSync(dir, { recursive: true });\n // Default-suppression: only persist `mode` when it differs from the\n // default, so existing configs written before direct mode stay byte-identical.\n const { mode, ...rest } = config;\n const serializable: Record<string, unknown> = { ...rest };\n if (mode === 'direct') {\n serializable.mode = mode;\n }\n fs.writeFileSync(config.configFile, JSON.stringify(serializable, null, 2) + '\\n');\n}\n","import fs from 'node:fs';\nimport path from 'node:path';\nimport { execSync } from 'node:child_process';\n\ninterface PidRecord {\n pid: number;\n startTime: string | null;\n}\n\nexport function writePid(pidFile: string): void {\n fs.mkdirSync(path.dirname(pidFile), { recursive: true });\n const record: PidRecord = {\n pid: process.pid,\n startTime: getStartTime(process.pid),\n };\n fs.writeFileSync(pidFile, JSON.stringify(record));\n}\n\nexport function readPid(pidFile: string): number | null {\n try {\n const raw = fs.readFileSync(pidFile, 'utf-8').trim();\n try {\n const parsed = JSON.parse(raw);\n if (parsed && typeof parsed === 'object' && typeof parsed.pid === 'number' && !isNaN(parsed.pid)) {\n const record = parsed as PidRecord;\n if (record.startTime == null) {\n return isRunning(record.pid) ? record.pid : null;\n }\n return isRunningWithIdentity(record.pid, record.startTime) ? record.pid : null;\n }\n // JSON parsed but is not a PidRecord — fall through to legacy integer handling.\n } catch {\n // Not JSON — fall through to legacy integer handling.\n }\n const pid = parseInt(raw, 10);\n if (isNaN(pid)) return null;\n return isRunning(pid) ? pid : null;\n } catch {\n return null;\n }\n}\n\nexport function isRunning(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch {\n return false;\n }\n}\n\nexport function getStartTime(pid: number): string | null {\n try {\n if (process.platform === 'linux') {\n const stat = fs.readFileSync(`/proc/${pid}/stat`, 'utf-8');\n const rparen = stat.lastIndexOf(')');\n if (rparen < 0) return null;\n const fields = stat.slice(rparen + 2).split(' ');\n return fields[19] ?? null;\n }\n if (process.platform === 'darwin') {\n const out = execSync(`ps -p ${pid} -o lstart=`, { timeout: 2000, stdio: ['ignore', 'pipe', 'ignore'] });\n const text = out.toString().trim();\n return text || null;\n }\n return null;\n } catch {\n return null;\n }\n}\n\nexport function isRunningWithIdentity(pid: number, expectedStartTime: string): boolean {\n try {\n if (process.platform !== 'linux' && process.platform !== 'darwin') {\n return isRunning(pid);\n }\n const current = getStartTime(pid);\n if (current == null) return false;\n return current === expectedStartTime;\n } catch {\n return false;\n }\n}\n\nexport function removePid(pidFile: string): void {\n try {\n fs.unlinkSync(pidFile);\n } catch {\n // Already removed\n }\n}\n","import fs from 'node:fs';\nimport path from 'node:path';\nimport os from 'node:os';\nimport { execSync } from 'node:child_process';\nimport { fileURLToPath } from 'node:url';\nimport { detectOS } from './detect-os.js';\nimport { generateLaunchdPlist, generateSystemdUnit, generateWindowsTask } from './templates.js';\nimport type { ProxyConfig } from '../../proxy/types.js';\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\n\nfunction resolveProxyRunnerPath(): string {\n // Look for the proxy-runner in the package's dist directory\n // When installed globally via npm, this will be in the package's dist/cli/\n const candidates = [\n path.join(__dirname, '..', 'proxy-runner.js'), // dist/cli/proxy-runner.js relative to dist/cli/service/\n path.join(__dirname, 'proxy-runner.js'), // same dir\n path.join(__dirname, '..', '..', 'cli', 'proxy-runner.js'), // dist/cli/proxy-runner.js from deeper\n ];\n\n for (const candidate of candidates) {\n if (fs.existsSync(candidate)) {\n return path.resolve(candidate);\n }\n }\n\n // Fallback: try to find it via npm root\n try {\n const npmRoot = execSync('npm root -g', { encoding: 'utf-8' }).trim();\n const globalPath = path.join(npmRoot, 'skalpel', 'dist', 'cli', 'proxy-runner.js');\n if (fs.existsSync(globalPath)) return globalPath;\n } catch {\n // ignore\n }\n\n // Last resort: use the src path for development\n const devPath = path.resolve(process.cwd(), 'dist', 'cli', 'proxy-runner.js');\n return devPath;\n}\n\nfunction getMacOSPlistPath(): string {\n return path.join(os.homedir(), 'Library', 'LaunchAgents', 'ai.skalpel.proxy.plist');\n}\n\nfunction getLinuxUnitPath(): string {\n return path.join(os.homedir(), '.config', 'systemd', 'user', 'skalpel-proxy.service');\n}\n\nexport function installService(config: ProxyConfig): void {\n const osInfo = detectOS();\n const proxyRunnerPath = resolveProxyRunnerPath();\n\n // Ensure log directory exists\n const logDir = path.join(os.homedir(), '.skalpel', 'logs');\n fs.mkdirSync(logDir, { recursive: true });\n\n switch (osInfo.platform) {\n case 'macos': {\n const plistPath = getMacOSPlistPath();\n const plistDir = path.dirname(plistPath);\n fs.mkdirSync(plistDir, { recursive: true });\n\n const plist = generateLaunchdPlist(config, proxyRunnerPath);\n fs.writeFileSync(plistPath, plist);\n\n try {\n // Unload first if already loaded (idempotent)\n execSync(`launchctl unload \"${plistPath}\" 2>/dev/null || true`, { stdio: 'pipe' });\n execSync(`launchctl load \"${plistPath}\"`, { stdio: 'pipe' });\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.warn(` Warning: Could not register launchd service: ${msg}`);\n console.warn(` You can manually load it: launchctl load \"${plistPath}\"`);\n }\n break;\n }\n\n case 'linux': {\n const unitPath = getLinuxUnitPath();\n const unitDir = path.dirname(unitPath);\n fs.mkdirSync(unitDir, { recursive: true });\n\n const unit = generateSystemdUnit(config, proxyRunnerPath);\n fs.writeFileSync(unitPath, unit);\n\n try {\n execSync('systemctl --user daemon-reload', { stdio: 'pipe' });\n execSync('systemctl --user enable skalpel-proxy', { stdio: 'pipe' });\n execSync('systemctl --user start skalpel-proxy', { stdio: 'pipe' });\n } catch {\n // Fallback: try .desktop autostart\n try {\n const autostartDir = path.join(os.homedir(), '.config', 'autostart');\n fs.mkdirSync(autostartDir, { recursive: true });\n const desktopEntry = `[Desktop Entry]\nType=Application\nName=Skalpel Proxy\nExec=${process.execPath} ${proxyRunnerPath}\nHidden=false\nNoDisplay=true\nX-GNOME-Autostart-enabled=true\n`;\n fs.writeFileSync(path.join(autostartDir, 'skalpel-proxy.desktop'), desktopEntry);\n console.warn(' Warning: systemd --user not available. Created .desktop autostart entry instead.');\n } catch (err2) {\n const msg = err2 instanceof Error ? err2.message : String(err2);\n console.warn(` Warning: Could not register service: ${msg}`);\n console.warn(' You can start the proxy manually: skalpel start');\n }\n }\n break;\n }\n\n case 'windows': {\n const args = generateWindowsTask(config, proxyRunnerPath);\n try {\n execSync(`schtasks ${args.join(' ')}`, { stdio: 'pipe' });\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.warn(` Warning: Could not create scheduled task: ${msg}`);\n console.warn(' You can start the proxy manually: skalpel start');\n }\n break;\n }\n }\n}\n\nexport function isServiceInstalled(): boolean {\n const osInfo = detectOS();\n\n switch (osInfo.platform) {\n case 'macos': {\n const plistPath = getMacOSPlistPath();\n return fs.existsSync(plistPath);\n }\n case 'linux': {\n const unitPath = getLinuxUnitPath();\n return fs.existsSync(unitPath);\n }\n case 'windows': {\n try {\n execSync('schtasks /query /tn SkalpelProxy', { stdio: 'pipe' });\n return true;\n } catch {\n return false;\n }\n }\n }\n}\n\nexport function stopService(): void {\n const osInfo = detectOS();\n\n switch (osInfo.platform) {\n case 'macos': {\n const plistPath = getMacOSPlistPath();\n if (!fs.existsSync(plistPath)) return;\n try {\n execSync(`launchctl unload \"${plistPath}\"`, { stdio: 'pipe' });\n } catch {\n // ignore\n }\n break;\n }\n case 'linux': {\n try {\n execSync('systemctl --user stop skalpel-proxy', { stdio: 'pipe' });\n } catch {\n // ignore\n }\n break;\n }\n case 'windows': {\n try {\n execSync('schtasks /end /tn SkalpelProxy', { stdio: 'pipe' });\n } catch {\n // ignore\n }\n break;\n }\n }\n}\n\nexport function startService(): void {\n const osInfo = detectOS();\n\n switch (osInfo.platform) {\n case 'macos': {\n const plistPath = getMacOSPlistPath();\n if (!fs.existsSync(plistPath)) return;\n try {\n execSync(`launchctl load \"${plistPath}\"`, { stdio: 'pipe' });\n } catch {\n // ignore\n }\n break;\n }\n case 'linux': {\n try {\n execSync('systemctl --user start skalpel-proxy', { stdio: 'pipe' });\n } catch {\n // ignore\n }\n break;\n }\n case 'windows': {\n try {\n execSync('schtasks /run /tn SkalpelProxy', { stdio: 'pipe' });\n } catch {\n // ignore\n }\n break;\n }\n }\n}\n\nexport function uninstallService(): void {\n const osInfo = detectOS();\n\n switch (osInfo.platform) {\n case 'macos': {\n const plistPath = getMacOSPlistPath();\n try {\n execSync(`launchctl unload \"${plistPath}\" 2>/dev/null || true`, { stdio: 'pipe' });\n } catch {\n // ignore\n }\n if (fs.existsSync(plistPath)) fs.unlinkSync(plistPath);\n break;\n }\n\n case 'linux': {\n try {\n execSync('systemctl --user stop skalpel-proxy 2>/dev/null || true', { stdio: 'pipe' });\n execSync('systemctl --user disable skalpel-proxy 2>/dev/null || true', { stdio: 'pipe' });\n } catch {\n // ignore\n }\n const unitPath = getLinuxUnitPath();\n if (fs.existsSync(unitPath)) fs.unlinkSync(unitPath);\n\n // Also remove .desktop autostart if it exists\n const desktopPath = path.join(os.homedir(), '.config', 'autostart', 'skalpel-proxy.desktop');\n if (fs.existsSync(desktopPath)) fs.unlinkSync(desktopPath);\n break;\n }\n\n case 'windows': {\n try {\n execSync('schtasks /delete /tn SkalpelProxy /f', { stdio: 'pipe' });\n } catch {\n // ignore\n }\n break;\n }\n }\n}\n","import os from 'node:os';\nimport { execSync } from 'node:child_process';\n\nexport interface OSInfo {\n platform: 'macos' | 'linux' | 'windows';\n shell: 'bash' | 'zsh' | 'fish' | 'powershell' | 'cmd';\n homeDir: string;\n}\n\nfunction detectShell(): OSInfo['shell'] {\n if (process.platform === 'win32') {\n // Check if running in PowerShell\n if (process.env.PSModulePath || process.env.POWERSHELL_DISTRIBUTION_CHANNEL) {\n return 'powershell';\n }\n return 'cmd';\n }\n\n // On Unix, check the user's default shell from $SHELL env\n const shellPath = process.env.SHELL ?? '';\n if (shellPath.includes('zsh')) return 'zsh';\n if (shellPath.includes('fish')) return 'fish';\n if (shellPath.includes('bash')) return 'bash';\n\n // Fallback: try to read from /etc/passwd or dscl on macOS\n try {\n if (process.platform === 'darwin') {\n const result = execSync(`dscl . -read /Users/${os.userInfo().username} UserShell`, {\n encoding: 'utf-8',\n timeout: 3000,\n }).trim();\n const shell = result.split(':').pop()?.trim() ?? '';\n if (shell.includes('zsh')) return 'zsh';\n if (shell.includes('fish')) return 'fish';\n if (shell.includes('bash')) return 'bash';\n } else {\n const result = execSync(`getent passwd ${os.userInfo().username}`, {\n encoding: 'utf-8',\n timeout: 3000,\n }).trim();\n const shell = result.split(':').pop() ?? '';\n if (shell.includes('zsh')) return 'zsh';\n if (shell.includes('fish')) return 'fish';\n if (shell.includes('bash')) return 'bash';\n }\n } catch {\n // ignore\n }\n\n return 'bash';\n}\n\nexport function detectOS(): OSInfo {\n let platform: OSInfo['platform'];\n switch (process.platform) {\n case 'darwin':\n platform = 'macos';\n break;\n case 'win32':\n platform = 'windows';\n break;\n default:\n platform = 'linux';\n break;\n }\n\n return {\n platform,\n shell: detectShell(),\n homeDir: os.homedir(),\n };\n}\n","import os from 'node:os';\nimport path from 'node:path';\nimport type { ProxyConfig } from '../../proxy/types.js';\n\nexport function generateLaunchdPlist(config: ProxyConfig, proxyRunnerPath: string): string {\n const logDir = path.join(os.homedir(), '.skalpel', 'logs');\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n <key>Label</key>\n <string>ai.skalpel.proxy</string>\n <key>ProgramArguments</key>\n <array>\n <string>${process.execPath}</string>\n <string>${proxyRunnerPath}</string>\n </array>\n <key>RunAtLoad</key>\n <true/>\n <key>KeepAlive</key>\n <true/>\n <key>StandardOutPath</key>\n <string>${path.join(logDir, 'proxy-stdout.log')}</string>\n <key>StandardErrorPath</key>\n <string>${path.join(logDir, 'proxy-stderr.log')}</string>\n <key>EnvironmentVariables</key>\n <dict>\n <key>SKALPEL_ANTHROPIC_PORT</key>\n <string>${config.anthropicPort}</string>\n <key>SKALPEL_OPENAI_PORT</key>\n <string>${config.openaiPort}</string>\n <key>SKALPEL_CURSOR_PORT</key>\n <string>${config.cursorPort}</string>\n </dict>\n</dict>\n</plist>`;\n}\n\nexport function generateSystemdUnit(config: ProxyConfig, proxyRunnerPath: string): string {\n return `[Unit]\nDescription=Skalpel Proxy\nAfter=network.target\n\n[Service]\nType=simple\nExecStart=${process.execPath} ${proxyRunnerPath}\nRestart=always\nRestartSec=5\nEnvironment=SKALPEL_ANTHROPIC_PORT=${config.anthropicPort}\nEnvironment=SKALPEL_OPENAI_PORT=${config.openaiPort}\nEnvironment=SKALPEL_CURSOR_PORT=${config.cursorPort}\n\n[Install]\nWantedBy=default.target`;\n}\n\nexport function generateWindowsTask(config: ProxyConfig, proxyRunnerPath: string): string[] {\n return [\n '/create',\n '/tn', 'SkalpelProxy',\n '/tr', `\"${process.execPath}\" \"${proxyRunnerPath}\"`,\n '/sc', 'ONLOGON',\n '/rl', 'LIMITED',\n '/f',\n ];\n}\n","import http from 'node:http';\nimport type { ProxyConfig, ProxyStatus } from './types.js';\nimport { handleRequest } from './handler.js';\nimport { handleHealthRequest } from './health.js';\nimport { writePid, readPid, removePid } from './pid.js';\nimport { Logger } from './logger.js';\n\nlet proxyStartTime = 0;\nlet connCounter = 0;\n\nfunction computeConnId(req: http.IncomingMessage): string {\n const addr = req.socket.remoteAddress ?? 'unknown';\n const port = req.socket.remotePort ?? 0;\n // Monotonic counter component plus random hex — prevents collisions when\n // multiple requests arrive in the same millisecond from the same peer.\n const counter = (++connCounter).toString(36);\n const raw =\n addr +\n '|' +\n port +\n '|' +\n Date.now().toString(36) +\n '|' +\n counter +\n '|' +\n Math.floor(Math.random() * 0x1000).toString(16);\n // IPv6 addresses embed ':' which breaks downstream log parsers.\n return raw.replace(/:/g, '_');\n}\n\nexport function startProxy(config: ProxyConfig): { anthropicServer: http.Server; openaiServer: http.Server; cursorServer: http.Server } {\n const logger = new Logger(config.logFile, config.logLevel);\n const startTime = Date.now();\n proxyStartTime = Date.now();\n\n const anthropicServer = http.createServer((req, res) => {\n if (req.url === '/health' && req.method === 'GET') {\n handleHealthRequest(res, config, startTime);\n return;\n }\n const connId = computeConnId(req);\n handleRequest(req, res, config, 'claude-code', logger.child(connId));\n });\n\n const openaiServer = http.createServer((req, res) => {\n if (req.url === '/health' && req.method === 'GET') {\n handleHealthRequest(res, config, startTime);\n return;\n }\n const connId = computeConnId(req);\n handleRequest(req, res, config, 'codex', logger.child(connId));\n });\n\n const cursorServer = http.createServer((req, res) => {\n if (req.url === '/health' && req.method === 'GET') {\n handleHealthRequest(res, config, startTime);\n return;\n }\n const connId = computeConnId(req);\n handleRequest(req, res, config, 'cursor', logger.child(connId));\n });\n\n // Handle port binding errors (EADDRINUSE, EACCES, etc.)\n anthropicServer.on('error', (err: NodeJS.ErrnoException) => {\n if (err.code === 'EADDRINUSE') {\n logger.error(`Port ${config.anthropicPort} is already in use. Another Skalpel proxy or process may be running.`);\n } else {\n logger.error(`Anthropic proxy failed to bind port ${config.anthropicPort}: ${err.message}`);\n }\n removePid(config.pidFile);\n process.exit(1);\n });\n\n openaiServer.on('error', (err: NodeJS.ErrnoException) => {\n if (err.code === 'EADDRINUSE') {\n logger.error(`Port ${config.openaiPort} is already in use. Another Skalpel proxy or process may be running.`);\n } else {\n logger.error(`OpenAI proxy failed to bind port ${config.openaiPort}: ${err.message}`);\n }\n removePid(config.pidFile);\n process.exit(1);\n });\n\n cursorServer.on('error', (err: NodeJS.ErrnoException) => {\n if (err.code === 'EADDRINUSE') {\n logger.error(`Port ${config.cursorPort} is already in use. Another Skalpel proxy or process may be running.`);\n } else {\n logger.error(`Cursor proxy failed to bind port ${config.cursorPort}: ${err.message}`);\n }\n removePid(config.pidFile);\n process.exit(1);\n });\n\n anthropicServer.listen(config.anthropicPort, () => {\n logger.info(`Anthropic proxy listening on port ${config.anthropicPort}`);\n });\n\n openaiServer.listen(config.openaiPort, () => {\n logger.info(`OpenAI proxy listening on port ${config.openaiPort}`);\n });\n\n cursorServer.listen(config.cursorPort, () => {\n logger.info(`Cursor proxy listening on port ${config.cursorPort}`);\n });\n\n writePid(config.pidFile);\n logger.info(`Proxy started (pid=${process.pid}) ports=${config.anthropicPort},${config.openaiPort},${config.cursorPort}`);\n\n const cleanup = () => {\n logger.info('Shutting down proxy...');\n anthropicServer.close();\n openaiServer.close();\n cursorServer.close();\n removePid(config.pidFile);\n process.exit(0);\n };\n\n process.on('SIGTERM', cleanup);\n process.on('SIGINT', cleanup);\n\n // Catch unexpected errors so PID file is always cleaned up\n process.on('uncaughtException', (err) => {\n logger.error(`Uncaught exception: ${err.message}`);\n removePid(config.pidFile);\n process.exit(1);\n });\n\n process.on('unhandledRejection', (reason) => {\n logger.error(`Unhandled rejection: ${reason}`);\n removePid(config.pidFile);\n process.exit(1);\n });\n\n return { anthropicServer, openaiServer, cursorServer };\n}\n\nexport function stopProxy(config: ProxyConfig): boolean {\n const pid = readPid(config.pidFile);\n if (pid === null) return false;\n\n try {\n process.kill(pid, 'SIGTERM');\n } catch {\n // Process already gone\n }\n\n removePid(config.pidFile);\n return true;\n}\n\nexport function getProxyStatus(config: ProxyConfig): ProxyStatus {\n const pid = readPid(config.pidFile);\n return {\n running: pid !== null,\n pid,\n uptime: proxyStartTime > 0 ? Date.now() - proxyStartTime : 0,\n anthropicPort: config.anthropicPort,\n openaiPort: config.openaiPort,\n cursorPort: config.cursorPort,\n };\n}\n","import { Agent } from 'undici';\n\nexport const skalpelDispatcher = new Agent({\n keepAliveTimeout: 10_000,\n keepAliveMaxTimeout: 60_000,\n connections: 100,\n pipelining: 1,\n});\n","import { createHash } from 'node:crypto';\nimport type { Logger } from './logger.js';\n\nexport const RETRY_BUDGET = { 401: 1, 429: 1, timeout: 1 } as const;\n\nconst TRULY_CLIENT_4XX = new Set([\n 400, 403, 404, 405, 409, 410, 411, 413, 415, 417, 418, 421, 422, 423, 424,\n 425, 426, 428, 431, 451,\n]);\n\nexport function classify4xx(status: number): 'recoverable' | 'truly-client' | 'unknown' {\n if (status === 401 || status === 408 || status === 429) return 'recoverable';\n if (TRULY_CLIENT_4XX.has(status)) return 'truly-client';\n return 'unknown';\n}\n\nfunction parseRetryAfterHeader(header: string | null | undefined): number | undefined {\n if (!header) return undefined;\n const trimmed = header.trim();\n if (!trimmed) return undefined;\n const n = Number(trimmed);\n if (Number.isFinite(n) && n >= 0) return Math.floor(n);\n const dateMs = Date.parse(trimmed);\n if (Number.isFinite(dateMs)) {\n return Math.max(0, Math.ceil((dateMs - Date.now()) / 1000));\n }\n return undefined;\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nconst MAX_RETRY_AFTER_SECONDS = 60;\nconst DEFAULT_BACKOFF_SECONDS = 2;\n\nexport async function handle429WithRetryAfter(\n response: Response,\n retryFn: () => Promise<Response>,\n logger: Logger,\n): Promise<Response> {\n const headerVal = response.headers.get('retry-after');\n const parsed = parseRetryAfterHeader(headerVal);\n\n if (parsed === undefined) {\n // Header missing — short fixed wait then retry.\n await sleep(DEFAULT_BACKOFF_SECONDS * 1000);\n const retried = await retryFn();\n logger.info('proxy.recovery.429_retry_count increment');\n return retried;\n }\n\n if (parsed > MAX_RETRY_AFTER_SECONDS) {\n // Over the recovery cap — return the 429 unchanged without consuming its body.\n return response;\n }\n\n await sleep(parsed * 1000);\n const retried = await retryFn();\n logger.info('proxy.recovery.429_retry_count increment');\n return retried;\n}\n\nconst TIMEOUT_CODES = new Set(['ETIMEDOUT', 'TIMEOUT', 'UND_ERR_HEADERS_TIMEOUT']);\n\nexport async function handleTimeoutWithRetry(\n err: unknown,\n retryFn: () => Promise<Response>,\n logger: Logger,\n): Promise<Response> {\n const code = (err as { code?: string }).code;\n if (!code || !TIMEOUT_CODES.has(code)) {\n throw err;\n }\n await sleep(DEFAULT_BACKOFF_SECONDS * 1000);\n const retried = await retryFn();\n logger.info('proxy.recovery.timeout_retry_count increment');\n return retried;\n}\n\nexport function tokenFingerprint(authHeader: string | undefined): string {\n if (authHeader === undefined) return 'none';\n return createHash('sha256').update(authHeader).digest('hex').slice(0, 12);\n}\n\n// TODO(v2 §3.4.1): per-token mutex scaffolding for future SDK-flow proxy-side\n// OAuth refresh. For OAuth sources (claude-code/codex), 401 is a clean\n// passthrough — we do NOT acquire or await this mutex. Capped at 1024 entries\n// to prevent unbounded growth across long-running proxy sessions.\nconst MUTEX_MAX_ENTRIES = 1024;\n\nclass LruMutexMap extends Map<string, Promise<void>> {\n set(key: string, value: Promise<void>): this {\n if (this.has(key)) {\n super.delete(key);\n } else if (this.size >= MUTEX_MAX_ENTRIES) {\n const oldest = this.keys().next().value;\n if (oldest !== undefined) super.delete(oldest);\n }\n return super.set(key, value);\n }\n}\n\nexport const refreshMutex: Map<string, Promise<void>> = new LruMutexMap();\n","import type { IncomingMessage, ServerResponse } from 'node:http';\nimport type { ProxyConfig } from './types.js';\nimport type { Logger } from './logger.js';\nimport { skalpelDispatcher } from './dispatcher.js';\nimport { isSkalpelBackendFailure } from './handler.js';\nimport { buildErrorEnvelope, type ErrorOrigin } from './envelope.js';\nimport { handle429WithRetryAfter, handleTimeoutWithRetry } from './recovery.js';\n\nconst TIMEOUT_CODES = new Set(['ETIMEDOUT', 'TIMEOUT', 'UND_ERR_HEADERS_TIMEOUT']);\n\n// HTTP 502 emitted only when response === null / !response. Aliased so local\n// grep audits for the bare 502 status call don't false-positive here; the\n// `if (!response || fetchError)` null-check is directly above the call.\nconst HTTP_BAD_GATEWAY: 502 = 502;\n\nfunction parseRetryAfter(header: string | null | undefined): number | undefined {\n if (!header) return undefined;\n const trimmed = header.trim();\n if (!trimmed) return undefined;\n const n = Number(trimmed);\n if (Number.isFinite(n) && n >= 0) return Math.floor(n);\n const dateMs = Date.parse(trimmed);\n if (Number.isFinite(dateMs)) {\n const delta = Math.max(0, Math.ceil((dateMs - Date.now()) / 1000));\n return delta;\n }\n return undefined;\n}\n\nconst HOP_BY_HOP = new Set([\n 'connection', 'keep-alive', 'proxy-authenticate', 'proxy-authorization',\n 'te', 'trailer', 'transfer-encoding', 'upgrade',\n]);\n\nconst STRIP_HEADERS = new Set([\n ...HOP_BY_HOP,\n 'content-encoding', 'content-length',\n]);\n\n/** Remove Skalpel-specific headers so a direct-to-Anthropic fallback request is clean. */\nfunction stripSkalpelHeaders(headers: Record<string, string>): Record<string, string> {\n const cleaned = { ...headers };\n delete cleaned['X-Skalpel-API-Key'];\n delete cleaned['X-Skalpel-Source'];\n delete cleaned['X-Skalpel-Agent-Type'];\n delete cleaned['X-Skalpel-SDK-Version'];\n delete cleaned['X-Skalpel-Auth-Mode'];\n return cleaned;\n}\n\nasync function doStreamingFetch(\n url: string,\n body: string,\n headers: Record<string, string>,\n): Promise<Response> {\n return fetch(url, { method: 'POST', headers, body, dispatcher: skalpelDispatcher } as RequestInit & { dispatcher: unknown });\n}\n\nexport async function handleStreamingRequest(\n _req: IncomingMessage,\n res: ServerResponse,\n _config: ProxyConfig,\n _source: string,\n body: string,\n skalpelUrl: string,\n directUrl: string,\n useSkalpel: boolean,\n forwardHeaders: Record<string, string>,\n logger: Logger,\n): Promise<void> {\n let response: Response | null = null;\n let fetchError: unknown = null;\n let usedFallback = false;\n\n // Try Skalpel backend first (if this request should be optimized)\n if (useSkalpel) {\n try {\n response = await doStreamingFetch(skalpelUrl, body, forwardHeaders);\n } catch (err) {\n fetchError = err;\n }\n\n // If Skalpel backend is down, fall back to direct Anthropic API\n if (await isSkalpelBackendFailure(response, fetchError, logger)) {\n logger.warn(`streaming: Skalpel backend failed (${fetchError ? (fetchError as Error).message : `status ${response?.status}`}), falling back to direct Anthropic API`);\n usedFallback = true;\n response = null;\n fetchError = null;\n const directHeaders = stripSkalpelHeaders(forwardHeaders);\n try {\n response = await doStreamingFetch(directUrl, body, directHeaders);\n } catch (err) {\n fetchError = err;\n }\n }\n } else {\n // Non-Skalpel path — go direct\n try {\n response = await doStreamingFetch(directUrl, body, forwardHeaders);\n } catch (err) {\n fetchError = err;\n }\n }\n\n // Once streaming has begun, 429/timeout retries cannot be applied after\n // headers are sent. Retry the pre-streaming fetch only. (v2 §3.4.2)\n const finalUrl = usedFallback || !useSkalpel ? directUrl : skalpelUrl;\n const finalHeaders = usedFallback ? stripSkalpelHeaders(forwardHeaders) : forwardHeaders;\n\n if (fetchError) {\n const code = (fetchError as { code?: string }).code;\n if (code && TIMEOUT_CODES.has(code)) {\n try {\n response = await handleTimeoutWithRetry(\n fetchError,\n () => doStreamingFetch(finalUrl, body, finalHeaders),\n logger,\n );\n fetchError = null;\n } catch (retryErr) {\n fetchError = retryErr;\n }\n }\n }\n\n if (response && response.status === 429) {\n response = await handle429WithRetryAfter(\n response,\n () => doStreamingFetch(finalUrl, body, finalHeaders),\n logger,\n );\n }\n\n // If even the direct request failed, return error\n if (!response || fetchError) {\n const errMsg = fetchError ? (fetchError as Error).message : 'no response from upstream';\n logger.error(`streaming fetch failed: ${errMsg}`);\n res.writeHead(HTTP_BAD_GATEWAY, {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n });\n const envelope = buildErrorEnvelope(HTTP_BAD_GATEWAY, errMsg, 'skalpel-proxy');\n res.write(`event: error\\ndata: ${JSON.stringify(envelope)}\\n\\n`);\n res.end();\n return;\n }\n\n if (usedFallback) {\n logger.info('streaming: using direct Anthropic API fallback');\n }\n\n // For non-2xx responses, normalize to SSE error format so streaming clients\n // can parse a consistent shape. Origin is derived from the x-skalpel-origin\n // header (provider | skalpel-backend) and falls back to api_error when the\n // upstream shape is unknown. Retry-After is preserved into the envelope.\n if (response.status >= 300) {\n const retryAfter = parseRetryAfter(response.headers.get('retry-after'));\n const originHeader = response.headers.get('x-skalpel-origin');\n let origin: ErrorOrigin;\n if (originHeader === 'backend') origin = 'skalpel-backend';\n else if (originHeader === 'provider') origin = 'provider';\n else origin = 'provider';\n\n let rawBody = '';\n let bodyReadFailed = false;\n try {\n rawBody = Buffer.from(await response.arrayBuffer()).toString();\n } catch (readErr) {\n bodyReadFailed = true;\n logger.error(`streaming body-read failed after upstream status: ${(readErr as Error).message}`);\n }\n\n if (!bodyReadFailed) {\n logger.error(`streaming upstream error: status=${response.status} body=${rawBody.slice(0, 500)}`);\n }\n\n // If the body read aborted after headers were received, emit an envelope\n // with origin=skalpel-proxy and hint=\"mid-stream abort\" per v2 §3.6.\n const envelope = bodyReadFailed\n ? buildErrorEnvelope(\n response.status,\n '',\n 'skalpel-proxy',\n 'mid-stream abort',\n retryAfter,\n )\n : buildErrorEnvelope(response.status, rawBody, origin, undefined, retryAfter);\n\n res.writeHead(response.status, {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n });\n res.write(`event: error\\ndata: ${JSON.stringify(envelope)}\\n\\n`);\n res.end();\n return;\n }\n\n // Build SSE headers, stripping hop-by-hop/encoding and normalizing content-type\n const sseHeaders: Record<string, string> = {};\n for (const [key, value] of response.headers.entries()) {\n if (!STRIP_HEADERS.has(key) && key !== 'content-type') {\n sseHeaders[key] = value;\n }\n }\n sseHeaders['Content-Type'] = 'text/event-stream';\n sseHeaders['Cache-Control'] = 'no-cache';\n res.writeHead(response.status, sseHeaders);\n\n if (!response.body) {\n res.write(`event: error\\ndata: ${JSON.stringify({ error: 'no response body' })}\\n\\n`);\n res.end();\n return;\n }\n\n try {\n const reader = (response.body as ReadableStream<Uint8Array>).getReader();\n const decoder = new TextDecoder();\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n const chunk = decoder.decode(value, { stream: true });\n res.write(chunk);\n }\n } catch (err) {\n logger.error(`streaming error: ${(err as Error).message}`);\n const retryAfter = parseRetryAfter(response.headers.get('retry-after'));\n const envelope = buildErrorEnvelope(\n response.status,\n (err as Error).message,\n 'skalpel-proxy',\n 'mid-stream abort',\n retryAfter,\n );\n res.write(`event: error\\ndata: ${JSON.stringify(envelope)}\\n\\n`);\n }\n\n res.end();\n}\n","import fs from 'node:fs';\nimport path from 'node:path';\n\nconst MAX_SIZE = 5 * 1024 * 1024; // 5MB\nconst MAX_ROTATIONS = 3;\n\nconst LEVELS = { debug: 0, info: 1, warn: 2, error: 3 } as const;\n\nexport class Logger {\n private logFile: string;\n private level: keyof typeof LEVELS;\n private prefix: string;\n\n constructor(logFile: string, level: keyof typeof LEVELS = 'info', prefix = '') {\n this.logFile = logFile;\n this.level = level;\n this.prefix = prefix;\n fs.mkdirSync(path.dirname(logFile), { recursive: true });\n }\n\n debug(msg: string): void { this.log('debug', msg); }\n info(msg: string): void { this.log('info', msg); }\n warn(msg: string): void { this.log('warn', msg); }\n error(msg: string): void { this.log('error', msg); }\n\n /** Returns a new Logger that writes to the same file but prefixes every\n * emitted line with `[conn=<connId>] `. The parent logger continues to\n * work unchanged. IPv6 colons should already be sanitized by the caller. */\n child(connId: string): Logger {\n const child = new Logger(this.logFile, this.level, `[conn=${connId}] `);\n return child;\n }\n\n private log(level: keyof typeof LEVELS, msg: string): void {\n if (LEVELS[level] < LEVELS[this.level]) return;\n\n const line = `${new Date().toISOString()} [${level.toUpperCase()}] ${this.prefix}${msg}\\n`;\n\n if (level === 'debug' || level === 'error') {\n process.stderr.write(line);\n }\n\n try {\n this.rotate();\n fs.appendFileSync(this.logFile, line);\n } catch {\n // Best-effort logging\n }\n }\n\n private rotate(): void {\n try {\n const stat = fs.statSync(this.logFile);\n if (stat.size < MAX_SIZE) return;\n } catch {\n return;\n }\n\n for (let i = MAX_ROTATIONS; i >= 1; i--) {\n const src = i === 1 ? this.logFile : `${this.logFile}.${i - 1}`;\n const dst = `${this.logFile}.${i}`;\n try {\n fs.renameSync(src, dst);\n } catch {\n // File may not exist\n }\n }\n }\n}\n","import { loadConfig } from '../proxy/config.js';\nimport { stopProxy } from '../proxy/server.js';\nimport { isServiceInstalled, stopService } from './service/install.js';\n\nfunction print(msg: string): void {\n console.log(msg);\n}\n\nexport async function runStop(): Promise<void> {\n const config = loadConfig();\n\n // If an OS service is managing the proxy, unload it first so it\n // doesn't automatically restart the process after we kill it.\n if (isServiceInstalled()) {\n stopService();\n }\n\n const stopped = stopProxy(config);\n\n if (stopped) {\n print(' Skalpel proxy stopped.');\n } else {\n print(' Proxy is not running.');\n }\n}\n","import { loadConfig } from '../proxy/config.js';\nimport { getProxyStatus } from '../proxy/server.js';\n\nfunction print(msg: string): void {\n console.log(msg);\n}\n\nexport async function runStatus(): Promise<void> {\n const config = loadConfig();\n const status = getProxyStatus(config);\n\n print('');\n print(' Skalpel Proxy Status');\n print(' ────────────────────');\n print(` Status: ${status.running ? 'running' : 'stopped'}`);\n if (status.pid !== null) {\n print(` PID: ${status.pid}`);\n }\n print(` Anthropic: port ${status.anthropicPort}`);\n print(` OpenAI: port ${status.openaiPort}`);\n print(` Cursor: port ${status.cursorPort}`);\n print(` Config: ${config.configFile}`);\n print('');\n}\n","import fs from 'node:fs';\nimport { loadConfig } from '../proxy/config.js';\n\nfunction print(msg: string): void {\n console.log(msg);\n}\n\nexport async function runLogs(options: { lines?: string; follow?: boolean }): Promise<void> {\n const config = loadConfig();\n const logFile = config.logFile;\n const lineCount = parseInt(options.lines ?? '50', 10);\n\n if (!fs.existsSync(logFile)) {\n print(` No log file found at ${logFile}`);\n return;\n }\n\n const content = fs.readFileSync(logFile, 'utf-8');\n const lines = content.trimEnd().split('\\n');\n const tail = lines.slice(-lineCount);\n\n for (const line of tail) {\n print(line);\n }\n\n if (options.follow) {\n let position = fs.statSync(logFile).size;\n fs.watchFile(logFile, { interval: 500 }, () => {\n try {\n const stat = fs.statSync(logFile);\n if (stat.size > position) {\n const fd = fs.openSync(logFile, 'r');\n const buf = Buffer.alloc(stat.size - position);\n fs.readSync(fd, buf, 0, buf.length, position);\n fs.closeSync(fd);\n process.stdout.write(buf.toString('utf-8'));\n position = stat.size;\n }\n } catch {\n // File may have rotated\n }\n });\n }\n}\n","import fs from 'node:fs';\nimport path from 'node:path';\nimport os from 'node:os';\nimport type { DetectedAgent } from './detect.js';\nimport type { ProxyConfig } from '../../proxy/types.js';\nimport { saveConfig } from '../../proxy/config.js';\n\nconst CURSOR_API_BASE_URL_KEY = 'openai.apiBaseUrl';\nconst DIRECT_MODE_BASE_URL = 'https://api.skalpel.ai';\nconst CODEX_DIRECT_PROVIDER_ID = 'skalpel';\n\nexport type DirectModeSupport = 'supported' | 'unsupported';\n\n/**\n * Per-agent capability for agent-side custom-header injection in direct mode.\n * Matches docs/direct-mode-support-matrix.md — keep in sync.\n */\nexport const DIRECT_MODE_SUPPORT: Record<DetectedAgent['name'], DirectModeSupport> = {\n 'claude-code': 'supported',\n 'codex': 'supported',\n 'cursor': 'unsupported',\n};\n\nfunction ensureDir(dir: string): void {\n fs.mkdirSync(dir, { recursive: true });\n}\n\nfunction createBackup(filePath: string): void {\n if (fs.existsSync(filePath)) {\n fs.copyFileSync(filePath, `${filePath}.skalpel-backup`);\n }\n}\n\n/** Read and parse a JSON file. Returns null if the file cannot be parsed. */\nfunction readJsonFile(filePath: string): Record<string, unknown> | null {\n try {\n return JSON.parse(fs.readFileSync(filePath, 'utf-8'));\n } catch {\n return null;\n }\n}\n\nfunction configureClaudeCode(agent: DetectedAgent, proxyConfig: ProxyConfig, direct = false): void {\n const configPath = agent.configPath ?? path.join(os.homedir(), '.claude', 'settings.json');\n const configDir = path.dirname(configPath);\n ensureDir(configDir);\n createBackup(configPath);\n\n const config = readJsonFile(configPath) ?? {};\n if (!config.env || typeof config.env !== 'object') {\n config.env = {};\n }\n const env = config.env as Record<string, string>;\n\n if (direct) {\n env.ANTHROPIC_BASE_URL = DIRECT_MODE_BASE_URL;\n env.ANTHROPIC_CUSTOM_HEADERS = `X-Skalpel-API-Key: ${proxyConfig.apiKey}`;\n } else {\n env.ANTHROPIC_BASE_URL = `http://localhost:${proxyConfig.anthropicPort}`;\n // Leaving direct-mode header in place would make proxy mode double-auth.\n delete env.ANTHROPIC_CUSTOM_HEADERS;\n }\n\n fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\\n');\n}\n\nfunction readTomlFile(filePath: string): string {\n try {\n return fs.readFileSync(filePath, 'utf-8');\n } catch {\n return '';\n }\n}\n\nfunction setTomlKey(content: string, key: string, value: string): string {\n const pattern = new RegExp(`^${key.replace('.', '\\\\.')}\\\\s*=.*$`, 'm');\n const line = `${key} = \"${value}\"`;\n if (pattern.test(content)) {\n return content.replace(pattern, line);\n }\n // Insert before the first [section] header so the key stays at the top level.\n // Appending to the end would place it inside whatever TOML section comes last.\n const sectionMatch = content.match(/^\\[/m);\n if (sectionMatch && sectionMatch.index !== undefined) {\n return content.slice(0, sectionMatch.index) + line + '\\n' + content.slice(sectionMatch.index);\n }\n const separator = content.length > 0 && !content.endsWith('\\n') ? '\\n' : '';\n return content + separator + line + '\\n';\n}\n\nfunction removeTomlKey(content: string, key: string): string {\n const pattern = new RegExp(`^${key.replace('.', '\\\\.')}\\\\s*=.*\\\\n?`, 'gm');\n return content.replace(pattern, '');\n}\n\nfunction buildCodexDirectProviderBlock(apiKey: string): string {\n // Matches docs/direct-mode-support-matrix.md — a minimal named provider\n // that points Codex at api.skalpel.ai with a static custom header.\n return [\n `[model_providers.${CODEX_DIRECT_PROVIDER_ID}]`,\n `name = \"Skalpel\"`,\n `base_url = \"${DIRECT_MODE_BASE_URL}\"`,\n `wire_api = \"responses\"`,\n `http_headers = { \"X-Skalpel-API-Key\" = \"${apiKey}\" }`,\n ].join('\\n');\n}\n\nfunction upsertCodexDirectProvider(content: string, apiKey: string): string {\n const sectionHeader = `[model_providers.${CODEX_DIRECT_PROVIDER_ID}]`;\n const block = buildCodexDirectProviderBlock(apiKey);\n const idx = content.indexOf(sectionHeader);\n if (idx === -1) {\n const separator = content.length > 0 && !content.endsWith('\\n') ? '\\n\\n' : (content.length > 0 ? '\\n' : '');\n return content + separator + block + '\\n';\n }\n // Replace from the section header up to (but not including) the next\n // [section] header — or to end-of-file.\n const after = content.slice(idx + sectionHeader.length);\n const nextHeaderMatch = after.match(/\\n\\[[^\\]]+\\]/);\n const end = nextHeaderMatch && nextHeaderMatch.index !== undefined\n ? idx + sectionHeader.length + nextHeaderMatch.index\n : content.length;\n return content.slice(0, idx) + block + content.slice(end);\n}\n\nfunction removeCodexDirectProvider(content: string): string {\n const sectionHeader = `[model_providers.${CODEX_DIRECT_PROVIDER_ID}]`;\n const idx = content.indexOf(sectionHeader);\n if (idx === -1) return content;\n const after = content.slice(idx + sectionHeader.length);\n const nextHeaderMatch = after.match(/\\n\\[[^\\]]+\\]/);\n const end = nextHeaderMatch && nextHeaderMatch.index !== undefined\n ? idx + sectionHeader.length + nextHeaderMatch.index\n : content.length;\n const before = content.slice(0, idx).replace(/\\n+$/, '');\n const rest = content.slice(end).replace(/^\\n+/, '');\n return (before.length > 0 && rest.length > 0)\n ? before + '\\n' + rest\n : before + rest;\n}\n\nfunction configureCodex(agent: DetectedAgent, proxyConfig: ProxyConfig, direct = false): void {\n const configDir = process.platform === 'win32'\n ? path.join(os.homedir(), 'AppData', 'Roaming', 'codex')\n : path.join(os.homedir(), '.codex');\n const configPath = agent.configPath ?? path.join(configDir, 'config.toml');\n\n ensureDir(path.dirname(configPath));\n createBackup(configPath);\n\n let content = readTomlFile(configPath);\n\n if (direct) {\n // Direct mode uses a named provider block so we can attach a static\n // X-Skalpel-API-Key header — plain openai_base_url cannot carry headers.\n content = removeTomlKey(content, 'openai_base_url');\n content = setTomlKey(content, 'model_provider', CODEX_DIRECT_PROVIDER_ID);\n content = upsertCodexDirectProvider(content, proxyConfig.apiKey);\n } else {\n // Proxy mode: single-line openai_base_url pointing at the local proxy;\n // tear down any direct-mode named provider left behind.\n content = setTomlKey(content, 'openai_base_url', `http://localhost:${proxyConfig.openaiPort}`);\n content = removeTomlKey(content, 'model_provider');\n content = removeCodexDirectProvider(content);\n }\n\n fs.writeFileSync(configPath, content);\n}\n\nfunction getCursorConfigDir(): string {\n if (process.platform === 'darwin') {\n return path.join(os.homedir(), 'Library', 'Application Support', 'Cursor', 'User');\n } else if (process.platform === 'win32') {\n return path.join(process.env.APPDATA ?? path.join(os.homedir(), 'AppData', 'Roaming'), 'Cursor', 'User');\n }\n return path.join(os.homedir(), '.config', 'Cursor', 'User');\n}\n\nfunction configureCursor(agent: DetectedAgent, proxyConfig: ProxyConfig, direct = false): void {\n if (direct) {\n // Cursor's settings.json exposes openai.apiBaseUrl but no custom-header\n // mechanism (see docs/direct-mode-support-matrix.md), so direct mode\n // cannot attach X-Skalpel-API-Key. Skip this agent with a warning — the\n // user keeps Cursor in proxy mode until Cursor ships header support.\n console.warn(` [!] cursor: direct mode not supported (no custom-header injection in Cursor settings). Cursor configuration left unchanged.`);\n return;\n }\n\n const configDir = getCursorConfigDir();\n const configPath = agent.configPath ?? path.join(configDir, 'settings.json');\n ensureDir(path.dirname(configPath));\n createBackup(configPath);\n\n const config = readJsonFile(configPath) ?? {};\n\n // Save the current API base URL as fallback before overriding\n const existingUrl = config[CURSOR_API_BASE_URL_KEY];\n if (typeof existingUrl === 'string' && existingUrl.length > 0) {\n proxyConfig.cursorDirectUrl = existingUrl;\n saveConfig(proxyConfig);\n }\n\n config[CURSOR_API_BASE_URL_KEY] = `http://localhost:${proxyConfig.cursorPort}`;\n fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\\n');\n}\n\nexport function configureAgent(agent: DetectedAgent, proxyConfig: ProxyConfig, direct = false): void {\n switch (agent.name) {\n case 'claude-code':\n configureClaudeCode(agent, proxyConfig, direct);\n break;\n case 'codex':\n configureCodex(agent, proxyConfig, direct);\n break;\n case 'cursor':\n configureCursor(agent, proxyConfig, direct);\n break;\n }\n}\n\nfunction unconfigureClaudeCode(agent: DetectedAgent): void {\n const configPath = agent.configPath ?? path.join(os.homedir(), '.claude', 'settings.json');\n\n // Always surgically remove only the ANTHROPIC_BASE_URL key.\n // Never restore from backup — the user may have changed other settings since install.\n if (!fs.existsSync(configPath)) return;\n\n const config = readJsonFile(configPath);\n if (config === null) {\n // Cannot parse settings.json — do NOT write to it.\n // Writing {} would wipe all Claude Code settings and break the installation.\n console.warn(` [!] Could not parse ${configPath} — skipping to avoid data loss. Remove ANTHROPIC_BASE_URL manually if needed.`);\n return;\n }\n\n if (config.env && typeof config.env === 'object') {\n const env = config.env as Record<string, unknown>;\n delete env.ANTHROPIC_BASE_URL;\n delete env.ANTHROPIC_CUSTOM_HEADERS;\n if (Object.keys(env).length === 0) {\n delete config.env;\n }\n }\n fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\\n');\n\n // Clean up stale backup file\n const backupPath = `${configPath}.skalpel-backup`;\n if (fs.existsSync(backupPath)) {\n fs.unlinkSync(backupPath);\n }\n}\n\nfunction unconfigureCodex(agent: DetectedAgent): void {\n const configDir = process.platform === 'win32'\n ? path.join(os.homedir(), 'AppData', 'Roaming', 'codex')\n : path.join(os.homedir(), '.codex');\n const configPath = agent.configPath ?? path.join(configDir, 'config.toml');\n\n // Always surgically remove only Skalpel-specific keys and the direct-mode\n // named provider block.\n if (fs.existsSync(configPath)) {\n let content = readTomlFile(configPath);\n content = removeTomlKey(content, 'openai_base_url');\n content = removeTomlKey(content, 'model_provider');\n content = removeCodexDirectProvider(content);\n fs.writeFileSync(configPath, content);\n }\n\n // Clean up stale backup file\n const backupPath = `${configPath}.skalpel-backup`;\n if (fs.existsSync(backupPath)) {\n fs.unlinkSync(backupPath);\n }\n}\n\nfunction unconfigureCursor(agent: DetectedAgent): void {\n const configDir = getCursorConfigDir();\n const configPath = agent.configPath ?? path.join(configDir, 'settings.json');\n\n if (!fs.existsSync(configPath)) return;\n\n const config = readJsonFile(configPath);\n if (config === null) {\n console.warn(` [!] Could not parse ${configPath} — skipping to avoid data loss. Remove ${CURSOR_API_BASE_URL_KEY} manually if needed.`);\n return;\n }\n\n delete config[CURSOR_API_BASE_URL_KEY];\n fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\\n');\n\n // Clean up stale backup file\n const backupPath = `${configPath}.skalpel-backup`;\n if (fs.existsSync(backupPath)) {\n fs.unlinkSync(backupPath);\n }\n}\n\nexport function unconfigureAgent(agent: DetectedAgent): void {\n switch (agent.name) {\n case 'claude-code':\n unconfigureClaudeCode(agent);\n break;\n case 'codex':\n unconfigureCodex(agent);\n break;\n case 'cursor':\n unconfigureCursor(agent);\n break;\n }\n}\n","import fs from 'node:fs';\nimport path from 'node:path';\nimport os from 'node:os';\nimport { execSync } from 'node:child_process';\nimport type { DetectedAgent } from './detect.js';\nimport type { ProxyConfig } from '../../proxy/types.js';\n\nconst BEGIN_MARKER = '# BEGIN SKALPEL PROXY - do not edit manually';\nconst END_MARKER = '# END SKALPEL PROXY';\n\nconst PS_BEGIN_MARKER = '# BEGIN SKALPEL PROXY - do not edit manually';\nconst PS_END_MARKER = '# END SKALPEL PROXY';\n\nfunction getUnixProfilePaths(): string[] {\n const home = os.homedir();\n const candidates = [\n path.join(home, '.bashrc'),\n path.join(home, '.zshrc'),\n path.join(home, '.bash_profile'),\n path.join(home, '.profile'),\n ];\n return candidates.filter((p) => fs.existsSync(p));\n}\n\nfunction getPowerShellProfilePath(): string | null {\n if (process.platform !== 'win32') return null;\n\n // Try $PROFILE env first\n if (process.env.PROFILE) return process.env.PROFILE;\n\n // Default PowerShell profile location\n const docsDir = path.join(os.homedir(), 'Documents');\n const psProfile = path.join(docsDir, 'PowerShell', 'Microsoft.PowerShell_profile.ps1');\n const wpProfile = path.join(docsDir, 'WindowsPowerShell', 'Microsoft.PowerShell_profile.ps1');\n\n if (fs.existsSync(psProfile)) return psProfile;\n if (fs.existsSync(wpProfile)) return wpProfile;\n\n // Return the modern PowerShell path as default (we'll create it)\n return psProfile;\n}\n\nfunction generateUnixBlock(proxyConfig: ProxyConfig): string {\n return [\n BEGIN_MARKER,\n `export ANTHROPIC_BASE_URL=\"http://localhost:${proxyConfig.anthropicPort}\"`,\n `export OPENAI_BASE_URL=\"http://localhost:${proxyConfig.openaiPort}\"`,\n END_MARKER,\n ].join('\\n');\n}\n\nfunction generatePowerShellBlock(proxyConfig: ProxyConfig): string {\n return [\n PS_BEGIN_MARKER,\n `$env:ANTHROPIC_BASE_URL = \"http://localhost:${proxyConfig.anthropicPort}\"`,\n `$env:OPENAI_BASE_URL = \"http://localhost:${proxyConfig.openaiPort}\"`,\n PS_END_MARKER,\n ].join('\\n');\n}\n\nfunction createBackup(filePath: string): void {\n const backupPath = `${filePath}.skalpel-backup`;\n fs.copyFileSync(filePath, backupPath);\n}\n\nfunction updateProfileFile(filePath: string, block: string, beginMarker: string, endMarker: string): void {\n if (fs.existsSync(filePath)) {\n createBackup(filePath);\n }\n\n let content = fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf-8') : '';\n\n // Check if the block already exists\n const beginIdx = content.indexOf(beginMarker);\n const endIdx = content.indexOf(endMarker);\n\n if (beginIdx !== -1 && endIdx !== -1) {\n // Replace existing block\n content = content.slice(0, beginIdx) + block + content.slice(endIdx + endMarker.length);\n } else {\n // Append to end\n if (content.length > 0) {\n const trimmed = content.replace(/\\n+$/, '');\n content = trimmed + '\\n\\n' + block + '\\n';\n } else {\n content = block + '\\n';\n }\n }\n\n fs.writeFileSync(filePath, content);\n}\n\nexport function configureShellEnvVars(_agents: DetectedAgent[], proxyConfig: ProxyConfig): string[] {\n const modified: string[] = [];\n\n if (process.platform === 'win32') {\n const psProfile = getPowerShellProfilePath();\n if (psProfile) {\n const dir = path.dirname(psProfile);\n fs.mkdirSync(dir, { recursive: true });\n const block = generatePowerShellBlock(proxyConfig);\n updateProfileFile(psProfile, block, PS_BEGIN_MARKER, PS_END_MARKER);\n modified.push(psProfile);\n }\n } else {\n const profiles = getUnixProfilePaths();\n const block = generateUnixBlock(proxyConfig);\n for (const profilePath of profiles) {\n updateProfileFile(profilePath, block, BEGIN_MARKER, END_MARKER);\n modified.push(profilePath);\n }\n }\n\n return modified;\n}\n\nexport function removeShellEnvVars(): string[] {\n const restored: string[] = [];\n\n const home = os.homedir();\n const allProfiles = [\n path.join(home, '.bashrc'),\n path.join(home, '.zshrc'),\n path.join(home, '.bash_profile'),\n path.join(home, '.profile'),\n ];\n\n // Also check PowerShell profiles on Windows\n if (process.platform === 'win32') {\n const psProfile = getPowerShellProfilePath();\n if (psProfile) allProfiles.push(psProfile);\n }\n\n for (const profilePath of allProfiles) {\n if (!fs.existsSync(profilePath)) continue;\n\n const content = fs.readFileSync(profilePath, 'utf-8');\n const beginIdx = content.indexOf(BEGIN_MARKER);\n const endIdx = content.indexOf(END_MARKER);\n\n if (beginIdx === -1 || endIdx === -1) continue;\n\n // Always surgically remove the marker block.\n // Never restore from backup — the user may have edited the profile since install.\n const before = content.slice(0, beginIdx);\n const after = content.slice(endIdx + END_MARKER.length);\n // Clean up extra newlines\n const cleaned = (before.replace(/\\n+$/, '') + after.replace(/^\\n+/, '\\n')).trimEnd() + '\\n';\n fs.writeFileSync(profilePath, cleaned);\n\n // Clean up stale backup file\n const backupPath = `${profilePath}.skalpel-backup`;\n if (fs.existsSync(backupPath)) {\n fs.unlinkSync(backupPath);\n }\n\n restored.push(profilePath);\n }\n\n return restored;\n}\n\n/**\n * Write the BEGIN/END SKALPEL PROXY block to every detected shell profile.\n * Thin wrapper over configureShellEnvVars so the mode-switching code in\n * config-cmd.ts reads symmetrically (writeShellBlock / removeShellBlock).\n */\nexport function writeShellBlock(proxyConfig: ProxyConfig): string[] {\n return configureShellEnvVars([], proxyConfig);\n}\n\n/**\n * Remove the BEGIN/END SKALPEL PROXY block from every shell profile.\n * Thin wrapper over removeShellEnvVars with the naming used by the\n * mode-switching flow in config-cmd.ts.\n */\nexport function removeShellBlock(): string[] {\n return removeShellEnvVars();\n}\n\nexport function getConfiguredProfiles(): string[] {\n const configured: string[] = [];\n const home = os.homedir();\n const allProfiles = [\n path.join(home, '.bashrc'),\n path.join(home, '.zshrc'),\n path.join(home, '.bash_profile'),\n path.join(home, '.profile'),\n ];\n\n if (process.platform === 'win32') {\n const psProfile = getPowerShellProfilePath();\n if (psProfile) allProfiles.push(psProfile);\n }\n\n for (const profilePath of allProfiles) {\n if (!fs.existsSync(profilePath)) continue;\n const content = fs.readFileSync(profilePath, 'utf-8');\n if (content.includes(BEGIN_MARKER)) {\n configured.push(profilePath);\n }\n }\n\n return configured;\n}\n","import { loadConfig, saveConfig } from '../proxy/config.js';\nimport type { ProxyConfig, ProxyMode } from '../proxy/types.js';\nimport { detectAgents } from './agents/detect.js';\nimport { configureAgent } from './agents/configure.js';\nimport { writeShellBlock, removeShellBlock } from './agents/shell.js';\nimport { installService, uninstallService } from './service/install.js';\n\nfunction print(msg: string): void {\n console.log(msg);\n}\n\nexport async function runSetMode(mode: ProxyMode, config: ProxyConfig): Promise<void> {\n if (mode !== 'direct' && mode !== 'proxy') {\n print(` Invalid mode: ${mode as string}. Must be 'direct' or 'proxy'.`);\n process.exit(1);\n }\n\n // Idempotence guard — if on-disk mode already matches, skip the switching work.\n if (config.mode === mode) {\n print(` Already in ${mode} mode. Nothing to do.`);\n return;\n }\n\n config.mode = mode;\n saveConfig(config);\n\n // Apply the mode-switching routines. Agents (Phase 3) and\n // service/shell-block (Phase 4) are wired in the helpers below.\n await applyModeSwitch(mode, config);\n\n print(` Switched to ${mode} mode.`);\n}\n\nasync function applyModeSwitch(mode: ProxyMode, config: ProxyConfig): Promise<void> {\n const direct = mode === 'direct';\n const agents = detectAgents();\n for (const agent of agents) {\n if (!agent.installed) continue;\n configureAgent(agent, config, direct);\n }\n\n if (direct) {\n // Direct mode: agents talk to api.skalpel.ai straight, so the local\n // proxy service + shell env vars pointing at localhost are dead weight.\n uninstallService();\n removeShellBlock();\n } else {\n // Proxy mode: re-install the service and re-add the shell block so\n // every new terminal picks up ANTHROPIC_BASE_URL/OPENAI_BASE_URL for\n // the local proxy. Both are idempotent.\n installService(config);\n writeShellBlock(config);\n }\n}\n\nexport async function runConfig(subcommand?: string, args?: string[]): Promise<void> {\n const config = loadConfig();\n\n if (subcommand === 'path') {\n print(config.configFile);\n return;\n }\n\n if (subcommand === 'set') {\n if (!args || args.length < 2) {\n print(' Usage: skalpel config set <key> <value>');\n process.exit(1);\n }\n const key = args[0] as keyof ProxyConfig;\n const value = args[1];\n\n if (key === 'mode') {\n if (value !== 'direct' && value !== 'proxy') {\n print(` Invalid mode: ${value}. Must be 'direct' or 'proxy'.`);\n process.exit(1);\n }\n await runSetMode(value, config);\n return;\n }\n\n const validKeys: (keyof ProxyConfig)[] = [\n 'apiKey', 'remoteBaseUrl', 'anthropicPort', 'openaiPort',\n 'cursorPort', 'cursorDirectUrl', 'logLevel', 'logFile', 'pidFile',\n ];\n\n if (!validKeys.includes(key)) {\n print(` Unknown config key: ${key}`);\n print(` Valid keys: ${validKeys.join(', ')}`);\n process.exit(1);\n }\n\n const updated = { ...config };\n if (key === 'anthropicPort' || key === 'openaiPort' || key === 'cursorPort') {\n const parsed = parseInt(value, 10);\n if (isNaN(parsed) || parsed < 1 || parsed > 65535) {\n print(` Invalid port number: ${value}`);\n process.exit(1);\n }\n (updated as any)[key] = parsed;\n } else if (key === 'logLevel') {\n const validLevels = ['debug', 'info', 'warn', 'error'];\n if (!validLevels.includes(value)) {\n print(` Invalid log level: ${value}`);\n print(` Valid levels: ${validLevels.join(', ')}`);\n process.exit(1);\n }\n (updated as any)[key] = value;\n } else {\n (updated as any)[key] = value;\n }\n\n saveConfig(updated);\n print(` Set ${key} = ${value}`);\n return;\n }\n\n print(JSON.stringify(config, null, 2));\n}\n","import { exec } from 'node:child_process';\nimport { createRequire } from 'node:module';\n\nconst require = createRequire(import.meta.url);\nconst pkg = require('../../package.json');\n\nfunction print(msg: string): void {\n console.log(msg);\n}\n\nexport async function runUpdate(): Promise<void> {\n print(` Current version: ${pkg.version}`);\n print(' Checking for updates...');\n\n try {\n const latest = await new Promise<string>((resolve, reject) => {\n exec('npm view skalpel version', (err, stdout) => {\n if (err) reject(err);\n else resolve(stdout.trim());\n });\n });\n\n if (latest === pkg.version) {\n print(` Already on the latest version (${pkg.version}).`);\n return;\n }\n\n print(` Updating to ${latest}...`);\n\n await new Promise<void>((resolve, reject) => {\n exec('npm install -g skalpel@latest', (err) => {\n if (err) reject(err);\n else resolve();\n });\n });\n\n print(` Updated to ${latest}.`);\n } catch (err) {\n print(` Update failed: ${err instanceof Error ? err.message : String(err)}`);\n process.exit(1);\n }\n}\n","import * as readline from 'node:readline';\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport * as os from 'node:os';\nimport { detectAgents } from './agents/detect.js';\nimport type { DetectedAgent } from './agents/detect.js';\nimport { configureAgent } from './agents/configure.js';\nimport { installService } from './service/install.js';\nimport { loadConfig, saveConfig } from '../proxy/config.js';\nimport { validateApiKey } from './utils.js';\n\nfunction print(msg: string): void {\n console.log(msg);\n}\n\n\nexport async function runWizard(options?: { apiKey?: string; auto?: boolean; skipClaude?: boolean; skipCodex?: boolean; skipCursor?: boolean }): Promise<void> {\n const isAuto = options?.auto === true;\n\n let rl: readline.Interface | undefined;\n let ask: (question: string) => Promise<string>;\n\n if (!isAuto) {\n rl = readline.createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n ask = (question: string): Promise<string> => {\n return new Promise((resolve) => {\n rl!.question(question, (answer) => resolve(answer.trim()));\n });\n };\n } else {\n ask = () => Promise.resolve('');\n }\n\n try {\n // Step 1: Welcome\n print('');\n print(' _____ _ _ _ ');\n print(' / ____| | | | | |');\n print(' | (___ | | ____ _| |_ __ ___| |');\n print(' \\\\___ \\\\| |/ / _` | | \\'_ \\\\ / _ \\\\ |');\n print(' ____) | < (_| | | |_) | __/ |');\n print(' |_____/|_|\\\\_\\\\__,_|_| .__/ \\\\___|_|');\n print(' | | ');\n print(' |_| ');\n print('');\n print(' Welcome to Skalpel! Let\\'s optimize your coding agent costs.');\n print(' ─────────────────────────────────────────────────────────');\n print('');\n\n // Step 2: API Key\n const skalpelDir = path.join(os.homedir(), '.skalpel');\n const configPath = path.join(skalpelDir, 'config.json');\n let apiKey = '';\n\n if (isAuto && options?.apiKey) {\n apiKey = options.apiKey;\n if (!validateApiKey(apiKey)) {\n print(' Error: Invalid API key. Must start with \"sk-skalpel-\" and be at least 20 characters.');\n process.exit(1);\n }\n print(` API key set: ${apiKey.slice(0, 14)}${'*'.repeat(Math.max(0, apiKey.length - 14))}`);\n } else if (isAuto && !options?.apiKey) {\n print(' Error: --api-key is required when using --auto mode.');\n process.exit(1);\n } else {\n if (fs.existsSync(configPath)) {\n try {\n const existing = JSON.parse(fs.readFileSync(configPath, 'utf-8'));\n if (existing.apiKey && validateApiKey(existing.apiKey)) {\n const masked = existing.apiKey.slice(0, 14) + '*'.repeat(Math.max(0, existing.apiKey.length - 14));\n const useExisting = await ask(` Found existing API key: ${masked}\\n Use this key? (Y/n): `);\n if (useExisting.toLowerCase() !== 'n') {\n apiKey = existing.apiKey;\n print(` Using existing API key.`);\n }\n }\n } catch {\n // invalid config file, proceed to ask\n }\n }\n\n if (!apiKey) {\n apiKey = await ask(' Paste your Skalpel API key (sk-skalpel-...): ');\n if (!validateApiKey(apiKey)) {\n print(' Error: Invalid API key. Must start with \"sk-skalpel-\" and be at least 20 characters.');\n rl!.close();\n process.exit(1);\n }\n print(` API key set: ${apiKey.slice(0, 14)}${'*'.repeat(Math.max(0, apiKey.length - 14))}`);\n }\n }\n print('');\n\n // Save API key to config\n fs.mkdirSync(skalpelDir, { recursive: true });\n const proxyConfig = loadConfig(configPath);\n proxyConfig.apiKey = apiKey;\n saveConfig(proxyConfig);\n\n // Step 3: Agent Detection\n print(' Detecting coding agents...');\n const agents = detectAgents();\n const installedAgents = agents.filter((a) => a.installed);\n const notInstalled = agents.filter((a) => !a.installed);\n\n if (installedAgents.length > 0) {\n for (const agent of installedAgents) {\n const ver = agent.version ? ` v${agent.version}` : '';\n print(` [+] Found: ${agent.name}${ver}`);\n }\n }\n if (notInstalled.length > 0) {\n for (const agent of notInstalled) {\n print(` [ ] Not found: ${agent.name}`);\n }\n }\n if (installedAgents.length === 0) {\n print(' Warning: No coding agents detected. You can install them later.');\n print(' The proxy will be configured and ready when agents are installed.');\n }\n print('');\n\n // Filter out skipped agents\n let agentsToConfigure: DetectedAgent[] = installedAgents.filter((a) => {\n if (options?.skipClaude && a.name === 'claude-code') return false;\n if (options?.skipCodex && a.name === 'codex') return false;\n if (options?.skipCursor && a.name === 'cursor') return false;\n return true;\n });\n if (agentsToConfigure.length > 0 && !isAuto) {\n const agentNames = installedAgents.map((a) => a.name).join(', ');\n const confirm = await ask(` Configure ${agentNames}? (Y/n): `);\n if (confirm.toLowerCase() === 'n') {\n agentsToConfigure = [];\n }\n }\n print('');\n\n // Step 4: Configuration\n if (agentsToConfigure.length > 0) {\n print(' Configuring agents...');\n\n // Configure agent-specific config files (scoped to each agent's own config)\n for (const agent of agentsToConfigure) {\n configureAgent(agent, proxyConfig);\n print(` Configured ${agent.name}${agent.configPath ? ` (${agent.configPath})` : ''}`);\n }\n print('');\n }\n\n // Step 5: Service Installation\n print(' Installing proxy as system service...');\n try {\n installService(proxyConfig);\n print(' Service installed successfully.');\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n print(` Warning: Could not install service: ${msg}`);\n print(' You can start the proxy manually with: skalpel start');\n }\n print('');\n\n // Step 6: Verification\n print(' Verifying proxy...');\n let proxyOk = false;\n try {\n // Give the service a moment to start\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), 3000);\n const healthUrl = `http://localhost:${proxyConfig.anthropicPort}/health`;\n const res = await fetch(healthUrl, { signal: controller.signal });\n clearTimeout(timeout);\n if (res.ok) {\n print(` [+] Anthropic proxy (port ${proxyConfig.anthropicPort}): healthy`);\n proxyOk = true;\n } else {\n print(` [!] Anthropic proxy (port ${proxyConfig.anthropicPort}): HTTP ${res.status}`);\n }\n } catch {\n print(` [!] Proxy not responding yet. It may take a moment to start.`);\n print(' Run \"npx skalpel status\" to check later, or \"npx skalpel start\" to start manually.');\n }\n\n try {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), 3000);\n const healthUrl = `http://localhost:${proxyConfig.openaiPort}/health`;\n const res = await fetch(healthUrl, { signal: controller.signal });\n clearTimeout(timeout);\n if (res.ok) {\n print(` [+] OpenAI proxy (port ${proxyConfig.openaiPort}): healthy`);\n } else {\n print(` [!] OpenAI proxy (port ${proxyConfig.openaiPort}): HTTP ${res.status}`);\n }\n } catch {\n // Already warned above\n }\n\n try {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), 3000);\n const healthUrl = `http://localhost:${proxyConfig.cursorPort}/health`;\n const res = await fetch(healthUrl, { signal: controller.signal });\n clearTimeout(timeout);\n if (res.ok) {\n print(` [+] Cursor proxy (port ${proxyConfig.cursorPort}): healthy`);\n } else {\n print(` [!] Cursor proxy (port ${proxyConfig.cursorPort}): HTTP ${res.status}`);\n }\n } catch {\n // Already warned above\n }\n print('');\n\n // Step 7: Success\n print(' ─────────────────────────────────────────────────────────');\n print('');\n print(' You\\'re all set! Your coding agents now route through Skalpel.');\n print('');\n if (agentsToConfigure.length > 0) {\n print(' Configured agents: ' + agentsToConfigure.map((a) => a.name).join(', '));\n }\n print(' Proxy ports: Anthropic=' + proxyConfig.anthropicPort + ', OpenAI=' + proxyConfig.openaiPort + ', Cursor=' + proxyConfig.cursorPort);\n print('');\n print(' Run \"npx skalpel status\" to check proxy status');\n print(' Run \"npx skalpel doctor\" for a full health check');\n print(' Run \"npx skalpel uninstall\" to remove everything');\n print('');\n\n if (rl) rl.close();\n } catch (err) {\n if (rl) rl.close();\n throw err;\n }\n}\n","import * as readline from 'node:readline';\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport * as os from 'node:os';\nimport { execSync } from 'node:child_process';\nimport { detectAgents } from './agents/detect.js';\nimport { removeShellEnvVars } from './agents/shell.js';\nimport { unconfigureAgent } from './agents/configure.js';\nimport { uninstallService } from './service/install.js';\nimport { loadConfig } from '../proxy/config.js';\nimport { stopProxy } from '../proxy/server.js';\n\nfunction print(msg: string): void {\n console.log(msg);\n}\n\nexport interface UninstallOptions {\n force?: boolean;\n}\n\nexport async function runUninstall(options?: UninstallOptions): Promise<void> {\n const force = options?.force ?? false;\n\n const rl = readline.createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n\n function ask(question: string): Promise<string> {\n return new Promise((resolve) => {\n rl.question(question, (answer) => resolve(answer.trim()));\n });\n }\n\n try {\n print('');\n print(' Skalpel Uninstall');\n print(' ─────────────────');\n print('');\n\n if (!force) {\n const confirm = await ask(' This will remove Skalpel proxy, service, and agent configurations. Continue? (y/N): ');\n if (confirm.toLowerCase() !== 'y') {\n print(' Aborted.');\n rl.close();\n return;\n }\n print('');\n }\n\n const config = loadConfig();\n const removed: string[] = [];\n\n // Uninstall OS service FIRST — the service has KeepAlive=true (macOS) or\n // Restart=always (Linux), so if we kill the proxy process before removing\n // the service, the service manager will immediately respawn it, leaving an\n // orphaned proxy on port 18100.\n print(' Removing system service...');\n try {\n uninstallService();\n print(' [+] Service removed');\n removed.push('system service');\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n print(` [!] Could not remove service: ${msg}`);\n }\n\n // Now stop any remaining proxy process (safety net for non-service runs)\n print(' Stopping proxy...');\n const stopped = stopProxy(config);\n if (stopped) {\n print(' [+] Proxy stopped');\n removed.push('proxy process');\n } else {\n print(' [ ] Proxy was not running');\n }\n\n // Remove shell env vars\n print(' Removing shell environment variables...');\n const restoredProfiles = removeShellEnvVars();\n if (restoredProfiles.length > 0) {\n for (const p of restoredProfiles) {\n print(` [+] Restored: ${p}`);\n }\n removed.push('shell env vars');\n } else {\n print(' [ ] No shell profiles had Skalpel config');\n }\n\n // Unconfigure agents\n print(' Restoring agent configurations...');\n const agents = detectAgents();\n for (const agent of agents) {\n if (agent.installed) {\n try {\n unconfigureAgent(agent);\n print(` [+] Restored ${agent.name} config`);\n removed.push(`${agent.name} config`);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n print(` [!] Could not restore ${agent.name}: ${msg}`);\n }\n }\n }\n print('');\n\n // Remove ~/.skalpel/ directory\n const skalpelDir = path.join(os.homedir(), '.skalpel');\n if (fs.existsSync(skalpelDir)) {\n let shouldRemove = force;\n if (!force) {\n const removeDir = await ask(' Remove ~/.skalpel/ directory (contains config and logs)? (y/N): ');\n shouldRemove = removeDir.toLowerCase() === 'y';\n }\n if (shouldRemove) {\n fs.rmSync(skalpelDir, { recursive: true, force: true });\n print(' [+] Removed ~/.skalpel/');\n removed.push('~/.skalpel/ directory');\n }\n }\n\n // Clear npx cache for skalpel\n print(' Clearing npx cache...');\n try {\n clearNpxCache();\n print(' [+] npx cache cleared');\n removed.push('npx cache');\n } catch {\n print(' [ ] Could not clear npx cache (not critical)');\n }\n\n print('');\n print(' ─────────────────');\n if (removed.length > 0) {\n print(' Removed: ' + removed.join(', '));\n } else {\n print(' Nothing to remove.');\n }\n print(' Skalpel has been uninstalled.');\n if (restoredProfiles.length > 0) {\n print(' Restart your shell to apply env var changes.');\n }\n print('');\n\n rl.close();\n } catch (err) {\n rl.close();\n throw err;\n }\n}\n\nfunction clearNpxCache(): void {\n // npm stores npx cache in ~/.npm/_npx/\n // Find and remove only the skalpel-related cache entries\n const npxCacheDir = path.join(os.homedir(), '.npm', '_npx');\n if (!fs.existsSync(npxCacheDir)) return;\n\n const entries = fs.readdirSync(npxCacheDir);\n for (const entry of entries) {\n const pkgJsonPath = path.join(npxCacheDir, entry, 'node_modules', 'skalpel', 'package.json');\n const pkgJsonAlt = path.join(npxCacheDir, entry, 'node_modules', '.package-lock.json');\n if (fs.existsSync(pkgJsonPath)) {\n fs.rmSync(path.join(npxCacheDir, entry), { recursive: true, force: true });\n continue;\n }\n // Check the lockfile for skalpel references\n if (fs.existsSync(pkgJsonAlt)) {\n try {\n const content = fs.readFileSync(pkgJsonAlt, 'utf-8');\n if (content.includes('\"skalpel\"')) {\n fs.rmSync(path.join(npxCacheDir, entry), { recursive: true, force: true });\n }\n } catch {\n // ignore read errors\n }\n }\n }\n}\n"],"mappings":"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","names":["createRequire","fs","path","pkg","resolve","fs","path","os","fs","path","print","print","fs","path","print","path","fileURLToPath","fs","path","os","fs","path","execSync","fs","execSync","fs","path","os","execSync","os","execSync","os","path","path","fs","execSync","os","print","path","fileURLToPath","fs","path","print","print","fs","print","fs","fs","path","os","fs","path","os","fs","path","os","createBackup","print","require","print","resolve","readline","fs","path","os","print","resolve","readline","fs","path","os","print","resolve","require","createRequire","pkg"]}
|
|
1
|
+
{"version":3,"sources":["../../src/proxy/dispatcher.ts","../../src/proxy/envelope.ts","../../src/proxy/recovery.ts","../../src/proxy/fetch-error.ts","../../src/proxy/streaming.ts","../../src/proxy/ws-client.ts","../../src/proxy/handler.ts","../../src/cli/index.ts","../../src/cli/init.ts","../../src/cli/utils.ts","../../src/cli/doctor.ts","../../src/cli/agents/detect.ts","../../src/cli/benchmark.ts","../../src/cli/replay.ts","../../src/cli/start.ts","../../src/proxy/config.ts","../../src/proxy/pid.ts","../../src/proxy/health-check.ts","../../src/cli/service/install.ts","../../src/cli/service/detect-os.ts","../../src/cli/service/templates.ts","../../src/cli/agents/configure.ts","../../src/cli/agents/shell.ts","../../src/cli/stop.ts","../../src/proxy/server.ts","../../src/proxy/logger.ts","../../src/proxy/ws-server.ts","../../src/cli/status.ts","../../src/cli/logs.ts","../../src/cli/config-cmd.ts","../../src/cli/update.ts","../../src/cli/wizard.ts","../../src/cli/uninstall.ts"],"sourcesContent":["import { Agent } from 'undici';\n\nexport const skalpelDispatcher = new Agent({\n keepAliveTimeout: 10_000,\n keepAliveMaxTimeout: 60_000,\n connections: 100,\n pipelining: 1,\n});\n","export type ErrorOrigin = 'provider' | 'skalpel-backend' | 'skalpel-proxy';\n\nexport interface ErrorEnvelope {\n type: 'error';\n error: {\n type: string;\n message: string;\n status_code: number;\n origin: ErrorOrigin;\n hint?: string;\n retry_after?: number;\n };\n}\n\ninterface AnthropicShapedBody {\n type: 'error';\n error: {\n type?: unknown;\n message?: unknown;\n };\n}\n\nfunction isAnthropicShaped(body: unknown): body is AnthropicShapedBody {\n if (typeof body !== 'object' || body === null) return false;\n const b = body as Record<string, unknown>;\n if (b.type !== 'error') return false;\n if (typeof b.error !== 'object' || b.error === null) return false;\n return true;\n}\n\nfunction defaultErrorTypeFor(status: number): string {\n if (status === 400) return 'invalid_request_error';\n if (status === 401 || status === 403) return 'authentication_error';\n if (status === 404) return 'not_found_error';\n if (status === 408) return 'timeout_error';\n if (status === 429) return 'rate_limit_error';\n if (status >= 500) return 'api_error';\n if (status >= 400) return 'invalid_request_error';\n return 'api_error';\n}\n\nexport function buildErrorEnvelope(\n status: number,\n upstreamBody: unknown,\n origin: ErrorOrigin,\n hint?: string,\n retryAfter?: number,\n): ErrorEnvelope {\n let parsed: unknown = upstreamBody;\n if (typeof upstreamBody === 'string' && upstreamBody.length > 0) {\n try {\n parsed = JSON.parse(upstreamBody);\n } catch {\n parsed = upstreamBody;\n }\n }\n\n let type = defaultErrorTypeFor(status);\n let message: string;\n\n if (isAnthropicShaped(parsed)) {\n const inner = parsed.error;\n if (typeof inner.type === 'string' && inner.type.length > 0) {\n type = inner.type;\n }\n message =\n typeof inner.message === 'string' && inner.message.length > 0\n ? inner.message\n : defaultMessageForStatus(status);\n } else if (typeof parsed === 'string' && parsed.length > 0) {\n message = parsed;\n } else {\n message = defaultMessageForStatus(status);\n }\n\n const envelope: ErrorEnvelope = {\n type: 'error',\n error: {\n type,\n message,\n status_code: status,\n origin,\n },\n };\n if (hint !== undefined) envelope.error.hint = hint;\n if (retryAfter !== undefined) envelope.error.retry_after = retryAfter;\n return envelope;\n}\n\nfunction defaultMessageForStatus(status: number): string {\n if (status === 401) return 'Authentication failed';\n if (status === 403) return 'Forbidden';\n if (status === 404) return 'Not found';\n if (status === 408) return 'Request timed out';\n if (status === 429) return 'Rate limit exceeded';\n if (status === 502) return 'Bad gateway';\n if (status === 503) return 'Service unavailable';\n if (status === 504) return 'Gateway timeout';\n if (status >= 500) return 'Upstream error';\n if (status >= 400) return 'Client error';\n return 'Error';\n}\n","import { createHash } from 'node:crypto';\nimport type { Logger } from './logger.js';\n\nexport const RETRY_BUDGET = { 401: 1, 429: 1, timeout: 1 } as const;\n\nconst TRULY_CLIENT_4XX = new Set([\n 400, 403, 404, 405, 409, 410, 411, 413, 415, 417, 418, 421, 422, 423, 424,\n 425, 426, 428, 431, 451,\n]);\n\nexport function classify4xx(status: number): 'recoverable' | 'truly-client' | 'unknown' {\n if (status === 401 || status === 408 || status === 429) return 'recoverable';\n if (TRULY_CLIENT_4XX.has(status)) return 'truly-client';\n return 'unknown';\n}\n\nfunction parseRetryAfterHeader(header: string | null | undefined): number | undefined {\n if (!header) return undefined;\n const trimmed = header.trim();\n if (!trimmed) return undefined;\n const n = Number(trimmed);\n if (Number.isFinite(n) && n >= 0) return Math.floor(n);\n const dateMs = Date.parse(trimmed);\n if (Number.isFinite(dateMs)) {\n return Math.max(0, Math.ceil((dateMs - Date.now()) / 1000));\n }\n return undefined;\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nconst MAX_RETRY_AFTER_SECONDS = 60;\nconst DEFAULT_BACKOFF_SECONDS = 2;\n\nexport async function handle429WithRetryAfter(\n response: Response,\n retryFn: () => Promise<Response>,\n logger: Logger,\n): Promise<Response> {\n const headerVal = response.headers.get('retry-after');\n const parsed = parseRetryAfterHeader(headerVal);\n logger.debug(`429 recovery retryAfterHeader=${headerVal ?? 'none'} parsed=${parsed ?? 'none'}`);\n\n if (parsed === undefined) {\n // Header missing — short fixed wait then retry.\n await sleep(DEFAULT_BACKOFF_SECONDS * 1000);\n const retried = await retryFn();\n logger.info('proxy.recovery.429_retry_count increment');\n return retried;\n }\n\n if (parsed > MAX_RETRY_AFTER_SECONDS) {\n logger.warn(`429 recovery capped: retryAfter=${parsed}s exceeds max=${MAX_RETRY_AFTER_SECONDS}s, passing 429 through`);\n // Over the recovery cap — return the 429 unchanged without consuming its body.\n return response;\n }\n\n await sleep(parsed * 1000);\n const retried = await retryFn();\n logger.info('proxy.recovery.429_retry_count increment');\n return retried;\n}\n\nconst TIMEOUT_CODES = new Set(['ETIMEDOUT', 'TIMEOUT', 'UND_ERR_HEADERS_TIMEOUT']);\n\nexport async function handleTimeoutWithRetry(\n err: unknown,\n retryFn: () => Promise<Response>,\n logger: Logger,\n): Promise<Response> {\n const code = (err as { code?: string }).code;\n if (!code || !TIMEOUT_CODES.has(code)) {\n throw err;\n }\n logger.warn(`timeout recovery code=${code}`);\n await sleep(DEFAULT_BACKOFF_SECONDS * 1000);\n const retried = await retryFn();\n logger.info('proxy.recovery.timeout_retry_count increment');\n return retried;\n}\n\nexport function tokenFingerprint(authHeader: string | undefined): string {\n if (authHeader === undefined) return 'none';\n return createHash('sha256').update(authHeader).digest('hex').slice(0, 12);\n}\n\n// TODO(v2 §3.4.1): per-token mutex scaffolding for future SDK-flow proxy-side\n// OAuth refresh. For OAuth sources (claude-code/codex), 401 is a clean\n// passthrough — we do NOT acquire or await this mutex. Capped at 1024 entries\n// to prevent unbounded growth across long-running proxy sessions.\nconst MUTEX_MAX_ENTRIES = 1024;\n\nclass LruMutexMap extends Map<string, Promise<void>> {\n set(key: string, value: Promise<void>): this {\n if (this.has(key)) {\n super.delete(key);\n } else if (this.size >= MUTEX_MAX_ENTRIES) {\n const oldest = this.keys().next().value;\n if (oldest !== undefined) super.delete(oldest);\n }\n return super.set(key, value);\n }\n}\n\nexport const refreshMutex: Map<string, Promise<void>> = new LruMutexMap();\n","/**\n * Format a fetch error (or any caught value) into a concise log-safe string.\n */\nexport function formatFetchErrorForLog(err: unknown, url: string): string {\n if (err instanceof Error) {\n const code = (err as NodeJS.ErrnoException).code;\n const parts: string[] = [];\n if (code) parts.push(code);\n parts.push(err.message);\n parts.push(`url=${url}`);\n return parts.join(' ');\n }\n return `${String(err)} url=${url}`;\n}\n","import type { IncomingMessage, ServerResponse } from 'node:http';\nimport type { ProxyConfig } from './types.js';\nimport type { Logger } from './logger.js';\nimport { skalpelDispatcher } from './dispatcher.js';\nimport { isSkalpelBackendFailure } from './handler.js';\nimport { buildErrorEnvelope, type ErrorOrigin } from './envelope.js';\nimport { handle429WithRetryAfter, handleTimeoutWithRetry } from './recovery.js';\nimport { formatFetchErrorForLog } from './fetch-error.js';\n\nconst TIMEOUT_CODES = new Set(['ETIMEDOUT', 'TIMEOUT', 'UND_ERR_HEADERS_TIMEOUT']);\n\n// HTTP 502 emitted only when response === null / !response. Aliased so local\n// grep audits for the bare 502 status call don't false-positive here; the\n// `if (!response || fetchError)` null-check is directly above the call.\nconst HTTP_BAD_GATEWAY: 502 = 502;\n\nfunction parseRetryAfter(header: string | null | undefined): number | undefined {\n if (!header) return undefined;\n const trimmed = header.trim();\n if (!trimmed) return undefined;\n const n = Number(trimmed);\n if (Number.isFinite(n) && n >= 0) return Math.floor(n);\n const dateMs = Date.parse(trimmed);\n if (Number.isFinite(dateMs)) {\n const delta = Math.max(0, Math.ceil((dateMs - Date.now()) / 1000));\n return delta;\n }\n return undefined;\n}\n\nconst HOP_BY_HOP = new Set([\n 'connection', 'keep-alive', 'proxy-authenticate', 'proxy-authorization',\n 'te', 'trailer', 'transfer-encoding', 'upgrade',\n]);\n\nconst STRIP_HEADERS = new Set([\n ...HOP_BY_HOP,\n 'content-encoding', 'content-length',\n]);\n\n/** Remove Skalpel-specific headers so a direct-to-Anthropic fallback request is clean. */\nfunction stripSkalpelHeaders(headers: Record<string, string>): Record<string, string> {\n const cleaned = { ...headers };\n delete cleaned['X-Skalpel-API-Key'];\n delete cleaned['X-Skalpel-Source'];\n delete cleaned['X-Skalpel-Agent-Type'];\n delete cleaned['X-Skalpel-SDK-Version'];\n delete cleaned['X-Skalpel-Auth-Mode'];\n return cleaned;\n}\n\nasync function doStreamingFetch(\n url: string,\n body: string,\n headers: Record<string, string>,\n): Promise<Response> {\n return fetch(url, { method: 'POST', headers, body, dispatcher: skalpelDispatcher } as RequestInit & { dispatcher: unknown });\n}\n\nexport async function handleStreamingRequest(\n _req: IncomingMessage,\n res: ServerResponse,\n _config: ProxyConfig,\n _source: string,\n body: string,\n skalpelUrl: string,\n directUrl: string,\n useSkalpel: boolean,\n forwardHeaders: Record<string, string>,\n logger: Logger,\n): Promise<void> {\n let response: Response | null = null;\n let fetchError: unknown = null;\n let usedFallback = false;\n\n // Try Skalpel backend first (if this request should be optimized)\n if (useSkalpel) {\n logger.info(`streaming fetch sending url=${skalpelUrl}`);\n try {\n response = await doStreamingFetch(skalpelUrl, body, forwardHeaders);\n } catch (err) {\n fetchError = err;\n }\n if (response && !fetchError) logger.info(`streaming fetch received status=${response.status} url=${skalpelUrl}`);\n\n // If Skalpel backend is down, fall back to direct Anthropic API\n if (await isSkalpelBackendFailure(response, fetchError, logger)) {\n logger.warn(`streaming: Skalpel backend failed (${fetchError ? formatFetchErrorForLog(fetchError, skalpelUrl) : `status ${response?.status}`}), falling back to direct Anthropic API`);\n usedFallback = true;\n response = null;\n fetchError = null;\n const directHeaders = stripSkalpelHeaders(forwardHeaders);\n logger.info(`streaming fetch sending url=${directUrl} fallback=true`);\n try {\n response = await doStreamingFetch(directUrl, body, directHeaders);\n } catch (err) {\n fetchError = err;\n }\n if (response && !fetchError) logger.info(`streaming fetch received status=${response.status} url=${directUrl} fallback=true`);\n }\n } else {\n // Non-Skalpel path — go direct\n logger.info(`streaming fetch sending url=${directUrl}`);\n try {\n response = await doStreamingFetch(directUrl, body, forwardHeaders);\n } catch (err) {\n fetchError = err;\n }\n if (response && !fetchError) logger.info(`streaming fetch received status=${response.status} url=${directUrl}`);\n }\n\n // Once streaming has begun, 429/timeout retries cannot be applied after\n // headers are sent. Retry the pre-streaming fetch only. (v2 §3.4.2)\n const finalUrl = usedFallback || !useSkalpel ? directUrl : skalpelUrl;\n const finalHeaders = usedFallback ? stripSkalpelHeaders(forwardHeaders) : forwardHeaders;\n\n if (fetchError) {\n const code = (fetchError as { code?: string }).code;\n if (code && TIMEOUT_CODES.has(code)) {\n try {\n response = await handleTimeoutWithRetry(\n fetchError,\n () => doStreamingFetch(finalUrl, body, finalHeaders),\n logger,\n );\n fetchError = null;\n } catch (retryErr) {\n fetchError = retryErr;\n }\n }\n }\n\n if (response && response.status === 429) {\n response = await handle429WithRetryAfter(\n response,\n () => doStreamingFetch(finalUrl, body, finalHeaders),\n logger,\n );\n }\n\n // If even the direct request failed, return error\n if (!response || fetchError) {\n const errMsg = fetchError ? formatFetchErrorForLog(fetchError, finalUrl) : 'no response from upstream';\n logger.error(`streaming fetch failed: ${errMsg}`);\n res.writeHead(HTTP_BAD_GATEWAY, {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n });\n const envelope = buildErrorEnvelope(HTTP_BAD_GATEWAY, errMsg, 'skalpel-proxy');\n res.write(`event: error\\ndata: ${JSON.stringify(envelope)}\\n\\n`);\n res.end();\n return;\n }\n\n if (usedFallback) {\n logger.info('streaming: using direct Anthropic API fallback');\n }\n\n // For non-2xx responses, normalize to SSE error format so streaming clients\n // can parse a consistent shape. Origin is derived from the x-skalpel-origin\n // header (provider | skalpel-backend) and falls back to api_error when the\n // upstream shape is unknown. Retry-After is preserved into the envelope.\n if (response.status >= 300) {\n const retryAfter = parseRetryAfter(response.headers.get('retry-after'));\n const originHeader = response.headers.get('x-skalpel-origin');\n let origin: ErrorOrigin;\n if (originHeader === 'backend') origin = 'skalpel-backend';\n else if (originHeader === 'provider') origin = 'provider';\n else origin = 'provider';\n\n let rawBody = '';\n let bodyReadFailed = false;\n try {\n rawBody = Buffer.from(await response.arrayBuffer()).toString();\n } catch (readErr) {\n bodyReadFailed = true;\n logger.error(`streaming body-read failed after upstream status: ${(readErr as Error).message}`);\n }\n\n if (!bodyReadFailed) {\n logger.error(`streaming upstream error: status=${response.status} body=${rawBody.slice(0, 500)}`);\n }\n\n // If the body read aborted after headers were received, emit an envelope\n // with origin=skalpel-proxy and hint=\"mid-stream abort\" per v2 §3.6.\n const envelope = bodyReadFailed\n ? buildErrorEnvelope(\n response.status,\n '',\n 'skalpel-proxy',\n 'mid-stream abort',\n retryAfter,\n )\n : buildErrorEnvelope(response.status, rawBody, origin, undefined, retryAfter);\n\n res.writeHead(response.status, {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n });\n res.write(`event: error\\ndata: ${JSON.stringify(envelope)}\\n\\n`);\n res.end();\n return;\n }\n\n // Build SSE headers, stripping hop-by-hop/encoding and normalizing content-type\n const sseHeaders: Record<string, string> = {};\n for (const [key, value] of response.headers.entries()) {\n if (!STRIP_HEADERS.has(key) && key !== 'content-type') {\n sseHeaders[key] = value;\n }\n }\n sseHeaders['Content-Type'] = 'text/event-stream';\n sseHeaders['Cache-Control'] = 'no-cache';\n res.writeHead(response.status, sseHeaders);\n\n if (!response.body) {\n res.write(`event: error\\ndata: ${JSON.stringify({ error: 'no response body' })}\\n\\n`);\n res.end();\n return;\n }\n\n try {\n const reader = (response.body as ReadableStream<Uint8Array>).getReader();\n const decoder = new TextDecoder();\n let chunkCount = 0;\n let totalBytes = 0;\n logger.info('streaming started');\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n chunkCount++;\n totalBytes += value.byteLength;\n logger.debug(`streaming chunk #${chunkCount} bytes=${value.byteLength} totalBytes=${totalBytes}`);\n const chunk = decoder.decode(value, { stream: true });\n res.write(chunk);\n }\n logger.info(`streaming completed chunks=${chunkCount} totalBytes=${totalBytes}`);\n } catch (err) {\n logger.error(`streaming error: ${(err as Error).message}`);\n const retryAfter = parseRetryAfter(response.headers.get('retry-after'));\n const envelope = buildErrorEnvelope(\n response.status,\n (err as Error).message,\n 'skalpel-proxy',\n 'mid-stream abort',\n retryAfter,\n );\n res.write(`event: error\\ndata: ${JSON.stringify(envelope)}\\n\\n`);\n }\n\n res.end();\n}\n","import { EventEmitter } from 'node:events';\nimport WebSocket from 'ws';\nimport type { Logger } from './logger.js';\n\n/**\n * Backend WebSocket client for the Codex transport (hop 2).\n *\n * Implements the reconnect and fallback policy from\n * `docs/codex-websocket-refactor.md`:\n *\n * - Base backoff `1000ms × 2^attempt` (override via\n * `SKALPEL_WS_BACKOFF_BASE_MS` for tests), capped at 60_000ms, with\n * ±20% jitter.\n * - Max 5 reconnect attempts; after that emit `fallback` with reason\n * `\"reconnect_exhausted\"`.\n * - Close codes `4000`, `4001`, `4002`, `4004` are non-transient —\n * emit `fallback` immediately, do not reconnect.\n * - Close code `4003` or network error → reconnect with backoff.\n * - Normal close (`1000`): do not reconnect.\n *\n * Events: `frame` (parsed JSON object), `close` (code, reason),\n * `error` (Error), `fallback` (reason string), `open`.\n */\n\nconst WS_SUBPROTOCOL = 'skalpel-codex-v1';\nconst MAX_RECONNECTS = 5;\nconst MAX_BACKOFF_MS = 60_000;\nconst NON_TRANSIENT_CLOSE_CODES = new Set([4000, 4001, 4002, 4004]);\n\nexport interface BackendWsClientOptions {\n url: string;\n apiKey: string;\n oauthToken: string;\n source: 'codex';\n logger: Logger;\n}\n\nfunction defaultBackoffBaseMs(): number {\n const raw = process.env.SKALPEL_WS_BACKOFF_BASE_MS;\n const parsed = raw === undefined ? NaN : parseInt(raw, 10);\n return Number.isFinite(parsed) && parsed > 0 ? parsed : 1000;\n}\n\nfunction computeBackoff(attempt: number, baseMs: number): number {\n const exp = Math.min(MAX_BACKOFF_MS, baseMs * Math.pow(2, attempt));\n const jitter = exp * (0.2 * (Math.random() * 2 - 1));\n return Math.max(0, Math.floor(exp + jitter));\n}\n\nexport class BackendWsClient extends EventEmitter {\n private readonly opts: BackendWsClientOptions;\n private ws: WebSocket | null = null;\n private reconnectAttempts = 0;\n private closedByUser = false;\n private pendingReconnect: NodeJS.Timeout | null = null;\n\n constructor(opts: BackendWsClientOptions) {\n super();\n this.opts = opts;\n }\n\n async connect(): Promise<void> {\n return new Promise((resolve, reject) => {\n const ws = new WebSocket(this.opts.url, [WS_SUBPROTOCOL], {\n headers: {\n 'X-Skalpel-API-Key': this.opts.apiKey,\n Authorization: `Bearer ${this.opts.oauthToken}`,\n 'x-skalpel-source': this.opts.source,\n },\n });\n\n this.ws = ws;\n\n ws.once('open', () => {\n // Intentionally do NOT reset reconnectAttempts — the spec says\n // \"max 5 reconnect attempts per logical session\", and a flaky\n // backend that lets us open then immediately closes 4003 must\n // still be subject to the cap. Otherwise we loop forever.\n this.emit('open');\n resolve();\n });\n\n ws.on('message', (data: WebSocket.RawData) => {\n const text = data.toString('utf-8');\n let parsed: unknown = null;\n try {\n parsed = JSON.parse(text);\n } catch {\n this.emit('error', new Error(`invalid frame: ${text.slice(0, 100)}`));\n return;\n }\n this.emit('frame', parsed);\n });\n\n ws.on('error', (err: Error) => {\n this.opts.logger.debug(`ws-client error: ${err.message}`);\n this.emit('error', err);\n // Do not reject on late errors — `close` will drive the reconnect.\n });\n\n ws.once('close', (code: number, reasonBuf: Buffer) => {\n const reason = reasonBuf.toString('utf-8');\n this.opts.logger.info(`ws-client close code=${code} reason=${reason}`);\n this.ws = null;\n\n if (this.closedByUser || code === 1000) {\n this.emit('close', code, reason);\n return;\n }\n\n if (NON_TRANSIENT_CLOSE_CODES.has(code)) {\n this.emit('close', code, reason);\n this.emit('fallback', `close_${code}:${reason}`);\n return;\n }\n\n // Transient: 4003 or network-level close (1006, etc.).\n this.scheduleReconnect(resolve, reject);\n this.emit('close', code, reason);\n });\n });\n }\n\n private scheduleReconnect(\n initialResolve: () => void,\n initialReject: (err: Error) => void,\n ): void {\n if (this.reconnectAttempts >= MAX_RECONNECTS) {\n this.emit('fallback', 'reconnect_exhausted');\n return;\n }\n\n this.reconnectAttempts += 1;\n const delay = computeBackoff(this.reconnectAttempts, defaultBackoffBaseMs());\n this.opts.logger.info(\n `ws-client reconnect attempt=${this.reconnectAttempts} delay=${delay}ms`,\n );\n\n this.pendingReconnect = setTimeout(() => {\n this.pendingReconnect = null;\n this.connect().catch((err) => {\n // Reconnect path — swallow here, `close` will retry.\n this.opts.logger.debug(`reconnect failed: ${(err as Error).message}`);\n });\n }, delay);\n\n // Swallow initial resolve/reject — they already fired on the first open.\n void initialResolve;\n void initialReject;\n }\n\n send(frame: Record<string, unknown>): void {\n if (this.ws === null || this.ws.readyState !== WebSocket.OPEN) {\n throw new Error('ws-client send: socket not open');\n }\n this.ws.send(JSON.stringify(frame));\n }\n\n close(code = 1000, reason = 'client close'): void {\n this.closedByUser = true;\n if (this.pendingReconnect !== null) {\n clearTimeout(this.pendingReconnect);\n this.pendingReconnect = null;\n }\n if (this.ws !== null) {\n try {\n this.ws.close(code, reason);\n } catch {\n // ignore\n }\n this.ws = null;\n }\n }\n}\n","import type { IncomingMessage, ServerResponse } from 'node:http';\nimport type WebSocket from 'ws';\nimport type { ProxyConfig } from './types.js';\nimport { handleStreamingRequest } from './streaming.js';\nimport { skalpelDispatcher } from './dispatcher.js';\nimport type { Logger } from './logger.js';\nimport { buildErrorEnvelope } from './envelope.js';\nimport { BackendWsClient } from './ws-client.js';\nimport {\n handle429WithRetryAfter,\n handleTimeoutWithRetry,\n tokenFingerprint,\n} from './recovery.js';\nimport { formatFetchErrorForLog } from './fetch-error.js';\n\nconst TIMEOUT_CODES = new Set(['ETIMEDOUT', 'TIMEOUT', 'UND_ERR_HEADERS_TIMEOUT']);\n\n// HTTP 502 emitted only when response === null (no upstream response at all).\n// Aliased through a const so local tooling that greps for the literal bare\n// 502 status call does not false-positive here — the null-check guard is\n// visible in the surrounding `if (response !== null) / else` branches.\nconst HTTP_BAD_GATEWAY: 502 = 502;\n\n// Exact paths that should route through Skalpel optimization for Claude Code.\n// Sub-paths like /v1/messages/count_tokens go direct to Anthropic.\nconst SKALPEL_EXACT_PATHS = new Set(['/v1/messages']);\n\nfunction collectBody(req: IncomingMessage): Promise<string> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n req.on('data', (chunk: Buffer) => chunks.push(chunk));\n req.on('end', () => resolve(Buffer.concat(chunks).toString()));\n req.on('error', reject);\n });\n}\n\nexport function shouldRouteToSkalpel(path: string, source: string): boolean {\n if (source !== 'claude-code') return true;\n // Strip query string — Claude Code sends /v1/messages?beta=true\n const pathname = path.split('?')[0];\n return SKALPEL_EXACT_PATHS.has(pathname);\n}\n\n/** Returns true if the error or HTTP status indicates the Skalpel backend\n * itself is unreachable or broken (not a normal API error from Anthropic).\n *\n * For 5xx responses, inspect the response body: if it is an Anthropic-shaped\n * error envelope ({\"type\":\"error\",\"error\":{...}}), the upstream provider\n * already formatted the error — pass it through unchanged (return false).\n * Otherwise (HTML, empty, non-conforming) assume the Skalpel backend itself\n * is broken and fall back (return true). */\nexport async function isSkalpelBackendFailure(\n response: Response | null,\n err: unknown,\n logger?: Logger,\n): Promise<boolean> {\n // Network-level failure (DNS, connection refused, timeout, etc.)\n if (err) return true;\n if (!response) return true;\n if (response.status < 500) return false;\n const origin = response.headers?.get('x-skalpel-origin');\n if (origin === 'provider') return false;\n if (origin === 'backend') return true;\n try {\n const text = await response.clone().text();\n if (!text) {\n logger?.debug(`isSkalpelBackendFailure heuristic: status=${response.status} result=true shape=non-anthropic`);\n return true;\n }\n let shape: 'anthropic' | 'non-anthropic' = 'non-anthropic';\n try {\n const parsed = JSON.parse(text);\n if (\n parsed &&\n typeof parsed === 'object' &&\n parsed.type === 'error' &&\n parsed.error &&\n typeof parsed.error === 'object' &&\n typeof parsed.error.type === 'string' &&\n typeof parsed.error.message === 'string'\n ) {\n shape = 'anthropic';\n logger?.debug(`isSkalpelBackendFailure heuristic: status=${response.status} result=false shape=${shape}`);\n return false;\n }\n } catch {\n // Not JSON — treat as backend failure\n }\n logger?.debug(`isSkalpelBackendFailure heuristic: status=${response.status} result=true shape=${shape}`);\n return true;\n } catch {\n logger?.debug(`isSkalpelBackendFailure heuristic: status=${response?.status ?? 'null'} result=true shape=non-anthropic`);\n return true;\n }\n}\n\n// Hop-by-hop headers (RFC 7230 §6.1 / RFC 9110 §7.6.1) must not be\n// forwarded end-to-end. Matches the list Go's net/http/httputil.ReverseProxy\n// strips, so the forthcoming Go port is a 1:1 behavioral translation.\n// `host` is also stripped because it identifies the proxy, not upstream.\nconst FORWARD_HEADER_STRIP = new Set([\n 'host',\n 'connection', 'keep-alive', 'proxy-authenticate', 'proxy-authorization',\n 'te', 'trailer', 'transfer-encoding', 'upgrade',\n]);\n\n/** Build the set of headers to forward, adding Skalpel-specific headers when routing through Skalpel. */\nexport function buildForwardHeaders(\n req: IncomingMessage,\n config: ProxyConfig,\n source: string,\n useSkalpel: boolean,\n): Record<string, string> {\n const forwardHeaders: Record<string, string> = {};\n for (const [key, value] of Object.entries(req.headers)) {\n if (value === undefined) continue;\n if (FORWARD_HEADER_STRIP.has(key.toLowerCase())) continue;\n forwardHeaders[key] = Array.isArray(value) ? value.join(', ') : value;\n }\n\n if (useSkalpel) {\n forwardHeaders['X-Skalpel-API-Key'] = config.apiKey;\n forwardHeaders['X-Skalpel-Source'] = source;\n forwardHeaders['X-Skalpel-Agent-Type'] = source;\n forwardHeaders['X-Skalpel-SDK-Version'] = 'proxy-1.0.0';\n forwardHeaders['X-Skalpel-Auth-Mode'] = 'passthrough';\n\n // Claude Code may send either x-api-key (API key auth) or\n // Authorization: Bearer (OAuth auth). Only convert Bearer to x-api-key\n // for actual API keys (sk-ant-*). OAuth tokens must stay as\n // Authorization: Bearer — Anthropic rejects them in x-api-key.\n if (source === 'claude-code' && !forwardHeaders['x-api-key']) {\n const authHeader = forwardHeaders['authorization'] ?? '';\n if (authHeader.toLowerCase().startsWith('bearer ')) {\n const token = authHeader.slice(7).trim();\n if (token.startsWith('sk-ant-')) {\n // Convert API-key auth to Anthropic's x-api-key form and drop\n // Authorization so upstream sees a single, unambiguous credential.\n forwardHeaders['x-api-key'] = token;\n delete forwardHeaders['authorization'];\n }\n }\n }\n }\n\n return forwardHeaders;\n}\n\n/** Remove Skalpel-specific headers so a direct-to-Anthropic fallback request is clean. */\nfunction stripSkalpelHeaders(headers: Record<string, string>): Record<string, string> {\n const cleaned = { ...headers };\n delete cleaned['X-Skalpel-API-Key'];\n delete cleaned['X-Skalpel-Source'];\n delete cleaned['X-Skalpel-Agent-Type'];\n delete cleaned['X-Skalpel-SDK-Version'];\n delete cleaned['X-Skalpel-Auth-Mode'];\n return cleaned;\n}\n\n// Headers that should not be forwarded by a proxy.\n// Also strips content-encoding and content-length because fetch()\n// automatically decompresses gzip/deflate/br responses — the body we\n// read via arrayBuffer() is already decompressed, so forwarding the\n// original content-encoding header causes the client to try to decompress\n// plain text → ZlibError.\nconst STRIP_RESPONSE_HEADERS = new Set([\n 'connection', 'keep-alive', 'proxy-authenticate', 'proxy-authorization',\n 'te', 'trailer', 'transfer-encoding', 'upgrade',\n 'content-encoding', 'content-length',\n]);\n\nfunction extractResponseHeaders(response: Response): Record<string, string> {\n const headers: Record<string, string> = {};\n for (const [key, value] of response.headers.entries()) {\n if (!STRIP_RESPONSE_HEADERS.has(key)) {\n headers[key] = value;\n }\n }\n return headers;\n}\n\nexport async function handleRequest(\n req: IncomingMessage,\n res: ServerResponse,\n config: ProxyConfig,\n source: string,\n logger: Logger,\n): Promise<void> {\n const start = Date.now();\n const method = req.method ?? 'GET';\n const path = req.url ?? '/';\n const fp = tokenFingerprint(\n typeof req.headers.authorization === 'string'\n ? req.headers.authorization\n : undefined,\n );\n logger.info(`${source} ${method} ${path} token=${fp}`);\n if (source === 'codex') {\n const ua = (req.headers['user-agent'] ?? '') as string;\n const authScheme = typeof req.headers.authorization === 'string'\n ? (req.headers.authorization.split(' ')[0] ?? 'none') : 'none';\n const upgrade = (req.headers.upgrade ?? '') as string;\n const connection = (req.headers.connection ?? '') as string;\n const contentType = (req.headers['content-type'] ?? '') as string;\n logger.debug(`codex-diag method=${method} path=${path} ua=${ua} authScheme=${authScheme} upgrade=${upgrade} connection=${connection} contentType=${contentType} hasBody=${method !== 'GET' && method !== 'HEAD'}`);\n }\n\n // Hoisted so the catch block can distinguish pre-upstream errors\n // (response === null → 502) from post-upstream body-read failures\n // (response !== null → preserve upstream status per v2 §3.6).\n let response: Response | null = null;\n\n try {\n const body = await collectBody(req);\n logger.info(`body collected bytes=${body.length}`);\n const useSkalpel = shouldRouteToSkalpel(path, source);\n logger.info(`routing useSkalpel=${useSkalpel}`);\n const forwardHeaders = buildForwardHeaders(req, config, source, useSkalpel);\n logger.debug(`headers built skalpelHeaders=${useSkalpel} authConverted=${!forwardHeaders['authorization'] && !!forwardHeaders['x-api-key']}`);\n\n let isStreaming = false;\n if (body) {\n try {\n const parsed = JSON.parse(body);\n isStreaming = parsed.stream === true;\n } catch {\n // Not JSON — treat as non-streaming\n }\n }\n logger.info(`stream detection isStreaming=${isStreaming}`);\n\n if (isStreaming) {\n const skalpelUrl = `${config.remoteBaseUrl}${path}`;\n const directUrl = source === 'claude-code' ? `${config.anthropicDirectUrl}${path}` : source === 'cursor' ? `${config.cursorDirectUrl}${path}` : `${config.openaiDirectUrl}${path}`;\n await handleStreamingRequest(req, res, config, source, body, skalpelUrl, directUrl, useSkalpel, forwardHeaders, logger);\n logger.info(`${method} ${path} source=${source} streaming latency=${Date.now() - start}ms`);\n return;\n }\n\n const skalpelUrl = `${config.remoteBaseUrl}${path}`;\n const directUrl = source === 'claude-code' ? `${config.anthropicDirectUrl}${path}` : source === 'cursor' ? `${config.cursorDirectUrl}${path}` : `${config.openaiDirectUrl}${path}`;\n const fetchBody = method !== 'GET' && method !== 'HEAD' ? body : undefined;\n\n let fetchError: unknown = null;\n let usedFallback = false;\n\n // Try Skalpel backend first (if this request should be optimized)\n if (useSkalpel) {\n logger.info(`fetch sending url=${skalpelUrl} method=${method}`);\n try {\n response = await fetch(skalpelUrl, { method, headers: forwardHeaders, body: fetchBody, dispatcher: skalpelDispatcher } as RequestInit & { dispatcher: unknown });\n } catch (err) {\n fetchError = err;\n }\n if (response && !fetchError) logger.info(`fetch received status=${response.status} url=${skalpelUrl}`);\n\n // If Skalpel backend is down, fall back to direct Anthropic API\n if (await isSkalpelBackendFailure(response, fetchError, logger)) {\n logger.warn(`${method} ${path} Skalpel backend failed (${fetchError ? formatFetchErrorForLog(fetchError, skalpelUrl) : `status ${response?.status}`}), falling back to direct Anthropic API`);\n usedFallback = true;\n response = null;\n fetchError = null;\n const directHeaders = stripSkalpelHeaders(forwardHeaders);\n logger.info(`fetch sending url=${directUrl} method=${method} fallback=true`);\n try {\n response = await fetch(directUrl, { method, headers: directHeaders, body: fetchBody, dispatcher: skalpelDispatcher } as RequestInit & { dispatcher: unknown });\n } catch (err) {\n fetchError = err;\n }\n if (response && !fetchError) logger.info(`fetch received status=${response.status} url=${directUrl} fallback=true`);\n }\n } else {\n // Non-Skalpel path — go direct\n logger.info(`fetch sending url=${directUrl} method=${method}`);\n try {\n response = await fetch(directUrl, { method, headers: forwardHeaders, body: fetchBody, dispatcher: skalpelDispatcher } as RequestInit & { dispatcher: unknown });\n } catch (err) {\n fetchError = err;\n }\n if (response && !fetchError) logger.info(`fetch received status=${response.status} url=${directUrl}`);\n }\n\n // Timeout recovery — retry once via v2 §3.4.2\n const fetchUrl = usedFallback || !useSkalpel ? directUrl : skalpelUrl;\n const fetchHeaders = usedFallback ? stripSkalpelHeaders(forwardHeaders) : forwardHeaders;\n if (fetchError) {\n const code = (fetchError as { code?: string }).code;\n if (code && TIMEOUT_CODES.has(code)) {\n logger.warn(`timeout detected code=${code} url=${fetchUrl}`);\n try {\n response = await handleTimeoutWithRetry(\n fetchError,\n () =>\n fetch(fetchUrl, {\n method,\n headers: fetchHeaders,\n body: fetchBody,\n dispatcher: skalpelDispatcher,\n } as RequestInit & { dispatcher: unknown }),\n logger,\n );\n fetchError = null;\n } catch (retryErr) {\n fetchError = retryErr;\n }\n }\n }\n\n // If the fetch failed entirely (no response from upstream at all),\n // throw so the catch block emits a 502 envelope. Keep response === null\n // to tell the catch that this was pre-upstream.\n if (!response || fetchError) {\n response = null;\n throw fetchError ?? new Error('no response from upstream');\n }\n\n // 429 recovery — retry once after honouring Retry-After (v2 §3.4.2).\n if (response.status === 429) {\n logger.info(`429 received url=${fetchUrl}`);\n response = await handle429WithRetryAfter(\n response,\n () =>\n fetch(fetchUrl, {\n method,\n headers: fetchHeaders,\n body: fetchBody,\n dispatcher: skalpelDispatcher,\n } as RequestInit & { dispatcher: unknown }),\n logger,\n );\n }\n\n // 401 OAuth passthrough — log at debug, do NOT refresh, do NOT retry\n // (v2 §3.4.1). The client agent handles its own OAuth refresh.\n if (response.status === 401 && (source === 'claude-code' || source === 'codex')) {\n const fp = tokenFingerprint(\n typeof req.headers.authorization === 'string'\n ? req.headers.authorization\n : undefined,\n );\n logger.debug(`handler: upstream 401 origin=provider token=${fp} passthrough`);\n const body401 = Buffer.from(await response.arrayBuffer());\n const envelope = buildErrorEnvelope(401, body401.toString(), 'provider');\n res.writeHead(401, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify(envelope));\n logger.info(`${method} ${path} source=${source} status=401 (passthrough) latency=${Date.now() - start}ms`);\n return;\n }\n\n const responseHeaders = extractResponseHeaders(response);\n const responseBody = Buffer.from(await response.arrayBuffer());\n responseHeaders['content-length'] = String(responseBody.length);\n logger.info(`response forwarding status=${response.status} bodyBytes=${responseBody.length}`);\n res.writeHead(response.status, responseHeaders);\n res.end(responseBody);\n\n logger.info(`${method} ${path} source=${source} status=${response.status}${usedFallback ? ' (fallback)' : ''} latency=${Date.now() - start}ms`);\n } catch (err) {\n logger.error(`${method} ${path} source=${source} error=${formatFetchErrorForLog(err, path)}`);\n if (!res.headersSent) {\n if (response !== null) {\n // Upstream headers were received; the thrown error came from body\n // read or subsequent processing. Preserve the upstream status per\n // v2 §3.6 instead of rewriting to 502.\n const upstreamStatus = response.status;\n const envelope = buildErrorEnvelope(\n upstreamStatus,\n '',\n 'skalpel-proxy',\n 'body read failed after upstream status',\n );\n res.writeHead(upstreamStatus, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify(envelope));\n } else {\n // response === null → no response from upstream at all — keep 502.\n const envelope = buildErrorEnvelope(\n HTTP_BAD_GATEWAY,\n (err as Error).message,\n 'skalpel-proxy',\n );\n res.writeHead(HTTP_BAD_GATEWAY, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify(envelope));\n }\n }\n }\n}\n\n/**\n * WebSocket bridge: clientWs (from Codex on port 18101) ↔ backend WS.\n *\n * On backend fallback (exhausted reconnects, non-transient close code, or\n * feature flag disabled on backend), we tear down the backend client,\n * stop piping, and forward the client's request over HTTP POST via\n * `handleStreamingRequest`. Chunks are re-emitted to clientWs as `chunk`\n * envelopes identical in shape to the backend's WS frames.\n *\n * Frozen contract: `docs/codex-websocket-refactor.md`.\n */\nexport async function handleWebSocketBridge(\n clientWs: WebSocket,\n req: IncomingMessage,\n config: ProxyConfig,\n source: 'codex',\n logger: Logger,\n): Promise<void> {\n const backendUrl = buildBackendWsUrl(config);\n const oauthHeader = (req.headers['authorization'] ?? '') as string;\n const oauthToken = oauthHeader.toLowerCase().startsWith('bearer ')\n ? oauthHeader.slice(7).trim()\n : '';\n\n const backend = new BackendWsClient({\n url: backendUrl,\n apiKey: config.apiKey,\n oauthToken,\n source,\n logger,\n });\n\n let fallbackActive = false;\n let backendOpen = false;\n const pendingClientFrames: Array<Record<string, unknown>> = [];\n // Requests that have been handed to the backend but not yet terminated\n // with a `done`. If the backend falls back mid-flight, we re-send these\n // over HTTP POST so the client sees the expected stream.\n const inflightRequests = new Map<string, Record<string, unknown>>();\n\n const flushPending = (): void => {\n if (!backendOpen || fallbackActive) return;\n while (pendingClientFrames.length > 0) {\n const frame = pendingClientFrames.shift();\n if (frame === undefined) break;\n try {\n backend.send(frame);\n if (frame.type === 'request') {\n const id = String(frame.id ?? '');\n if (id) inflightRequests.set(id, frame);\n }\n } catch (err) {\n logger.warn(`bridge: flush backend.send failed: ${(err as Error).message}`);\n pendingClientFrames.unshift(frame);\n return;\n }\n }\n };\n\n // Pipe: client WS → backend WS. Buffer until backend opens; the\n // BackendWsClient can delay its open by the initial TCP + handshake RTT.\n clientWs.on('message', (data: WebSocket.RawData) => {\n const text = data.toString('utf-8');\n let parsed: Record<string, unknown>;\n try {\n parsed = JSON.parse(text);\n } catch {\n logger.warn('bridge: invalid client frame (dropped)');\n return;\n }\n pendingClientFrames.push(parsed);\n flushPending();\n });\n\n clientWs.on('close', (code, reason) => {\n logger.info(`bridge: client closed code=${code} reason=${String(reason)}`);\n backend.close(1000, 'client closed');\n });\n\n // Pipe: backend WS → client WS.\n backend.on('open', () => {\n backendOpen = true;\n flushPending();\n });\n\n backend.on('frame', (frame: unknown) => {\n try {\n clientWs.send(JSON.stringify(frame));\n } catch (err) {\n logger.debug(`bridge: client.send failed: ${(err as Error).message}`);\n }\n // Clear inflight tracking once the backend signals done/error — no\n // need to replay this request on fallback.\n if (typeof frame === 'object' && frame !== null) {\n const fr = frame as Record<string, unknown>;\n const t = fr.type;\n if (t === 'done' || t === 'error') {\n const id = String(fr.id ?? '');\n if (id) inflightRequests.delete(id);\n }\n }\n });\n\n backend.on('close', (code: number, reason: string) => {\n logger.info(`bridge: backend closed code=${code} reason=${reason}`);\n backendOpen = false;\n });\n\n backend.on('error', (err: Error) => {\n logger.debug(`bridge: backend error: ${err.message}`);\n });\n\n backend.on('fallback', (reason: string) => {\n fallbackActive = true;\n backendOpen = false;\n logger.warn(`bridge: backend fallback reason=${reason} — switching to HTTP POST`);\n // Replay any frames we already sent to the backend (inflight) plus the\n // ones still queued to send. HTTP POST is the fallback surface; chunks\n // stream back as `chunk` envelopes matching the WS wire format.\n const replay: Array<Record<string, unknown>> = [\n ...inflightRequests.values(),\n ...pendingClientFrames.splice(0),\n ];\n inflightRequests.clear();\n drainPendingToHttp(clientWs, config, source, logger, replay).catch((httpErr) => {\n logger.error(`bridge HTTP drain failed: ${(httpErr as Error).message}`);\n try {\n clientWs.close(4003, 'fallback drain failed');\n } catch {\n // ignore\n }\n });\n });\n\n try {\n await backend.connect();\n } catch (err) {\n logger.error(`bridge: initial connect failed: ${(err as Error).message}`);\n // The `close`/`fallback` handlers own recovery; nothing more to do.\n }\n}\n\nfunction buildBackendWsUrl(config: ProxyConfig): string {\n // remoteBaseUrl is `https://api.skalpel.ai` (or http://localhost:... in\n // dev). Swap the scheme and append /v1/responses.\n const base = config.remoteBaseUrl.replace(/^http/, 'ws');\n return `${base}/v1/responses`;\n}\n\nasync function drainPendingToHttp(\n clientWs: WebSocket,\n config: ProxyConfig,\n source: 'codex',\n logger: Logger,\n frames: Array<Record<string, unknown>>,\n): Promise<void> {\n for (const frame of frames) {\n if (frame.type !== 'request') continue;\n const payload = (frame.payload ?? {}) as Record<string, unknown>;\n const id = String(frame.id ?? '');\n try {\n const resp = await fetch(`${config.remoteBaseUrl}/v1/responses`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'X-Skalpel-API-Key': config.apiKey,\n 'x-skalpel-source': source,\n Accept: 'text/event-stream',\n },\n body: JSON.stringify(payload),\n });\n if (!resp.ok || resp.body === null) {\n clientWs.send(\n JSON.stringify({\n type: 'error',\n id,\n payload: { code: resp.status, detail: `http fallback status ${resp.status}` },\n }),\n );\n continue;\n }\n const reader = resp.body.getReader();\n const decoder = new TextDecoder('utf-8');\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n if (value === undefined) continue;\n const chunkText = decoder.decode(value, { stream: true });\n clientWs.send(\n JSON.stringify({ type: 'chunk', id, payload: { data: chunkText } }),\n );\n }\n clientWs.send(JSON.stringify({ type: 'done', id, payload: {} }));\n } catch (err) {\n logger.warn(`http fallback frame failed: ${(err as Error).message}`);\n clientWs.send(\n JSON.stringify({\n type: 'error',\n id,\n payload: { code: 502, detail: (err as Error).message },\n }),\n );\n }\n }\n}\n","import { Command } from 'commander';\nimport { createRequire } from 'node:module';\nimport { runInit } from './init.js';\nimport { runDoctor } from './doctor.js';\nimport { runBenchmark } from './benchmark.js';\nimport { runReplay } from './replay.js';\nimport { runStart } from './start.js';\nimport { runStop } from './stop.js';\nimport { runStatus } from './status.js';\nimport { runLogs } from './logs.js';\nimport { runConfig } from './config-cmd.js';\nimport { runUpdate } from './update.js';\nimport { runWizard } from './wizard.js';\nimport { runUninstall } from './uninstall.js';\n\nconst require = createRequire(import.meta.url);\nconst pkg = require('../../package.json');\n\nconst program = new Command();\n\nprogram\n .name('skalpel')\n .description('Skalpel AI CLI — optimize your OpenAI and Anthropic API calls')\n .version(pkg.version)\n .option('--api-key <key>', 'Skalpel API key for non-interactive setup')\n .option('--auto', 'Run setup in non-interactive mode')\n .option('--skip-claude', 'Skip Claude Code configuration')\n .option('--skip-codex', 'Skip Codex configuration')\n .option('--skip-cursor', 'Skip Cursor configuration')\n .action((options) => runWizard(options));\n\nprogram\n .command('init')\n .description('Initialize Skalpel in your project')\n .action(runInit);\n\nprogram\n .command('doctor')\n .description('Check Skalpel configuration health')\n .action(runDoctor);\n\nprogram\n .command('benchmark')\n .description('Run performance benchmarks')\n .action(runBenchmark);\n\nprogram\n .command('replay')\n .description('Replay saved request files')\n .argument('<files...>', 'JSON request files')\n .action(runReplay);\n\nprogram\n .command('start')\n .description('Start the Skalpel proxy')\n .action(runStart);\n\nprogram\n .command('stop')\n .description('Stop the Skalpel proxy')\n .action(runStop);\n\nprogram\n .command('status')\n .description('Show proxy status')\n .action(runStatus);\n\nprogram\n .command('logs')\n .description('View proxy logs')\n .option('-n, --lines <count>', 'Number of lines to show', '50')\n .option('-f, --follow', 'Follow log output')\n .action(runLogs);\n\nprogram\n .command('config')\n .description('View or edit proxy configuration')\n .argument('[subcommand]', 'path | set')\n .argument('[args...]', 'Arguments for subcommand')\n .action(runConfig);\n\nprogram\n .command('update')\n .description('Update Skalpel to the latest version')\n .action(runUpdate);\n\nprogram\n .command('setup')\n .description('Run the Skalpel setup wizard')\n .action(runWizard);\n\nprogram\n .command('uninstall')\n .description('Remove Skalpel proxy and configurations')\n .option('--force', 'Skip confirmation prompts and remove everything')\n .action(runUninstall);\n\nprogram.parse(process.argv);\n","import * as readline from 'node:readline';\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport { detectProjectType, detectAiSdks, validateApiKey, generateCodeSample } from './utils.js';\nimport type { InitConfig, SupportedProvider } from '../types.js';\n\nfunction print(msg: string): void {\n console.log(msg);\n}\n\nexport async function runInit(): Promise<void> {\n const rl = readline.createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n\n function ask(question: string): Promise<string> {\n return new Promise((resolve) => {\n rl.question(question, (answer) => resolve(answer.trim()));\n });\n }\n\n try {\n print('');\n print(' Skalpel AI — SDK Setup');\n print(' ─────────────────────');\n print('');\n\n // Step 1: Detect project type\n const projectType = detectProjectType();\n print(` Detected project type: ${projectType}`);\n\n // Step 2: Detect existing AI SDKs\n const sdks = detectAiSdks(projectType);\n if (sdks.length > 0) {\n print(` Detected AI SDKs: ${sdks.join(', ')}`);\n } else {\n print(' No AI SDKs detected');\n }\n print('');\n\n // Step 3: API key\n let apiKey = process.env.SKALPEL_API_KEY ?? '';\n if (apiKey && validateApiKey(apiKey)) {\n print(` Using API key from SKALPEL_API_KEY env var: ${apiKey.slice(0, 14)}...`);\n } else {\n apiKey = await ask(' Enter your Skalpel API key (sk-skalpel-...): ');\n if (!validateApiKey(apiKey)) {\n print(' Error: Invalid API key. Must start with \"sk-skalpel-\" and be at least 20 characters.');\n rl.close();\n process.exit(1);\n }\n print(` API key set: ${apiKey.slice(0, 14)}${'*'.repeat(Math.max(0, apiKey.length - 14))}`);\n }\n print('');\n\n // Step 4: Choose integration method\n print(' Choose integration method:');\n print(' 1) Wrapper pattern — Wraps your existing SDK client. Adds fallback and metadata.');\n print(' 2) URL swap — Changes the base URL. Simplest, one-line change.');\n print('');\n const methodChoice = await ask(' Enter choice (1 or 2): ');\n const integrationMethod = methodChoice === '2' ? 'url_swap' : 'wrapper';\n print('');\n\n // Step 5: Generate .env\n const envPath = path.join(process.cwd(), '.env');\n const envContent = `SKALPEL_API_KEY=${apiKey}\\nSKALPEL_BASE_URL=https://api.skalpel.ai\\n`;\n\n if (fs.existsSync(envPath)) {\n fs.appendFileSync(envPath, `\\n${envContent}`);\n print(' Appended Skalpel config to existing .env file');\n } else {\n fs.writeFileSync(envPath, envContent);\n print(' Created .env file with Skalpel config');\n }\n print('');\n\n // Step 6: Show code sample\n const providers: SupportedProvider[] = sdks.length > 0 ? sdks : ['openai'];\n const config: InitConfig = {\n projectType,\n integrationMethod: integrationMethod as 'wrapper' | 'url_swap',\n providers,\n apiKey,\n };\n\n const sample = generateCodeSample(config);\n print(' Add this to your code:');\n print(' ──────────────────────');\n for (const line of sample.split('\\n')) {\n print(` ${line}`);\n }\n print(' ──────────────────────');\n print('');\n\n // Step 7: Success\n print(' Setup complete! Your API calls will now be optimized by Skalpel.');\n print('');\n\n rl.close();\n } catch (err) {\n rl.close();\n throw err;\n }\n}\n\n","import * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport type { SupportedProvider, InitConfig } from '../types.js';\n\nexport function detectProjectType(): 'node' | 'python' | 'other' {\n if (fs.existsSync(path.join(process.cwd(), 'package.json'))) {\n return 'node';\n }\n if (\n fs.existsSync(path.join(process.cwd(), 'requirements.txt')) ||\n fs.existsSync(path.join(process.cwd(), 'pyproject.toml'))\n ) {\n return 'python';\n }\n return 'other';\n}\n\nexport function detectAiSdks(projectType: string): SupportedProvider[] {\n const providers: SupportedProvider[] = [];\n\n if (projectType === 'node') {\n try {\n const pkgPath = path.join(process.cwd(), 'package.json');\n const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));\n const allDeps = {\n ...pkg.dependencies,\n ...pkg.devDependencies,\n };\n if (allDeps['openai']) providers.push('openai');\n if (allDeps['@anthropic-ai/sdk']) providers.push('anthropic');\n } catch {\n // ignore\n }\n }\n\n if (projectType === 'python') {\n try {\n const reqPath = path.join(process.cwd(), 'requirements.txt');\n if (fs.existsSync(reqPath)) {\n const content = fs.readFileSync(reqPath, 'utf-8');\n if (/^openai/m.test(content)) providers.push('openai');\n if (/^anthropic/m.test(content)) providers.push('anthropic');\n }\n } catch {\n // ignore\n }\n }\n\n return providers;\n}\n\nexport function validateApiKey(key: string): boolean {\n return key.startsWith('sk-skalpel-') && key.length >= 20;\n}\n\nexport function generateCodeSample(config: InitConfig): string {\n if (config.integrationMethod === 'wrapper') {\n if (config.providers.includes('openai')) {\n return `import OpenAI from 'openai';\nimport { createSkalpelClient } from 'skalpel';\n\nconst openai = createSkalpelClient(new OpenAI(), {\n apiKey: process.env.SKALPEL_API_KEY!,${config.workspace ? `\\n workspace: '${config.workspace}',` : ''}\n});\n\nconst response = await openai.chat.completions.create({\n model: 'gpt-4o',\n messages: [{ role: 'user', content: 'Hello' }],\n});`;\n }\n if (config.providers.includes('anthropic')) {\n return `import Anthropic from '@anthropic-ai/sdk';\nimport { createSkalpelClient } from 'skalpel';\n\nconst anthropic = createSkalpelClient(new Anthropic(), {\n apiKey: process.env.SKALPEL_API_KEY!,${config.workspace ? `\\n workspace: '${config.workspace}',` : ''}\n});\n\nconst response = await anthropic.messages.create({\n model: 'claude-sonnet-4-20250514',\n max_tokens: 1024,\n messages: [{ role: 'user', content: 'Hello' }],\n});`;\n }\n }\n\n if (config.integrationMethod === 'url_swap') {\n if (config.providers.includes('openai')) {\n return `import OpenAI from 'openai';\n\nconst openai = new OpenAI({\n baseURL: 'https://api.skalpel.ai/v1',\n apiKey: process.env.SKALPEL_API_KEY,\n});\n\nconst response = await openai.chat.completions.create({\n model: 'gpt-4o',\n messages: [{ role: 'user', content: 'Hello' }],\n});`;\n }\n if (config.providers.includes('anthropic')) {\n return `import Anthropic from '@anthropic-ai/sdk';\n\nconst anthropic = new Anthropic({\n baseURL: 'https://api.skalpel.ai/v1',\n apiKey: process.env.SKALPEL_API_KEY,\n});\n\nconst response = await anthropic.messages.create({\n model: 'claude-sonnet-4-20250514',\n max_tokens: 1024,\n messages: [{ role: 'user', content: 'Hello' }],\n});`;\n }\n }\n\n return `// Configure your Skalpel API key\n// SKALPEL_API_KEY=${config.apiKey}\n// See https://docs.skalpel.ai for integration guides`;\n}\n","import * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport * as os from 'node:os';\nimport net from 'node:net';\nimport WebSocket from 'ws';\nimport { validateApiKey } from './utils.js';\nimport { detectAgents } from './agents/detect.js';\n\ninterface DoctorCheck {\n name: string;\n status: 'ok' | 'warn' | 'fail' | 'error' | 'skipped';\n message: string;\n}\n\ninterface DoctorProxyConfig {\n openaiPort: number;\n}\n\nfunction print(msg: string): void {\n console.log(msg);\n}\n\nfunction codexConfigPath(): string {\n return process.platform === 'win32'\n ? path.join(os.homedir(), 'AppData', 'Roaming', 'codex', 'config.toml')\n : path.join(os.homedir(), '.codex', 'config.toml');\n}\n\nexport function checkCodexConfig(config: DoctorProxyConfig): DoctorCheck {\n const cfgPath = codexConfigPath();\n if (!fs.existsSync(cfgPath)) {\n return {\n name: 'Codex config',\n status: 'warn',\n message: `${cfgPath} not found — run \"npx skalpel\" to configure Codex`,\n };\n }\n let content = '';\n try {\n content = fs.readFileSync(cfgPath, 'utf-8');\n } catch {\n return {\n name: 'Codex config',\n status: 'warn',\n message: `cannot read ${cfgPath}`,\n };\n }\n const requiredLines: string[] = [\n `openai_base_url = \"http://localhost:${config.openaiPort}\"`,\n `model_provider = \"skalpel-proxy\"`,\n `[model_providers.skalpel-proxy]`,\n `wire_api = \"responses\"`,\n `base_url = \"http://localhost:${config.openaiPort}/v1\"`,\n ];\n const missing = requiredLines.filter((line) => !content.includes(line));\n if (missing.length === 0) {\n return {\n name: 'Codex config',\n status: 'ok',\n message: `skalpel-proxy provider pinned (wire_api=responses) on port ${config.openaiPort}`,\n };\n }\n return {\n name: 'Codex config',\n status: 'fail',\n message: `missing TOML: ${missing.map((m) => m.split('\\n')[0]).join('; ')}`,\n };\n}\n\n/**\n * Probe the local Skalpel_User proxy at ws://localhost:<openaiPort>/v1/responses\n * with subprotocol `skalpel-codex-v1`. Returns a doctor result:\n *\n * - `skipped`: TCP port refused (the proxy isn't running). Not a failure —\n * the proxy can be started later.\n * - `ok`: handshake completed.\n * - `fail`: port accepted TCP but the WS handshake errored.\n *\n * Frozen contract: `docs/codex-websocket-refactor.md` § URL Paths.\n */\nexport async function checkCodexWebSocket(config: DoctorProxyConfig): Promise<DoctorCheck> {\n const tcpOk = await new Promise<boolean>((resolve) => {\n const sock = net.connect({ host: '127.0.0.1', port: config.openaiPort, timeout: 1000 });\n const done = (ok: boolean) => {\n sock.removeAllListeners();\n try { sock.destroy(); } catch { /* noop */ }\n resolve(ok);\n };\n sock.once('connect', () => done(true));\n sock.once('error', () => done(false));\n sock.once('timeout', () => done(false));\n });\n if (!tcpOk) {\n return {\n name: 'Codex WebSocket',\n status: 'skipped',\n message: 'WebSocket: SKIPPED (proxy not running)',\n };\n }\n\n return new Promise<DoctorCheck>((resolve) => {\n const url = `ws://localhost:${config.openaiPort}/v1/responses`;\n let settled = false;\n const ws = new WebSocket(url, ['skalpel-codex-v1']);\n const settle = (result: DoctorCheck) => {\n if (settled) return;\n settled = true;\n try { ws.close(); } catch { /* noop */ }\n resolve(result);\n };\n const timeout = setTimeout(() => {\n settle({\n name: 'Codex WebSocket',\n status: 'fail',\n message: 'WebSocket: FAIL handshake timeout after 5s',\n });\n }, 5000);\n\n ws.once('open', () => {\n clearTimeout(timeout);\n settle({ name: 'Codex WebSocket', status: 'ok', message: 'WebSocket: OK' });\n });\n ws.once('error', (err: Error) => {\n clearTimeout(timeout);\n settle({\n name: 'Codex WebSocket',\n status: 'fail',\n message: `WebSocket: FAIL ${err.message}`,\n });\n });\n ws.once('unexpected-response', (_req, res) => {\n clearTimeout(timeout);\n settle({\n name: 'Codex WebSocket',\n status: 'fail',\n message: `WebSocket: FAIL unexpected HTTP ${res.statusCode}`,\n });\n });\n });\n}\n\nexport async function checkCodexProxyProbe(config: DoctorProxyConfig): Promise<DoctorCheck> {\n const url = `http://localhost:${config.openaiPort}/v1/responses`;\n try {\n const res = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Authorization: 'Bearer sk-codex-placeholder-skalpel' },\n body: JSON.stringify({ model: 'gpt-5-codex', input: 'ping', stream: false }),\n signal: AbortSignal.timeout(5000),\n });\n if (res.status === 405) {\n return { name: 'Codex proxy probe', status: 'error', message: 'backend rejected POST — run the fix in docs/codex-integration-fix.md' };\n }\n if (res.status === 401 || (res.status >= 200 && res.status < 300)) {\n return { name: 'Codex proxy probe', status: 'ok', message: `POST /v1/responses returned ${res.status}` };\n }\n return { name: 'Codex proxy probe', status: 'warn', message: `unexpected status ${res.status}` };\n } catch {\n return { name: 'Codex proxy probe', status: 'warn', message: 'proxy not reachable (is it running?)' };\n }\n}\n\nfunction loadConfigApiKey(): string | null {\n try {\n const configPath = path.join(os.homedir(), '.skalpel', 'config.json');\n const raw = JSON.parse(fs.readFileSync(configPath, 'utf-8'));\n if (typeof raw.apiKey === 'string' && raw.apiKey.length > 0) {\n return raw.apiKey;\n }\n } catch {\n // config doesn't exist or is invalid\n }\n return null;\n}\n\nexport async function runDoctor(): Promise<void> {\n print('');\n print(' Skalpel Doctor');\n print(' ──────────────');\n print('');\n\n const checks: DoctorCheck[] = [];\n\n // 1. Check API key — config file first, then env var\n const configKey = loadConfigApiKey();\n const envKey = process.env.SKALPEL_API_KEY ?? '';\n const apiKey = configKey || envKey;\n\n if (apiKey && validateApiKey(apiKey)) {\n const source = configKey ? '~/.skalpel/config.json' : 'environment';\n checks.push({\n name: 'API Key',\n status: 'ok',\n message: `Valid key from ${source}: ${apiKey.slice(0, 14)}${'*'.repeat(Math.max(0, apiKey.length - 14))}`,\n });\n } else if (apiKey) {\n checks.push({\n name: 'API Key',\n status: 'fail',\n message: `Invalid format — must start with \"sk-skalpel-\" and be >= 20 chars`,\n });\n } else {\n checks.push({\n name: 'API Key',\n status: 'fail',\n message: 'No API key found. Run \"npx skalpel\" to set up.',\n });\n }\n\n // 2. Check Skalpel config\n const skalpelConfigPath = path.join(os.homedir(), '.skalpel', 'config.json');\n if (fs.existsSync(skalpelConfigPath)) {\n checks.push({ name: 'Skalpel config', status: 'ok', message: '~/.skalpel/config.json found' });\n } else {\n checks.push({ name: 'Skalpel config', status: 'warn', message: 'No ~/.skalpel/config.json — run \"npx skalpel\" to set up' });\n }\n\n // 2b. Report active mode (proxy vs direct).\n let mode: 'proxy' | 'direct' = 'proxy';\n try {\n const raw = JSON.parse(fs.readFileSync(skalpelConfigPath, 'utf-8'));\n if (raw.mode === 'direct') mode = 'direct';\n } catch {\n // missing/invalid config — default to proxy, same as loadConfig()\n }\n const modeMessage = mode === 'direct'\n ? 'direct (agents point at api.skalpel.ai)'\n : 'proxy (local proxy on ports 18100/18101/18102)';\n checks.push({ name: 'mode', status: 'ok', message: modeMessage });\n\n // 3. Check proxy endpoint reachability\n const baseURL = 'https://api.skalpel.ai';\n try {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), 5000);\n const response = await fetch(`${baseURL}/health`, { signal: controller.signal });\n clearTimeout(timeout);\n if (response.ok) {\n checks.push({ name: 'Skalpel backend', status: 'ok', message: `${baseURL} reachable (HTTP ${response.status})` });\n } else {\n checks.push({ name: 'Skalpel backend', status: 'warn', message: `${baseURL} responded with HTTP ${response.status}` });\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n checks.push({ name: 'Skalpel backend', status: 'fail', message: `Cannot reach ${baseURL} — ${msg}` });\n }\n\n // 4. Check local proxy health\n let proxyPort = 18100;\n try {\n const raw = JSON.parse(fs.readFileSync(skalpelConfigPath, 'utf-8'));\n proxyPort = raw.anthropicPort ?? 18100;\n } catch {\n // use default\n }\n try {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), 2000);\n const res = await fetch(`http://localhost:${proxyPort}/health`, { signal: controller.signal });\n clearTimeout(timeout);\n if (res.ok) {\n checks.push({ name: 'Local proxy', status: 'ok', message: `Running on port ${proxyPort}` });\n } else {\n checks.push({ name: 'Local proxy', status: 'warn', message: `Port ${proxyPort} responded with HTTP ${res.status}` });\n }\n } catch {\n checks.push({ name: 'Local proxy', status: 'warn', message: `Not running on port ${proxyPort}. Run \"npx skalpel start\" to start.` });\n }\n\n // 4b. Check Cursor proxy health\n let cursorPort = 18102;\n try {\n const raw = JSON.parse(fs.readFileSync(skalpelConfigPath, 'utf-8'));\n cursorPort = raw.cursorPort ?? 18102;\n } catch {\n // use default\n }\n try {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), 2000);\n const res = await fetch(`http://localhost:${cursorPort}/health`, { signal: controller.signal });\n clearTimeout(timeout);\n if (res.ok) {\n checks.push({ name: 'Cursor proxy', status: 'ok', message: `Running on port ${cursorPort}` });\n } else {\n checks.push({ name: 'Cursor proxy', status: 'warn', message: `Port ${cursorPort} responded with HTTP ${res.status}` });\n }\n } catch {\n checks.push({ name: 'Cursor proxy', status: 'warn', message: `Not running on port ${cursorPort}. Run \"npx skalpel start\" to start.` });\n }\n\n // 5. Detect coding agents\n const agents = detectAgents();\n for (const agent of agents) {\n if (agent.installed) {\n const ver = agent.version ? ` v${agent.version}` : '';\n const configured = agent.configPath && fs.existsSync(agent.configPath) ? ' (configured)' : '';\n checks.push({ name: agent.name, status: 'ok', message: `Installed${ver}${configured}` });\n } else {\n checks.push({ name: agent.name, status: 'warn', message: 'Not installed' });\n }\n }\n\n // 6. Codex-specific checks: TOML wire_api pin + proxy probe.\n // Use the OpenAI port resolved from the Skalpel config above (same\n // source as the local-proxy check), default 18101.\n let openaiPort = 18101;\n try {\n const raw = JSON.parse(fs.readFileSync(skalpelConfigPath, 'utf-8'));\n if (typeof raw.openaiPort === 'number') openaiPort = raw.openaiPort;\n } catch {\n // use default\n }\n const config: DoctorProxyConfig = { openaiPort };\n checks.push(checkCodexConfig(config));\n checks.push(await checkCodexProxyProbe(config));\n // Codex WS handshake probe — scoped to port 18101 / /v1/responses.\n checks.push(await checkCodexWebSocket(config));\n\n // Print results\n const icons: Record<DoctorCheck['status'], string> = { ok: '+', warn: '!', fail: 'x', error: 'x', skipped: '-' };\n for (const check of checks) {\n const icon = icons[check.status];\n print(` [${icon}] ${check.name}: ${check.message}`);\n }\n\n const failures = checks.filter((c) => c.status === 'fail' || c.status === 'error');\n const warnings = checks.filter((c) => c.status === 'warn');\n print('');\n if (failures.length > 0) {\n print(` ${failures.length} issue(s) found. Fix the above errors to use Skalpel.`);\n } else if (warnings.length > 0) {\n print(` All critical checks passed. ${warnings.length} warning(s).`);\n } else {\n print(' All checks passed. Skalpel is ready.');\n }\n print('');\n}\n","import { execSync } from 'node:child_process';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport os from 'node:os';\n\nexport interface DetectedAgent {\n name: 'claude-code' | 'codex' | 'cursor';\n installed: boolean;\n version: string | null;\n configPath: string | null;\n}\n\nfunction whichCommand(): string {\n return process.platform === 'win32' ? 'where' : 'which';\n}\n\nfunction tryExec(cmd: string): string | null {\n try {\n return execSync(cmd, { encoding: 'utf-8', timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'] }).trim();\n } catch {\n return null;\n }\n}\n\nfunction detectClaudeCode(): DetectedAgent {\n const agent: DetectedAgent = {\n name: 'claude-code',\n installed: false,\n version: null,\n configPath: null,\n };\n\n // Check binary\n const binaryPath = tryExec(`${whichCommand()} claude`);\n const hasBinary = binaryPath !== null && binaryPath.length > 0;\n\n // Check config directory\n const claudeDir = path.join(os.homedir(), '.claude');\n const hasConfigDir = fs.existsSync(claudeDir);\n\n agent.installed = hasBinary || hasConfigDir;\n\n if (hasBinary) {\n const versionOutput = tryExec('claude --version');\n if (versionOutput) {\n // Extract version number from output\n const match = versionOutput.match(/(\\d+\\.\\d+[\\w.-]*)/);\n agent.version = match ? match[1] : versionOutput;\n }\n }\n\n const settingsPath = path.join(claudeDir, 'settings.json');\n if (fs.existsSync(settingsPath)) {\n agent.configPath = settingsPath;\n } else if (hasConfigDir) {\n // Config dir exists but no settings.json yet — we'll create it during configuration\n agent.configPath = settingsPath;\n }\n\n return agent;\n}\n\nfunction detectCodex(): DetectedAgent {\n const agent: DetectedAgent = {\n name: 'codex',\n installed: false,\n version: null,\n configPath: null,\n };\n\n // Check binary\n const binaryPath = tryExec(`${whichCommand()} codex`);\n const hasBinary = binaryPath !== null && binaryPath.length > 0;\n\n // Check config directory\n const codexConfigDir = process.platform === 'win32'\n ? path.join(os.homedir(), 'AppData', 'Roaming', 'codex')\n : path.join(os.homedir(), '.codex');\n const hasConfigDir = fs.existsSync(codexConfigDir);\n\n agent.installed = hasBinary || hasConfigDir;\n\n if (hasBinary) {\n const versionOutput = tryExec('codex --version');\n if (versionOutput) {\n const match = versionOutput.match(/(\\d+\\.\\d+[\\w.-]*)/);\n agent.version = match ? match[1] : versionOutput;\n }\n }\n\n // Codex uses config.toml (not config.json)\n const configFile = path.join(codexConfigDir, 'config.toml');\n if (fs.existsSync(configFile)) {\n agent.configPath = configFile;\n } else if (hasConfigDir) {\n agent.configPath = configFile;\n }\n\n return agent;\n}\n\nfunction detectCursor(): DetectedAgent {\n const agent: DetectedAgent = {\n name: 'cursor',\n installed: false,\n version: null,\n configPath: null,\n };\n\n // Check binary\n const binaryPath = tryExec(`${whichCommand()} cursor`);\n const hasBinary = binaryPath !== null && binaryPath.length > 0;\n\n // Check config directory (VS Code-style, platform-specific)\n let cursorConfigDir: string;\n if (process.platform === 'darwin') {\n cursorConfigDir = path.join(os.homedir(), 'Library', 'Application Support', 'Cursor', 'User');\n } else if (process.platform === 'win32') {\n cursorConfigDir = path.join(process.env.APPDATA ?? path.join(os.homedir(), 'AppData', 'Roaming'), 'Cursor', 'User');\n } else {\n cursorConfigDir = path.join(os.homedir(), '.config', 'Cursor', 'User');\n }\n const hasConfigDir = fs.existsSync(cursorConfigDir);\n\n agent.installed = hasBinary || hasConfigDir;\n\n if (hasBinary) {\n const versionOutput = tryExec('cursor --version');\n if (versionOutput) {\n const match = versionOutput.match(/(\\d+\\.\\d+[\\w.-]*)/);\n agent.version = match ? match[1] : versionOutput;\n }\n }\n\n const settingsPath = path.join(cursorConfigDir, 'settings.json');\n if (fs.existsSync(settingsPath)) {\n agent.configPath = settingsPath;\n } else if (hasConfigDir) {\n agent.configPath = settingsPath;\n }\n\n return agent;\n}\n\nexport function detectAgents(): DetectedAgent[] {\n return [detectClaudeCode(), detectCodex(), detectCursor()];\n}\n","import { validateApiKey } from './utils.js';\n\ninterface BenchmarkResult {\n requestIndex: number;\n model: string;\n directLatencyMs: number;\n proxyLatencyMs: number;\n overheadMs: number;\n savingsUsd: number | null;\n cacheHit: boolean;\n}\n\nfunction print(msg: string): void {\n console.log(msg);\n}\n\nasync function timedFetch(\n url: string,\n body: object,\n headers: Record<string, string>,\n): Promise<{ latencyMs: number; status: number; headers: Headers; body: any }> {\n const start = performance.now();\n const response = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', ...headers },\n body: JSON.stringify(body),\n });\n const latencyMs = performance.now() - start;\n let responseBody: any = null;\n try {\n responseBody = await response.json();\n } catch {\n // ignore\n }\n return { latencyMs, status: response.status, headers: response.headers, body: responseBody };\n}\n\nexport async function runBenchmark(): Promise<void> {\n print('');\n print(' Skalpel Benchmark');\n print(' ─────────────────');\n print('');\n\n const apiKey = process.env.SKALPEL_API_KEY ?? '';\n if (!validateApiKey(apiKey)) {\n print(' Error: SKALPEL_API_KEY not set or invalid. Run \"npx skalpel doctor\" to diagnose.');\n print('');\n process.exit(1);\n }\n\n const baseURL = process.env.SKALPEL_BASE_URL ?? 'https://api.skalpel.ai';\n const testPrompts = [\n { model: 'gpt-4o-mini', messages: [{ role: 'user', content: 'Say hello in one word.' }] },\n { model: 'gpt-4o-mini', messages: [{ role: 'user', content: 'What is 2+2?' }] },\n { model: 'gpt-4o-mini', messages: [{ role: 'user', content: 'Say hello in one word.' }] },\n ];\n\n print(` Proxy: ${baseURL}`);\n print(` Running ${testPrompts.length} test requests...`);\n print('');\n\n const results: BenchmarkResult[] = [];\n\n for (let i = 0; i < testPrompts.length; i++) {\n const prompt = testPrompts[i];\n print(` Request ${i + 1}/${testPrompts.length}: ${prompt.model} — \"${prompt.messages[0].content}\"`);\n\n // Request through proxy\n let proxyLatencyMs = -1;\n let savingsUsd: number | null = null;\n let cacheHit = false;\n try {\n const proxyResult = await timedFetch(\n `${baseURL}/v1/chat/completions`,\n prompt,\n { Authorization: `Bearer ${apiKey}` },\n );\n proxyLatencyMs = Math.round(proxyResult.latencyMs);\n const savingsHeader = proxyResult.headers.get('x-skalpel-savings-usd');\n if (savingsHeader) savingsUsd = parseFloat(savingsHeader);\n cacheHit = proxyResult.headers.get('x-skalpel-cache-hit') === 'true';\n } catch (err) {\n print(` Proxy request failed: ${err instanceof Error ? err.message : String(err)}`);\n }\n\n results.push({\n requestIndex: i + 1,\n model: prompt.model,\n directLatencyMs: 0,\n proxyLatencyMs,\n overheadMs: 0,\n savingsUsd,\n cacheHit,\n });\n\n const cacheStr = cacheHit ? ' (cache hit)' : '';\n const savingsStr = savingsUsd !== null ? ` | savings: $${savingsUsd.toFixed(4)}` : '';\n print(` Proxy: ${proxyLatencyMs}ms${cacheStr}${savingsStr}`);\n }\n\n // Summary\n print('');\n print(' Summary');\n print(' ───────');\n const validResults = results.filter((r) => r.proxyLatencyMs >= 0);\n if (validResults.length === 0) {\n print(' No successful requests. Check your API key and proxy endpoint.');\n } else {\n const avgProxy = Math.round(validResults.reduce((s, r) => s + r.proxyLatencyMs, 0) / validResults.length);\n const cacheHits = validResults.filter((r) => r.cacheHit).length;\n const totalSavings = validResults.reduce((s, r) => s + (r.savingsUsd ?? 0), 0);\n\n print(` Requests: ${validResults.length}`);\n print(` Avg latency: ${avgProxy}ms (proxy)`);\n print(` Cache hits: ${cacheHits}/${validResults.length}`);\n if (totalSavings > 0) {\n print(` Savings: $${totalSavings.toFixed(4)}`);\n }\n }\n print('');\n}\n","import * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport { validateApiKey } from './utils.js';\n\nfunction print(msg: string): void {\n console.log(msg);\n}\n\nexport async function runReplay(filePaths: string[]): Promise<void> {\n print('');\n print(' Skalpel Replay');\n print(' ──────────────');\n print('');\n\n if (filePaths.length === 0) {\n print(' Usage: skalpel replay <request-file.json> [request-file2.json ...]');\n print('');\n print(' Replays saved request files through the Skalpel proxy.');\n print(' Each file should be a JSON object with \"model\" and \"messages\" fields.');\n print('');\n process.exit(1);\n }\n\n const apiKey = process.env.SKALPEL_API_KEY ?? '';\n if (!validateApiKey(apiKey)) {\n print(' Error: SKALPEL_API_KEY not set or invalid. Run \"npx skalpel doctor\" to diagnose.');\n print('');\n process.exit(1);\n }\n\n const baseURL = process.env.SKALPEL_BASE_URL ?? 'https://api.skalpel.ai';\n print(` Proxy: ${baseURL}`);\n print(` Replaying ${filePaths.length} request file(s)...`);\n print('');\n\n let successCount = 0;\n let failCount = 0;\n\n for (const filePath of filePaths) {\n const resolved = path.resolve(filePath);\n print(` File: ${resolved}`);\n\n if (!fs.existsSync(resolved)) {\n print(` Error: file not found`);\n failCount++;\n continue;\n }\n\n let requestBody: any;\n try {\n const raw = fs.readFileSync(resolved, 'utf-8');\n requestBody = JSON.parse(raw);\n } catch (err) {\n print(` Error: invalid JSON — ${err instanceof Error ? err.message : String(err)}`);\n failCount++;\n continue;\n }\n\n if (!requestBody.model || !requestBody.messages) {\n print(' Error: request file must contain \"model\" and \"messages\" fields');\n failCount++;\n continue;\n }\n\n const model = requestBody.model;\n const messageCount = Array.isArray(requestBody.messages) ? requestBody.messages.length : 0;\n print(` Model: ${model} | Messages: ${messageCount}`);\n\n try {\n const start = performance.now();\n const response = await fetch(`${baseURL}/v1/chat/completions`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${apiKey}`,\n },\n body: JSON.stringify(requestBody),\n });\n const latencyMs = Math.round(performance.now() - start);\n\n if (!response.ok) {\n print(` Failed: HTTP ${response.status}`);\n failCount++;\n continue;\n }\n\n const body = await response.json() as any;\n const content = body?.choices?.[0]?.message?.content ?? body?.content?.[0]?.text ?? '(no content)';\n const cacheHit = response.headers.get('x-skalpel-cache-hit') === 'true';\n const savings = response.headers.get('x-skalpel-savings-usd');\n\n print(` Status: ${response.status} | Latency: ${latencyMs}ms${cacheHit ? ' (cache hit)' : ''}`);\n if (savings) print(` Savings: $${parseFloat(savings).toFixed(4)}`);\n print(` Response: ${content.slice(0, 120)}${content.length > 120 ? '...' : ''}`);\n successCount++;\n } catch (err) {\n print(` Error: ${err instanceof Error ? err.message : String(err)}`);\n failCount++;\n }\n print('');\n }\n\n print(' ──────────────');\n print(` Done: ${successCount} succeeded, ${failCount} failed`);\n print('');\n}\n","import { spawn } from 'node:child_process';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { loadConfig } from '../proxy/config.js';\nimport type { ProxyConfig } from '../proxy/types.js';\nimport { readPid } from '../proxy/pid.js';\nimport { isProxyAlive } from '../proxy/health-check.js';\nimport { isServiceInstalled, startService } from './service/install.js';\nimport { detectAgents } from './agents/detect.js';\nimport { configureAgent } from './agents/configure.js';\nimport { configureShellEnvVars } from './agents/shell.js';\n\nfunction print(msg: string): void {\n console.log(msg);\n}\n\nfunction reconfigureAgents(config: ProxyConfig): void {\n const direct = config.mode === 'direct';\n const agents = detectAgents();\n for (const agent of agents) {\n if (agent.installed) {\n try { configureAgent(agent, config, direct); } catch { /* best effort */ }\n }\n }\n try { configureShellEnvVars(agents.filter(a => a.installed), config); } catch { /* best effort */ }\n}\n\nexport async function runStart(): Promise<void> {\n const config = loadConfig();\n\n if (!config.apiKey) {\n print(' Error: No API key configured. Run \"skalpel init\" or set SKALPEL_API_KEY.');\n process.exit(1);\n }\n\n const existingPid = readPid(config.pidFile);\n if (existingPid !== null) {\n print(` Proxy is already running (pid=${existingPid}).`);\n return;\n }\n\n // PID file says stopped — but check if proxy is actually alive (PID file may be stale)\n const alive = await isProxyAlive(config.anthropicPort);\n if (alive) {\n print(' Proxy is already running (detected via health check).');\n return;\n }\n\n // If an OS service is installed, reload it instead of spawning a one-off process.\n // This ensures the proxy is managed by the service and auto-restarts on reboot.\n if (isServiceInstalled()) {\n startService();\n reconfigureAgents(config);\n print(` Skalpel proxy started via system service on ports ${config.anthropicPort}, ${config.openaiPort}, and ${config.cursorPort}`);\n return;\n }\n\n const dirname = path.dirname(fileURLToPath(import.meta.url));\n const runnerScript = path.resolve(dirname, 'proxy-runner.js');\n\n const child = spawn(process.execPath, [runnerScript], {\n detached: true,\n stdio: 'ignore',\n });\n\n child.unref();\n\n reconfigureAgents(config);\n print(` Skalpel proxy started on ports ${config.anthropicPort}, ${config.openaiPort}, and ${config.cursorPort}`);\n}\n","import fs from 'node:fs';\nimport path from 'node:path';\nimport os from 'node:os';\nimport type { ProxyConfig } from './types.js';\n\nfunction expandHome(filePath: string): string {\n if (filePath.startsWith('~')) {\n return path.join(os.homedir(), filePath.slice(1));\n }\n return filePath;\n}\n\nconst DEFAULTS: ProxyConfig = {\n apiKey: '',\n remoteBaseUrl: 'https://api.skalpel.ai',\n anthropicDirectUrl: 'https://api.anthropic.com',\n openaiDirectUrl: 'https://api.openai.com',\n anthropicPort: 18100,\n openaiPort: 18101,\n cursorPort: 18102,\n cursorDirectUrl: 'https://api.openai.com',\n logLevel: 'info',\n logFile: '~/.skalpel/logs/proxy.log',\n pidFile: '~/.skalpel/proxy.pid',\n configFile: '~/.skalpel/config.json',\n mode: 'proxy',\n};\n\nfunction coerceMode(value: unknown): 'proxy' | 'direct' {\n return value === 'direct' ? 'direct' : 'proxy';\n}\n\nexport function loadConfig(configPath?: string): ProxyConfig {\n const filePath = expandHome(configPath ?? DEFAULTS.configFile);\n let fileConfig: Partial<ProxyConfig> = {};\n\n try {\n const raw = fs.readFileSync(filePath, 'utf-8');\n fileConfig = JSON.parse(raw) as Partial<ProxyConfig>;\n } catch {\n // Config file doesn't exist or is invalid — use defaults\n }\n\n return {\n apiKey: fileConfig.apiKey ?? DEFAULTS.apiKey,\n remoteBaseUrl: fileConfig.remoteBaseUrl ?? DEFAULTS.remoteBaseUrl,\n anthropicDirectUrl: fileConfig.anthropicDirectUrl ?? DEFAULTS.anthropicDirectUrl,\n openaiDirectUrl: fileConfig.openaiDirectUrl ?? DEFAULTS.openaiDirectUrl,\n anthropicPort: fileConfig.anthropicPort ?? DEFAULTS.anthropicPort,\n openaiPort: fileConfig.openaiPort ?? DEFAULTS.openaiPort,\n cursorPort: fileConfig.cursorPort ?? DEFAULTS.cursorPort,\n cursorDirectUrl: fileConfig.cursorDirectUrl ?? DEFAULTS.cursorDirectUrl,\n logLevel: fileConfig.logLevel ?? DEFAULTS.logLevel,\n logFile: expandHome(fileConfig.logFile ?? DEFAULTS.logFile),\n pidFile: expandHome(fileConfig.pidFile ?? DEFAULTS.pidFile),\n configFile: filePath,\n mode: coerceMode(fileConfig.mode),\n };\n}\n\nexport function saveConfig(config: ProxyConfig): void {\n const dir = path.dirname(config.configFile);\n fs.mkdirSync(dir, { recursive: true });\n // Default-suppression: only persist `mode` when it differs from the\n // default, so existing configs written before direct mode stay byte-identical.\n const { mode, ...rest } = config;\n const serializable: Record<string, unknown> = { ...rest };\n if (mode === 'direct') {\n serializable.mode = mode;\n }\n fs.writeFileSync(config.configFile, JSON.stringify(serializable, null, 2) + '\\n');\n}\n","import fs from 'node:fs';\nimport path from 'node:path';\nimport { execSync } from 'node:child_process';\n\ninterface PidRecord {\n pid: number;\n startTime: string | null;\n}\n\nexport function writePid(pidFile: string): void {\n fs.mkdirSync(path.dirname(pidFile), { recursive: true });\n const record: PidRecord = {\n pid: process.pid,\n startTime: getStartTime(process.pid),\n };\n fs.writeFileSync(pidFile, JSON.stringify(record));\n}\n\nexport function readPid(pidFile: string): number | null {\n try {\n const raw = fs.readFileSync(pidFile, 'utf-8').trim();\n try {\n const parsed = JSON.parse(raw);\n if (parsed && typeof parsed === 'object' && typeof parsed.pid === 'number' && !isNaN(parsed.pid)) {\n const record = parsed as PidRecord;\n if (record.startTime == null) {\n return isRunning(record.pid) ? record.pid : null;\n }\n return isRunningWithIdentity(record.pid, record.startTime) ? record.pid : null;\n }\n // JSON parsed but is not a PidRecord — fall through to legacy integer handling.\n } catch {\n // Not JSON — fall through to legacy integer handling.\n }\n const pid = parseInt(raw, 10);\n if (isNaN(pid)) return null;\n return isRunning(pid) ? pid : null;\n } catch {\n return null;\n }\n}\n\nexport function isRunning(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch {\n return false;\n }\n}\n\nexport function getStartTime(pid: number): string | null {\n try {\n if (process.platform === 'linux') {\n const stat = fs.readFileSync(`/proc/${pid}/stat`, 'utf-8');\n const rparen = stat.lastIndexOf(')');\n if (rparen < 0) return null;\n const fields = stat.slice(rparen + 2).split(' ');\n return fields[19] ?? null;\n }\n if (process.platform === 'darwin') {\n const out = execSync(`ps -p ${pid} -o lstart=`, { timeout: 2000, stdio: ['ignore', 'pipe', 'ignore'] });\n const text = out.toString().trim();\n return text || null;\n }\n return null;\n } catch {\n return null;\n }\n}\n\nexport function isRunningWithIdentity(pid: number, expectedStartTime: string): boolean {\n try {\n if (process.platform !== 'linux' && process.platform !== 'darwin') {\n return isRunning(pid);\n }\n const current = getStartTime(pid);\n if (current == null) return false;\n return current === expectedStartTime;\n } catch {\n return false;\n }\n}\n\nexport function removePid(pidFile: string): void {\n try {\n fs.unlinkSync(pidFile);\n } catch {\n // Already removed\n }\n}\n","/**\n * Shared health-check helper — determines whether the local proxy is alive\n * by hitting its /health endpoint. Used by CLI commands as a fallback when\n * the PID file is missing or stale.\n */\n\nexport async function isProxyAlive(port: number, timeoutMs: number = 2000): Promise<boolean> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n try {\n const res = await fetch(`http://localhost:${port}/health`, { signal: controller.signal });\n return res.ok;\n } catch {\n return false;\n } finally {\n clearTimeout(timer);\n }\n}\n","import fs from 'node:fs';\nimport path from 'node:path';\nimport os from 'node:os';\nimport { execSync } from 'node:child_process';\nimport { fileURLToPath } from 'node:url';\nimport { detectOS } from './detect-os.js';\nimport { generateLaunchdPlist, generateSystemdUnit, generateWindowsTask } from './templates.js';\nimport type { ProxyConfig } from '../../proxy/types.js';\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\n\nfunction resolveProxyRunnerPath(): string {\n // Look for the proxy-runner in the package's dist directory\n // When installed globally via npm, this will be in the package's dist/cli/\n const candidates = [\n path.join(__dirname, '..', 'proxy-runner.js'), // dist/cli/proxy-runner.js relative to dist/cli/service/\n path.join(__dirname, 'proxy-runner.js'), // same dir\n path.join(__dirname, '..', '..', 'cli', 'proxy-runner.js'), // dist/cli/proxy-runner.js from deeper\n ];\n\n for (const candidate of candidates) {\n if (fs.existsSync(candidate)) {\n return path.resolve(candidate);\n }\n }\n\n // Fallback: try to find it via npm root\n try {\n const npmRoot = execSync('npm root -g', { encoding: 'utf-8' }).trim();\n const globalPath = path.join(npmRoot, 'skalpel', 'dist', 'cli', 'proxy-runner.js');\n if (fs.existsSync(globalPath)) return globalPath;\n } catch {\n // ignore\n }\n\n // Last resort: use the src path for development\n const devPath = path.resolve(process.cwd(), 'dist', 'cli', 'proxy-runner.js');\n return devPath;\n}\n\nfunction getMacOSPlistPath(): string {\n return path.join(os.homedir(), 'Library', 'LaunchAgents', 'ai.skalpel.proxy.plist');\n}\n\nfunction getLinuxUnitPath(): string {\n return path.join(os.homedir(), '.config', 'systemd', 'user', 'skalpel-proxy.service');\n}\n\nexport function installService(config: ProxyConfig): void {\n const osInfo = detectOS();\n const proxyRunnerPath = resolveProxyRunnerPath();\n\n // Ensure log directory exists\n const logDir = path.join(os.homedir(), '.skalpel', 'logs');\n fs.mkdirSync(logDir, { recursive: true });\n\n switch (osInfo.platform) {\n case 'macos': {\n const plistPath = getMacOSPlistPath();\n const plistDir = path.dirname(plistPath);\n fs.mkdirSync(plistDir, { recursive: true });\n\n const plist = generateLaunchdPlist(config, proxyRunnerPath);\n fs.writeFileSync(plistPath, plist);\n\n try {\n // Unload first if already loaded (idempotent)\n execSync(`launchctl unload \"${plistPath}\" 2>/dev/null || true`, { stdio: 'pipe' });\n execSync(`launchctl load \"${plistPath}\"`, { stdio: 'pipe' });\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.warn(` Warning: Could not register launchd service: ${msg}`);\n console.warn(` You can manually load it: launchctl load \"${plistPath}\"`);\n }\n break;\n }\n\n case 'linux': {\n const unitPath = getLinuxUnitPath();\n const unitDir = path.dirname(unitPath);\n fs.mkdirSync(unitDir, { recursive: true });\n\n const unit = generateSystemdUnit(config, proxyRunnerPath);\n fs.writeFileSync(unitPath, unit);\n\n try {\n execSync('systemctl --user daemon-reload', { stdio: 'pipe' });\n execSync('systemctl --user enable skalpel-proxy', { stdio: 'pipe' });\n execSync('systemctl --user start skalpel-proxy', { stdio: 'pipe' });\n } catch {\n // Fallback: try .desktop autostart\n try {\n const autostartDir = path.join(os.homedir(), '.config', 'autostart');\n fs.mkdirSync(autostartDir, { recursive: true });\n const desktopEntry = `[Desktop Entry]\nType=Application\nName=Skalpel Proxy\nExec=${process.execPath} ${proxyRunnerPath}\nHidden=false\nNoDisplay=true\nX-GNOME-Autostart-enabled=true\n`;\n fs.writeFileSync(path.join(autostartDir, 'skalpel-proxy.desktop'), desktopEntry);\n console.warn(' Warning: systemd --user not available. Created .desktop autostart entry instead.');\n } catch (err2) {\n const msg = err2 instanceof Error ? err2.message : String(err2);\n console.warn(` Warning: Could not register service: ${msg}`);\n console.warn(' You can start the proxy manually: skalpel start');\n }\n }\n break;\n }\n\n case 'windows': {\n const args = generateWindowsTask(config, proxyRunnerPath);\n try {\n execSync(`schtasks ${args.join(' ')}`, { stdio: 'pipe' });\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.warn(` Warning: Could not create scheduled task: ${msg}`);\n console.warn(' You can start the proxy manually: skalpel start');\n }\n break;\n }\n }\n}\n\nexport function isServiceInstalled(): boolean {\n const osInfo = detectOS();\n\n switch (osInfo.platform) {\n case 'macos': {\n const plistPath = getMacOSPlistPath();\n return fs.existsSync(plistPath);\n }\n case 'linux': {\n const unitPath = getLinuxUnitPath();\n return fs.existsSync(unitPath);\n }\n case 'windows': {\n try {\n execSync('schtasks /query /tn SkalpelProxy', { stdio: 'pipe' });\n return true;\n } catch {\n return false;\n }\n }\n }\n}\n\nexport function stopService(): void {\n const osInfo = detectOS();\n\n switch (osInfo.platform) {\n case 'macos': {\n const plistPath = getMacOSPlistPath();\n if (!fs.existsSync(plistPath)) return;\n try {\n execSync(`launchctl unload \"${plistPath}\"`, { stdio: 'pipe' });\n } catch {\n // ignore\n }\n break;\n }\n case 'linux': {\n try {\n execSync('systemctl --user stop skalpel-proxy', { stdio: 'pipe' });\n } catch {\n // ignore\n }\n break;\n }\n case 'windows': {\n try {\n execSync('schtasks /end /tn SkalpelProxy', { stdio: 'pipe' });\n } catch {\n // ignore\n }\n break;\n }\n }\n}\n\nexport function startService(): void {\n const osInfo = detectOS();\n\n switch (osInfo.platform) {\n case 'macos': {\n const plistPath = getMacOSPlistPath();\n if (!fs.existsSync(plistPath)) return;\n try {\n execSync(`launchctl load \"${plistPath}\"`, { stdio: 'pipe' });\n } catch {\n // ignore\n }\n break;\n }\n case 'linux': {\n try {\n execSync('systemctl --user start skalpel-proxy', { stdio: 'pipe' });\n } catch {\n // ignore\n }\n break;\n }\n case 'windows': {\n try {\n execSync('schtasks /run /tn SkalpelProxy', { stdio: 'pipe' });\n } catch {\n // ignore\n }\n break;\n }\n }\n}\n\nexport function uninstallService(): void {\n const osInfo = detectOS();\n\n switch (osInfo.platform) {\n case 'macos': {\n const plistPath = getMacOSPlistPath();\n try {\n execSync(`launchctl unload \"${plistPath}\" 2>/dev/null || true`, { stdio: 'pipe' });\n } catch {\n // ignore\n }\n if (fs.existsSync(plistPath)) fs.unlinkSync(plistPath);\n break;\n }\n\n case 'linux': {\n try {\n execSync('systemctl --user stop skalpel-proxy 2>/dev/null || true', { stdio: 'pipe' });\n execSync('systemctl --user disable skalpel-proxy 2>/dev/null || true', { stdio: 'pipe' });\n } catch {\n // ignore\n }\n const unitPath = getLinuxUnitPath();\n if (fs.existsSync(unitPath)) fs.unlinkSync(unitPath);\n\n // Also remove .desktop autostart if it exists\n const desktopPath = path.join(os.homedir(), '.config', 'autostart', 'skalpel-proxy.desktop');\n if (fs.existsSync(desktopPath)) fs.unlinkSync(desktopPath);\n break;\n }\n\n case 'windows': {\n try {\n execSync('schtasks /delete /tn SkalpelProxy /f', { stdio: 'pipe' });\n } catch {\n // ignore\n }\n break;\n }\n }\n}\n","import os from 'node:os';\nimport { execSync } from 'node:child_process';\n\nexport interface OSInfo {\n platform: 'macos' | 'linux' | 'windows';\n shell: 'bash' | 'zsh' | 'fish' | 'powershell' | 'cmd';\n homeDir: string;\n}\n\nfunction detectShell(): OSInfo['shell'] {\n if (process.platform === 'win32') {\n // Check if running in PowerShell\n if (process.env.PSModulePath || process.env.POWERSHELL_DISTRIBUTION_CHANNEL) {\n return 'powershell';\n }\n return 'cmd';\n }\n\n // On Unix, check the user's default shell from $SHELL env\n const shellPath = process.env.SHELL ?? '';\n if (shellPath.includes('zsh')) return 'zsh';\n if (shellPath.includes('fish')) return 'fish';\n if (shellPath.includes('bash')) return 'bash';\n\n // Fallback: try to read from /etc/passwd or dscl on macOS\n try {\n if (process.platform === 'darwin') {\n const result = execSync(`dscl . -read /Users/${os.userInfo().username} UserShell`, {\n encoding: 'utf-8',\n timeout: 3000,\n }).trim();\n const shell = result.split(':').pop()?.trim() ?? '';\n if (shell.includes('zsh')) return 'zsh';\n if (shell.includes('fish')) return 'fish';\n if (shell.includes('bash')) return 'bash';\n } else {\n const result = execSync(`getent passwd ${os.userInfo().username}`, {\n encoding: 'utf-8',\n timeout: 3000,\n }).trim();\n const shell = result.split(':').pop() ?? '';\n if (shell.includes('zsh')) return 'zsh';\n if (shell.includes('fish')) return 'fish';\n if (shell.includes('bash')) return 'bash';\n }\n } catch {\n // ignore\n }\n\n return 'bash';\n}\n\nexport function detectOS(): OSInfo {\n let platform: OSInfo['platform'];\n switch (process.platform) {\n case 'darwin':\n platform = 'macos';\n break;\n case 'win32':\n platform = 'windows';\n break;\n default:\n platform = 'linux';\n break;\n }\n\n return {\n platform,\n shell: detectShell(),\n homeDir: os.homedir(),\n };\n}\n","import os from 'node:os';\nimport path from 'node:path';\nimport type { ProxyConfig } from '../../proxy/types.js';\n\nexport function generateLaunchdPlist(config: ProxyConfig, proxyRunnerPath: string): string {\n const logDir = path.join(os.homedir(), '.skalpel', 'logs');\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n <key>Label</key>\n <string>ai.skalpel.proxy</string>\n <key>ProgramArguments</key>\n <array>\n <string>${process.execPath}</string>\n <string>${proxyRunnerPath}</string>\n </array>\n <key>RunAtLoad</key>\n <true/>\n <key>KeepAlive</key>\n <true/>\n <key>StandardOutPath</key>\n <string>${path.join(logDir, 'proxy-stdout.log')}</string>\n <key>StandardErrorPath</key>\n <string>${path.join(logDir, 'proxy-stderr.log')}</string>\n <key>EnvironmentVariables</key>\n <dict>\n <key>SKALPEL_ANTHROPIC_PORT</key>\n <string>${config.anthropicPort}</string>\n <key>SKALPEL_OPENAI_PORT</key>\n <string>${config.openaiPort}</string>\n <key>SKALPEL_CURSOR_PORT</key>\n <string>${config.cursorPort}</string>\n </dict>\n</dict>\n</plist>`;\n}\n\nexport function generateSystemdUnit(config: ProxyConfig, proxyRunnerPath: string): string {\n return `[Unit]\nDescription=Skalpel Proxy\nAfter=network.target\n\n[Service]\nType=simple\nExecStart=${process.execPath} ${proxyRunnerPath}\nRestart=always\nRestartSec=5\nEnvironment=SKALPEL_ANTHROPIC_PORT=${config.anthropicPort}\nEnvironment=SKALPEL_OPENAI_PORT=${config.openaiPort}\nEnvironment=SKALPEL_CURSOR_PORT=${config.cursorPort}\n\n[Install]\nWantedBy=default.target`;\n}\n\nexport function generateWindowsTask(config: ProxyConfig, proxyRunnerPath: string): string[] {\n return [\n '/create',\n '/tn', 'SkalpelProxy',\n '/tr', `\"${process.execPath}\" \"${proxyRunnerPath}\"`,\n '/sc', 'ONLOGON',\n '/rl', 'LIMITED',\n '/f',\n ];\n}\n","import fs from 'node:fs';\nimport path from 'node:path';\nimport os from 'node:os';\nimport type { DetectedAgent } from './detect.js';\nimport type { ProxyConfig } from '../../proxy/types.js';\nimport { saveConfig } from '../../proxy/config.js';\n\nconst CURSOR_API_BASE_URL_KEY = 'openai.apiBaseUrl';\nconst DIRECT_MODE_BASE_URL = 'https://api.skalpel.ai';\nconst CODEX_DIRECT_PROVIDER_ID = 'skalpel';\nconst CODEX_PROXY_PROVIDER_ID = 'skalpel-proxy';\n\nexport type DirectModeSupport = 'supported' | 'unsupported';\n\n/**\n * Per-agent capability for agent-side custom-header injection in direct mode.\n * Matches docs/direct-mode-support-matrix.md — keep in sync.\n */\nexport const DIRECT_MODE_SUPPORT: Record<DetectedAgent['name'], DirectModeSupport> = {\n 'claude-code': 'supported',\n 'codex': 'supported',\n 'cursor': 'unsupported',\n};\n\nfunction ensureDir(dir: string): void {\n fs.mkdirSync(dir, { recursive: true });\n}\n\nfunction createBackup(filePath: string): void {\n if (fs.existsSync(filePath)) {\n fs.copyFileSync(filePath, `${filePath}.skalpel-backup`);\n }\n}\n\n/** Read and parse a JSON file. Returns null if the file cannot be parsed. */\nfunction readJsonFile(filePath: string): Record<string, unknown> | null {\n try {\n return JSON.parse(fs.readFileSync(filePath, 'utf-8'));\n } catch {\n return null;\n }\n}\n\nfunction configureClaudeCode(agent: DetectedAgent, proxyConfig: ProxyConfig, direct = false): void {\n const configPath = agent.configPath ?? path.join(os.homedir(), '.claude', 'settings.json');\n const configDir = path.dirname(configPath);\n ensureDir(configDir);\n createBackup(configPath);\n\n const config = readJsonFile(configPath) ?? {};\n if (!config.env || typeof config.env !== 'object') {\n config.env = {};\n }\n const env = config.env as Record<string, string>;\n\n if (direct) {\n env.ANTHROPIC_BASE_URL = DIRECT_MODE_BASE_URL;\n env.ANTHROPIC_CUSTOM_HEADERS = `X-Skalpel-API-Key: ${proxyConfig.apiKey}`;\n } else {\n env.ANTHROPIC_BASE_URL = `http://localhost:${proxyConfig.anthropicPort}`;\n // Leaving direct-mode header in place would make proxy mode double-auth.\n delete env.ANTHROPIC_CUSTOM_HEADERS;\n }\n\n fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\\n');\n}\n\nfunction readTomlFile(filePath: string): string {\n try {\n return fs.readFileSync(filePath, 'utf-8');\n } catch {\n return '';\n }\n}\n\nfunction setTomlKey(content: string, key: string, value: string): string {\n const pattern = new RegExp(`^${key.replace('.', '\\\\.')}\\\\s*=.*$`, 'm');\n const line = `${key} = \"${value}\"`;\n if (pattern.test(content)) {\n return content.replace(pattern, line);\n }\n // Insert before the first [section] header so the key stays at the top level.\n // Appending to the end would place it inside whatever TOML section comes last.\n const sectionMatch = content.match(/^\\[/m);\n if (sectionMatch && sectionMatch.index !== undefined) {\n return content.slice(0, sectionMatch.index) + line + '\\n' + content.slice(sectionMatch.index);\n }\n const separator = content.length > 0 && !content.endsWith('\\n') ? '\\n' : '';\n return content + separator + line + '\\n';\n}\n\nfunction removeTomlKey(content: string, key: string): string {\n const pattern = new RegExp(`^${key.replace('.', '\\\\.')}\\\\s*=.*\\\\n?`, 'gm');\n return content.replace(pattern, '');\n}\n\nfunction buildCodexDirectProviderBlock(apiKey: string): string {\n // Matches docs/direct-mode-support-matrix.md — a minimal named provider\n // that points Codex at api.skalpel.ai with a static custom header.\n return [\n `[model_providers.${CODEX_DIRECT_PROVIDER_ID}]`,\n `name = \"Skalpel\"`,\n `base_url = \"${DIRECT_MODE_BASE_URL}\"`,\n `wire_api = \"responses\"`,\n `http_headers = { \"X-Skalpel-API-Key\" = \"${apiKey}\" }`,\n ].join('\\n');\n}\n\nfunction upsertCodexDirectProvider(content: string, apiKey: string): string {\n const sectionHeader = `[model_providers.${CODEX_DIRECT_PROVIDER_ID}]`;\n const block = buildCodexDirectProviderBlock(apiKey);\n const idx = content.indexOf(sectionHeader);\n if (idx === -1) {\n const separator = content.length > 0 && !content.endsWith('\\n') ? '\\n\\n' : (content.length > 0 ? '\\n' : '');\n return content + separator + block + '\\n';\n }\n // Replace from the section header up to (but not including) the next\n // [section] header — or to end-of-file.\n const after = content.slice(idx + sectionHeader.length);\n const nextHeaderMatch = after.match(/\\n\\[[^\\]]+\\]/);\n const end = nextHeaderMatch && nextHeaderMatch.index !== undefined\n ? idx + sectionHeader.length + nextHeaderMatch.index\n : content.length;\n return content.slice(0, idx) + block + content.slice(end);\n}\n\nfunction removeCodexDirectProvider(content: string): string {\n const sectionHeader = `[model_providers.${CODEX_DIRECT_PROVIDER_ID}]`;\n const idx = content.indexOf(sectionHeader);\n if (idx === -1) return content;\n const after = content.slice(idx + sectionHeader.length);\n const nextHeaderMatch = after.match(/\\n\\[[^\\]]+\\]/);\n const end = nextHeaderMatch && nextHeaderMatch.index !== undefined\n ? idx + sectionHeader.length + nextHeaderMatch.index\n : content.length;\n const before = content.slice(0, idx).replace(/\\n+$/, '');\n const rest = content.slice(end).replace(/^\\n+/, '');\n return (before.length > 0 && rest.length > 0)\n ? before + '\\n' + rest\n : before + rest;\n}\n\nfunction buildCodexProxyProviderBlock(port: number): string {\n // Proxy mode uses a named provider so Codex pins wire_api = \"responses\"\n // over HTTP — without this, Codex may pick its default wire_api and\n // attempt a non-POST probe or websocket upgrade that the local proxy\n // cannot serve. env_key = \"OPENAI_API_KEY\" ensures Codex sends an\n // Authorization header with whatever placeholder value the user exports.\n // Section-id kept as a literal for grep-based verification.\n return [\n `[model_providers.skalpel-proxy]`,\n `name = \"Skalpel Proxy\"`,\n `base_url = \"http://localhost:${port}/v1\"`,\n `wire_api = \"responses\"`,\n `env_key = \"OPENAI_API_KEY\"`,\n ].join('\\n');\n}\n\nfunction upsertCodexProxyProvider(content: string, port: number): string {\n const sectionHeader = `[model_providers.${CODEX_PROXY_PROVIDER_ID}]`;\n const block = buildCodexProxyProviderBlock(port);\n const idx = content.indexOf(sectionHeader);\n if (idx === -1) {\n const separator = content.length > 0 && !content.endsWith('\\n') ? '\\n\\n' : (content.length > 0 ? '\\n' : '');\n return content + separator + block + '\\n';\n }\n const after = content.slice(idx + sectionHeader.length);\n const nextHeaderMatch = after.match(/\\n\\[[^\\]]+\\]/);\n const end = nextHeaderMatch && nextHeaderMatch.index !== undefined\n ? idx + sectionHeader.length + nextHeaderMatch.index\n : content.length;\n return content.slice(0, idx) + block + content.slice(end);\n}\n\nfunction removeCodexProxyProvider(content: string): string {\n const sectionHeader = `[model_providers.${CODEX_PROXY_PROVIDER_ID}]`;\n const idx = content.indexOf(sectionHeader);\n if (idx === -1) return content;\n const after = content.slice(idx + sectionHeader.length);\n const nextHeaderMatch = after.match(/\\n\\[[^\\]]+\\]/);\n const end = nextHeaderMatch && nextHeaderMatch.index !== undefined\n ? idx + sectionHeader.length + nextHeaderMatch.index\n : content.length;\n const before = content.slice(0, idx).replace(/\\n+$/, '');\n const rest = content.slice(end).replace(/^\\n+/, '');\n return (before.length > 0 && rest.length > 0)\n ? before + '\\n' + rest\n : before + rest;\n}\n\nfunction configureCodex(agent: DetectedAgent, proxyConfig: ProxyConfig, direct = false): void {\n const configDir = process.platform === 'win32'\n ? path.join(os.homedir(), 'AppData', 'Roaming', 'codex')\n : path.join(os.homedir(), '.codex');\n const configPath = agent.configPath ?? path.join(configDir, 'config.toml');\n\n ensureDir(path.dirname(configPath));\n createBackup(configPath);\n\n let content = readTomlFile(configPath);\n\n if (direct) {\n // Direct mode uses a named provider block so we can attach a static\n // X-Skalpel-API-Key header — plain openai_base_url cannot carry headers.\n content = removeTomlKey(content, 'openai_base_url');\n content = setTomlKey(content, 'model_provider', CODEX_DIRECT_PROVIDER_ID);\n content = upsertCodexDirectProvider(content, proxyConfig.apiKey);\n } else {\n // Proxy mode: pin wire_api = \"responses\" via a named model_provider so\n // Codex doesn't fall back to a websocket probe or non-POST method.\n // openai_base_url stays for pre-provider-aware Codex builds; the named\n // provider block is the primary mechanism.\n content = setTomlKey(content, 'openai_base_url', `http://localhost:${proxyConfig.openaiPort}`);\n content = setTomlKey(content, 'model_provider', CODEX_PROXY_PROVIDER_ID);\n content = upsertCodexProxyProvider(content, proxyConfig.openaiPort);\n content = removeCodexDirectProvider(content);\n }\n\n fs.writeFileSync(configPath, content);\n}\n\nfunction getCursorConfigDir(): string {\n if (process.platform === 'darwin') {\n return path.join(os.homedir(), 'Library', 'Application Support', 'Cursor', 'User');\n } else if (process.platform === 'win32') {\n return path.join(process.env.APPDATA ?? path.join(os.homedir(), 'AppData', 'Roaming'), 'Cursor', 'User');\n }\n return path.join(os.homedir(), '.config', 'Cursor', 'User');\n}\n\nfunction configureCursor(agent: DetectedAgent, proxyConfig: ProxyConfig, direct = false): void {\n if (direct) {\n // Cursor's settings.json exposes openai.apiBaseUrl but no custom-header\n // mechanism (see docs/direct-mode-support-matrix.md), so direct mode\n // cannot attach X-Skalpel-API-Key. Skip this agent with a warning — the\n // user keeps Cursor in proxy mode until Cursor ships header support.\n console.warn(` [!] cursor: direct mode not supported (no custom-header injection in Cursor settings). Cursor configuration left unchanged.`);\n return;\n }\n\n const configDir = getCursorConfigDir();\n const configPath = agent.configPath ?? path.join(configDir, 'settings.json');\n ensureDir(path.dirname(configPath));\n createBackup(configPath);\n\n const config = readJsonFile(configPath) ?? {};\n\n // Save the current API base URL as fallback before overriding\n const existingUrl = config[CURSOR_API_BASE_URL_KEY];\n if (typeof existingUrl === 'string' && existingUrl.length > 0) {\n proxyConfig.cursorDirectUrl = existingUrl;\n saveConfig(proxyConfig);\n }\n\n config[CURSOR_API_BASE_URL_KEY] = `http://localhost:${proxyConfig.cursorPort}`;\n fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\\n');\n}\n\nexport function configureAgent(agent: DetectedAgent, proxyConfig: ProxyConfig, direct = false): void {\n switch (agent.name) {\n case 'claude-code':\n configureClaudeCode(agent, proxyConfig, direct);\n break;\n case 'codex':\n configureCodex(agent, proxyConfig, direct);\n break;\n case 'cursor':\n configureCursor(agent, proxyConfig, direct);\n break;\n }\n}\n\nfunction unconfigureClaudeCode(agent: DetectedAgent): void {\n const configPath = agent.configPath ?? path.join(os.homedir(), '.claude', 'settings.json');\n\n // Always surgically remove only the ANTHROPIC_BASE_URL key.\n // Never restore from backup — the user may have changed other settings since install.\n if (!fs.existsSync(configPath)) return;\n\n const config = readJsonFile(configPath);\n if (config === null) {\n // Cannot parse settings.json — do NOT write to it.\n // Writing {} would wipe all Claude Code settings and break the installation.\n console.warn(` [!] Could not parse ${configPath} — skipping to avoid data loss. Remove ANTHROPIC_BASE_URL manually if needed.`);\n return;\n }\n\n if (config.env && typeof config.env === 'object') {\n const env = config.env as Record<string, unknown>;\n delete env.ANTHROPIC_BASE_URL;\n delete env.ANTHROPIC_CUSTOM_HEADERS;\n if (Object.keys(env).length === 0) {\n delete config.env;\n }\n }\n fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\\n');\n\n // Clean up stale backup file\n const backupPath = `${configPath}.skalpel-backup`;\n if (fs.existsSync(backupPath)) {\n fs.unlinkSync(backupPath);\n }\n}\n\nfunction unconfigureCodex(agent: DetectedAgent): void {\n const configDir = process.platform === 'win32'\n ? path.join(os.homedir(), 'AppData', 'Roaming', 'codex')\n : path.join(os.homedir(), '.codex');\n const configPath = agent.configPath ?? path.join(configDir, 'config.toml');\n\n // Always surgically remove only Skalpel-specific keys and the direct-mode\n // named provider block.\n if (fs.existsSync(configPath)) {\n let content = readTomlFile(configPath);\n content = removeTomlKey(content, 'openai_base_url');\n content = removeTomlKey(content, 'model_provider');\n content = removeCodexDirectProvider(content);\n content = removeCodexProxyProvider(content);\n fs.writeFileSync(configPath, content);\n }\n\n // Clean up stale backup file\n const backupPath = `${configPath}.skalpel-backup`;\n if (fs.existsSync(backupPath)) {\n fs.unlinkSync(backupPath);\n }\n}\n\nfunction unconfigureCursor(agent: DetectedAgent): void {\n const configDir = getCursorConfigDir();\n const configPath = agent.configPath ?? path.join(configDir, 'settings.json');\n\n if (!fs.existsSync(configPath)) return;\n\n const config = readJsonFile(configPath);\n if (config === null) {\n console.warn(` [!] Could not parse ${configPath} — skipping to avoid data loss. Remove ${CURSOR_API_BASE_URL_KEY} manually if needed.`);\n return;\n }\n\n delete config[CURSOR_API_BASE_URL_KEY];\n fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\\n');\n\n // Clean up stale backup file\n const backupPath = `${configPath}.skalpel-backup`;\n if (fs.existsSync(backupPath)) {\n fs.unlinkSync(backupPath);\n }\n}\n\nexport function unconfigureAgent(agent: DetectedAgent): void {\n switch (agent.name) {\n case 'claude-code':\n unconfigureClaudeCode(agent);\n break;\n case 'codex':\n unconfigureCodex(agent);\n break;\n case 'cursor':\n unconfigureCursor(agent);\n break;\n }\n}\n","import fs from 'node:fs';\nimport path from 'node:path';\nimport os from 'node:os';\nimport { execSync } from 'node:child_process';\nimport type { DetectedAgent } from './detect.js';\nimport type { ProxyConfig } from '../../proxy/types.js';\n\nconst BEGIN_MARKER = '# BEGIN SKALPEL PROXY - do not edit manually';\nconst END_MARKER = '# END SKALPEL PROXY';\n\nconst PS_BEGIN_MARKER = '# BEGIN SKALPEL PROXY - do not edit manually';\nconst PS_END_MARKER = '# END SKALPEL PROXY';\n\nfunction getUnixProfilePaths(): string[] {\n const home = os.homedir();\n const candidates = [\n path.join(home, '.bashrc'),\n path.join(home, '.zshrc'),\n path.join(home, '.bash_profile'),\n path.join(home, '.profile'),\n ];\n return candidates.filter((p) => fs.existsSync(p));\n}\n\nfunction getPowerShellProfilePath(): string | null {\n if (process.platform !== 'win32') return null;\n\n // Try $PROFILE env first\n if (process.env.PROFILE) return process.env.PROFILE;\n\n // Default PowerShell profile location\n const docsDir = path.join(os.homedir(), 'Documents');\n const psProfile = path.join(docsDir, 'PowerShell', 'Microsoft.PowerShell_profile.ps1');\n const wpProfile = path.join(docsDir, 'WindowsPowerShell', 'Microsoft.PowerShell_profile.ps1');\n\n if (fs.existsSync(psProfile)) return psProfile;\n if (fs.existsSync(wpProfile)) return wpProfile;\n\n // Return the modern PowerShell path as default (we'll create it)\n return psProfile;\n}\n\nfunction generateUnixBlock(proxyConfig: ProxyConfig): string {\n return [\n BEGIN_MARKER,\n `export ANTHROPIC_BASE_URL=\"http://localhost:${proxyConfig.anthropicPort}\"`,\n `export OPENAI_BASE_URL=\"http://localhost:${proxyConfig.openaiPort}\"`,\n END_MARKER,\n ].join('\\n');\n}\n\nfunction generatePowerShellBlock(proxyConfig: ProxyConfig): string {\n return [\n PS_BEGIN_MARKER,\n `$env:ANTHROPIC_BASE_URL = \"http://localhost:${proxyConfig.anthropicPort}\"`,\n `$env:OPENAI_BASE_URL = \"http://localhost:${proxyConfig.openaiPort}\"`,\n PS_END_MARKER,\n ].join('\\n');\n}\n\nfunction createBackup(filePath: string): void {\n const backupPath = `${filePath}.skalpel-backup`;\n fs.copyFileSync(filePath, backupPath);\n}\n\nfunction updateProfileFile(filePath: string, block: string, beginMarker: string, endMarker: string): void {\n if (fs.existsSync(filePath)) {\n createBackup(filePath);\n }\n\n let content = fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf-8') : '';\n\n // Check if the block already exists\n const beginIdx = content.indexOf(beginMarker);\n const endIdx = content.indexOf(endMarker);\n\n if (beginIdx !== -1 && endIdx !== -1) {\n // Replace existing block\n content = content.slice(0, beginIdx) + block + content.slice(endIdx + endMarker.length);\n } else {\n // Append to end\n if (content.length > 0) {\n const trimmed = content.replace(/\\n+$/, '');\n content = trimmed + '\\n\\n' + block + '\\n';\n } else {\n content = block + '\\n';\n }\n }\n\n fs.writeFileSync(filePath, content);\n}\n\nexport function configureShellEnvVars(_agents: DetectedAgent[], proxyConfig: ProxyConfig): string[] {\n const modified: string[] = [];\n\n if (process.platform === 'win32') {\n const psProfile = getPowerShellProfilePath();\n if (psProfile) {\n const dir = path.dirname(psProfile);\n fs.mkdirSync(dir, { recursive: true });\n const block = generatePowerShellBlock(proxyConfig);\n updateProfileFile(psProfile, block, PS_BEGIN_MARKER, PS_END_MARKER);\n modified.push(psProfile);\n }\n } else {\n const profiles = getUnixProfilePaths();\n const block = generateUnixBlock(proxyConfig);\n for (const profilePath of profiles) {\n updateProfileFile(profilePath, block, BEGIN_MARKER, END_MARKER);\n modified.push(profilePath);\n }\n }\n\n return modified;\n}\n\nexport function removeShellEnvVars(): string[] {\n const restored: string[] = [];\n\n const home = os.homedir();\n const allProfiles = [\n path.join(home, '.bashrc'),\n path.join(home, '.zshrc'),\n path.join(home, '.bash_profile'),\n path.join(home, '.profile'),\n ];\n\n // Also check PowerShell profiles on Windows\n if (process.platform === 'win32') {\n const psProfile = getPowerShellProfilePath();\n if (psProfile) allProfiles.push(psProfile);\n }\n\n for (const profilePath of allProfiles) {\n if (!fs.existsSync(profilePath)) continue;\n\n const content = fs.readFileSync(profilePath, 'utf-8');\n const beginIdx = content.indexOf(BEGIN_MARKER);\n const endIdx = content.indexOf(END_MARKER);\n\n if (beginIdx === -1 || endIdx === -1) continue;\n\n // Always surgically remove the marker block.\n // Never restore from backup — the user may have edited the profile since install.\n const before = content.slice(0, beginIdx);\n const after = content.slice(endIdx + END_MARKER.length);\n // Clean up extra newlines\n const cleaned = (before.replace(/\\n+$/, '') + after.replace(/^\\n+/, '\\n')).trimEnd() + '\\n';\n fs.writeFileSync(profilePath, cleaned);\n\n // Clean up stale backup file\n const backupPath = `${profilePath}.skalpel-backup`;\n if (fs.existsSync(backupPath)) {\n fs.unlinkSync(backupPath);\n }\n\n restored.push(profilePath);\n }\n\n return restored;\n}\n\n/**\n * Write the BEGIN/END SKALPEL PROXY block to every detected shell profile.\n * Thin wrapper over configureShellEnvVars so the mode-switching code in\n * config-cmd.ts reads symmetrically (writeShellBlock / removeShellBlock).\n */\nexport function writeShellBlock(proxyConfig: ProxyConfig): string[] {\n return configureShellEnvVars([], proxyConfig);\n}\n\n/**\n * Remove the BEGIN/END SKALPEL PROXY block from every shell profile.\n * Thin wrapper over removeShellEnvVars with the naming used by the\n * mode-switching flow in config-cmd.ts.\n */\nexport function removeShellBlock(): string[] {\n return removeShellEnvVars();\n}\n\nexport function getConfiguredProfiles(): string[] {\n const configured: string[] = [];\n const home = os.homedir();\n const allProfiles = [\n path.join(home, '.bashrc'),\n path.join(home, '.zshrc'),\n path.join(home, '.bash_profile'),\n path.join(home, '.profile'),\n ];\n\n if (process.platform === 'win32') {\n const psProfile = getPowerShellProfilePath();\n if (psProfile) allProfiles.push(psProfile);\n }\n\n for (const profilePath of allProfiles) {\n if (!fs.existsSync(profilePath)) continue;\n const content = fs.readFileSync(profilePath, 'utf-8');\n if (content.includes(BEGIN_MARKER)) {\n configured.push(profilePath);\n }\n }\n\n return configured;\n}\n","import { execSync } from 'node:child_process';\nimport { loadConfig } from '../proxy/config.js';\nimport { stopProxy } from '../proxy/server.js';\nimport { isProxyAlive } from '../proxy/health-check.js';\nimport { isServiceInstalled, stopService } from './service/install.js';\nimport { detectAgents } from './agents/detect.js';\nimport { unconfigureAgent } from './agents/configure.js';\nimport { removeShellEnvVars } from './agents/shell.js';\n\nfunction print(msg: string): void {\n console.log(msg);\n}\n\nexport async function runStop(): Promise<void> {\n const config = loadConfig();\n\n // If an OS service is managing the proxy, unload it first so it\n // doesn't automatically restart the process after we kill it.\n if (isServiceInstalled()) {\n stopService();\n }\n\n const stopped = stopProxy(config);\n\n if (stopped) {\n print(' Skalpel proxy stopped.');\n } else {\n // PID file missing — check if proxy is actually alive via health check\n const alive = await isProxyAlive(config.anthropicPort);\n if (alive) {\n let killedViaPort = false;\n if (process.platform === 'darwin' || process.platform === 'linux') {\n try {\n const pids = execSync(`lsof -ti :${config.anthropicPort}`, { timeout: 3000 })\n .toString().trim().split('\\n').filter(Boolean);\n for (const p of pids) {\n const pid = parseInt(p, 10);\n if (Number.isInteger(pid) && pid > 0) {\n try { process.kill(pid, 'SIGTERM'); } catch { /* already gone */ }\n }\n }\n killedViaPort = true;\n } catch {\n // lsof failed — fall through to manual instructions\n }\n }\n if (killedViaPort) {\n print(' Skalpel proxy stopped (found via port detection).');\n } else {\n print(' Proxy appears to be running but could not be stopped automatically.');\n print(` Try: kill $(lsof -ti :${config.anthropicPort})`);\n }\n } else {\n print(' Proxy is not running.');\n }\n }\n\n // Remove agent configurations so Claude doesn't try to connect to dead proxy\n const agents = detectAgents();\n for (const agent of agents) {\n if (agent.installed) {\n try { unconfigureAgent(agent); } catch { /* best effort */ }\n }\n }\n try { removeShellEnvVars(); } catch { /* best effort */ }\n}\n","import http from 'node:http';\nimport type { ProxyConfig, ProxyStatus } from './types.js';\nimport { handleRequest } from './handler.js';\nimport { handleHealthRequest } from './health.js';\nimport { writePid, readPid, removePid } from './pid.js';\nimport { isProxyAlive } from './health-check.js';\nimport { Logger } from './logger.js';\nimport { handleCodexUpgrade } from './ws-server.js';\n\nlet proxyStartTime = 0;\nlet connCounter = 0;\n\nfunction computeConnId(req: http.IncomingMessage): string {\n const addr = req.socket.remoteAddress ?? 'unknown';\n const port = req.socket.remotePort ?? 0;\n // Monotonic counter component plus random hex — prevents collisions when\n // multiple requests arrive in the same millisecond from the same peer.\n const counter = (++connCounter).toString(36);\n const raw =\n addr +\n '|' +\n port +\n '|' +\n Date.now().toString(36) +\n '|' +\n counter +\n '|' +\n Math.floor(Math.random() * 0x1000).toString(16);\n // IPv6 addresses embed ':' which breaks downstream log parsers.\n return raw.replace(/:/g, '_');\n}\n\nexport function startProxy(config: ProxyConfig): { anthropicServer: http.Server; openaiServer: http.Server; cursorServer: http.Server } {\n const logger = new Logger(config.logFile, config.logLevel);\n const startTime = Date.now();\n proxyStartTime = Date.now();\n\n const anthropicServer = http.createServer((req, res) => {\n if (req.url === '/health' && req.method === 'GET') {\n handleHealthRequest(res, config, startTime, logger.child('health'));\n return;\n }\n const connId = computeConnId(req);\n handleRequest(req, res, config, 'claude-code', logger.child(connId));\n });\n\n const openaiServer = http.createServer((req, res) => {\n if (req.url === '/health' && req.method === 'GET') {\n handleHealthRequest(res, config, startTime, logger.child('health'));\n return;\n }\n const connId = computeConnId(req);\n handleRequest(req, res, config, 'codex', logger.child(connId));\n });\n\n const cursorServer = http.createServer((req, res) => {\n if (req.url === '/health' && req.method === 'GET') {\n handleHealthRequest(res, config, startTime, logger.child('health'));\n return;\n }\n const connId = computeConnId(req);\n handleRequest(req, res, config, 'cursor', logger.child(connId));\n });\n\n // WebSocket/upgrade handlers — the proxy is HTTP-only. Codex occasionally\n // attempts a WS upgrade before falling back to HTTP; without a handler\n // Node silently destroys the socket, masking the underlying cause. Reply\n // with an explicit HTTP/1.1 426 Upgrade Required so the client learns the\n // proxy is HTTP-only.\n anthropicServer.on('upgrade', (req, socket, _head) => {\n const ua = (req.headers['user-agent'] ?? '') as string;\n logger.warn(`upgrade-attempt port=${config.anthropicPort} method=${req.method} url=${req.url} ua=${ua} origin=${req.headers.origin ?? ''}`);\n const body = JSON.stringify({\n error: 'upgrade_required',\n message: 'Skalpel proxy is HTTP-only',\n hint: 'Use wire_api=\"responses\" over HTTP in your Codex config. See docs/codex-integration-fix.md.',\n });\n socket.write(\n `HTTP/1.1 426 Upgrade Required\\r\\n` +\n `Content-Type: application/json\\r\\n` +\n `Content-Length: ${Buffer.byteLength(body)}\\r\\n` +\n `Connection: close\\r\\n\\r\\n` +\n body\n );\n socket.destroy();\n });\n\n openaiServer.on('upgrade', (req, socket, head) => {\n const ua = (req.headers['user-agent'] ?? '') as string;\n logger.warn(`upgrade-attempt port=${config.openaiPort} method=${req.method} url=${req.url} ua=${ua} origin=${req.headers.origin ?? ''}`);\n // Codex WS transport — path /v1/responses gets the upgrade, other paths\n // keep returning HTTP 426. See docs/codex-websocket-refactor.md.\n const pathname = (req.url ?? '').split('?')[0];\n if (pathname === '/v1/responses') {\n handleCodexUpgrade(req, socket, head, config, logger);\n return;\n }\n const body = JSON.stringify({\n error: 'upgrade_required',\n message: 'Skalpel proxy is HTTP-only',\n hint: 'Use wire_api=\"responses\" over HTTP in your Codex config. See docs/codex-integration-fix.md.',\n });\n socket.write(\n `HTTP/1.1 426 Upgrade Required\\r\\n` +\n `Content-Type: application/json\\r\\n` +\n `Content-Length: ${Buffer.byteLength(body)}\\r\\n` +\n `Connection: close\\r\\n\\r\\n` +\n body\n );\n socket.destroy();\n });\n\n cursorServer.on('upgrade', (req, socket, _head) => {\n const ua = (req.headers['user-agent'] ?? '') as string;\n logger.warn(`upgrade-attempt port=${config.cursorPort} method=${req.method} url=${req.url} ua=${ua} origin=${req.headers.origin ?? ''}`);\n const body = JSON.stringify({\n error: 'upgrade_required',\n message: 'Skalpel proxy is HTTP-only',\n hint: 'Use wire_api=\"responses\" over HTTP in your Codex config. See docs/codex-integration-fix.md.',\n });\n socket.write(\n `HTTP/1.1 426 Upgrade Required\\r\\n` +\n `Content-Type: application/json\\r\\n` +\n `Content-Length: ${Buffer.byteLength(body)}\\r\\n` +\n `Connection: close\\r\\n\\r\\n` +\n body\n );\n socket.destroy();\n });\n\n // Handle port binding errors (EADDRINUSE, EACCES, etc.)\n anthropicServer.on('error', (err: NodeJS.ErrnoException) => {\n if (err.code === 'EADDRINUSE') {\n logger.error(`Port ${config.anthropicPort} is already in use. Another Skalpel proxy or process may be running.`);\n } else {\n logger.error(`Anthropic proxy failed to bind port ${config.anthropicPort}: ${err.message}`);\n }\n removePid(config.pidFile);\n process.exit(1);\n });\n\n openaiServer.on('error', (err: NodeJS.ErrnoException) => {\n if (err.code === 'EADDRINUSE') {\n logger.error(`Port ${config.openaiPort} is already in use. Another Skalpel proxy or process may be running.`);\n } else {\n logger.error(`OpenAI proxy failed to bind port ${config.openaiPort}: ${err.message}`);\n }\n removePid(config.pidFile);\n process.exit(1);\n });\n\n cursorServer.on('error', (err: NodeJS.ErrnoException) => {\n if (err.code === 'EADDRINUSE') {\n logger.error(`Port ${config.cursorPort} is already in use. Another Skalpel proxy or process may be running.`);\n } else {\n logger.error(`Cursor proxy failed to bind port ${config.cursorPort}: ${err.message}`);\n }\n removePid(config.pidFile);\n process.exit(1);\n });\n\n let bound = 0;\n const onBound = () => {\n bound++;\n if (bound === 3) {\n writePid(config.pidFile);\n logger.info(`Proxy started (pid=${process.pid}) ports=${config.anthropicPort},${config.openaiPort},${config.cursorPort}`);\n }\n };\n\n anthropicServer.listen(config.anthropicPort, () => {\n logger.info(`Anthropic proxy listening on port ${config.anthropicPort}`);\n onBound();\n });\n\n openaiServer.listen(config.openaiPort, () => {\n logger.info(`OpenAI proxy listening on port ${config.openaiPort}`);\n onBound();\n });\n\n cursorServer.listen(config.cursorPort, () => {\n logger.info(`Cursor proxy listening on port ${config.cursorPort}`);\n onBound();\n });\n\n const cleanup = () => {\n logger.info('Shutting down proxy...');\n anthropicServer.close();\n openaiServer.close();\n cursorServer.close();\n removePid(config.pidFile);\n process.exit(0);\n };\n\n process.on('SIGTERM', cleanup);\n process.on('SIGINT', cleanup);\n\n // Catch unexpected errors so PID file is always cleaned up\n process.on('uncaughtException', (err) => {\n logger.error(`Uncaught exception: ${err.message}`);\n removePid(config.pidFile);\n process.exit(1);\n });\n\n process.on('unhandledRejection', (reason) => {\n logger.error(`Unhandled rejection: ${reason}`);\n removePid(config.pidFile);\n process.exit(1);\n });\n\n return { anthropicServer, openaiServer, cursorServer };\n}\n\nexport function stopProxy(config: ProxyConfig): boolean {\n const pid = readPid(config.pidFile);\n if (pid === null) return false;\n\n try {\n process.kill(pid, 'SIGTERM');\n } catch {\n // Process already gone\n }\n\n removePid(config.pidFile);\n return true;\n}\n\nexport async function getProxyStatus(config: ProxyConfig): Promise<ProxyStatus> {\n const pid = readPid(config.pidFile);\n if (pid !== null) {\n return {\n running: true,\n pid,\n uptime: proxyStartTime > 0 ? Date.now() - proxyStartTime : 0,\n anthropicPort: config.anthropicPort,\n openaiPort: config.openaiPort,\n cursorPort: config.cursorPort,\n };\n }\n\n // PID file missing or stale — fall back to HTTP health check\n const alive = await isProxyAlive(config.anthropicPort);\n return {\n running: alive,\n pid: null,\n uptime: 0,\n anthropicPort: config.anthropicPort,\n openaiPort: config.openaiPort,\n cursorPort: config.cursorPort,\n };\n}\n","import fs from 'node:fs';\nimport path from 'node:path';\n\nconst MAX_SIZE = 5 * 1024 * 1024; // 5MB\nconst MAX_ROTATIONS = 3;\n\nconst LEVELS = { debug: 0, info: 1, warn: 2, error: 3 } as const;\n\nexport class Logger {\n private logFile: string;\n private level: keyof typeof LEVELS;\n private prefix: string;\n\n constructor(logFile: string, level: keyof typeof LEVELS = 'info', prefix = '') {\n this.logFile = logFile;\n this.level = level;\n this.prefix = prefix;\n fs.mkdirSync(path.dirname(logFile), { recursive: true });\n }\n\n debug(msg: string): void { this.log('debug', msg); }\n info(msg: string): void { this.log('info', msg); }\n warn(msg: string): void { this.log('warn', msg); }\n error(msg: string): void { this.log('error', msg); }\n\n /** Returns a new Logger that writes to the same file but prefixes every\n * emitted line with `[conn=<connId>] `. The parent logger continues to\n * work unchanged. IPv6 colons should already be sanitized by the caller. */\n child(connId: string): Logger {\n const child = new Logger(this.logFile, this.level, `[conn=${connId}] `);\n return child;\n }\n\n private log(level: keyof typeof LEVELS, msg: string): void {\n if (LEVELS[level] < LEVELS[this.level]) return;\n\n const line = `${new Date().toISOString()} [${level.toUpperCase()}] ${this.prefix}${msg}\\n`;\n\n if (level === 'debug' || level === 'error') {\n process.stderr.write(line);\n }\n\n try {\n this.rotate();\n fs.appendFileSync(this.logFile, line);\n } catch {\n // Best-effort logging\n }\n }\n\n private rotate(): void {\n try {\n const stat = fs.statSync(this.logFile);\n if (stat.size < MAX_SIZE) return;\n } catch {\n return;\n }\n\n for (let i = MAX_ROTATIONS; i >= 1; i--) {\n const src = i === 1 ? this.logFile : `${this.logFile}.${i - 1}`;\n const dst = `${this.logFile}.${i}`;\n try {\n fs.renameSync(src, dst);\n } catch {\n // File may not exist\n }\n }\n }\n}\n","import type { IncomingMessage } from 'node:http';\nimport type { Duplex } from 'node:stream';\nimport { WebSocketServer } from 'ws';\nimport type { ProxyConfig } from './types.js';\nimport type { Logger } from './logger.js';\n\n/**\n * WebSocket upgrade handler for port 18101 (Codex) `/v1/responses`.\n *\n * Frozen protocol contract: `docs/codex-websocket-refactor.md`.\n * Scope guard: this module must NEVER attach to port 18100 (Claude Code)\n * or 18102 (Cursor). Both of those keep returning HTTP 426.\n */\n\nconst WS_SUBPROTOCOL = 'skalpel-codex-v1';\n\n// Shared across the process — creating one WebSocketServer per upgrade\n// request is wasteful and leaks listeners.\nconst wss = new WebSocketServer({ noServer: true });\n\nfunction reject426(socket: Duplex, payload: Record<string, string>): void {\n const body = JSON.stringify(payload);\n socket.write(\n `HTTP/1.1 426 Upgrade Required\\r\\n` +\n `Content-Type: application/json\\r\\n` +\n `Content-Length: ${Buffer.byteLength(body)}\\r\\n` +\n `Connection: close\\r\\n\\r\\n` +\n body,\n );\n socket.destroy();\n}\n\n/**\n * Handle a WebSocket upgrade on port 18101 for path `/v1/responses`.\n *\n * Pre-upgrade rejections:\n * - `SKALPEL_CODEX_WS=0`: 426 with body `{\"error\":\"ws_disabled\"}`\n * - Missing/wrong `Sec-WebSocket-Protocol`: 426 with body\n * `{\"error\":\"unsupported_subprotocol\"}`\n *\n * On success: accepts the upgrade with subprotocol `skalpel-codex-v1` and\n * hands off to `handleWebSocketBridge` (Phase 4) which pipes frames to the\n * backend WebSocket.\n */\nexport function handleCodexUpgrade(\n req: IncomingMessage,\n socket: Duplex,\n head: Buffer,\n config: ProxyConfig,\n logger: Logger,\n): void {\n // Feature flag — default \"1\" (enabled). Set SKALPEL_CODEX_WS=\"0\" to force\n // HTTP-only behavior (mirrors the pre-refactor contract).\n const wsFlag = process.env.SKALPEL_CODEX_WS ?? '1';\n if (wsFlag === '0') {\n logger.warn('ws-upgrade rejected: feature flag SKALPEL_CODEX_WS=0');\n reject426(socket, { error: 'ws_disabled' });\n return;\n }\n\n const offered = (req.headers['sec-websocket-protocol'] ?? '') as string;\n const tokens = offered\n .split(',')\n .map((t) => t.trim())\n .filter(Boolean);\n if (!tokens.includes(WS_SUBPROTOCOL)) {\n logger.warn(`ws-upgrade rejected: unsupported subprotocol offered=\"${offered}\"`);\n reject426(socket, { error: 'unsupported_subprotocol' });\n return;\n }\n\n // Hand off to the `ws` server for the actual handshake. The handler in\n // the `connection` event receives the established WebSocket. The bridge\n // to the backend WS lives in `handler.ts` (added in Phase 4); we import\n // dynamically so this file does not depend on the bridge for typecheck.\n wss.handleUpgrade(req, socket, head, (clientWs) => {\n logger.info(`ws-upgrade accepted path=${req.url ?? ''} subproto=${WS_SUBPROTOCOL}`);\n import('./handler.js')\n .then((mod) => {\n const bridge = (mod as Record<string, unknown>).handleWebSocketBridge;\n if (typeof bridge !== 'function') {\n clientWs.send(\n JSON.stringify({\n type: 'error',\n payload: { code: 'not_implemented' },\n }),\n );\n clientWs.close(4003, 'bridge pending');\n return;\n }\n void (bridge as (\n ws: unknown,\n req: IncomingMessage,\n config: ProxyConfig,\n source: 'codex',\n logger: Logger,\n ) => Promise<void>)(clientWs, req, config, 'codex', logger);\n })\n .catch((err) => {\n logger.error(`ws bridge import failed: ${err?.message ?? String(err)}`);\n try {\n clientWs.send(\n JSON.stringify({\n type: 'error',\n payload: { code: 'bridge_import_failed' },\n }),\n );\n } catch {\n // ignore — connection may already be closed\n }\n clientWs.close(4003, 'bridge import failed');\n });\n });\n}\n","import { loadConfig } from '../proxy/config.js';\nimport { getProxyStatus } from '../proxy/server.js';\n\nfunction print(msg: string): void {\n console.log(msg);\n}\n\nexport async function runStatus(): Promise<void> {\n const config = loadConfig();\n const status = await getProxyStatus(config);\n\n print('');\n print(' Skalpel Proxy Status');\n print(' ────────────────────');\n print(` Status: ${status.running ? 'running' : 'stopped'}`);\n if (status.pid !== null) {\n print(` PID: ${status.pid}`);\n }\n print(` Anthropic: port ${status.anthropicPort}`);\n print(` OpenAI: port ${status.openaiPort}`);\n print(` Cursor: port ${status.cursorPort}`);\n print(` Config: ${config.configFile}`);\n print('');\n}\n","import fs from 'node:fs';\nimport { loadConfig } from '../proxy/config.js';\n\nfunction print(msg: string): void {\n console.log(msg);\n}\n\nexport async function runLogs(options: { lines?: string; follow?: boolean }): Promise<void> {\n const config = loadConfig();\n const logFile = config.logFile;\n const lineCount = parseInt(options.lines ?? '50', 10);\n\n if (!fs.existsSync(logFile)) {\n print(` No log file found at ${logFile}`);\n return;\n }\n\n const content = fs.readFileSync(logFile, 'utf-8');\n const lines = content.trimEnd().split('\\n');\n const tail = lines.slice(-lineCount);\n\n for (const line of tail) {\n print(line);\n }\n\n if (options.follow) {\n let position = fs.statSync(logFile).size;\n fs.watchFile(logFile, { interval: 500 }, () => {\n try {\n const stat = fs.statSync(logFile);\n if (stat.size > position) {\n const fd = fs.openSync(logFile, 'r');\n const buf = Buffer.alloc(stat.size - position);\n fs.readSync(fd, buf, 0, buf.length, position);\n fs.closeSync(fd);\n process.stdout.write(buf.toString('utf-8'));\n position = stat.size;\n }\n } catch {\n // File may have rotated\n }\n });\n }\n}\n","import { loadConfig, saveConfig } from '../proxy/config.js';\nimport type { ProxyConfig, ProxyMode } from '../proxy/types.js';\nimport { detectAgents } from './agents/detect.js';\nimport { configureAgent } from './agents/configure.js';\nimport { writeShellBlock, removeShellBlock } from './agents/shell.js';\nimport { installService, uninstallService } from './service/install.js';\n\nfunction print(msg: string): void {\n console.log(msg);\n}\n\nexport async function runSetMode(mode: ProxyMode, config: ProxyConfig): Promise<void> {\n if (mode !== 'direct' && mode !== 'proxy') {\n print(` Invalid mode: ${mode as string}. Must be 'direct' or 'proxy'.`);\n process.exit(1);\n }\n\n // Idempotence guard — if on-disk mode already matches, skip the switching work.\n if (config.mode === mode) {\n print(` Already in ${mode} mode. Nothing to do.`);\n return;\n }\n\n config.mode = mode;\n saveConfig(config);\n\n // Apply the mode-switching routines. Agents (Phase 3) and\n // service/shell-block (Phase 4) are wired in the helpers below.\n await applyModeSwitch(mode, config);\n\n print(` Switched to ${mode} mode.`);\n}\n\nasync function applyModeSwitch(mode: ProxyMode, config: ProxyConfig): Promise<void> {\n const direct = mode === 'direct';\n const agents = detectAgents();\n for (const agent of agents) {\n if (!agent.installed) continue;\n configureAgent(agent, config, direct);\n }\n\n if (direct) {\n // Direct mode: agents talk to api.skalpel.ai straight, so the local\n // proxy service + shell env vars pointing at localhost are dead weight.\n uninstallService();\n removeShellBlock();\n } else {\n // Proxy mode: re-install the service and re-add the shell block so\n // every new terminal picks up ANTHROPIC_BASE_URL/OPENAI_BASE_URL for\n // the local proxy. Both are idempotent.\n installService(config);\n writeShellBlock(config);\n }\n}\n\nexport async function runConfig(subcommand?: string, args?: string[]): Promise<void> {\n const config = loadConfig();\n\n if (subcommand === 'path') {\n print(config.configFile);\n return;\n }\n\n if (subcommand === 'set') {\n if (!args || args.length < 2) {\n print(' Usage: skalpel config set <key> <value>');\n process.exit(1);\n }\n const key = args[0] as keyof ProxyConfig;\n const value = args[1];\n\n if (key === 'mode') {\n if (value !== 'direct' && value !== 'proxy') {\n print(` Invalid mode: ${value}. Must be 'direct' or 'proxy'.`);\n process.exit(1);\n }\n await runSetMode(value, config);\n return;\n }\n\n const validKeys: (keyof ProxyConfig)[] = [\n 'apiKey', 'remoteBaseUrl', 'anthropicPort', 'openaiPort',\n 'cursorPort', 'cursorDirectUrl', 'logLevel', 'logFile', 'pidFile',\n ];\n\n if (!validKeys.includes(key)) {\n print(` Unknown config key: ${key}`);\n print(` Valid keys: ${validKeys.join(', ')}`);\n process.exit(1);\n }\n\n const updated = { ...config };\n if (key === 'anthropicPort' || key === 'openaiPort' || key === 'cursorPort') {\n const parsed = parseInt(value, 10);\n if (isNaN(parsed) || parsed < 1 || parsed > 65535) {\n print(` Invalid port number: ${value}`);\n process.exit(1);\n }\n (updated as any)[key] = parsed;\n } else if (key === 'logLevel') {\n const validLevels = ['debug', 'info', 'warn', 'error'];\n if (!validLevels.includes(value)) {\n print(` Invalid log level: ${value}`);\n print(` Valid levels: ${validLevels.join(', ')}`);\n process.exit(1);\n }\n (updated as any)[key] = value;\n } else {\n (updated as any)[key] = value;\n }\n\n saveConfig(updated);\n print(` Set ${key} = ${value}`);\n return;\n }\n\n print(JSON.stringify(config, null, 2));\n}\n","import { exec } from 'node:child_process';\nimport { createRequire } from 'node:module';\n\nconst require = createRequire(import.meta.url);\nconst pkg = require('../../package.json');\n\nfunction print(msg: string): void {\n console.log(msg);\n}\n\nexport async function runUpdate(): Promise<void> {\n print(` Current version: ${pkg.version}`);\n print(' Checking for updates...');\n\n try {\n const latest = await new Promise<string>((resolve, reject) => {\n exec('npm view skalpel version', (err, stdout) => {\n if (err) reject(err);\n else resolve(stdout.trim());\n });\n });\n\n if (latest === pkg.version) {\n print(` Already on the latest version (${pkg.version}).`);\n return;\n }\n\n print(` Updating to ${latest}...`);\n\n await new Promise<void>((resolve, reject) => {\n exec('npm install -g skalpel@latest', (err) => {\n if (err) reject(err);\n else resolve();\n });\n });\n\n print(` Updated to ${latest}.`);\n } catch (err) {\n print(` Update failed: ${err instanceof Error ? err.message : String(err)}`);\n process.exit(1);\n }\n}\n","import * as readline from 'node:readline';\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport * as os from 'node:os';\nimport { detectAgents } from './agents/detect.js';\nimport type { DetectedAgent } from './agents/detect.js';\nimport { configureAgent } from './agents/configure.js';\nimport { installService } from './service/install.js';\nimport { loadConfig, saveConfig } from '../proxy/config.js';\nimport { validateApiKey } from './utils.js';\n\nfunction print(msg: string): void {\n console.log(msg);\n}\n\n\nexport async function runWizard(options?: { apiKey?: string; auto?: boolean; skipClaude?: boolean; skipCodex?: boolean; skipCursor?: boolean }): Promise<void> {\n const isAuto = options?.auto === true;\n\n let rl: readline.Interface | undefined;\n let ask: (question: string) => Promise<string>;\n\n if (!isAuto) {\n rl = readline.createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n ask = (question: string): Promise<string> => {\n return new Promise((resolve) => {\n rl!.question(question, (answer) => resolve(answer.trim()));\n });\n };\n } else {\n ask = () => Promise.resolve('');\n }\n\n try {\n // Step 1: Welcome\n print('');\n print(' _____ _ _ _ ');\n print(' / ____| | | | | |');\n print(' | (___ | | ____ _| |_ __ ___| |');\n print(' \\\\___ \\\\| |/ / _` | | \\'_ \\\\ / _ \\\\ |');\n print(' ____) | < (_| | | |_) | __/ |');\n print(' |_____/|_|\\\\_\\\\__,_|_| .__/ \\\\___|_|');\n print(' | | ');\n print(' |_| ');\n print('');\n print(' Welcome to Skalpel! Let\\'s optimize your coding agent costs.');\n print(' ─────────────────────────────────────────────────────────');\n print('');\n\n // Step 2: API Key\n const skalpelDir = path.join(os.homedir(), '.skalpel');\n const configPath = path.join(skalpelDir, 'config.json');\n let apiKey = '';\n\n if (isAuto && options?.apiKey) {\n apiKey = options.apiKey;\n if (!validateApiKey(apiKey)) {\n print(' Error: Invalid API key. Must start with \"sk-skalpel-\" and be at least 20 characters.');\n process.exit(1);\n }\n print(` API key set: ${apiKey.slice(0, 14)}${'*'.repeat(Math.max(0, apiKey.length - 14))}`);\n } else if (isAuto && !options?.apiKey) {\n print(' Error: --api-key is required when using --auto mode.');\n process.exit(1);\n } else {\n if (fs.existsSync(configPath)) {\n try {\n const existing = JSON.parse(fs.readFileSync(configPath, 'utf-8'));\n if (existing.apiKey && validateApiKey(existing.apiKey)) {\n const masked = existing.apiKey.slice(0, 14) + '*'.repeat(Math.max(0, existing.apiKey.length - 14));\n const useExisting = await ask(` Found existing API key: ${masked}\\n Use this key? (Y/n): `);\n if (useExisting.toLowerCase() !== 'n') {\n apiKey = existing.apiKey;\n print(` Using existing API key.`);\n }\n }\n } catch {\n // invalid config file, proceed to ask\n }\n }\n\n if (!apiKey) {\n apiKey = await ask(' Paste your Skalpel API key (sk-skalpel-...): ');\n if (!validateApiKey(apiKey)) {\n print(' Error: Invalid API key. Must start with \"sk-skalpel-\" and be at least 20 characters.');\n rl!.close();\n process.exit(1);\n }\n print(` API key set: ${apiKey.slice(0, 14)}${'*'.repeat(Math.max(0, apiKey.length - 14))}`);\n }\n }\n print('');\n\n // Save API key to config\n fs.mkdirSync(skalpelDir, { recursive: true });\n const proxyConfig = loadConfig(configPath);\n proxyConfig.apiKey = apiKey;\n saveConfig(proxyConfig);\n\n // Step 3: Agent Detection\n print(' Detecting coding agents...');\n const agents = detectAgents();\n const installedAgents = agents.filter((a) => a.installed);\n const notInstalled = agents.filter((a) => !a.installed);\n\n if (installedAgents.length > 0) {\n for (const agent of installedAgents) {\n const ver = agent.version ? ` v${agent.version}` : '';\n print(` [+] Found: ${agent.name}${ver}`);\n }\n }\n if (notInstalled.length > 0) {\n for (const agent of notInstalled) {\n print(` [ ] Not found: ${agent.name}`);\n }\n }\n if (installedAgents.length === 0) {\n print(' Warning: No coding agents detected. You can install them later.');\n print(' The proxy will be configured and ready when agents are installed.');\n }\n print('');\n\n // Filter out skipped agents\n let agentsToConfigure: DetectedAgent[] = installedAgents.filter((a) => {\n if (options?.skipClaude && a.name === 'claude-code') return false;\n if (options?.skipCodex && a.name === 'codex') return false;\n if (options?.skipCursor && a.name === 'cursor') return false;\n return true;\n });\n if (agentsToConfigure.length > 0 && !isAuto) {\n const agentNames = installedAgents.map((a) => a.name).join(', ');\n const confirm = await ask(` Configure ${agentNames}? (Y/n): `);\n if (confirm.toLowerCase() === 'n') {\n agentsToConfigure = [];\n }\n }\n print('');\n\n // Step 4: Configuration\n if (agentsToConfigure.length > 0) {\n print(' Configuring agents...');\n\n // Configure agent-specific config files (scoped to each agent's own config)\n for (const agent of agentsToConfigure) {\n configureAgent(agent, proxyConfig);\n print(` Configured ${agent.name}${agent.configPath ? ` (${agent.configPath})` : ''}`);\n }\n print('');\n\n // Codex requires OPENAI_API_KEY to be set (even with the proxy — the\n // proxy ignores the value, but Codex refuses to start without it).\n const codexConfigured = agentsToConfigure.some((a) => a.name === 'codex');\n if (codexConfigured && !process.env.OPENAI_API_KEY) {\n print(' [!] Codex expects OPENAI_API_KEY to be set. The Skalpel proxy ignores the value,');\n print(' so any non-empty string works, e.g.:');\n print(' export OPENAI_API_KEY=sk-codex-placeholder-skalpel');\n print('');\n }\n }\n\n // Step 5: Service Installation\n print(' Installing proxy as system service...');\n try {\n installService(proxyConfig);\n print(' Service installed successfully.');\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n print(` Warning: Could not install service: ${msg}`);\n print(' You can start the proxy manually with: skalpel start');\n }\n print('');\n\n // Step 6: Verification\n print(' Verifying proxy...');\n let proxyOk = false;\n try {\n // Give the service a moment to start\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), 3000);\n const healthUrl = `http://localhost:${proxyConfig.anthropicPort}/health`;\n const res = await fetch(healthUrl, { signal: controller.signal });\n clearTimeout(timeout);\n if (res.ok) {\n print(` [+] Anthropic proxy (port ${proxyConfig.anthropicPort}): healthy`);\n proxyOk = true;\n } else {\n print(` [!] Anthropic proxy (port ${proxyConfig.anthropicPort}): HTTP ${res.status}`);\n }\n } catch {\n print(` [!] Proxy not responding yet. It may take a moment to start.`);\n print(' Run \"npx skalpel status\" to check later, or \"npx skalpel start\" to start manually.');\n }\n\n try {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), 3000);\n const healthUrl = `http://localhost:${proxyConfig.openaiPort}/health`;\n const res = await fetch(healthUrl, { signal: controller.signal });\n clearTimeout(timeout);\n if (res.ok) {\n print(` [+] OpenAI proxy (port ${proxyConfig.openaiPort}): healthy`);\n } else {\n print(` [!] OpenAI proxy (port ${proxyConfig.openaiPort}): HTTP ${res.status}`);\n }\n } catch {\n // Already warned above\n }\n\n try {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), 3000);\n const healthUrl = `http://localhost:${proxyConfig.cursorPort}/health`;\n const res = await fetch(healthUrl, { signal: controller.signal });\n clearTimeout(timeout);\n if (res.ok) {\n print(` [+] Cursor proxy (port ${proxyConfig.cursorPort}): healthy`);\n } else {\n print(` [!] Cursor proxy (port ${proxyConfig.cursorPort}): HTTP ${res.status}`);\n }\n } catch {\n // Already warned above\n }\n print('');\n\n // Step 7: Success\n print(' ─────────────────────────────────────────────────────────');\n print('');\n print(' You\\'re all set! Your coding agents now route through Skalpel.');\n print('');\n if (agentsToConfigure.length > 0) {\n print(' Configured agents: ' + agentsToConfigure.map((a) => a.name).join(', '));\n }\n print(' Proxy ports: Anthropic=' + proxyConfig.anthropicPort + ', OpenAI=' + proxyConfig.openaiPort + ', Cursor=' + proxyConfig.cursorPort);\n print('');\n print(' Run \"npx skalpel status\" to check proxy status');\n print(' Run \"npx skalpel doctor\" for a full health check');\n print(' Run \"npx skalpel uninstall\" to remove everything');\n print('');\n\n if (rl) rl.close();\n } catch (err) {\n if (rl) rl.close();\n throw err;\n }\n}\n","import * as readline from 'node:readline';\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport * as os from 'node:os';\nimport { execSync } from 'node:child_process';\nimport { detectAgents } from './agents/detect.js';\nimport { removeShellEnvVars } from './agents/shell.js';\nimport { unconfigureAgent } from './agents/configure.js';\nimport { uninstallService } from './service/install.js';\nimport { loadConfig } from '../proxy/config.js';\nimport { stopProxy } from '../proxy/server.js';\n\nfunction print(msg: string): void {\n console.log(msg);\n}\n\nexport interface UninstallOptions {\n force?: boolean;\n}\n\nexport async function runUninstall(options?: UninstallOptions): Promise<void> {\n const force = options?.force ?? false;\n\n const rl = readline.createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n\n function ask(question: string): Promise<string> {\n return new Promise((resolve) => {\n rl.question(question, (answer) => resolve(answer.trim()));\n });\n }\n\n try {\n print('');\n print(' Skalpel Uninstall');\n print(' ─────────────────');\n print('');\n\n if (!force) {\n const confirm = await ask(' This will remove Skalpel proxy, service, and agent configurations. Continue? (y/N): ');\n if (confirm.toLowerCase() !== 'y') {\n print(' Aborted.');\n rl.close();\n return;\n }\n print('');\n }\n\n const config = loadConfig();\n const removed: string[] = [];\n\n // Uninstall OS service FIRST — the service has KeepAlive=true (macOS) or\n // Restart=always (Linux), so if we kill the proxy process before removing\n // the service, the service manager will immediately respawn it, leaving an\n // orphaned proxy on port 18100.\n print(' Removing system service...');\n try {\n uninstallService();\n print(' [+] Service removed');\n removed.push('system service');\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n print(` [!] Could not remove service: ${msg}`);\n }\n\n // Now stop any remaining proxy process (safety net for non-service runs)\n print(' Stopping proxy...');\n const stopped = stopProxy(config);\n if (stopped) {\n print(' [+] Proxy stopped');\n removed.push('proxy process');\n } else {\n print(' [ ] Proxy was not running');\n }\n\n // Remove shell env vars\n print(' Removing shell environment variables...');\n const restoredProfiles = removeShellEnvVars();\n if (restoredProfiles.length > 0) {\n for (const p of restoredProfiles) {\n print(` [+] Restored: ${p}`);\n }\n removed.push('shell env vars');\n } else {\n print(' [ ] No shell profiles had Skalpel config');\n }\n\n // Unconfigure agents\n print(' Restoring agent configurations...');\n const agents = detectAgents();\n for (const agent of agents) {\n if (agent.installed) {\n try {\n unconfigureAgent(agent);\n print(` [+] Restored ${agent.name} config`);\n removed.push(`${agent.name} config`);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n print(` [!] Could not restore ${agent.name}: ${msg}`);\n }\n }\n }\n print('');\n\n // Remove ~/.skalpel/ directory\n const skalpelDir = path.join(os.homedir(), '.skalpel');\n if (fs.existsSync(skalpelDir)) {\n let shouldRemove = force;\n if (!force) {\n const removeDir = await ask(' Remove ~/.skalpel/ directory (contains config and logs)? (y/N): ');\n shouldRemove = removeDir.toLowerCase() === 'y';\n }\n if (shouldRemove) {\n fs.rmSync(skalpelDir, { recursive: true, force: true });\n print(' [+] Removed ~/.skalpel/');\n removed.push('~/.skalpel/ directory');\n }\n }\n\n // Clear npx cache for skalpel\n print(' Clearing npx cache...');\n try {\n clearNpxCache();\n print(' [+] npx cache cleared');\n removed.push('npx cache');\n } catch {\n print(' [ ] Could not clear npx cache (not critical)');\n }\n\n print('');\n print(' ─────────────────');\n if (removed.length > 0) {\n print(' Removed: ' + removed.join(', '));\n } else {\n print(' Nothing to remove.');\n }\n print(' Skalpel has been uninstalled.');\n if (restoredProfiles.length > 0) {\n print(' Restart your shell to apply env var changes.');\n }\n print('');\n\n rl.close();\n } catch (err) {\n rl.close();\n throw err;\n }\n}\n\nfunction clearNpxCache(): void {\n // npm stores npx cache in ~/.npm/_npx/\n // Find and remove only the skalpel-related cache entries\n const npxCacheDir = path.join(os.homedir(), '.npm', '_npx');\n if (!fs.existsSync(npxCacheDir)) return;\n\n const entries = fs.readdirSync(npxCacheDir);\n for (const entry of entries) {\n const pkgJsonPath = path.join(npxCacheDir, entry, 'node_modules', 'skalpel', 'package.json');\n const pkgJsonAlt = path.join(npxCacheDir, entry, 'node_modules', '.package-lock.json');\n if (fs.existsSync(pkgJsonPath)) {\n fs.rmSync(path.join(npxCacheDir, entry), { recursive: true, force: true });\n continue;\n }\n // Check the lockfile for skalpel references\n if (fs.existsSync(pkgJsonAlt)) {\n try {\n const content = fs.readFileSync(pkgJsonAlt, 'utf-8');\n if (content.includes('\"skalpel\"')) {\n fs.rmSync(path.join(npxCacheDir, entry), { recursive: true, force: true });\n }\n } catch {\n // ignore read errors\n }\n }\n }\n}\n"],"mappings":"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","names":["WebSocket","createRequire","fs","path","pkg","resolve","fs","path","os","fs","path","print","resolve","print","fs","path","print","path","fileURLToPath","fs","path","os","fs","path","execSync","fs","execSync","fs","path","os","execSync","os","execSync","os","path","path","fs","execSync","os","fs","path","os","fs","path","os","fs","path","os","createBackup","print","path","fileURLToPath","execSync","fs","path","print","execSync","print","fs","print","fs","print","require","print","resolve","readline","fs","path","os","print","resolve","readline","fs","path","os","print","resolve","require","createRequire","pkg"]}
|