skalpel 2.0.19 → 2.0.21
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 +19 -8
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/proxy-runner.js +61 -9
- package/dist/cli/proxy-runner.js.map +1 -1
- package/dist/index.cjs +61 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +61 -9
- package/dist/index.js.map +1 -1
- package/dist/proxy/index.cjs +61 -9
- package/dist/proxy/index.cjs.map +1 -1
- package/dist/proxy/index.js +61 -9
- package/dist/proxy/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/proxy/codex-oauth.ts","../../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","../../src/cli/auth/callback-server.ts","../../src/cli/auth/session-storage.ts","../../src/cli/auth/browser.ts","../../src/cli/login.ts","../../src/cli/logout.ts"],"sourcesContent":["import { readFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\n\n/**\n * OAuth credentials stored by the official OpenAI Codex CLI at\n * `~/.codex/auth.json`. Skalpel only reads this file — refresh and rotation\n * are owned by Codex. Field shape mirrors what `codex login` writes today.\n */\nexport interface CodexAuth {\n access_token: string;\n refresh_token: string;\n expires_at: string;\n account_id?: string;\n}\n\n/**\n * Buffer (in milliseconds) applied to `expires_at` before treating a token\n * as fresh. Keeps us from forwarding a token that will expire mid-flight.\n */\nexport const TOKEN_FRESHNESS_BUFFER_MS = 10_000;\n\nfunction authFilePath(): string {\n return join(homedir(), '.codex', 'auth.json');\n}\n\n/**\n * Read `~/.codex/auth.json` if present. Returns null on missing file or\n * malformed JSON; warnings are written to stderr but never thrown so the\n * proxy hot path keeps serving requests via the API-key fallback.\n *\n * Note: the access_token value is intentionally never logged; only its\n * presence is observable via the function's return value.\n */\nexport function readCodexAuth(): CodexAuth | null {\n const path = authFilePath();\n let raw: string;\n try {\n raw = readFileSync(path, 'utf-8');\n } catch (err) {\n const code = (err as NodeJS.ErrnoException)?.code;\n if (code !== 'ENOENT') {\n // Other read errors (EACCES, etc.) — log without exposing the path or\n // contents.\n process.stderr.write(`skalpel: codex-oauth: cannot read auth file (${code ?? 'unknown'})\\n`);\n }\n return null;\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n process.stderr.write('skalpel: codex-oauth: auth file is not valid JSON\\n');\n return null;\n }\n\n if (\n parsed === null ||\n typeof parsed !== 'object' ||\n typeof (parsed as Record<string, unknown>).access_token !== 'string' ||\n typeof (parsed as Record<string, unknown>).refresh_token !== 'string' ||\n typeof (parsed as Record<string, unknown>).expires_at !== 'string'\n ) {\n process.stderr.write('skalpel: codex-oauth: auth file missing required fields\\n');\n return null;\n }\n\n const obj = parsed as Record<string, unknown>;\n const auth: CodexAuth = {\n access_token: obj.access_token as string,\n refresh_token: obj.refresh_token as string,\n expires_at: obj.expires_at as string,\n };\n if (typeof obj.account_id === 'string') {\n auth.account_id = obj.account_id;\n }\n return auth;\n}\n\n/**\n * Returns true if `auth.expires_at` is more than TOKEN_FRESHNESS_BUFFER_MS\n * in the future. Codex refreshes pre-emptively at the 5-minute mark, so any\n * token still within its window is safe to forward.\n */\nexport function isTokenFresh(auth: CodexAuth): boolean {\n const expiresAtMs = Date.parse(auth.expires_at);\n if (!Number.isFinite(expiresAtMs)) return false;\n return expiresAtMs > Date.now() + TOKEN_FRESHNESS_BUFFER_MS;\n}\n\n/**\n * Convenience helper: read the file and return the access_token only when\n * the OAuth credential is both present and fresh. Returns null in all other\n * cases (missing file, malformed, expired). Callers should fall back to\n * the inbound API key when null is returned.\n */\nexport function getFreshAccessToken(): string | null {\n const auth = readCodexAuth();\n if (auth === null) return null;\n if (!isTokenFresh(auth)) return null;\n return auth.access_token;\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","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 / undici error into a single line with cause chain and URL,\n * for logging. Replaces opaque `(err as Error).message` strings that hide\n * the actual network-layer failure code.\n */\nexport function formatFetchErrorForLog(err: unknown, url: string): string {\n const parts: string[] = [];\n const top = err as { code?: string; name?: string; message?: string; cause?: unknown } | null;\n if (top && typeof top === 'object') {\n if (top.name) parts.push(`name=${top.name}`);\n if (top.code) parts.push(`code=${top.code}`);\n if (top.message) parts.push(`msg=${top.message}`);\n // Walk cause chain (undici wraps the underlying errno in `.cause`).\n let cause: unknown = top.cause;\n let depth = 0;\n while (cause && depth < 4) {\n const c = cause as { code?: string; name?: string; message?: string; cause?: unknown };\n const causeBits: string[] = [];\n if (c.name) causeBits.push(`name=${c.name}`);\n if (c.code) causeBits.push(`code=${c.code}`);\n if (c.message) causeBits.push(`msg=${c.message}`);\n if (causeBits.length > 0) parts.push(`cause[${causeBits.join(',')}]`);\n cause = c.cause;\n depth += 1;\n }\n } else if (err !== undefined && err !== null) {\n parts.push(String(err));\n }\n parts.push(`url=${url}`);\n return parts.join(' ');\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 https from 'node:https';\nimport http from 'node:http';\nimport WebSocket from 'ws';\nimport type { Logger } from './logger.js';\nimport { getFreshAccessToken } from './codex-oauth.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\n// GCP's GCE Load Balancer advertises h2 in ALPN by default. When Node's\n// ws client negotiates h2, the LB downgrades the Upgrade: websocket\n// request to a plain HTTP/2 GET and the FastAPI backend returns 405\n// (\"Use POST /v1/responses\"). Forcing http/1.1 in ALPN on the TLS\n// ClientHello keeps the connection on HTTP/1.1 where WS upgrades work.\nconst H1_HTTPS_AGENT = new https.Agent({\n ALPNProtocols: ['http/1.1'],\n keepAlive: true,\n});\nconst H1_HTTP_AGENT = new http.Agent({ keepAlive: true });\n\nfunction pickAgent(url: string): https.Agent | http.Agent {\n return url.startsWith('wss://') ? H1_HTTPS_AGENT : H1_HTTP_AGENT;\n}\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 // On every connect (initial + reconnect) re-read ~/.codex/auth.json so\n // long-running sessions pick up Codex's pre-emptive token rotation. Fall\n // back to the constructor-supplied token when OAuth is missing/expired\n // so the API-key path still works.\n const freshToken = getFreshAccessToken();\n const bearer = freshToken ?? this.opts.oauthToken;\n return new Promise((resolve, reject) => {\n const ws = new WebSocket(this.opts.url, [WS_SUBPROTOCOL], {\n agent: pickAgent(this.opts.url),\n headers: {\n 'X-Skalpel-API-Key': this.opts.apiKey,\n Authorization: `Bearer ${bearer}`,\n 'x-skalpel-source': this.opts.source,\n },\n });\n\n this.ws = ws;\n\n // Handshake failure (server replied with a non-101 HTTP status —\n // 405 is what GCP's GCE LB returns when ALPN negotiates HTTP/2 and\n // the request gets downgraded to a plain GET). Emit `fallback`\n // synchronously so handleWebSocketBridge switches to HTTP without\n // burning the 5 reconnect attempts.\n ws.once('unexpected-response', (_req, res) => {\n const status = (res as unknown as { statusCode?: number }).statusCode ?? 0;\n this.opts.logger.warn(\n `ws-client handshake rejected status=${status} — no retry, falling back to HTTP`,\n );\n // Mark not-connecting so the upcoming 'error'/'close' events from\n // the aborted handshake do not trigger scheduleReconnect.\n this.closedByUser = true;\n this.emit('fallback', `handshake_${status}`);\n try {\n (res as unknown as { destroy?: () => void }).destroy?.();\n } catch {\n // ignore\n }\n this.ws = null;\n reject(new Error(`ws handshake status ${status}`));\n });\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 { getFreshAccessToken } from './codex-oauth.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 // Codex traffic: when a fresh OAuth token is present in\n // ~/.codex/auth.json, override the inbound Authorization (which is a\n // placeholder set by the Codex TOML OPENAI_API_KEY env var) with the\n // real ChatGPT-plan bearer. When OAuth is missing or expired, keep the\n // inbound header so the API-key fallback continues to work.\n if (source === 'codex') {\n const oauthToken = getFreshAccessToken();\n if (oauthToken !== null) {\n forwardHeaders['authorization'] = `Bearer ${oauthToken}`;\n delete forwardHeaders['Authorization'];\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 * **Pass-through mode.** The wire format is OpenAI's native Responses API\n * WS protocol — no Skalpel envelope on either direction. Real Codex sends\n * the bare request body as the first text frame; the server streams back\n * native OpenAI event frames (`response.created`, `response.output_text\n * .delta`, `response.completed`, ...). We just pipe bytes.\n *\n * On backend fallback (exhausted reconnects, non-transient close code,\n * or feature flag disabled on backend), we fall over to HTTP POST, using\n * the cached first client frame as the POST body. SSE events from the\n * HTTP response are re-emitted to clientWs as individual WS text frames\n * (same shape as the backend WS path).\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 // Prefer a fresh ChatGPT-plan OAuth token from ~/.codex/auth.json; fall\n // back to whatever Authorization header the client sent (typically the\n // Codex TOML placeholder, i.e. OPENAI_API_KEY). ws-client also refreshes\n // on reconnect so long-running sessions pick up rotations.\n const freshOauthToken = getFreshAccessToken();\n let oauthToken: string;\n if (freshOauthToken !== null) {\n oauthToken = freshOauthToken;\n } else {\n const oauthHeader = (req.headers['authorization'] ?? '') as string;\n oauthToken = oauthHeader.toLowerCase().startsWith('bearer ')\n ? oauthHeader.slice(7).trim()\n : '';\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 // Raw text frames buffered from the client. The first one is always\n // the Responses API request body (Codex sends exactly one). We replay\n // the cached body on HTTP fallback.\n const pendingClientText: string[] = [];\n let firstClientFrameBody: string | null = null;\n\n const flushPending = (): void => {\n if (!backendOpen || fallbackActive) return;\n while (pendingClientText.length > 0) {\n const text = pendingClientText.shift();\n if (text === undefined) break;\n try {\n // BackendWsClient.send takes a JSON object (stringified again).\n // For pass-through we already have a JSON text frame, so bypass\n // via the underlying ws. BackendWsClient exposes send(frame:object)\n // but we need raw text; JSON-parse to let it re-stringify. If the\n // client sent non-JSON, log and drop.\n const parsed = JSON.parse(text);\n backend.send(parsed as Record<string, unknown>);\n } catch (err) {\n logger.warn(`bridge: flush backend.send failed: ${(err as Error).message}`);\n pendingClientText.unshift(text);\n return;\n }\n }\n };\n\n // client WS → backend WS (pass-through). Cache the first frame for\n // HTTP fallback.\n clientWs.on('message', (data: WebSocket.RawData) => {\n const text = data.toString('utf-8');\n if (firstClientFrameBody === null) {\n firstClientFrameBody = text;\n }\n pendingClientText.push(text);\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 // backend WS → client WS (pass-through).\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 });\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 if (fallbackActive) return;\n fallbackActive = true;\n backendOpen = false;\n logger.warn(`bridge: backend fallback reason=${reason} — switching to HTTP POST`);\n const body =\n firstClientFrameBody ??\n (pendingClientText.length > 0 ? pendingClientText[0] : null);\n if (body === null) {\n logger.warn('bridge: no request body cached for HTTP fallback');\n return;\n }\n const inboundAuth = (req.headers['authorization'] ?? '') as string;\n fallbackToHttp(clientWs, config, source, logger, body, inboundAuth).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\nfunction parseSseEvents(buffer: string): { events: string[]; rest: string } {\n const events: string[] = [];\n let rest = buffer.replace(/\\r\\n/g, '\\n');\n while (rest.includes('\\n\\n')) {\n const idx = rest.indexOf('\\n\\n');\n const block = rest.slice(0, idx);\n rest = rest.slice(idx + 2);\n const dataLines: string[] = [];\n for (const line of block.split('\\n')) {\n const trimmed = line.replace(/^\\s+/, '');\n if (trimmed.startsWith('data:')) {\n dataLines.push(trimmed.slice(5).replace(/^\\s+/, ''));\n }\n }\n if (dataLines.length === 0) continue;\n const joined = dataLines.join('\\n').trim();\n if (joined.length === 0 || joined === '[DONE]') continue;\n events.push(joined);\n }\n return { events, rest };\n}\n\nasync function fallbackToHttp(\n clientWs: WebSocket,\n config: ProxyConfig,\n source: 'codex',\n logger: Logger,\n requestBody: string,\n inboundAuth: string,\n): Promise<void> {\n try {\n // Prefer a fresh ChatGPT-plan OAuth bearer; fall back to whatever the\n // client sent (typically the Codex OPENAI_API_KEY placeholder) so the\n // API-key path to api.openai.com still works. Backend's /v1/responses\n // rejects the request with 400 if Authorization is absent — never\n // send the request without an auth header.\n const freshToken = getFreshAccessToken();\n const authHeader = freshToken !== null\n ? `Bearer ${freshToken}`\n : inboundAuth;\n if (!authHeader) {\n logger.error('http fallback aborted: no Authorization available');\n try {\n clientWs.send(\n JSON.stringify({\n type: 'error',\n error: { code: 401, message: 'no credentials available for fallback' },\n }),\n );\n clientWs.close(1011, 'no credentials');\n } catch { /* ignore */ }\n return;\n }\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 'X-Skalpel-Auth-Mode': 'passthrough',\n Authorization: authHeader,\n Accept: 'text/event-stream',\n },\n body: requestBody,\n });\n if (!resp.ok || resp.body === null) {\n clientWs.send(\n JSON.stringify({\n type: 'error',\n error: { code: resp.status, message: `http fallback status ${resp.status}` },\n }),\n );\n try { clientWs.close(1011, 'http fallback failed'); } catch { /* noop */ }\n return;\n }\n const reader = resp.body.getReader();\n const decoder = new TextDecoder('utf-8');\n let buf = '';\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n if (value === undefined) continue;\n buf += decoder.decode(value, { stream: true });\n const { events, rest } = parseSseEvents(buf);\n buf = rest;\n for (const evt of events) {\n try { clientWs.send(evt); } catch { /* ignore */ }\n }\n }\n // Flush any trailing complete event.\n const { events } = parseSseEvents(buf + '\\n\\n');\n for (const evt of events) {\n try { clientWs.send(evt); } catch { /* ignore */ }\n }\n try { clientWs.close(1000, 'fallback complete'); } catch { /* noop */ }\n } catch (err) {\n logger.warn(`http fallback failed: ${(err as Error).message}`);\n try {\n clientWs.send(\n JSON.stringify({\n type: 'error',\n error: { code: 502, message: (err as Error).message },\n }),\n );\n clientWs.close(1011, 'http fallback error');\n } catch {\n // ignore\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';\nimport { runLogin } from './login.js';\nimport { runLogout } from './logout.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('login')\n .description('Log in to your Skalpel account (opens browser)')\n .action(() => runLogin());\n\nprogram\n .command('logout')\n .description('Log out of your Skalpel account')\n .action(() => runLogout());\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';\nimport { readCodexAuth } from '../proxy/codex-oauth.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 interface CodexOAuthValidation {\n present: boolean;\n reason?: string;\n}\n\n/**\n * Check whether ~/.codex/auth.json is present and readable. Delegates to\n * readCodexAuth() so the file-read logic is not duplicated. Returns a\n * structured result the wizard uses to decide whether to print the OAuth\n * detect-and-warn message after writing the Codex TOML.\n */\nexport function validateCodexOAuth(): CodexOAuthValidation {\n try {\n const auth = readCodexAuth();\n if (auth !== null) {\n return { present: true };\n }\n } catch (err) {\n return { present: false, reason: `other: ${(err as Error).message}` };\n }\n const candidatePath = path.join(\n process.env.HOME ?? process.env.USERPROFILE ?? '',\n '.codex',\n 'auth.json',\n );\n if (!fs.existsSync(candidatePath)) {\n return { present: false, reason: 'file missing' };\n }\n return { present: false, reason: 'malformed JSON' };\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';\nimport { validateCodexOAuth } from '../utils.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 // OAuth detect-and-warn: if the user has not yet run `codex login`, the\n // proxy will keep forwarding the OPENAI_API_KEY placeholder instead of a\n // real ChatGPT-plan bearer. Write a non-emoji stderr warning matching the\n // existing wizard style. Warning is advisory only; the API-key fallback\n // continues to work for headless and CI setups.\n const oauth = validateCodexOAuth();\n if (!oauth.present) {\n process.stderr.write('OAuth not configured. Run: codex login\\n');\n process.stderr.write(' Then re-run: skalpel configure\\n');\n process.stderr.write(' (Skalpel will fall back to OPENAI_API_KEY env var if OAuth missing.)\\n');\n }\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';\nimport { getFreshAccessToken } from './codex-oauth.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 // Record OAuth presence (not the token value) so operators can see in\n // logs whether a Codex WS upgrade is proceeding via ChatGPT-plan\n // OAuth or falling back to the API-key placeholder. The downstream\n // bridge in handler.ts reads ~/.codex/auth.json itself so the token\n // flows through to BackendWsClient construction.\n const oauthPresent = getFreshAccessToken() !== null;\n logger.info(`codex-upgrade oauth_present=${oauthPresent}`);\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. `handleProtocols` echoes back\n// `skalpel-codex-v1` when the client offers it (our own clients do); if\n// the client offers nothing (real Codex), we return `false` which omits\n// the Sec-WebSocket-Protocol response header per RFC 6455.\nconst wss = new WebSocketServer({\n noServer: true,\n handleProtocols: (protocols: Set<string>) =>\n protocols.has(WS_SUBPROTOCOL) ? WS_SUBPROTOCOL : false,\n});\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 // Subprotocol negotiation is OPTIONAL. Real OpenAI Codex clients do not\n // send a Sec-WebSocket-Protocol header — rejecting them would force\n // Codex's session into a permanent HTTP fallback. The shared `wss` has\n // `handleProtocols` configured to echo `skalpel-codex-v1` when offered\n // and omit the header otherwise (RFC 6455 compliant).\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 // For users on a paid ChatGPT plan, running `codex login` first lets\n // the proxy pick up a fresh OAuth bearer and bill against the\n // ChatGPT plan instead of the user's OPENAI_API_KEY.\n const codexConfigured = agentsToConfigure.some((a) => a.name === 'codex');\n if (codexConfigured) {\n print(\" [!] Codex auth: recommended -> run 'codex login' first to enable ChatGPT plan billing.\");\n print(' The OPENAI_API_KEY env var (placeholder OK) is still required for Codex startup');\n print(' but is ignored by Skalpel when OAuth is present.');\n if (!process.env.OPENAI_API_KEY) {\n print(' example: export OPENAI_API_KEY=sk-codex-placeholder-skalpel');\n }\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","import * as http from 'node:http';\nimport * as net from 'node:net';\nimport type { Session } from './session-storage.js';\nimport { isValidSession } from './session-storage.js';\n\nconst MAX_BODY_BYTES = 16 * 1024;\nconst DEFAULT_TIMEOUT_MS = 180_000;\n\nconst DEFAULT_ALLOWED_ORIGINS = [\n 'https://app.skalpel.ai',\n 'https://skalpel.ai',\n];\n\nfunction allowedOrigins(): string[] {\n const extras: string[] = [];\n const webappUrl = process.env.SKALPEL_WEBAPP_URL;\n if (webappUrl) {\n try {\n const u = new URL(webappUrl);\n extras.push(`${u.protocol}//${u.host}`);\n } catch {\n }\n }\n return [...DEFAULT_ALLOWED_ORIGINS, ...extras];\n}\n\nfunction validatePort(port: number): void {\n if (!Number.isInteger(port) || port < 1024 || port > 65535) {\n throw new Error(`Invalid port: ${port} (must be an integer in 1024-65535)`);\n }\n}\n\nexport async function findOpenPort(preferred: number = 51732): Promise<number> {\n if (preferred !== 0) validatePort(preferred);\n\n const tryBind = (port: number): Promise<number | null> =>\n new Promise((resolve) => {\n const server = net.createServer();\n server.once('error', () => {\n server.close(() => resolve(null));\n });\n server.listen(port, '127.0.0.1', () => {\n const address = server.address();\n const boundPort =\n address && typeof address === 'object' ? address.port : null;\n server.close(() => resolve(boundPort));\n });\n });\n\n const preferredResult = await tryBind(preferred);\n if (preferredResult !== null) return preferredResult;\n\n const fallback = await tryBind(0);\n if (fallback !== null) return fallback;\n\n throw new Error('findOpenPort: no open port available');\n}\n\nfunction buildCorsHeaders(origin: string | undefined): Record<string, string> {\n const allowed = allowedOrigins();\n const selected = origin && allowed.includes(origin) ? origin : allowed[0];\n return {\n 'Access-Control-Allow-Origin': selected,\n 'Access-Control-Allow-Methods': 'POST, OPTIONS',\n 'Access-Control-Allow-Headers': 'content-type',\n 'Access-Control-Max-Age': '600',\n Vary: 'Origin',\n };\n}\n\nexport interface StartCallbackServerOptions {\n timeoutMs?: number;\n maxBodyBytes?: number;\n}\n\nexport async function startCallbackServer(\n port: number,\n timeoutMsOrOptions: number | StartCallbackServerOptions = DEFAULT_TIMEOUT_MS,\n maxBodyBytesArg?: number,\n): Promise<Session> {\n validatePort(port);\n\n const opts: StartCallbackServerOptions =\n typeof timeoutMsOrOptions === 'number'\n ? { timeoutMs: timeoutMsOrOptions, maxBodyBytes: maxBodyBytesArg }\n : timeoutMsOrOptions ?? {};\n const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const maxBytes = opts.maxBodyBytes ?? MAX_BODY_BYTES;\n\n return new Promise<Session>((resolve, reject) => {\n let settled = false;\n let timer: NodeJS.Timeout | undefined;\n\n const server = http.createServer((req, res) => {\n const origin = req.headers.origin;\n const corsHeaders = buildCorsHeaders(origin);\n\n if (req.method === 'OPTIONS') {\n res.writeHead(204, corsHeaders);\n res.end();\n return;\n }\n\n if (req.method === 'GET' && (req.url === '/callback' || req.url === '/')) {\n res.writeHead(200, {\n ...corsHeaders,\n 'Content-Type': 'text/html; charset=utf-8',\n });\n res.end(\n '<!doctype html><meta charset=\"utf-8\"><title>Skalpel CLI</title>' +\n '<p>You can close this tab and return to your terminal.</p>',\n );\n return;\n }\n\n if (req.method !== 'POST' || req.url !== '/callback') {\n res.writeHead(404, corsHeaders);\n res.end();\n return;\n }\n\n const contentType = (req.headers['content-type'] || '').toLowerCase();\n if (!contentType.includes('application/json')) {\n res.writeHead(415, { ...corsHeaders, 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Unsupported Media Type' }));\n return;\n }\n\n let total = 0;\n const chunks: Buffer[] = [];\n let aborted = false;\n\n req.on('data', (chunk: Buffer) => {\n if (aborted) return;\n total += chunk.length;\n if (total > maxBytes) {\n aborted = true;\n res.writeHead(413, {\n ...corsHeaders,\n 'Content-Type': 'application/json',\n Connection: 'close',\n });\n res.end(JSON.stringify({ error: 'Payload too large' }));\n return;\n }\n chunks.push(chunk);\n });\n\n req.on('end', () => {\n if (aborted) return;\n const raw = Buffer.concat(chunks).toString('utf-8');\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n res.writeHead(400, { ...corsHeaders, 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Invalid JSON' }));\n return;\n }\n if (!isValidSession(parsed)) {\n res.writeHead(400, { ...corsHeaders, 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Invalid session shape' }));\n if (!settled) {\n settled = true;\n if (timer) clearTimeout(timer);\n server.close(() => reject(new Error('Invalid session received')));\n }\n return;\n }\n\n res.writeHead(200, { ...corsHeaders, 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ ok: true }));\n\n if (!settled) {\n settled = true;\n if (timer) clearTimeout(timer);\n server.close(() => resolve(parsed));\n }\n });\n\n req.on('error', () => {\n });\n });\n\n server.once('error', (err) => {\n if (settled) return;\n settled = true;\n if (timer) clearTimeout(timer);\n reject(err);\n });\n\n server.listen(port, '127.0.0.1', () => {\n timer = setTimeout(() => {\n if (settled) return;\n settled = true;\n server.close(() => reject(new Error('Login timed out')));\n }, timeoutMs);\n if (timer.unref) timer.unref();\n });\n });\n}\n","import * as fs from 'node:fs';\nimport * as os from 'node:os';\nimport * as path from 'node:path';\n\nexport interface Session {\n accessToken: string;\n refreshToken: string;\n expiresAt: number;\n user: {\n id: string;\n email: string;\n };\n}\n\nexport function sessionFilePath(): string {\n return path.join(os.homedir(), '.skalpel', 'session.json');\n}\n\nfunction skalpelDir(): string {\n return path.join(os.homedir(), '.skalpel');\n}\n\nexport function isValidSession(value: unknown): value is Session {\n if (!value || typeof value !== 'object') return false;\n const v = value as Record<string, unknown>;\n if (typeof v.accessToken !== 'string' || v.accessToken.length === 0) return false;\n if (typeof v.refreshToken !== 'string' || v.refreshToken.length === 0) return false;\n if (typeof v.expiresAt !== 'number' || !Number.isFinite(v.expiresAt)) return false;\n if (!v.user || typeof v.user !== 'object') return false;\n const user = v.user as Record<string, unknown>;\n if (typeof user.id !== 'string' || user.id.length === 0) return false;\n if (typeof user.email !== 'string' || user.email.length === 0) return false;\n return true;\n}\n\nexport async function readSession(): Promise<Session | null> {\n const file = sessionFilePath();\n try {\n const raw = await fs.promises.readFile(file, 'utf-8');\n const parsed: unknown = JSON.parse(raw);\n if (!isValidSession(parsed)) return null;\n return parsed;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return null;\n return null;\n }\n}\n\nexport async function writeSession(session: Session): Promise<void> {\n if (!isValidSession(session)) {\n throw new Error('writeSession: invalid session shape');\n }\n const dir = skalpelDir();\n await fs.promises.mkdir(dir, { recursive: true, mode: 0o700 });\n const file = sessionFilePath();\n const tmp = `${file}.tmp-${process.pid}-${Date.now()}`;\n const json = JSON.stringify(session, null, 2);\n await fs.promises.writeFile(tmp, json, { mode: 0o600 });\n try {\n await fs.promises.chmod(tmp, 0o600);\n } catch {\n }\n await fs.promises.rename(tmp, file);\n try {\n await fs.promises.chmod(file, 0o600);\n } catch {\n }\n}\n\nexport async function deleteSession(): Promise<void> {\n const file = sessionFilePath();\n try {\n await fs.promises.unlink(file);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return;\n throw err;\n }\n}\n","export interface OpenUrlResult {\n opened: boolean;\n fallback: boolean;\n}\n\nexport async function openUrl(url: string): Promise<OpenUrlResult> {\n if (!/^https?:\\/\\//i.test(url)) {\n throw new Error(`openUrl: refusing to open non-http(s) URL: ${url}`);\n }\n try {\n const mod: unknown = await import('open');\n const opener = (mod as { default?: (u: string) => Promise<unknown> }).default;\n if (typeof opener !== 'function') {\n throw new Error('open package exports no default function');\n }\n await opener(url);\n return { opened: true, fallback: false };\n } catch {\n console.log('');\n console.log(' Could not open your browser automatically.');\n console.log(' Please open this URL manually to continue:');\n console.log('');\n console.log(` ${url}`);\n console.log('');\n return { opened: false, fallback: true };\n }\n}\n","import {\n findOpenPort,\n startCallbackServer,\n} from './auth/callback-server.js';\nimport { openUrl } from './auth/browser.js';\nimport { writeSession } from './auth/session-storage.js';\n\nconst DEFAULT_WEBAPP_URL = 'https://app.skalpel.ai';\nconst DEFAULT_TIMEOUT_MS = 180_000;\n\nexport interface RunLoginOptions {\n webappUrl?: string;\n preferredPort?: number;\n timeoutMs?: number;\n openUrl?: typeof openUrl;\n startCallbackServer?: typeof startCallbackServer;\n writeSession?: typeof writeSession;\n findOpenPort?: typeof findOpenPort;\n logger?: Pick<Console, 'log' | 'error'>;\n}\n\nexport async function runLogin(options: RunLoginOptions = {}): Promise<void> {\n const webappUrl =\n options.webappUrl ??\n process.env.SKALPEL_WEBAPP_URL ??\n DEFAULT_WEBAPP_URL;\n const preferredPort = options.preferredPort ?? 51732;\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const log = options.logger ?? console;\n const _findPort = options.findOpenPort ?? findOpenPort;\n const _startServer = options.startCallbackServer ?? startCallbackServer;\n const _openUrl = options.openUrl ?? openUrl;\n const _writeSession = options.writeSession ?? writeSession;\n\n const port = await _findPort(preferredPort);\n const authorizeUrl = `${webappUrl.replace(/\\/$/, '')}/cli/authorize?port=${port}`;\n\n log.log('');\n log.log(` Opening browser to ${authorizeUrl}`);\n log.log(' Waiting for authentication (timeout 3 min)...');\n log.log('');\n\n const serverPromise = _startServer(port, timeoutMs);\n\n try {\n await _openUrl(authorizeUrl);\n } catch {\n log.log(' Browser launch failed. Please open the URL above manually.');\n }\n\n let session;\n try {\n session = await serverPromise;\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n log.error('');\n log.error(` Login failed: ${msg}`);\n log.error('');\n process.exitCode = 1;\n throw err;\n }\n\n await _writeSession(session);\n\n log.log('');\n log.log(` \\u2713 Logged in as ${session.user.email}`);\n log.log('');\n}\n","import { deleteSession, readSession } from './auth/session-storage.js';\n\nexport interface RunLogoutOptions {\n logger?: Pick<Console, 'log'>;\n readSession?: typeof readSession;\n deleteSession?: typeof deleteSession;\n}\n\nexport async function runLogout(options: RunLogoutOptions = {}): Promise<void> {\n const log = options.logger ?? console;\n const _readSession = options.readSession ?? readSession;\n const _deleteSession = options.deleteSession ?? deleteSession;\n\n const existing = await _readSession();\n if (!existing) {\n log.log(' Not logged in.');\n return;\n }\n await _deleteSession();\n log.log(` \\u2713 Logged out.`);\n}\n"],"mappings":"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","names":["path","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","http","fs","path","print","execSync","print","fs","print","fs","print","require","print","resolve","readline","fs","path","os","print","resolve","skalpelDir","readline","fs","path","os","print","resolve","skalpelDir","http","net","fs","os","path","resolve","DEFAULT_TIMEOUT_MS","require","createRequire","pkg"]}
|
|
1
|
+
{"version":3,"sources":["../../src/proxy/codex-oauth.ts","../../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","../../src/cli/auth/callback-server.ts","../../src/cli/auth/session-storage.ts","../../src/cli/auth/browser.ts","../../src/cli/login.ts","../../src/cli/logout.ts"],"sourcesContent":["import { readFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\n\n/**\n * OAuth credentials stored by the official OpenAI Codex CLI at\n * `~/.codex/auth.json`. Skalpel only reads this file — refresh and rotation\n * are owned by Codex. Field shape mirrors what `codex login` writes today.\n */\nexport interface CodexAuth {\n access_token: string;\n refresh_token: string;\n expires_at: string;\n account_id?: string;\n}\n\n/**\n * Buffer (in milliseconds) applied to `expires_at` before treating a token\n * as fresh. Keeps us from forwarding a token that will expire mid-flight.\n */\nexport const TOKEN_FRESHNESS_BUFFER_MS = 10_000;\n\nfunction authFilePath(): string {\n return join(homedir(), '.codex', 'auth.json');\n}\n\n/**\n * Read `~/.codex/auth.json` if present. Returns null on missing file or\n * malformed JSON; warnings are written to stderr but never thrown so the\n * proxy hot path keeps serving requests via the API-key fallback.\n *\n * Note: the access_token value is intentionally never logged; only its\n * presence is observable via the function's return value.\n */\nexport function readCodexAuth(): CodexAuth | null {\n const path = authFilePath();\n let raw: string;\n try {\n raw = readFileSync(path, 'utf-8');\n } catch (err) {\n const code = (err as NodeJS.ErrnoException)?.code;\n if (code !== 'ENOENT') {\n // Other read errors (EACCES, etc.) — log without exposing the path or\n // contents.\n process.stderr.write(`skalpel: codex-oauth: cannot read auth file (${code ?? 'unknown'})\\n`);\n }\n return null;\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n process.stderr.write('skalpel: codex-oauth: auth file is not valid JSON\\n');\n return null;\n }\n\n if (\n parsed === null ||\n typeof parsed !== 'object' ||\n typeof (parsed as Record<string, unknown>).access_token !== 'string' ||\n typeof (parsed as Record<string, unknown>).refresh_token !== 'string' ||\n typeof (parsed as Record<string, unknown>).expires_at !== 'string'\n ) {\n process.stderr.write('skalpel: codex-oauth: auth file missing required fields\\n');\n return null;\n }\n\n const obj = parsed as Record<string, unknown>;\n const auth: CodexAuth = {\n access_token: obj.access_token as string,\n refresh_token: obj.refresh_token as string,\n expires_at: obj.expires_at as string,\n };\n if (typeof obj.account_id === 'string') {\n auth.account_id = obj.account_id;\n }\n return auth;\n}\n\n/**\n * Returns true if `auth.expires_at` is more than TOKEN_FRESHNESS_BUFFER_MS\n * in the future. Codex refreshes pre-emptively at the 5-minute mark, so any\n * token still within its window is safe to forward.\n */\nexport function isTokenFresh(auth: CodexAuth): boolean {\n const expiresAtMs = Date.parse(auth.expires_at);\n if (!Number.isFinite(expiresAtMs)) return false;\n return expiresAtMs > Date.now() + TOKEN_FRESHNESS_BUFFER_MS;\n}\n\n/**\n * Convenience helper: read the file and return the access_token only when\n * the OAuth credential is both present and fresh. Returns null in all other\n * cases (missing file, malformed, expired). Callers should fall back to\n * the inbound API key when null is returned.\n */\nexport function getFreshAccessToken(): string | null {\n const auth = readCodexAuth();\n if (auth === null) return null;\n if (!isTokenFresh(auth)) return null;\n return auth.access_token;\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 allowH2: false, // Force HTTP/1.1 to prevent GCP LB WebSocket downgrade\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 / undici error into a single line with cause chain and URL,\n * for logging. Replaces opaque `(err as Error).message` strings that hide\n * the actual network-layer failure code.\n */\nexport function formatFetchErrorForLog(err: unknown, url: string): string {\n const parts: string[] = [];\n const top = err as { code?: string; name?: string; message?: string; cause?: unknown } | null;\n if (top && typeof top === 'object') {\n if (top.name) parts.push(`name=${top.name}`);\n if (top.code) parts.push(`code=${top.code}`);\n if (top.message) parts.push(`msg=${top.message}`);\n // Walk cause chain (undici wraps the underlying errno in `.cause`).\n let cause: unknown = top.cause;\n let depth = 0;\n while (cause && depth < 4) {\n const c = cause as { code?: string; name?: string; message?: string; cause?: unknown };\n const causeBits: string[] = [];\n if (c.name) causeBits.push(`name=${c.name}`);\n if (c.code) causeBits.push(`code=${c.code}`);\n if (c.message) causeBits.push(`msg=${c.message}`);\n if (causeBits.length > 0) parts.push(`cause[${causeBits.join(',')}]`);\n cause = c.cause;\n depth += 1;\n }\n } else if (err !== undefined && err !== null) {\n parts.push(String(err));\n }\n parts.push(`url=${url}`);\n return parts.join(' ');\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 https from 'node:https';\nimport http from 'node:http';\nimport WebSocket from 'ws';\nimport type { Logger } from './logger.js';\nimport { getFreshAccessToken } from './codex-oauth.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\n// GCP's GCE Load Balancer advertises h2 in ALPN by default. When Node's\n// ws client negotiates h2, the LB downgrades the Upgrade: websocket\n// request to a plain HTTP/2 GET and the FastAPI backend returns 405\n// (\"Use POST /v1/responses\"). Forcing http/1.1 in ALPN on the TLS\n// ClientHello keeps the connection on HTTP/1.1 where WS upgrades work.\nconst H1_HTTPS_AGENT = new https.Agent({\n ALPNProtocols: ['http/1.1'],\n keepAlive: true,\n});\nconst H1_HTTP_AGENT = new http.Agent({ keepAlive: true });\n\nfunction pickAgent(url: string): https.Agent | http.Agent {\n return url.startsWith('wss://') ? H1_HTTPS_AGENT : H1_HTTP_AGENT;\n}\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 // On every connect (initial + reconnect) re-read ~/.codex/auth.json so\n // long-running sessions pick up Codex's pre-emptive token rotation. Fall\n // back to the constructor-supplied token when OAuth is missing/expired\n // so the API-key path still works.\n const freshToken = getFreshAccessToken();\n const bearer = freshToken ?? this.opts.oauthToken;\n return new Promise((resolve, reject) => {\n const ws = new WebSocket(this.opts.url, [WS_SUBPROTOCOL], {\n agent: pickAgent(this.opts.url),\n headers: {\n 'X-Skalpel-API-Key': this.opts.apiKey,\n Authorization: `Bearer ${bearer}`,\n 'x-skalpel-source': this.opts.source,\n },\n });\n\n this.ws = ws;\n\n // Handshake failure (server replied with a non-101 HTTP status —\n // 405 is what GCP's GCE LB returns when ALPN negotiates HTTP/2 and\n // the request gets downgraded to a plain GET). Emit `fallback`\n // synchronously so handleWebSocketBridge switches to HTTP without\n // burning the 5 reconnect attempts.\n ws.once('unexpected-response', (_req, res) => {\n const status = (res as unknown as { statusCode?: number }).statusCode ?? 0;\n this.opts.logger.warn(\n `ws-client handshake rejected status=${status} — no retry, falling back to HTTP`,\n );\n // Mark not-connecting so the upcoming 'error'/'close' events from\n // the aborted handshake do not trigger scheduleReconnect.\n this.closedByUser = true;\n this.emit('fallback', `handshake_${status}`);\n try {\n (res as unknown as { destroy?: () => void }).destroy?.();\n } catch {\n // ignore\n }\n this.ws = null;\n reject(new Error(`ws handshake status ${status}`));\n });\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 { getFreshAccessToken } from './codex-oauth.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\nfunction validateCodexAuth(oauthToken: string | null, inboundAuth: string): { valid: boolean; error?: string } {\n if (!oauthToken && !inboundAuth) {\n return { valid: false, error: 'no_credentials' };\n }\n return { valid: true };\n}\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 // Codex traffic: when a fresh OAuth token is present in\n // ~/.codex/auth.json, override the inbound Authorization (which is a\n // placeholder set by the Codex TOML OPENAI_API_KEY env var) with the\n // real ChatGPT-plan bearer. When OAuth is missing or expired, keep the\n // inbound header so the API-key fallback continues to work.\n if (source === 'codex') {\n const oauthToken = getFreshAccessToken();\n if (oauthToken !== null) {\n forwardHeaders['authorization'] = `Bearer ${oauthToken}`;\n delete forwardHeaders['Authorization'];\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 * **Pass-through mode.** The wire format is OpenAI's native Responses API\n * WS protocol — no Skalpel envelope on either direction. Real Codex sends\n * the bare request body as the first text frame; the server streams back\n * native OpenAI event frames (`response.created`, `response.output_text\n * .delta`, `response.completed`, ...). We just pipe bytes.\n *\n * On backend fallback (exhausted reconnects, non-transient close code,\n * or feature flag disabled on backend), we fall over to HTTP POST, using\n * the cached first client frame as the POST body. SSE events from the\n * HTTP response are re-emitted to clientWs as individual WS text frames\n * (same shape as the backend WS path).\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 // Prefer a fresh ChatGPT-plan OAuth token from ~/.codex/auth.json; fall\n // back to whatever Authorization header the client sent (typically the\n // Codex TOML placeholder, i.e. OPENAI_API_KEY). ws-client also refreshes\n // on reconnect so long-running sessions pick up rotations.\n const freshOauthToken = getFreshAccessToken();\n let oauthToken: string;\n if (freshOauthToken !== null) {\n oauthToken = freshOauthToken;\n } else {\n const oauthHeader = (req.headers['authorization'] ?? '') as string;\n oauthToken = oauthHeader.toLowerCase().startsWith('bearer ')\n ? oauthHeader.slice(7).trim()\n : '';\n }\n\n const authResult = validateCodexAuth(freshOauthToken, oauthToken);\n if (!authResult.valid) {\n clientWs.send(JSON.stringify({\n type: 'error',\n error: {\n code: 'no_credentials',\n message: 'Codex requires OAuth login. Run: codex login',\n },\n }));\n clientWs.close(4000, 'no credentials');\n return;\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 // Raw text frames buffered from the client. The first one is always\n // the Responses API request body (Codex sends exactly one). We replay\n // the cached body on HTTP fallback.\n const pendingClientText: string[] = [];\n let firstClientFrameBody: string | null = null;\n\n const flushPending = (): void => {\n if (!backendOpen || fallbackActive) return;\n while (pendingClientText.length > 0) {\n const text = pendingClientText.shift();\n if (text === undefined) break;\n try {\n // BackendWsClient.send takes a JSON object (stringified again).\n // For pass-through we already have a JSON text frame, so bypass\n // via the underlying ws. BackendWsClient exposes send(frame:object)\n // but we need raw text; JSON-parse to let it re-stringify. If the\n // client sent non-JSON, log and drop.\n const parsed = JSON.parse(text);\n backend.send(parsed as Record<string, unknown>);\n } catch (err) {\n logger.warn(`bridge: flush backend.send failed: ${(err as Error).message}`);\n pendingClientText.unshift(text);\n return;\n }\n }\n };\n\n // client WS → backend WS (pass-through). Cache the first frame for\n // HTTP fallback.\n clientWs.on('message', (data: WebSocket.RawData) => {\n const text = data.toString('utf-8');\n if (firstClientFrameBody === null) {\n firstClientFrameBody = text;\n }\n pendingClientText.push(text);\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 // backend WS → client WS (pass-through).\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 });\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 if (fallbackActive) return;\n fallbackActive = true;\n backendOpen = false;\n logger.warn(`bridge: backend fallback reason=${reason} — switching to HTTP POST`);\n const body =\n firstClientFrameBody ??\n (pendingClientText.length > 0 ? pendingClientText[0] : null);\n if (body === null) {\n logger.warn('bridge: no request body cached for HTTP fallback');\n return;\n }\n const inboundAuth = (req.headers['authorization'] ?? '') as string;\n fallbackToHttp(clientWs, config, source, logger, body, inboundAuth).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\nfunction parseSseEvents(buffer: string): { events: string[]; rest: string } {\n const events: string[] = [];\n let rest = buffer.replace(/\\r\\n/g, '\\n');\n while (rest.includes('\\n\\n')) {\n const idx = rest.indexOf('\\n\\n');\n const block = rest.slice(0, idx);\n rest = rest.slice(idx + 2);\n const dataLines: string[] = [];\n for (const line of block.split('\\n')) {\n const trimmed = line.replace(/^\\s+/, '');\n if (trimmed.startsWith('data:')) {\n dataLines.push(trimmed.slice(5).replace(/^\\s+/, ''));\n }\n }\n if (dataLines.length === 0) continue;\n const joined = dataLines.join('\\n').trim();\n if (joined.length === 0 || joined === '[DONE]') continue;\n events.push(joined);\n }\n return { events, rest };\n}\n\nasync function fallbackToHttp(\n clientWs: WebSocket,\n config: ProxyConfig,\n source: 'codex',\n logger: Logger,\n requestBody: string,\n inboundAuth: string,\n): Promise<void> {\n // Pre-flight validation: reject if no credentials available\n const preflightAuth = validateCodexAuth(getFreshAccessToken(), inboundAuth);\n if (!preflightAuth.valid) {\n clientWs.send(JSON.stringify({\n type: 'error',\n error: {\n code: 'no_credentials',\n message: 'Codex requires OAuth login. Run: codex login',\n },\n }));\n clientWs.close(4000, 'no credentials');\n return;\n }\n\n try {\n // Prefer a fresh ChatGPT-plan OAuth bearer; fall back to whatever the\n // client sent (typically the Codex OPENAI_API_KEY placeholder) so the\n // API-key path to api.openai.com still works. Backend's /v1/responses\n // rejects the request with 400 if Authorization is absent — never\n // send the request without an auth header.\n const freshToken = getFreshAccessToken();\n const authHeader = freshToken !== null\n ? `Bearer ${freshToken}`\n : inboundAuth;\n if (!authHeader) {\n logger.error('http fallback aborted: no Authorization available');\n try {\n clientWs.send(\n JSON.stringify({\n type: 'error',\n error: { code: 401, message: 'no credentials available for fallback' },\n }),\n );\n clientWs.close(1011, 'no credentials');\n } catch { /* ignore */ }\n return;\n }\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 'X-Skalpel-Auth-Mode': 'passthrough',\n Authorization: authHeader,\n Accept: 'text/event-stream',\n },\n body: requestBody,\n });\n if (!resp.ok) {\n let errorBody = '';\n try {\n // Read body with 5-second timeout to prevent hangs\n const bodyPromise = resp.text();\n const timeoutPromise = new Promise<string>((_, reject) =>\n setTimeout(() => reject(new Error('body read timeout')), 5000)\n );\n errorBody = await Promise.race([bodyPromise, timeoutPromise]);\n } catch (e) {\n errorBody = `status ${resp.status}`;\n }\n let errorMessage = `Backend error: ${resp.status}`;\n try {\n const parsed = JSON.parse(errorBody);\n errorMessage = parsed?.error?.message || parsed?.detail || errorMessage;\n } catch {\n // Use raw body if not JSON\n if (errorBody.length < 200) errorMessage = errorBody;\n }\n clientWs.send(JSON.stringify({\n type: 'error',\n error: { code: resp.status, message: errorMessage },\n }));\n clientWs.close(1011, 'backend error');\n return;\n }\n if (resp.body === null) {\n clientWs.send(JSON.stringify({\n type: 'error',\n error: { code: resp.status, message: 'empty response body' },\n }));\n clientWs.close(1011, 'empty body');\n return;\n }\n const reader = resp.body.getReader();\n const decoder = new TextDecoder('utf-8');\n let buf = '';\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n if (value === undefined) continue;\n buf += decoder.decode(value, { stream: true });\n const { events, rest } = parseSseEvents(buf);\n buf = rest;\n for (const evt of events) {\n try { clientWs.send(evt); } catch { /* ignore */ }\n }\n }\n // Flush any trailing complete event.\n const { events } = parseSseEvents(buf + '\\n\\n');\n for (const evt of events) {\n try { clientWs.send(evt); } catch { /* ignore */ }\n }\n try { clientWs.close(1000, 'fallback complete'); } catch { /* noop */ }\n } catch (err) {\n logger.warn(`http fallback failed: ${(err as Error).message}`);\n try {\n clientWs.send(\n JSON.stringify({\n type: 'error',\n error: { code: 502, message: (err as Error).message },\n }),\n );\n clientWs.close(1011, 'http fallback error');\n } catch {\n // ignore\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';\nimport { runLogin } from './login.js';\nimport { runLogout } from './logout.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('login')\n .description('Log in to your Skalpel account (opens browser)')\n .action(() => runLogin());\n\nprogram\n .command('logout')\n .description('Log out of your Skalpel account')\n .action(() => runLogout());\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';\nimport { readCodexAuth } from '../proxy/codex-oauth.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 interface CodexOAuthValidation {\n present: boolean;\n reason?: string;\n}\n\n/**\n * Check whether ~/.codex/auth.json is present and readable. Delegates to\n * readCodexAuth() so the file-read logic is not duplicated. Returns a\n * structured result the wizard uses to decide whether to print the OAuth\n * detect-and-warn message after writing the Codex TOML.\n */\nexport function validateCodexOAuth(): CodexOAuthValidation {\n try {\n const auth = readCodexAuth();\n if (auth !== null) {\n return { present: true };\n }\n } catch (err) {\n return { present: false, reason: `other: ${(err as Error).message}` };\n }\n const candidatePath = path.join(\n process.env.HOME ?? process.env.USERPROFILE ?? '',\n '.codex',\n 'auth.json',\n );\n if (!fs.existsSync(candidatePath)) {\n return { present: false, reason: 'file missing' };\n }\n return { present: false, reason: 'malformed JSON' };\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';\nimport { validateCodexOAuth } from '../utils.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 // OAuth detect-and-warn: if the user has not yet run `codex login`, the\n // proxy will keep forwarding the OPENAI_API_KEY placeholder instead of a\n // real ChatGPT-plan bearer. Write a non-emoji stderr warning matching the\n // existing wizard style. Warning is advisory only; the API-key fallback\n // continues to work for headless and CI setups.\n const oauth = validateCodexOAuth();\n if (!oauth.present) {\n process.stderr.write('OAuth not configured. Run: codex login\\n');\n process.stderr.write(' Then re-run: npx skalpel\\n');\n process.stderr.write(' (Skalpel will fall back to OPENAI_API_KEY env var if OAuth missing.)\\n');\n }\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 crypto from 'node:crypto';\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\n/**\n * Stable placeholder for OPENAI_API_KEY. Codex CLI refuses to start unless\n * the env var is non-empty; when the user hasn't run `codex login` and\n * hasn't set their own key, we default it so `codex` boots and hits the\n * proxy, which handles OAuth injection / error reporting from there.\n *\n * Value format: `sk-codex-skalpel-<16 hex>`. Random per install (we don't\n * persist it across runs); meaningful to the backend only as \"clearly a\n * placeholder\" — it starts with sk- so the backend routes to api.openai.com\n * where OpenAI will return 401 with a clear \"invalid api key\" error, which\n * is the right outcome when the user has neither OAuth nor a real key.\n */\nfunction randomPlaceholder(): string {\n return `sk-codex-skalpel-${crypto.randomBytes(8).toString('hex')}`;\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 // `:-` only evaluates the default when OPENAI_API_KEY is unset or empty,\n // so a user-provided key always wins. Bash, zsh, and POSIX sh all\n // support this syntax.\n `export OPENAI_API_KEY=\"\\${OPENAI_API_KEY:-${randomPlaceholder()}}\"`,\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 // Conditional assignment — only set when the user hasn't already\n // exported OPENAI_API_KEY in their session or parent profile.\n `if (-not $env:OPENAI_API_KEY) { $env:OPENAI_API_KEY = \"${randomPlaceholder()}\" }`,\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';\nimport { getFreshAccessToken } from './codex-oauth.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 // Record OAuth presence (not the token value) so operators can see in\n // logs whether a Codex WS upgrade is proceeding via ChatGPT-plan\n // OAuth or falling back to the API-key placeholder. The downstream\n // bridge in handler.ts reads ~/.codex/auth.json itself so the token\n // flows through to BackendWsClient construction.\n const oauthPresent = getFreshAccessToken() !== null;\n logger.info(`codex-upgrade oauth_present=${oauthPresent}`);\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. `handleProtocols` echoes back\n// `skalpel-codex-v1` when the client offers it (our own clients do); if\n// the client offers nothing (real Codex), we return `false` which omits\n// the Sec-WebSocket-Protocol response header per RFC 6455.\nconst wss = new WebSocketServer({\n noServer: true,\n handleProtocols: (protocols: Set<string>) =>\n protocols.has(WS_SUBPROTOCOL) ? WS_SUBPROTOCOL : false,\n});\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 // Subprotocol negotiation is OPTIONAL. Real OpenAI Codex clients do not\n // send a Sec-WebSocket-Protocol header — rejecting them would force\n // Codex's session into a permanent HTTP fallback. The shared `wss` has\n // `handleProtocols` configured to echo `skalpel-codex-v1` when offered\n // and omit the header otherwise (RFC 6455 compliant).\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 // The shell block installed above defaults OPENAI_API_KEY to a random\n // placeholder if the user has not already exported one, so Codex\n // boots cleanly with no manual env-var setup. For ChatGPT-plan\n // billing, running `codex login` lets the proxy pick up a fresh\n // OAuth bearer and override the placeholder per request.\n const codexConfigured = agentsToConfigure.some((a) => a.name === 'codex');\n if (codexConfigured) {\n print(\" [!] Codex auth: recommended -> run 'codex login' to enable ChatGPT plan billing.\");\n print(' Without OAuth, Skalpel uses the OPENAI_API_KEY env var; if not already set,');\n print(' Skalpel exports a placeholder so Codex starts. A real sk-... key is only');\n print(' needed if you have no ChatGPT plan and want pay-as-you-go API billing.');\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","import * as http from 'node:http';\nimport * as net from 'node:net';\nimport type { Session } from './session-storage.js';\nimport { isValidSession } from './session-storage.js';\n\nconst MAX_BODY_BYTES = 16 * 1024;\nconst DEFAULT_TIMEOUT_MS = 180_000;\n\nconst DEFAULT_ALLOWED_ORIGINS = [\n 'https://app.skalpel.ai',\n 'https://skalpel.ai',\n];\n\nfunction allowedOrigins(): string[] {\n const extras: string[] = [];\n const webappUrl = process.env.SKALPEL_WEBAPP_URL;\n if (webappUrl) {\n try {\n const u = new URL(webappUrl);\n extras.push(`${u.protocol}//${u.host}`);\n } catch {\n }\n }\n return [...DEFAULT_ALLOWED_ORIGINS, ...extras];\n}\n\nfunction validatePort(port: number): void {\n if (!Number.isInteger(port) || port < 1024 || port > 65535) {\n throw new Error(`Invalid port: ${port} (must be an integer in 1024-65535)`);\n }\n}\n\nexport async function findOpenPort(preferred: number = 51732): Promise<number> {\n if (preferred !== 0) validatePort(preferred);\n\n const tryBind = (port: number): Promise<number | null> =>\n new Promise((resolve) => {\n const server = net.createServer();\n server.once('error', () => {\n server.close(() => resolve(null));\n });\n server.listen(port, '127.0.0.1', () => {\n const address = server.address();\n const boundPort =\n address && typeof address === 'object' ? address.port : null;\n server.close(() => resolve(boundPort));\n });\n });\n\n const preferredResult = await tryBind(preferred);\n if (preferredResult !== null) return preferredResult;\n\n const fallback = await tryBind(0);\n if (fallback !== null) return fallback;\n\n throw new Error('findOpenPort: no open port available');\n}\n\nfunction buildCorsHeaders(origin: string | undefined): Record<string, string> {\n const allowed = allowedOrigins();\n const selected = origin && allowed.includes(origin) ? origin : allowed[0];\n return {\n 'Access-Control-Allow-Origin': selected,\n 'Access-Control-Allow-Methods': 'POST, OPTIONS',\n 'Access-Control-Allow-Headers': 'content-type',\n 'Access-Control-Max-Age': '600',\n Vary: 'Origin',\n };\n}\n\nexport interface StartCallbackServerOptions {\n timeoutMs?: number;\n maxBodyBytes?: number;\n}\n\nexport async function startCallbackServer(\n port: number,\n timeoutMsOrOptions: number | StartCallbackServerOptions = DEFAULT_TIMEOUT_MS,\n maxBodyBytesArg?: number,\n): Promise<Session> {\n validatePort(port);\n\n const opts: StartCallbackServerOptions =\n typeof timeoutMsOrOptions === 'number'\n ? { timeoutMs: timeoutMsOrOptions, maxBodyBytes: maxBodyBytesArg }\n : timeoutMsOrOptions ?? {};\n const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const maxBytes = opts.maxBodyBytes ?? MAX_BODY_BYTES;\n\n return new Promise<Session>((resolve, reject) => {\n let settled = false;\n let timer: NodeJS.Timeout | undefined;\n\n const server = http.createServer((req, res) => {\n const origin = req.headers.origin;\n const corsHeaders = buildCorsHeaders(origin);\n\n if (req.method === 'OPTIONS') {\n res.writeHead(204, corsHeaders);\n res.end();\n return;\n }\n\n if (req.method === 'GET' && (req.url === '/callback' || req.url === '/')) {\n res.writeHead(200, {\n ...corsHeaders,\n 'Content-Type': 'text/html; charset=utf-8',\n });\n res.end(\n '<!doctype html><meta charset=\"utf-8\"><title>Skalpel CLI</title>' +\n '<p>You can close this tab and return to your terminal.</p>',\n );\n return;\n }\n\n if (req.method !== 'POST' || req.url !== '/callback') {\n res.writeHead(404, corsHeaders);\n res.end();\n return;\n }\n\n const contentType = (req.headers['content-type'] || '').toLowerCase();\n if (!contentType.includes('application/json')) {\n res.writeHead(415, { ...corsHeaders, 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Unsupported Media Type' }));\n return;\n }\n\n let total = 0;\n const chunks: Buffer[] = [];\n let aborted = false;\n\n req.on('data', (chunk: Buffer) => {\n if (aborted) return;\n total += chunk.length;\n if (total > maxBytes) {\n aborted = true;\n res.writeHead(413, {\n ...corsHeaders,\n 'Content-Type': 'application/json',\n Connection: 'close',\n });\n res.end(JSON.stringify({ error: 'Payload too large' }));\n return;\n }\n chunks.push(chunk);\n });\n\n req.on('end', () => {\n if (aborted) return;\n const raw = Buffer.concat(chunks).toString('utf-8');\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n res.writeHead(400, { ...corsHeaders, 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Invalid JSON' }));\n return;\n }\n if (!isValidSession(parsed)) {\n res.writeHead(400, { ...corsHeaders, 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Invalid session shape' }));\n if (!settled) {\n settled = true;\n if (timer) clearTimeout(timer);\n server.close(() => reject(new Error('Invalid session received')));\n }\n return;\n }\n\n res.writeHead(200, { ...corsHeaders, 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ ok: true }));\n\n if (!settled) {\n settled = true;\n if (timer) clearTimeout(timer);\n server.close(() => resolve(parsed));\n }\n });\n\n req.on('error', () => {\n });\n });\n\n server.once('error', (err) => {\n if (settled) return;\n settled = true;\n if (timer) clearTimeout(timer);\n reject(err);\n });\n\n server.listen(port, '127.0.0.1', () => {\n timer = setTimeout(() => {\n if (settled) return;\n settled = true;\n server.close(() => reject(new Error('Login timed out')));\n }, timeoutMs);\n if (timer.unref) timer.unref();\n });\n });\n}\n","import * as fs from 'node:fs';\nimport * as os from 'node:os';\nimport * as path from 'node:path';\n\nexport interface Session {\n accessToken: string;\n refreshToken: string;\n expiresAt: number;\n user: {\n id: string;\n email: string;\n };\n}\n\nexport function sessionFilePath(): string {\n return path.join(os.homedir(), '.skalpel', 'session.json');\n}\n\nfunction skalpelDir(): string {\n return path.join(os.homedir(), '.skalpel');\n}\n\nexport function isValidSession(value: unknown): value is Session {\n if (!value || typeof value !== 'object') return false;\n const v = value as Record<string, unknown>;\n if (typeof v.accessToken !== 'string' || v.accessToken.length === 0) return false;\n if (typeof v.refreshToken !== 'string' || v.refreshToken.length === 0) return false;\n if (typeof v.expiresAt !== 'number' || !Number.isFinite(v.expiresAt)) return false;\n if (!v.user || typeof v.user !== 'object') return false;\n const user = v.user as Record<string, unknown>;\n if (typeof user.id !== 'string' || user.id.length === 0) return false;\n if (typeof user.email !== 'string' || user.email.length === 0) return false;\n return true;\n}\n\nexport async function readSession(): Promise<Session | null> {\n const file = sessionFilePath();\n try {\n const raw = await fs.promises.readFile(file, 'utf-8');\n const parsed: unknown = JSON.parse(raw);\n if (!isValidSession(parsed)) return null;\n return parsed;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return null;\n return null;\n }\n}\n\nexport async function writeSession(session: Session): Promise<void> {\n if (!isValidSession(session)) {\n throw new Error('writeSession: invalid session shape');\n }\n const dir = skalpelDir();\n await fs.promises.mkdir(dir, { recursive: true, mode: 0o700 });\n const file = sessionFilePath();\n const tmp = `${file}.tmp-${process.pid}-${Date.now()}`;\n const json = JSON.stringify(session, null, 2);\n await fs.promises.writeFile(tmp, json, { mode: 0o600 });\n try {\n await fs.promises.chmod(tmp, 0o600);\n } catch {\n }\n await fs.promises.rename(tmp, file);\n try {\n await fs.promises.chmod(file, 0o600);\n } catch {\n }\n}\n\nexport async function deleteSession(): Promise<void> {\n const file = sessionFilePath();\n try {\n await fs.promises.unlink(file);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return;\n throw err;\n }\n}\n","export interface OpenUrlResult {\n opened: boolean;\n fallback: boolean;\n}\n\nexport async function openUrl(url: string): Promise<OpenUrlResult> {\n if (!/^https?:\\/\\//i.test(url)) {\n throw new Error(`openUrl: refusing to open non-http(s) URL: ${url}`);\n }\n try {\n const mod: unknown = await import('open');\n const opener = (mod as { default?: (u: string) => Promise<unknown> }).default;\n if (typeof opener !== 'function') {\n throw new Error('open package exports no default function');\n }\n await opener(url);\n return { opened: true, fallback: false };\n } catch {\n console.log('');\n console.log(' Could not open your browser automatically.');\n console.log(' Please open this URL manually to continue:');\n console.log('');\n console.log(` ${url}`);\n console.log('');\n return { opened: false, fallback: true };\n }\n}\n","import {\n findOpenPort,\n startCallbackServer,\n} from './auth/callback-server.js';\nimport { openUrl } from './auth/browser.js';\nimport { writeSession } from './auth/session-storage.js';\n\nconst DEFAULT_WEBAPP_URL = 'https://app.skalpel.ai';\nconst DEFAULT_TIMEOUT_MS = 180_000;\n\nexport interface RunLoginOptions {\n webappUrl?: string;\n preferredPort?: number;\n timeoutMs?: number;\n openUrl?: typeof openUrl;\n startCallbackServer?: typeof startCallbackServer;\n writeSession?: typeof writeSession;\n findOpenPort?: typeof findOpenPort;\n logger?: Pick<Console, 'log' | 'error'>;\n}\n\nexport async function runLogin(options: RunLoginOptions = {}): Promise<void> {\n const webappUrl =\n options.webappUrl ??\n process.env.SKALPEL_WEBAPP_URL ??\n DEFAULT_WEBAPP_URL;\n const preferredPort = options.preferredPort ?? 51732;\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const log = options.logger ?? console;\n const _findPort = options.findOpenPort ?? findOpenPort;\n const _startServer = options.startCallbackServer ?? startCallbackServer;\n const _openUrl = options.openUrl ?? openUrl;\n const _writeSession = options.writeSession ?? writeSession;\n\n const port = await _findPort(preferredPort);\n const authorizeUrl = `${webappUrl.replace(/\\/$/, '')}/cli/authorize?port=${port}`;\n\n log.log('');\n log.log(` Opening browser to ${authorizeUrl}`);\n log.log(' Waiting for authentication (timeout 3 min)...');\n log.log('');\n\n const serverPromise = _startServer(port, timeoutMs);\n\n try {\n await _openUrl(authorizeUrl);\n } catch {\n log.log(' Browser launch failed. Please open the URL above manually.');\n }\n\n let session;\n try {\n session = await serverPromise;\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n log.error('');\n log.error(` Login failed: ${msg}`);\n log.error('');\n process.exitCode = 1;\n throw err;\n }\n\n await _writeSession(session);\n\n log.log('');\n log.log(` \\u2713 Logged in as ${session.user.email}`);\n log.log('');\n}\n","import { deleteSession, readSession } from './auth/session-storage.js';\n\nexport interface RunLogoutOptions {\n logger?: Pick<Console, 'log'>;\n readSession?: typeof readSession;\n deleteSession?: typeof deleteSession;\n}\n\nexport async function runLogout(options: RunLogoutOptions = {}): Promise<void> {\n const log = options.logger ?? console;\n const _readSession = options.readSession ?? readSession;\n const _deleteSession = options.deleteSession ?? deleteSession;\n\n const existing = await _readSession();\n if (!existing) {\n log.log(' Not logged in.');\n return;\n }\n await _deleteSession();\n log.log(` \\u2713 Logged out.`);\n}\n"],"mappings":"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","names":["path","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","http","fs","path","print","execSync","print","fs","print","fs","print","require","print","resolve","readline","fs","path","os","print","resolve","skalpelDir","readline","fs","path","os","print","resolve","skalpelDir","http","net","fs","os","path","resolve","DEFAULT_TIMEOUT_MS","require","createRequire","pkg"]}
|