github-router 0.3.304 → 0.3.306
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/{attribution-settings-C8iSwEAZ.js → attribution-settings-C5ScaOmz.js} +7 -7
- package/dist/{attribution-settings-C8iSwEAZ.js.map → attribution-settings-C5ScaOmz.js.map} +1 -1
- package/dist/{auth-CpjOjoUG.js → auth-D9kUMR37.js} +3 -3
- package/dist/{auth-CpjOjoUG.js.map → auth-D9kUMR37.js.map} +1 -1
- package/dist/browser-ext/manifest.json +1 -1
- package/dist/{check-usage-BsN3S4Fu.js → check-usage-C7MfBexN.js} +4 -4
- package/dist/{check-usage-BsN3S4Fu.js.map → check-usage-C7MfBexN.js.map} +1 -1
- package/dist/{claude-Drv0roq1.js → claude-CsGXL12X.js} +28 -19
- package/dist/claude-CsGXL12X.js.map +1 -0
- package/dist/{codex-kgVL5OyN.js → codex-DpwyLLTp.js} +5 -5
- package/dist/{codex-kgVL5OyN.js.map → codex-DpwyLLTp.js.map} +1 -1
- package/dist/{debug-BXRPyeqO.js → debug-CDu4RIgW.js} +2 -2
- package/dist/{debug-BXRPyeqO.js.map → debug-CDu4RIgW.js.map} +1 -1
- package/dist/engine-j34DK_PM.js +2 -0
- package/dist/{gate-discovery-D4-F99e_.js → gate-discovery-BVLZH7-6.js} +5 -5
- package/dist/{gate-discovery-D4-F99e_.js.map → gate-discovery-BVLZH7-6.js.map} +1 -1
- package/dist/{get-copilot-usage-ByjQqhdi.js → get-copilot-usage-CNMvWsMD.js} +2 -2
- package/dist/{get-copilot-usage-ByjQqhdi.js.map → get-copilot-usage-CNMvWsMD.js.map} +1 -1
- package/dist/{internal-artifact-open-DRSxShAW.js → internal-artifact-open-wYwuChib.js} +2 -2
- package/dist/{internal-artifact-open-DRSxShAW.js.map → internal-artifact-open-wYwuChib.js.map} +1 -1
- package/dist/{internal-fast-dispatch-guard-ByzwIsxA.js → internal-fast-dispatch-guard-CXs4ympz.js} +1 -1
- package/dist/{internal-fast-dispatch-guard-BaUB-F8o.js → internal-fast-dispatch-guard-u3vxgazZ.js} +2 -2
- package/dist/{internal-fast-dispatch-guard-BaUB-F8o.js.map → internal-fast-dispatch-guard-u3vxgazZ.js.map} +1 -1
- package/dist/{internal-first-mate-guard-Brh_uois.js → internal-first-mate-guard-BGaGGIBS.js} +1 -1
- package/dist/{internal-first-mate-guard-Bcb5JwiO.js → internal-first-mate-guard-C8GG8i5H.js} +3 -3
- package/dist/{internal-first-mate-guard-Bcb5JwiO.js.map → internal-first-mate-guard-C8GG8i5H.js.map} +1 -1
- package/dist/{internal-max-dispatch-guard-Sb6ACr_0.js → internal-max-dispatch-guard-B-n687ho.js} +1 -1
- package/dist/{internal-max-dispatch-guard-CXRB-4a3.js → internal-max-dispatch-guard-Cw_9Qx-9.js} +2 -2
- package/dist/{internal-max-dispatch-guard-CXRB-4a3.js.map → internal-max-dispatch-guard-Cw_9Qx-9.js.map} +1 -1
- package/dist/{internal-plan-review-DVY-iV2x.js → internal-plan-review-DCbxSIUR.js} +3 -3
- package/dist/{internal-plan-review-DVY-iV2x.js.map → internal-plan-review-DCbxSIUR.js.map} +1 -1
- package/dist/{internal-prompt-submit-BTqRn4FW.js → internal-prompt-submit-DIn8b_Zt.js} +4 -4
- package/dist/{internal-prompt-submit-BTqRn4FW.js.map → internal-prompt-submit-DIn8b_Zt.js.map} +1 -1
- package/dist/{internal-session-bind-YfH656lt.js → internal-session-bind-DXosxciQ.js} +2 -2
- package/dist/{internal-session-bind-YfH656lt.js.map → internal-session-bind-DXosxciQ.js.map} +1 -1
- package/dist/{internal-stop-hook-D8LIWryj.js → internal-stop-hook-C2lMgQuU.js} +5 -5
- package/dist/{internal-stop-hook-D8LIWryj.js.map → internal-stop-hook-C2lMgQuU.js.map} +1 -1
- package/dist/{internal-stop-review-B8MmEyQ1.js → internal-stop-review-CnYqJTiC.js} +2 -2
- package/dist/{internal-stop-review-B8MmEyQ1.js.map → internal-stop-review-CnYqJTiC.js.map} +1 -1
- package/dist/{internal-worker-guard-Dr6W83TC.js → internal-worker-guard-DdwLtAMn.js} +2 -2
- package/dist/{internal-worker-guard-Dr6W83TC.js.map → internal-worker-guard-DdwLtAMn.js.map} +1 -1
- package/dist/{internal-workspace-header-Cep0WSIb.js → internal-workspace-header-B4qymRDb.js} +2 -2
- package/dist/{internal-workspace-header-Cep0WSIb.js.map → internal-workspace-header-B4qymRDb.js.map} +1 -1
- package/dist/lifecycle-B4DDf-Me.js +2 -0
- package/dist/{lifecycle-CmPJ6e_6.js → lifecycle-CSHU91pP.js} +2 -2
- package/dist/{lifecycle-CmPJ6e_6.js.map → lifecycle-CSHU91pP.js.map} +1 -1
- package/dist/lifecycle-D0BpTV9A.js +2 -0
- package/dist/{lifecycle-DgsJ_8Am.js → lifecycle-DOvQozrm.js} +2 -2
- package/dist/{lifecycle-DgsJ_8Am.js.map → lifecycle-DOvQozrm.js.map} +1 -1
- package/dist/main.js +19 -19
- package/dist/{mcp-workspace-header-NzFD0Wgd.js → mcp-workspace-header-Cv4QWFVV.js} +2 -2
- package/dist/{mcp-workspace-header-NzFD0Wgd.js.map → mcp-workspace-header-Cv4QWFVV.js.map} +1 -1
- package/dist/{models-1KwEUgzd.js → models-BDhlItXr.js} +3 -3
- package/dist/{models-1KwEUgzd.js.map → models-BDhlItXr.js.map} +1 -1
- package/dist/{orchestration-CYyp9w-t.js → orchestration-C2Q_Qynl.js} +2 -2
- package/dist/{orchestration-CYyp9w-t.js.map → orchestration-C2Q_Qynl.js.map} +1 -1
- package/dist/paths-Bmj5b7PS.js +2 -0
- package/dist/{paths-IcZW8gZx.js → paths-D6SI5bmY.js} +234 -3
- package/dist/paths-D6SI5bmY.js.map +1 -0
- package/dist/{peer-mcp-personas-D4bHdjOx.js → peer-mcp-personas-DXOdPbev.js} +11 -11
- package/dist/{peer-mcp-personas-D4bHdjOx.js.map → peer-mcp-personas-DXOdPbev.js.map} +1 -1
- package/dist/{plan-review-hook-szSNt6uU.js → plan-review-hook-CnRRttwl.js} +3 -3
- package/dist/{plan-review-hook-szSNt6uU.js.map → plan-review-hook-CnRRttwl.js.map} +1 -1
- package/dist/{prompt-submit-hook-ZUxo_jEl.js → prompt-submit-hook-G84QAm_F.js} +3 -3
- package/dist/{prompt-submit-hook-ZUxo_jEl.js.map → prompt-submit-hook-G84QAm_F.js.map} +1 -1
- package/dist/{provision-D3_GtXze.js → provision-CfB_DkuL.js} +4 -4
- package/dist/{provision-D3_GtXze.js.map → provision-CfB_DkuL.js.map} +1 -1
- package/dist/{self-invocation-BJWbA8ka.js → self-invocation-Da5gSWen.js} +2 -2
- package/dist/{self-invocation-BJWbA8ka.js.map → self-invocation-Da5gSWen.js.map} +1 -1
- package/dist/{serve-DPRZX-2j.js → serve-Bfw7xEkR.js} +29 -13
- package/dist/serve-Bfw7xEkR.js.map +1 -0
- package/dist/{server-setup-CSkdFAPP.js → server-setup-Cbuq2-Bi.js} +161 -195
- package/dist/server-setup-Cbuq2-Bi.js.map +1 -0
- package/dist/{start-C6G5tu81.js → start-CLSlFGUF.js} +3 -3
- package/dist/{start-C6G5tu81.js.map → start-CLSlFGUF.js.map} +1 -1
- package/dist/{stop-gate-hook-CJOk28zd.js → stop-gate-hook-rjDyq1lk.js} +3 -3
- package/dist/{stop-gate-hook-CJOk28zd.js.map → stop-gate-hook-rjDyq1lk.js.map} +1 -1
- package/dist/{stop-gate-policy-zhzRIj6G.js → stop-gate-policy-m3dvbV10.js} +2 -2
- package/dist/{stop-gate-policy-zhzRIj6G.js.map → stop-gate-policy-m3dvbV10.js.map} +1 -1
- package/dist/{token-CbuFyx-B.js → token-DnTZlcc6.js} +2 -2
- package/dist/{token-CbuFyx-B.js.map → token-DnTZlcc6.js.map} +1 -1
- package/dist/{worker-dispatch-DgP5yT26.js → worker-dispatch-CQ2y0aBP.js} +2 -2
- package/dist/{worker-dispatch-DgP5yT26.js.map → worker-dispatch-CQ2y0aBP.js.map} +1 -1
- package/package.json +1 -1
- package/dist/claude-Drv0roq1.js.map +0 -1
- package/dist/engine-xuIzQNV1.js +0 -2
- package/dist/lifecycle-4TZrJuAa.js +0 -2
- package/dist/lifecycle-O91Vzzb-.js +0 -2
- package/dist/paths-DDAiPVto.js +0 -2
- package/dist/paths-IcZW8gZx.js.map +0 -1
- package/dist/serve-DPRZX-2j.js.map +0 -1
- package/dist/server-setup-CSkdFAPP.js.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"paths-IcZW8gZx.js","names":[],"sources":["../src/lib/paths.ts"],"sourcesContent":["import { randomBytes } from \"node:crypto\"\nimport fs from \"node:fs/promises\"\nimport os from \"node:os\"\nimport path from \"node:path\"\n\nimport consola from \"consola\"\n\nfunction appDir(): string {\n return path.join(os.homedir(), \".local\", \"share\", \"github-router\")\n}\n\nexport const PATHS = {\n get APP_DIR() {\n return appDir()\n },\n get GITHUB_TOKEN_PATH() {\n return path.join(appDir(), \"github_token\")\n },\n /**\n * Second, WRITE-capable GitHub token for the first-mate\n * agent-orchestration surface (`--agents`), stored apart from\n * `github_token` (the Copilot App token). Minted by\n * `setupGitHubAgentToken()` via a device-flow login against the GitHub\n * CLI OAuth client. Mode 0o600.\n */\n get GITHUB_AGENT_TOKEN_PATH() {\n return path.join(appDir(), \"github_agent_token\")\n },\n /**\n * Root of the durable first-mate fleet registry (`--agents`): the\n * global `missions.json` mission index + per-repo `<owner>__<repo>.json`\n * unit ledgers + the decisions/approvals ledger. Cross-launch and\n * OUTSIDE the per-launch CLAUDE_CONFIG_DIR mirror (which is swept each\n * launch) — the first mate must survive proxy restarts / context\n * clears. Modeled on the worker-agent lifecycle ledger.\n */\n get FIRST_MATE_DIR() {\n return (\n process.env.GH_ROUTER_FIRST_MATE_DIR ?? path.join(appDir(), \"first-mate\")\n )\n },\n get ERROR_LOG_PATH() {\n return path.join(appDir(), \"error.log\")\n },\n /**\n * Durable, router-owned directory for worker OVERFLOW artifacts. Two\n * kinds land here, both kept OUT of the MCP result relay so they don't\n * overflow Claude Code's 25k-token cap:\n * - `<pid>-<8hex>.patch` — a `worker_implement`/`worker_test` worktree\n * diff larger than `PREVIEW_CAP` (worker-agent/worktree.ts): the FULL\n * `git diff --binary --full-index` patch, written BEFORE the worktree\n * is removed so the (possibly binary) change stays `git apply`-able.\n * - `<pid>-<8hex>.txt` — any worker result over the relay byte cap\n * (worker-agent/relay-cap.ts `relaySafeText`): the full result text,\n * with a bounded preview + this path returned in its place.\n * `sweepAgedWorkerDiffs()` reaps entries older than 7 days on each write.\n * Cross-launch cache (like TOOLBELT_BIN_DIR) — deliberately OUTSIDE the\n * per-launch CLAUDE_CONFIG_DIR mirror and OUTSIDE the user's repo.\n */\n get WORKER_DIFFS_DIR() {\n return path.join(appDir(), \"worker-diffs\")\n },\n /**\n * Stable home for the relocated hook launcher (`hooks-<sha256>.mjs`).\n *\n * Claude Code hook commands are persisted into a settings.json that outlives\n * the process that wrote them. Under `bunx` the package tree lives in\n * `$TMPDIR`, which macOS reaps — so a command pointing there dies on its\n * first bare import and every hook in the session fails. This directory is\n * under APP_DIR precisely because APP_DIR is neither a cache nor a temp dir\n * and is therefore never reaped. Entries are content-addressed and immutable,\n * so two proxies on different versions coexist and neither can delete a\n * launcher the other is still using.\n */\n get HOOK_LAUNCHER_DIR() {\n return path.join(appDir(), \"hooks\")\n },\n /**\n * Durable copy of the tree-sitter WASM runtime and the grammar subset used\n * by code search. The package tree can live under a reaped bunx temp dir;\n * APP_DIR is stable across launches, so structural ranking and outlines do\n * not silently disappear after the original install files are removed.\n */\n get TREE_SITTER_ASSETS_DIR() {\n return path.join(appDir(), \"tree-sitter\")\n },\n /**\n * Isolated CODEX_HOME for the spawned Codex CLI. Masks any cached\n * ChatGPT subscription login (openai/codex#2733 — cached login can\n * override OPENAI_API_KEY) so the proxy's dummy key is authoritative.\n */\n get CODEX_HOME() {\n return path.join(appDir(), \"codex-isolated\")\n },\n /**\n * Router-owned install + data dir for CloudCLI (the `serve` browser control\n * plane). Holds the pinned local `npm install` (node_modules/), an isolated\n * `auth.db` (so we never collide with a user's standalone CloudCLI DB), and\n * the persisted `.serve-secret.json` login. See docs/serve-control-plane.md.\n */\n get CLOUDCLI_HOME() {\n return path.join(appDir(), \"cloudcli\")\n },\n /**\n * Runtime tempfiles for the per-launch peer-MCP wiring (the\n * `--mcp-config` JSON and `--agents` JSON written before spawning\n * Claude Code). Mode 0o700 to match the security review's mandate;\n * cleaned on shutdown via the per-launch `cleanup()`, plus a\n * boot-time sweep of stale files (dead PIDs, >24h old).\n */\n get CLAUDE_RUNTIME_DIR() {\n return path.join(appDir(), \"runtime\")\n },\n /**\n * Router-owned CLAUDE_CONFIG_DIR. The spawned Claude Code (and any\n * teammates it spawns via the agent-teams primitive) reads its\n * config — including `.credentials.json` — from this dir. We\n * snapshot-copy the user's `~/.claude/` here at startup (excluding\n * `.credentials.json` and volatile state), then write our own\n * synthetic Console OAuth credential. The teammate-spawn allowlist\n * propagates `CLAUDE_CONFIG_DIR` to children, so teammates find the\n * synthetic credential and authenticate instead of falling into the\n * \"Not logged in · Run /login\" gate that would otherwise leave\n * them mute. See `ensureClaudeConfigMirror` below.\n *\n * Per-launch dir: `<appDir>/claude-config/<pid>-<8 hex>`. Two\n * concurrent `github-router claude` launches each get their own\n * isolated mirror, so per-launch state (synthetic credential,\n * snapshot copy of `~/.claude/`, future per-launch `.claude.json`\n * mutation with the peer-MCP entry) cannot cross-talk. The\n * per-launch suffix is cached on first access (see\n * `claudeConfigDirSuffix()`) so all callers within a single proxy\n * lifetime see the same value. Boot-time `sweepStaleClaudeConfigMirrors`\n * reaps mirrors from crashed prior PIDs.\n */\n get CLAUDE_CONFIG_DIR() {\n return path.join(appDir(), \"claude-config\", claudeConfigDirSuffix())\n },\n /**\n * Durable router-owned `bin/` for the LLM toolbelt — curated CLI\n * tools (rg/fd/jq/sd/sg/yq) materialized here and prepended to the\n * spawned agent's PATH. Intentionally OUTSIDE the per-launch\n * CLAUDE_CONFIG_DIR mirror (which is swept per launch): the toolbelt\n * is a cross-launch cache, like `last-update-check`.\n */\n get TOOLBELT_BIN_DIR() {\n return path.join(appDir(), \"bin\")\n },\n /**\n * Root of the github-router-managed ColBERT semantic-search sidecar\n * (the `--semantic-search` opt-in). Everything heavy that the\n * `colgrep` child needs is provisioned, SHA-verified, and stored here\n * by the router — never in the user's repo, never in colgrep's own\n * global `%APPDATA%\\colgrep` / `~/.config/colgrep`. Cross-launch cache\n * (like TOOLBELT_BIN_DIR), deliberately OUTSIDE the per-launch\n * CLAUDE_CONFIG_DIR mirror.\n *\n * Layout (see docs/research/colbert-sidecar-design.md §4):\n * colbert/\n * bin/colgrep[.exe] # provisioned binary\n * models/LateOn-Code-edge/<rev>/ # 5 model files\n * onnxruntime/1.23.0/cpu/<libname> # ORT dylib (ORT_DYLIB_PATH)\n * indices/ # === COLGREP_DATA_DIR ===\n * <project>-<xxh3(path|model)>/ # colgrep-owned per-(path,model)\n * .gh-router-meta/<hash>.json # router-owned sidecar metadata\n */\n get COLBERT_DIR() {\n return path.join(appDir(), \"colbert\")\n },\n get COLBERT_BIN_DIR() {\n return path.join(appDir(), \"colbert\", \"bin\")\n },\n get COLBERT_MODELS_DIR() {\n return path.join(appDir(), \"colbert\", \"models\")\n },\n get COLBERT_ORT_DIR() {\n return path.join(appDir(), \"colbert\", \"onnxruntime\")\n },\n /**\n * Value passed to colgrep as `COLGREP_DATA_DIR`. colgrep keys the\n * physical index dir by `xxh3(canonical_project_path | model)` under\n * here, so every distinct workspace gets its own index dir for free.\n * The router-owned freshness sidecar lives at\n * `<COLBERT_INDICES_DIR>/.gh-router-meta/<hash>.json`.\n */\n get COLBERT_INDICES_DIR() {\n return path.join(appDir(), \"colbert\", \"indices\")\n },\n get COLBERT_META_DIR() {\n return path.join(appDir(), \"colbert\", \"indices\", \".gh-router-meta\")\n },\n}\n\n/**\n * Per-launch suffix for `PATHS.CLAUDE_CONFIG_DIR`. Lazily generated on\n * first access and cached for the lifetime of the process so every\n * caller (env-var injection in `getClaudeCodeEnvVars`,\n * `ensureClaudeConfigMirror` provisioning, peer-agent `.md` writes\n * under `<dir>/agents/`, the shutdown cleanup) resolves the same path.\n *\n * Shape: `<pid>-<8 hex>`. The PID prefix is what\n * `sweepStaleClaudeConfigMirrors` keys off to drop orphans from\n * crashed prior sessions; the 8-hex random suffix prevents collision\n * if a future caller (tests, internal relaunch) ever clears the cache\n * within a single PID lifetime.\n *\n * NOT exported — every consumer should go through `PATHS.CLAUDE_CONFIG_DIR`\n * so the homedir-mock pattern used in the test suite keeps working.\n */\nlet _claudeConfigDirSuffix: string | undefined\nfunction claudeConfigDirSuffix(): string {\n if (_claudeConfigDirSuffix === undefined) {\n _claudeConfigDirSuffix = `${process.pid}-${randomBytes(4).toString(\"hex\")}`\n }\n return _claudeConfigDirSuffix\n}\n\n/**\n * Predicate: is `target` inside the current launch's `CLAUDE_CONFIG_DIR`?\n *\n * Used as a defence-in-depth safety guard by helpers that mutate files\n * under the mirror dir (e.g. `appendPeerAwarenessToMirroredClaudeMd`).\n * If `PATHS.CLAUDE_CONFIG_DIR` has somehow been pointed at the user's\n * real `~/.claude/` (configuration drift, future code regression), the\n * helper refuses to write — the proxy should never mutate user files.\n *\n * Pure-prefix `startsWith` is unsafe (`/a/foo` matches `/a/foobar`), so\n * this requires either equality or `<root><sep>...` strictly.\n * Synchronous because we don't need real-path resolution; symlink\n * refusal lives at the consuming helper.\n */\nexport function isUnderClaudeConfigMirror(target: string): boolean {\n const resolvedRoot = path.resolve(PATHS.CLAUDE_CONFIG_DIR)\n const resolvedTarget = path.resolve(target)\n if (resolvedTarget === resolvedRoot) return true\n return resolvedTarget.startsWith(resolvedRoot + path.sep)\n}\n\nexport async function ensurePaths(): Promise<void> {\n await fs.mkdir(PATHS.APP_DIR, { recursive: true })\n await fs.mkdir(PATHS.CODEX_HOME, { recursive: true })\n await fs.mkdir(PATHS.CLAUDE_RUNTIME_DIR, { recursive: true })\n // mkdir({recursive: true}) does NOT chmod an existing directory, so\n // explicitly tighten in case the dir was created by an older version.\n await chmodIfPossible(PATHS.CLAUDE_RUNTIME_DIR, 0o700)\n await ensureFile(PATHS.GITHUB_TOKEN_PATH)\n // Second (write-capable) GitHub token file for `--agents`. Always\n // ensured so the path exists; only populated by a device-flow login\n // when the agent surface is opted in. Tightened to 0o600 (it holds a\n // repo/workflow-scoped credential).\n await ensureFile(PATHS.GITHUB_AGENT_TOKEN_PATH)\n await chmodIfPossible(PATHS.GITHUB_AGENT_TOKEN_PATH, 0o600)\n await sweepStaleRuntimeFiles().catch((err) => {\n consola.debug(\"Runtime sweep skipped:\", err)\n })\n // Sweep stale per-launch CLAUDE_CONFIG_DIR mirrors left behind by\n // crashed prior proxy sessions BEFORE peer-agent .md sweep, since\n // the .md sweep is scoped to THIS launch's mirror and the per-launch\n // dir sweep is the parent cleanup for the same orphan class.\n await sweepStaleClaudeConfigMirrors().catch((err) => {\n consola.debug(\"Per-launch claude-config sweep skipped:\", err)\n })\n // Phase 2.5: also sweep stale peer-* subagent .md files from this\n // launch's CLAUDE_CONFIG_DIR/agents/ (defense-in-depth — should be\n // a no-op since the per-launch dir didn't exist before this PID\n // started; keeps the safety net in case a future change ever shares\n // an agents/ dir across launches).\n await sweepStalePeerAgentMdFiles().catch((err) => {\n consola.debug(\"Peer-agent .md sweep skipped:\", err)\n })\n // Worker-agent boot-time PID+instance safety net. Walks the\n // worker-repos.json ledger and removes any worktree dir whose\n // <pid> is dead OR whose <instance> UUID doesn't match this proxy.\n // Catches SIGKILL/OOM/host-crash escapees from prior sessions.\n // Lazy-imported so the worker-agent module doesn't get loaded by\n // every consumer of `paths.ts`.\n await (async () => {\n const mod = await import(\"./worker-agent/lifecycle\")\n await mod.sweepStaleWorktreesAtBoot()\n })().catch((err) => {\n consola.debug(\"Worker worktree boot sweep skipped:\", err)\n })\n // ColBERT sidecar boot sweep: reclassify any `.gh-router-meta/*.json`\n // entry stuck in `building` with a dead `buildPid` → `failed`, so the\n // next `code` semantic-mode call re-kicks a build instead of routing to\n // a never-finishing one. NEVER kills a PID from a prior boot. Lazy-\n // imported so paths.ts doesn't drag the colbert module into every\n // consumer.\n await (async () => {\n const mod = await import(\"./colbert/lifecycle\")\n await mod.sweepStaleColbertMetaAtBoot()\n })().catch((err) => {\n consola.debug(\"ColBERT meta boot sweep skipped:\", err)\n })\n}\n\n/**\n * Per-entry mirror policy. Every top-level entry under `~/.claude/` falls\n * into exactly one bucket; unlisted names default to `MIRRORED` so a future\n * Claude-Code-side addition flows through as a snapshot copy rather than\n * being silently lost.\n *\n * Three policies:\n *\n * - `ISOLATED` — not present in the mirror at all. The proxy owns its\n * own copy (synthetic `.credentials.json`, the `.github-router-managed`\n * marker) or the entry has no place in a proxy session\n * (`.credentials.json.lock`, `.oauth_refresh.lock` couple refresh loops\n * across sessions; `statsig/` is write-heavy and would constantly\n * re-copy; `cache/` and `logs/` are ephemeral; `paste-cache/` holds\n * sensitive clipboard extracts and shouldn't leak across sessions —\n * gemini-critic finding).\n *\n * - `SHARED` — symlink `<mirror>/<X>` → `~/.claude/<X>` so writes made\n * during the proxy session land in the user's real `~/.claude/` and\n * chat history is visible in both proxy and plain-`claude` sessions.\n * **Directories only.** Never use this for individual files: Claude\n * Code's atomic-write pattern (`fs.writeFile(temp); fs.rename(temp,\n * target)`) does NOT follow symlinks — a `rename` over the symlink\n * replaces it with a regular file, silently severing the connection\n * to `~/.claude/<X>`. Gemini-critic finding from the 3-lab review.\n *\n * - `MIRRORED` (default) — snapshot-copy with mtime skip. Use for static\n * or settings-shaped state where proxy-session writes should NOT flow\n * back to `~/.claude/` (e.g. `settings.json`, `.claude.json`,\n * `teams/`, `session-env/`) and for `agents/` — the proxy itself\n * writes per-launch `peer-<pid>-*.md` files into the mirror's `agents/`\n * and `sweepStalePeerAgentMdFiles` deletes them; a symlink would route\n * those writes/deletes into the user's real `~/.claude/agents/` and\n * destroy the user's own subagent files. **Hard regression test**:\n * `policyFor(\"agents\") === \"MIRRORED\"` is asserted in\n * `tests/isolated/lib-paths.test.ts` to prevent accidental reclassification.\n *\n * Sub-paths within MIRRORED dirs cascade recursively (existing behavior).\n */\ntype MirrorPolicy = \"ISOLATED\" | \"SHARED\" | \"MIRRORED\"\n\nconst CLAUDE_HOME_POLICY: ReadonlyMap<string, MirrorPolicy> = new Map<\n string,\n MirrorPolicy\n>([\n // ISOLATED\n [\".credentials.json\", \"ISOLATED\"],\n [\".credentials.json.lock\", \"ISOLATED\"],\n [\".oauth_refresh.lock\", \"ISOLATED\"],\n // Defense-in-depth: don't let a user-side file/symlink with the same\n // name as our marker collide with what we write. The marker write\n // logic also lstat-checks before writing (refuses if a non-regular\n // file exists at the path), but excluding it here removes the\n // attack vector entirely.\n [\".github-router-managed\", \"ISOLATED\"],\n [\"statsig\", \"ISOLATED\"],\n [\"cache\", \"ISOLATED\"],\n [\"logs\", \"ISOLATED\"],\n [\"paste-cache\", \"ISOLATED\"],\n [\"jobs\", \"ISOLATED\"],\n [\"daemon\", \"ISOLATED\"],\n [\"daemon.log\", \"ISOLATED\"],\n // SHARED — directories only (see policy doc above)\n [\"projects\", \"SHARED\"],\n [\"sessions\", \"SHARED\"],\n [\"tasks\", \"SHARED\"],\n [\"todos\", \"SHARED\"],\n [\"transcripts\", \"SHARED\"],\n [\"shell-snapshots\", \"SHARED\"],\n // The underscored variant is the historical exclude-list name; some\n // Claude Code versions may still use it. Classify SHARED so either\n // spelling resolves correctly.\n [\"shell_snapshots\", \"SHARED\"],\n [\"plans\", \"SHARED\"],\n [\"file-history\", \"SHARED\"],\n [\"backups\", \"SHARED\"],\n])\n\nfunction policyFor(name: string): MirrorPolicy {\n return CLAUDE_HOME_POLICY.get(name) ?? \"MIRRORED\"\n}\n\n/**\n * Test-only export: lets the test suite assert hard regression guards\n * such as `policyFor(\"agents\") === \"MIRRORED\"` (preventing accidental\n * reclassification that would let `sweepStalePeerAgentMdFiles` delete\n * files in the user's real `~/.claude/agents/`).\n */\nexport const __testing = {\n policyFor,\n ensureSharedSymlink,\n // The mirror walk runs concurrently under a bounded semaphore; both are\n // exposed so tests can prove the copy stays byte-identical to the serial\n // version and that the bound actually bounds.\n mirrorDirRecursive: (sourceDir: string, targetDir: string, relPath: string) =>\n mirrorDirRecursive(sourceDir, targetDir, relPath),\n createSemaphore,\n get MIRROR_COPY_CONCURRENCY() {\n return MIRROR_COPY_CONCURRENCY\n },\n}\n\n/**\n * Names with `SHARED` policy, materialized once for iteration in\n * `ensureClaudeConfigMirror`'s post-copy phase.\n */\nconst SHARED_TOPLEVEL_NAMES: ReadonlyArray<string> = Array.from(\n CLAUDE_HOME_POLICY.entries(),\n)\n .filter(([, kind]) => kind === \"SHARED\")\n .map(([name]) => name)\n\n/**\n * Marker file written into the router-owned CLAUDE_CONFIG_DIR so users\n * (and our own future sweeps) can identify that the dir is managed by\n * github-router. Content is informational only; no logic depends on\n * its presence.\n */\nconst MANAGED_MARKER_FILENAME = \".github-router-managed\"\n\n/**\n * Synthetic Console OAuth credential the router writes into its own\n * `CLAUDE_CONFIG_DIR/.credentials.json` so spawned Claude Code (and\n * any teammates it spawns) can authenticate without a real user\n * `/login`.\n *\n * Schema verified verbatim from `claude` v2.1.140 binary, function\n * `guH` (the credentials-save mutation). Fields:\n * - `accessToken` — sent as `Authorization: Bearer ...` to the\n * proxy. Proxy accepts any bearer (per CLAUDE.md \"doesn't enforce\n * auth\").\n * - `refreshToken` — only used by Claude Code's reactive refresh\n * path (function `nH8`), which fires on 401 from upstream. The\n * proxy maintains the no-401 invariant on the Anthropic-shape\n * boundary, so this is never invoked. Synthetic value is fine.\n * - `expiresAt` — far-future (2099-01-01 ms epoch). Sidesteps the\n * proactive refresh path (`R8H(expiresAt)` returns false).\n * - `scopes` — claude-ai-shaped so `tB(scopes)` returns true,\n * making `Hq()` true (full feature surface, not \"inference only\").\n * - `subscriptionType` — `\"max\"`. Pure client-side label\n * (`e7()` / `Zc_()` / `CZ1()`); no server validation since\n * `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1` suppresses\n * subscription-validation calls. Picks the most-permissive gating.\n * - `rateLimitTier` — `\"default_claude_max_20x\"`. Paired with\n * `subscriptionType:\"max\"` this is the real Max-20x tier, so the\n * credential is internally consistent (vs the prior odd `max`+`null`).\n * Verified live (claude v2.1.158) call-sites are cosmetic billing /\n * upsell-suppression UI plus the `getPlanModeV2AgentCount` (`bGK`)\n * `max && 20x → 3` branch — which `CLAUDE_CODE_PLAN_V2_AGENT_COUNT`\n * (set to 7 in server-setup) already overrides, so this is\n * belt-and-suspenders for the natural code path. No client-side quota\n * enforcement keys off the tier (rate-limit UI reads server\n * `x-ratelimit-*` headers; the proxy holds the no-429 invariant).\n */\nconst SYNTHETIC_CREDENTIAL = {\n claudeAiOauth: {\n accessToken: \"github-router-synthetic\",\n refreshToken: \"github-router-synthetic\",\n expiresAt: 4_070_908_800_000,\n scopes: [\"user:inference\", \"user:profile\"],\n subscriptionType: \"max\",\n rateLimitTier: \"default_claude_max_20x\",\n clientId: \"github-router\",\n },\n} as const\n\n/**\n * Router-owned fields injected into `<CLAUDE_CONFIG_DIR>/.claude.json` so\n * Claude Code skips its first-launch onboarding wizard. The synthetic\n * `.credentials.json` covers the OAuth *token*, but on a fresh machine\n * the wizard runs anyway — and one of its steps is the browser-OAuth\n * \"Sign in to your Anthropic account\" prompt that the user sees today.\n *\n * Schema verified against `claude` v2.1.158 binary strings:\n *\n * - `hasCompletedOnboarding: true` — single load-bearing gate.\n * Binary code path: `if (!E_().hasCompletedOnboarding) { ... await\n * Onboarding.default(...) }`. Setting true causes the wizard\n * (including the OAuth login step) to be skipped entirely.\n *\n * - `bypassPermissionsModeAccepted: true` — skips the\n * `--dangerously-skip-permissions` trust disclaimer. Binary gate:\n * `K = Ip() || Boolean(E_().bypassPermissionsModeAccepted)`. The\n * proxy passes `--dangerously-skip-permissions` so `Ip()` typically\n * suffices, but pinning the field covers edge code paths (e.g.\n * `--bg --dangerously-skip-permissions`) that still consult it\n * directly and would otherwise throw \"requires accepting the\n * disclaimer first.\"\n *\n * - `oauthAccount` — nice-to-have for UI consistency. Claude Code\n * reads sub-fields with optional chaining (`oauthAccount?.accountUuid`,\n * `oauthAccount?.organizationUuid`, `oauthAccount?.organizationRole`)\n * so undefined doesn't trigger login — but populating gives any\n * \"logged in as X\" / status-line UI something deterministic and\n * obviously-synthetic to display. Values are intentionally\n * non-credible (`github-router@local`, all-zero UUIDs) so any\n * leak into logs is self-documenting, not impersonation.\n *\n * Merge semantics: the two boolean gates are **force-overridden** —\n * even an existing `false` is flipped to `true`. (A user logging out\n * via the Claude Code UI cannot defeat the proxy-session bypass.)\n * `oauthAccount` is overwritten only when the existing value isn't a\n * *structurally usable* one (presence-only would preserve `{}` or\n * `{foo:1}` shapes that defeat the fix); a real OAuth identity with\n * non-empty `accountUuid` and `organizationUuid` is preserved as-is.\n * Every OTHER top-level field the user brought in via the mirror walk\n * (settings, project history, user-side MCP entries, etc.) is preserved\n * verbatim. The mirror is a router-controlled view, not a faithful\n * copy — same trade-off the synthetic credential already makes.\n */\nconst SYNTHETIC_CLAUDE_JSON_FIELDS = {\n hasCompletedOnboarding: true,\n bypassPermissionsModeAccepted: true,\n oauthAccount: {\n accountUuid: \"00000000-0000-0000-0000-000000000000\",\n organizationUuid: \"00000000-0000-0000-0000-000000000000\",\n organizationRole: \"member\",\n emailAddress: \"github-router@local\",\n organizationName: \"github-router\",\n },\n} as const\n\n/**\n * Snapshot-copy the user's `~/.claude/` into the router-owned\n * CLAUDE_CONFIG_DIR (real files, not symlinks — symlinks don't isolate\n * writes), classifying each top-level entry per `CLAUDE_HOME_POLICY`:\n * ISOLATED entries are skipped, MIRRORED entries are copied, and\n * SHARED entries become directory symlinks back to `~/.claude/<X>` so\n * chat history (in `projects/<cwd-hash>/<session-uuid>.jsonl`) and\n * other durable user state flow between proxy and plain-`claude`\n * sessions. Then writes the synthetic `.credentials.json` so spawned\n * Claude Code (and teammates that inherit `CLAUDE_CONFIG_DIR`)\n * authenticate.\n *\n * Idempotent: only re-copies files whose source `mtime` is newer than\n * target; SHARED-symlink creation no-ops when the symlink already\n * points at the right target. Concurrent-safe: `mkdir({recursive:true})`\n * is idempotent; symlinks are created via atomic temp+rename so two\n * parallel github-router-claude startups can't race to EEXIST; the\n * credentials write uses temp-file + atomic rename so Claude Code's\n * `EZ1()` mtime watcher never sees a partial write.\n *\n * Walks with `lstat` (does NOT follow symlinks during traversal — a\n * symlink-into-`/` would otherwise let the walk escape). Symlink leaves\n * in the source tree are skipped during the MIRRORED copy walk (per the\n * symlink-confused-deputy security finding); SHARED symlinks are\n * created on the mirror side only, pointing at predetermined targets\n * inside the user's real `~/.claude/`.\n *\n * Caller is expected to invoke this after `ensurePaths()` and before\n * spawning Claude Code (`launchChild`). The mirror must exist before\n * the child reads it. Currently called from the `claude` subcommand\n * entry point only; `start` and `codex` subcommands don't need it.\n */\nexport async function ensureClaudeConfigMirror(opts: {\n realHome?: string\n} = {}): Promise<void> {\n const realHome = opts.realHome ?? os.homedir()\n const sourceDir = path.join(realHome, \".claude\")\n const targetDir = PATHS.CLAUDE_CONFIG_DIR\n\n // 1. Create our config dir (idempotent, mode 0o700)\n await fs.mkdir(targetDir, { recursive: true, mode: 0o700 })\n await chmodIfPossible(targetDir, 0o700)\n\n // 2. Snapshot-copy from ~/.claude if it exists. Only MIRRORED entries\n // flow through this walk; ISOLATED and SHARED entries are filtered\n // in `mirrorDirRecursive` and handled separately.\n let sourceExists = false\n try {\n const sourceStat = await fs.stat(sourceDir)\n sourceExists = sourceStat.isDirectory()\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") {\n consola.debug(`ensureClaudeConfigMirror: cannot stat ${sourceDir}:`, err)\n }\n }\n if (sourceExists) {\n await mirrorDirRecursive(sourceDir, targetDir, \"\")\n }\n\n // 3. Always ensure agents/ exists (even if user has none) so the\n // peer-agent .md emission has a place to write. Empty dir is fine.\n // agents/ is MIRRORED, not SHARED — the proxy writes per-launch\n // `peer-<pid>-*.md` files here and `sweepStalePeerAgentMdFiles`\n // deletes them; routing those operations into the user's real\n // `~/.claude/agents/` would destroy their custom subagent files.\n await fs.mkdir(path.join(targetDir, \"agents\"), { recursive: true })\n\n // 4. Create symlinks for SHARED entries so chat history (and other\n // durable user state) is visible in both proxy and plain-`claude`.\n for (const name of SHARED_TOPLEVEL_NAMES) {\n await ensureSharedSymlink(name, sourceDir, targetDir).catch((err) => {\n consola.debug(\n `ensureClaudeConfigMirror: SHARED symlink for ${name} skipped:`,\n err,\n )\n })\n }\n\n // 5. Write synthetic .credentials.json (only if content differs)\n const credentialsPath = path.join(targetDir, \".credentials.json\")\n const desiredJson = JSON.stringify(SYNTHETIC_CREDENTIAL, null, 2)\n let needsWrite = true\n try {\n const existing = await fs.readFile(credentialsPath, \"utf8\")\n needsWrite = existing.trim() !== desiredJson.trim()\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") {\n consola.debug(`ensureClaudeConfigMirror: cannot read existing credentials:`, err)\n }\n }\n if (needsWrite) {\n // Atomic temp-file + rename so EZ1()'s mtime watcher doesn't see\n // a partial write. wx flag ensures we don't clobber a concurrent\n // writer's tempfile.\n const tempPath = `${credentialsPath}.${process.pid}.tmp`\n try {\n await fs.writeFile(tempPath, desiredJson + \"\\n\", { mode: 0o600, flag: \"wx\" })\n await fs.rename(tempPath, credentialsPath)\n } catch (err) {\n // EEXIST on the tempfile means another concurrent startup is\n // mid-write. Best-effort: skip — the other writer will produce\n // identical content (deterministic constant blob).\n if ((err as NodeJS.ErrnoException).code === \"EEXIST\") {\n consola.debug(\n \"ensureClaudeConfigMirror: concurrent credentials-write detected, skipping\",\n )\n } else {\n await fs.unlink(tempPath).catch(() => {})\n throw err\n }\n }\n }\n await chmodIfPossible(credentialsPath, 0o600)\n\n // 6. Inject onboarding-skip fields into <CLAUDE_CONFIG_DIR>/.claude.json.\n // Runs AFTER the mirror walk (step 2) so a user's existing\n // .claude.json content (settings, projects, user-side mcpServers)\n // is preserved, and BEFORE `injectPeerMcpIntoMirror` (called later\n // from `src/claude.ts`) which read-merge-writes the same file and\n // will preserve our fields naturally.\n //\n // Without this step, on a brand-new machine where ~/.claude/.claude.json\n // doesn't exist, nothing lands in the mirror, Claude Code's\n // `hasCompletedOnboarding` gate defaults to falsy, and the\n // first-launch wizard runs — including the \"Sign in to your\n // Anthropic account\" OAuth step that defeats the synthetic\n // credential. See SYNTHETIC_CLAUDE_JSON_FIELDS for the gate\n // rationale + binary-verified field shape.\n await injectSyntheticClaudeJsonFields(targetDir, sourceDir, realHome)\n\n // 6a. Postcondition: verify the gate fields actually landed in the\n // mirror's .claude.json. The helper has several \"warn and\n // return\" branches (non-regular target, non-ENOENT read failure)\n // where falling through silently would let Claude Code's\n // first-launch wizard fire — the exact bug this whole\n // subsystem exists to prevent. Re-reading the file and\n // asserting the booleans converts every silent-fail path into a\n // fail-loud launch error, which `src/claude.ts` surfaces to the\n // user with `process.exit(1)`. Cheap (one extra readFile +\n // JSON.parse on a small file we just wrote) and gives the\n // no-silent-degradation invariant teeth even if a future\n // contributor adds a new bail-out branch.\n await assertOnboardingGateInjected(targetDir)\n\n // 7. Write/refresh marker file. Use lstat (not access) to detect\n // symlinks at the marker path — a previously-mirrored or\n // user-placed symlink could otherwise let our `fs.writeFile`\n // follow through to an arbitrary target. With the symlink-skip\n // policy in `mirrorDirRecursive` this is defense-in-depth, but\n // cheap and definitive.\n const markerPath = path.join(targetDir, MANAGED_MARKER_FILENAME)\n let markerExists = false\n try {\n const markerStat = await fs.lstat(markerPath)\n if (markerStat.isFile()) {\n markerExists = true\n } else {\n // Anything non-regular (symlink, dir, special file) is a red flag —\n // refuse to overwrite, log loudly. The user can investigate.\n consola.warn(\n `ensureClaudeConfigMirror: ${markerPath} exists but is not a regular file (mode=${markerStat.mode.toString(8)}); refusing to overwrite. Inspect and remove manually if safe.`,\n )\n markerExists = true\n }\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") {\n consola.debug(`ensureClaudeConfigMirror: cannot lstat marker:`, err)\n markerExists = true\n }\n }\n if (!markerExists) {\n const body = `Managed by github-router. Created ${new Date().toISOString()}. Safe to delete (will be recreated).\\n`\n // wx flag (O_CREAT | O_EXCL) refuses to clobber an existing\n // file or symlink (POSIX O_EXCL behavior) — additional protection\n // against the marker-symlink confused-deputy vector.\n await fs\n .writeFile(markerPath, body, { mode: 0o600, flag: \"wx\" })\n .catch((err) => {\n consola.debug(`ensureClaudeConfigMirror: marker write skipped:`, err)\n })\n }\n}\n\n/**\n * Read-merge-atomic-write the router-required onboarding-skip fields\n * (see `SYNTHETIC_CLAUDE_JSON_FIELDS`) into `<targetDir>/.claude.json`.\n *\n * Inputs:\n * - `targetDir` — the per-launch mirror dir we own.\n * - `sourceDir` — the user's `~/.claude/` (subdir-level legacy path).\n * - `realHome` — the user's `$HOME`. Used to locate the **canonical**\n * `~/.claude.json` (HOME level), which is what Claude Code's path\n * resolver `qG` actually reads when `CLAUDE_CONFIG_DIR` is unset:\n * `path.join(process.env.CLAUDE_CONFIG_DIR || homedir(),\n * \".claude.json\")`. The mirror walk operates on `~/.claude/` and\n * thus can only see the subdir-level `~/.claude/.claude.json`\n * (often a stale shadow); the canonical HOME-level file — where\n * `claude mcp add` writes and Claude Code reads on default\n * invocations — would otherwise be invisible to the proxy session.\n *\n * Behaviour:\n * - Mirror's `.claude.json` is a non-regular file (symlink, dir,\n * etc.) → log warn, refuse to touch. The postcondition check in\n * `ensureClaudeConfigMirror` then throws, blocking launch — the\n * user investigates the warn-flagged path rather than landing in\n * the OAuth wizard.\n * - Read existing content from the first VALID source, in priority\n * order:\n * (a) `~/.claude.json` HOME-level (canonical Claude Code path)\n * (b) The mirror file itself (snapshot-walk's product; only\n * consulted if it's a regular file per step 1)\n * (c) `~/.claude/.claude.json` subdir-level (legacy)\n * A candidate that's missing (ENOENT), too large (> 50 MiB cap),\n * or fails to parse as a non-empty object is **skipped** — the\n * loop falls through to the next candidate, so a malformed\n * higher-priority source can't drop valid lower-priority content\n * on the floor. HOME-level non-ENOENT read failures warn\n * (user-visible — corp perms / OneDrive). All other failures\n * debug-log. `fs.readFile` follows symlinks; reading user-owned\n * content into a per-launch user-owned dir is not a privilege\n * escalation (same uid).\n * - Merge: **force-override** all three synthetic fields\n * unconditionally — `hasCompletedOnboarding`,\n * `bypassPermissionsModeAccepted`, AND `oauthAccount`. A\n * within-proxy \"log out\" is impossible by design. `oauthAccount`\n * is overwritten even when the user has a real one, because\n * pairing the synthetic OAuth token (from `.credentials.json`)\n * with a real-user identity blob creates a split-brain auth\n * state that Claude Code's identity-vs-token cross-checks may\n * treat as session corruption (triggering re-login → defeats\n * the fix). The user's real identity remains intact in their\n * own `~/.claude.json`; only the proxy session sees synthetic.\n * `Object.assign(Object.create(null), existing)` instead of spread\n * defuses the `__proto__` prototype-pollution sink from JSON.parse.\n * - Idempotency: skip the write IFF the source we read FROM is the\n * mirror itself AND the current mirror content is byte-equivalent\n * to what we'd write. Bytes-on-disk comparison is robust against\n * filesystem mtime resolution (FAT/exFAT 2 s buckets) and the\n * source-presence-vs-mirror-presence conflation that broke\n * round-2's `needsWrite = !hadExisting` heuristic.\n *\n * Atomic write: tempfile (per-PID + 4-byte random suffix matching\n * `injectPeerMcpIntoMirror`'s pattern, so collision is effectively\n * impossible) + rename, mode 0o600. Pre-chmod the destination before\n * rename to defuse Windows read-only attribute issues. Any write\n * error throws to the caller — `src/claude.ts` fails the launch\n * loudly. Combined with the postcondition check, every silent-\n * degradation path is converted to a fail-loud launch error.\n */\nasync function injectSyntheticClaudeJsonFields(\n targetDir: string,\n sourceDir: string,\n realHome: string,\n): Promise<void> {\n const claudeJsonPath = path.join(targetDir, \".claude.json\")\n\n // 1. lstat the mirror path before touching it (defense-in-depth,\n // matching the marker-write at step 7). If something other than a\n // regular file occupies the slot, refuse — `fs.readFile` /\n // `chmodIfPossible` / `fs.rename` would otherwise follow / clobber\n // in surprising ways. The postcondition then throws.\n let mirrorIsRegularFile = false\n try {\n const mirrorStat = await fs.lstat(claudeJsonPath)\n if (mirrorStat.isFile()) {\n mirrorIsRegularFile = true\n } else {\n consola.warn(\n `ensureClaudeConfigMirror: ${claudeJsonPath} exists but is not a regular file (mode=${mirrorStat.mode.toString(8)}); refusing to inject synthetic fields. Inspect and remove manually if safe.`,\n )\n return\n }\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") {\n consola.warn(\n `ensureClaudeConfigMirror: cannot lstat ${claudeJsonPath}; refusing to inject synthetic fields (overwriting an unreadable file would risk destroying user content):`,\n err,\n )\n return\n }\n // ENOENT: mirror file doesn't exist yet — fine; step 2 skips the\n // mirror candidate and step 4 creates a new file.\n }\n\n // 2. Read existing content from the first VALID source. Priority:\n // (a) `~/.claude.json` HOME-level (canonical Claude Code path)\n // (b) Mirror file (the snapshot-walk's product; only if regular)\n // (c) `~/.claude/.claude.json` subdir-level (legacy)\n // A candidate that's missing (ENOENT) or fails to parse as a\n // non-empty object is SKIPPED — the loop falls through to the\n // next candidate. This is the load-bearing fix for \"corrupt HOME\n // blocks valid subdir\": a zero-byte or malformed HOME file no\n // longer drops the user's good subdir content on the floor.\n // Size cap (`MAX_CLAUDE_JSON_BYTES`) skips absurdly large files\n // that would risk OOM. HOME read failures (non-ENOENT) warn\n // loudly — the canonical path failing is user-visible. Mirror /\n // subdir read failures debug-log and fall through.\n // `fs.readFile` follows symlinks; reading user-owned content\n // into a per-launch user-owned dir is not a privilege escalation.\n let existing: Record<string, unknown> = {}\n let selectedSource: string | null = null\n const homeLevelClaudeJson = path.join(realHome, \".claude.json\")\n const subdirClaudeJson = path.join(sourceDir, \".claude.json\")\n const sourceCandidates: Array<string | null> = [\n homeLevelClaudeJson,\n mirrorIsRegularFile ? claudeJsonPath : null,\n subdirClaudeJson,\n ]\n for (const sourceCandidate of sourceCandidates) {\n if (sourceCandidate === null) continue\n try {\n const stat = await fs.stat(sourceCandidate)\n if (stat.size > MAX_CLAUDE_JSON_BYTES) {\n consola.warn(\n `ensureClaudeConfigMirror: ${sourceCandidate} is ${stat.size} bytes (> ${MAX_CLAUDE_JSON_BYTES} cap); skipping this source to avoid OOM. Inspect for accidental log/state accumulation.`,\n )\n continue\n }\n const raw = await fs.readFile(sourceCandidate, \"utf8\")\n const parsed = parseExistingClaudeJson(raw, sourceCandidate)\n if (parsed === null) {\n // Malformed (parse failure or non-object). Skip; try next candidate.\n continue\n }\n existing = parsed\n selectedSource = sourceCandidate\n consola.debug(\n `ensureClaudeConfigMirror: read .claude.json content from ${sourceCandidate}`,\n )\n break\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code\n if (code === \"ENOENT\") {\n // Normal case — skip silently.\n continue\n }\n // Non-ENOENT read/stat failure. Severity depends on which source:\n // - HOME-level (canonical): warn (user-visible — corp perms,\n // OneDrive cloud-only, EACCES, etc.). Fall through, since\n // the gate-injection still needs to happen.\n // - Mirror / subdir: debug. Mirror failures used to abort,\n // but that locked out the still-valid subdir-level fallback;\n // we can always re-create the mirror from any valid source.\n if (sourceCandidate === homeLevelClaudeJson) {\n consola.warn(\n `ensureClaudeConfigMirror: cannot read canonical ${homeLevelClaudeJson}; falling back to other sources. User-scope MCPs added via 'claude mcp add' may be invisible to the proxy session:`,\n err,\n )\n } else {\n consola.debug(\n `ensureClaudeConfigMirror: cannot read source ${sourceCandidate}:`,\n err,\n )\n }\n // Fall through to next candidate. If all miss/fail, proceed\n // with synthetic-only content (existing stays {}).\n }\n }\n\n // 3. Build merged content. `Object.assign(Object.create(null), ...)`\n // instead of `{...existing}` avoids the `__proto__` prototype-\n // pollution sink (`JSON.parse('{\"__proto__\":{...}}')` sets the\n // object's prototype via defineProperty; spreading then triggers\n // the standard `__proto__` setter and mutates merged's prototype\n // chain. Cheap defense, prevents the entire class.\n //\n // All three gate fields are FORCE-OVERRIDDEN regardless of\n // existing value:\n // - `hasCompletedOnboarding` and `bypassPermissionsModeAccepted`\n // are the load-bearing gates we exist to set; a within-proxy\n // \"log out\" is impossible by design.\n // - `oauthAccount` is overwritten unconditionally with the\n // synthetic identity (NOT preserved even when real). Pairing\n // the synthetic OAuth token (from `.credentials.json`) with\n // a real-user `accountUuid` / `organizationUuid` creates a\n // split-brain auth state where Claude Code's identity-vs-\n // token cross-checks (binary `oauthAccount?.accountUuid`\n // consumers) may treat the mismatch as session corruption\n // and trigger re-login — the exact bug we're preventing.\n // The user's real identity remains intact in their own\n // `~/.claude.json`; only the proxy session sees synthetic.\n const merged: Record<string, unknown> = Object.assign(\n Object.create(null) as Record<string, unknown>,\n existing,\n )\n merged.hasCompletedOnboarding = SYNTHETIC_CLAUDE_JSON_FIELDS.hasCompletedOnboarding\n merged.bypassPermissionsModeAccepted = SYNTHETIC_CLAUDE_JSON_FIELDS.bypassPermissionsModeAccepted\n merged.oauthAccount = SYNTHETIC_CLAUDE_JSON_FIELDS.oauthAccount\n\n // 4. Decide if a write is needed. Idempotency contract: skip the\n // write IFF we read FROM the mirror itself AND the current mirror\n // content is byte-equivalent to what we'd write. Bytes-on-disk\n // comparison (vs. inferring from source state) is robust against:\n // - source-presence ≠ mirror-presence (a fully-onboarded HOME\n // file with no mirror yet must still trigger a mirror write)\n // - HOME content newer than mirror (HOME wins → mirror is\n // always rewritten when source was HOME)\n // - filesystem mtime resolution (FAT/exFAT 2s buckets)\n const desiredJson = JSON.stringify(merged, null, 2) + \"\\n\"\n if (selectedSource === claudeJsonPath) {\n try {\n const currentMirrorJson = await fs.readFile(claudeJsonPath, \"utf8\")\n if (currentMirrorJson === desiredJson) {\n await chmodIfPossible(claudeJsonPath, 0o600).catch(() => {})\n return\n }\n } catch {\n // Mirror read failed between step 2's success and now — fall\n // through to write. Worst case we rewrite identical content.\n }\n }\n\n // 5. Atomic temp + rename. Mode 0o600. Per-PID + 4-byte random\n // suffix matches `injectPeerMcpIntoMirror`'s pattern. Any write\n // error throws to the caller — `src/claude.ts` exits the launch\n // with a loud error message rather than silently degrading to\n // the OAuth wizard.\n //\n // Pre-chmod the destination before rename to defuse Windows\n // read-only attribute issues (FAT/exFAT volumes, corp-managed\n // perms): `fs.rename` over a read-only file can fail with EPERM\n // on Windows even though POSIX would silently succeed.\n //\n // Concurrent-racer tolerance: when multiple `ensureClaudeConfigMirror()`\n // calls run in parallel (e.g., from tests, or a future caller),\n // Windows `fs.rename` can fail with EBUSY/EPERM/EACCES if\n // another racer has the destination open or mid-write. The\n // write content is fully deterministic (same SYNTHETIC fields,\n // same force-override merge of the same source-priority list),\n // so a successful rename by ANY racer produces the same bytes\n // we'd produce. On rename failure, verify the on-disk content\n // matches `desiredJson` — if so, accept silently (another\n // racer won the race; the file is correct).\n const tempPath = `${claudeJsonPath}.${process.pid}.${randomBytes(4).toString(\"hex\")}.tmp`\n try {\n await fs.writeFile(tempPath, desiredJson, { mode: 0o600, flag: \"wx\" })\n if (mirrorIsRegularFile) {\n await chmodIfPossible(claudeJsonPath, 0o600).catch(() => {})\n }\n await fs.rename(tempPath, claudeJsonPath)\n } catch (err) {\n await fs.unlink(tempPath).catch(() => {})\n try {\n const observed = await fs.readFile(claudeJsonPath, \"utf8\")\n if (observed === desiredJson) {\n consola.debug(\n `ensureClaudeConfigMirror: rename failed but mirror already holds expected content (concurrent racer won the race):`,\n err,\n )\n await chmodIfPossible(claudeJsonPath, 0o600).catch(() => {})\n return\n }\n } catch {\n // Fall through to rethrow the original error.\n }\n throw err\n }\n await chmodIfPossible(claudeJsonPath, 0o600).catch(() => {})\n}\n\n/**\n * Size cap on candidate `.claude.json` reads in\n * `injectSyntheticClaudeJsonFields`. Real-world files are KB-MB scale;\n * if a `.claude.json` has grown to hundreds of MB (corruption, runaway\n * project history accumulation, accidental log redirect) reading it\n * blocks the event loop and risks OOM. 50 MiB is comfortably above\n * any legitimate Claude Code state and well below typical RSS budgets.\n */\nconst MAX_CLAUDE_JSON_BYTES = 50 * 1024 * 1024\n\n/**\n * Structural check for `oauthAccount`: must be a non-null, non-array\n * object whose `accountUuid` AND `organizationUuid` are non-empty\n * strings. Empty objects / partial blobs (missing UUIDs) do NOT count\n * as \"usable\" — preserving them would let Claude Code's various\n * \"logged in as X\" UIs read undefined sub-fields and (in some flows)\n * fall back to a re-login prompt that defeats the synthetic-credential\n * fix. Field names match the binary's optional-chain reads\n * (`oauthAccount?.accountUuid`, `oauthAccount?.organizationUuid`).\n */\nfunction isUsableOauthAccount(value: unknown): boolean {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) return false\n const obj = value as Record<string, unknown>\n const accountUuid = obj.accountUuid\n const organizationUuid = obj.organizationUuid\n return (\n typeof accountUuid === \"string\"\n && accountUuid.length > 0\n && typeof organizationUuid === \"string\"\n && organizationUuid.length > 0\n )\n}\n\n/**\n * Parse a JSON blob expected to be an object. Returns `null` on parse\n * failure or non-object/array value (so callers can SKIP this source\n * rather than treating an empty object as a successful read — a\n * round-1 finding from gemini-critic). Logs a warn so the user can\n * investigate. Used in `injectSyntheticClaudeJsonFields`'s source-\n * priority loop: a malformed HOME-level file falls through to the\n * mirror or subdir candidate instead of dropping valid lower-priority\n * content on the floor.\n */\nfunction parseExistingClaudeJson(\n raw: string,\n contextPath: string,\n): Record<string, unknown> | null {\n try {\n const parsed = JSON.parse(raw) as unknown\n if (parsed && typeof parsed === \"object\" && !Array.isArray(parsed)) {\n return parsed as Record<string, unknown>\n }\n consola.warn(\n `ensureClaudeConfigMirror: ${contextPath} parsed to non-object `\n + `(typeof=${typeof parsed}); skipping this source.`,\n )\n return null\n } catch (err) {\n consola.warn(\n `ensureClaudeConfigMirror: cannot parse ${contextPath} as JSON; `\n + `skipping this source:`,\n err,\n )\n return null\n }\n}\n\n/**\n * Postcondition for `ensureClaudeConfigMirror`: re-reads the mirror's\n * `.claude.json` and verifies that both boolean gate fields actually\n * landed. Converts every \"warn and return\" branch in\n * `injectSyntheticClaudeJsonFields` (non-regular target, non-ENOENT\n * read failure, postcondition lstat race) into a launch-failing\n * throw — `src/claude.ts:197`'s catch path turns it into a clear\n * `process.exit(1)` rather than silently spawning Claude Code into\n * the OAuth wizard. The whole point of this subsystem is preventing\n * that silent degradation; a warn that scrolls by in the launch log\n * is not enough.\n *\n * Cheap (one extra readFile + JSON.parse on a small file we just\n * wrote) and load-bearing: every future contributor who adds a new\n * bail-out branch in the helper above automatically gets fail-loud\n * for free instead of having to remember to thread the failure\n * upward by hand.\n */\nasync function assertOnboardingGateInjected(targetDir: string): Promise<void> {\n const claudeJsonPath = path.join(targetDir, \".claude.json\")\n\n // 1. lstat first — never follow a symlink. A planted symlink at the\n // mirror path pointing at an attacker-controlled file with valid\n // gate fields would otherwise pass a plain readFile check and let\n // the helper's \"non-regular target\" warn-and-return slip through.\n let lst: import(\"node:fs\").Stats\n try {\n lst = await fs.lstat(claudeJsonPath)\n } catch (err) {\n throw new Error(\n `ensureClaudeConfigMirror: postcondition failed — cannot lstat ${claudeJsonPath} after injection (synthetic onboarding-skip fields are required to prevent Claude Code's first-launch wizard, which would defeat the synthetic credential): ${(err as Error).message}`,\n { cause: err },\n )\n }\n if (!lst.isFile()) {\n throw new Error(\n `ensureClaudeConfigMirror: postcondition failed — ${claudeJsonPath} exists but is not a regular file (mode=${lst.mode.toString(8)}). Check the preceding warn-level log lines for the underlying cause; without a regular file holding the gate fields, Claude Code's first-launch wizard fires and defeats the synthetic credential.`,\n )\n }\n\n let raw: string\n try {\n raw = await fs.readFile(claudeJsonPath, \"utf8\")\n } catch (err) {\n throw new Error(\n `ensureClaudeConfigMirror: postcondition failed — cannot read ${claudeJsonPath} after injection: ${(err as Error).message}`,\n { cause: err },\n )\n }\n let parsed: Record<string, unknown>\n try {\n const json = JSON.parse(raw) as unknown\n if (!json || typeof json !== \"object\" || Array.isArray(json)) {\n throw new Error(`parsed to non-object (typeof=${typeof json})`)\n }\n parsed = json as Record<string, unknown>\n } catch (err) {\n throw new Error(\n `ensureClaudeConfigMirror: postcondition failed — ${claudeJsonPath} is not a valid JSON object after injection: ${(err as Error).message}`,\n { cause: err },\n )\n }\n if (\n parsed.hasCompletedOnboarding !== true\n || parsed.bypassPermissionsModeAccepted !== true\n ) {\n throw new Error(\n `ensureClaudeConfigMirror: postcondition failed — ${claudeJsonPath} is missing required gate fields after injection (hasCompletedOnboarding=${String(parsed.hasCompletedOnboarding)}, bypassPermissionsModeAccepted=${String(parsed.bypassPermissionsModeAccepted)}). Check the preceding warn-level log lines for the underlying cause; without these fields Claude Code's first-launch wizard fires and defeats the synthetic credential.`,\n )\n }\n // Reuse `isUsableOauthAccount` so a future change to the structural\n // definition stays consistent across helper write-decision and\n // postcondition. The synthetic blob we always write satisfies this\n // by construction; a regression that drops oauthAccount during the\n // merge would otherwise slip past (per the round-2 codex/opus/\n // gemini-3-lab finding).\n if (!isUsableOauthAccount(parsed.oauthAccount)) {\n throw new Error(\n `ensureClaudeConfigMirror: postcondition failed — ${claudeJsonPath} has invalid oauthAccount after injection (got ${JSON.stringify(parsed.oauthAccount)}). The synthetic identity blob is required so Claude Code's identity-vs-token cross-checks don't trigger a re-login that defeats the synthetic credential.`,\n )\n }\n}\n\n/**\n * Recursive snapshot-copy helper for `ensureClaudeConfigMirror`. Walks\n * `sourceDir/relPath` and mirrors each entry into `targetDir/relPath`.\n * - Top-level entries are dispatched on `policyFor(name)`:\n * - `ISOLATED` → skipped entirely (no presence in mirror).\n * - `SHARED` → skipped from the copy walk; handled by\n * `ensureSharedSymlink` in the post-copy phase.\n * - `MIRRORED` → copied as today.\n * - Symlinks are skipped (not recreated) so the walk never follows out\n * of `sourceDir` and we don't reintroduce a confused-deputy vector.\n * - Files copy only if source mtime > target mtime (idempotent).\n */\n/**\n * Max file operations in flight across the WHOLE mirror walk.\n *\n * The walk is I/O-bound over hundreds of independent small files, so it\n * parallelizes well: measured 1021ms serial vs 311ms at this limit for a real\n * 394-file / 4.7MB `~/.claude` (Windows 11, 16 cores). It must be BOUNDED\n * rather than a bare `Promise.all` over every entry — the mirror is re-copied\n * on every launch (the target is a fresh per-launch dir, so the mtime skip\n * below never fires across launches), and firing hundreds of simultaneous\n * `copyFile`s just trades one bottleneck for file-handle exhaustion.\n *\n * The limit is shared across the recursion via a semaphore rather than applied\n * per-directory: a per-directory bound would multiply with nesting depth and\n * stop being a bound at all.\n */\nconst MIRROR_COPY_CONCURRENCY = 16\n\n/** Counting semaphore. FIFO, so a deep subtree cannot starve a shallow one. */\nfunction createSemaphore(limit: number) {\n let active = 0\n const waiters: Array<() => void> = []\n return async function withSlot<T>(fn: () => Promise<T>): Promise<T> {\n if (active >= limit) {\n await new Promise<void>((resolve) => waiters.push(resolve))\n // Woken by a HAND-OFF: the releaser kept the slot counted on our behalf\n // rather than decrementing, so `active` is already correct here. Doing\n // `active++` would double-count — and worse, decrementing first would\n // open a window where a fresh caller sees a free slot and takes it too,\n // letting the pool exceed `limit`.\n } else {\n active++\n }\n try {\n return await fn()\n } finally {\n const next = waiters.shift()\n // Hand the slot straight to the next waiter (keeping `active` as-is), or\n // give it back to the pool when nobody is queued.\n if (next) next()\n else active--\n }\n }\n}\n\nasync function mirrorDirRecursive(\n sourceDir: string,\n targetDir: string,\n relPath: string,\n withSlot: <T>(fn: () => Promise<T>) => Promise<T> = createSemaphore(\n MIRROR_COPY_CONCURRENCY,\n ),\n): Promise<void> {\n const sourcePath = path.join(sourceDir, relPath)\n let entries: Array<string>\n try {\n // Through the semaphore like every other syscall here. Subdirectories are\n // traversed concurrently, so an unslotted readdir would let a wide or deep\n // tree dispatch hundreds at once — defeating the file-handle bound the\n // semaphore exists to enforce.\n entries = await withSlot(() => fs.readdir(sourcePath))\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return\n consola.debug(`mirrorDirRecursive: cannot readdir ${sourcePath}:`, err)\n return\n }\n // Entries within a directory are independent, so they run concurrently under\n // the shared slot budget. Semantics per entry are unchanged from the serial\n // version; only the scheduling differs.\n await Promise.all(\n entries.map(async (name) => {\n // Policy dispatch at top-level only. Sub-paths within MIRRORED\n // dirs always cascade as MIRRORED.\n if (relPath === \"\") {\n const policy = policyFor(name)\n if (policy === \"ISOLATED\" || policy === \"SHARED\") return\n }\n const childRel = relPath === \"\" ? name : path.join(relPath, name)\n // Never mirror a proxy-authored agent `.md` out of the user's real\n // `~/.claude/agents/`. A pre-mirror release wrote `peer-<pid>-<hex>-<name>.md`\n // files there; left alone they get copied into every new mirror and register\n // alongside the current launch's files under the SAME frontmatter `name:`,\n // so Claude Code resolves between a live definition and a dead one\n // nondeterministically. Filtering at the COPY step fixes that without\n // deleting anything we do not own: the user's directory is left exactly as\n // it is, and the current launch writes its own files into the mirror after\n // this walk. Deleting them instead would be unsound, because the filename\n // shape is evidence of provenance, not proof of it, and a user who copied a\n // generated file to customize it would lose it.\n if (relPath === \"agents\" && PEER_AGENT_MD_FILENAME.test(name)) {\n return\n }\n const childSource = path.join(sourceDir, childRel)\n const childTarget = path.join(targetDir, childRel)\n let stats: Awaited<ReturnType<typeof fs.lstat>>\n try {\n stats = await withSlot(() => fs.lstat(childSource))\n } catch (err) {\n consola.debug(`mirrorDirRecursive: cannot lstat ${childSource}:`, err)\n return\n }\n if (stats.isSymbolicLink()) {\n // Skip symlinks during mirror copy. gemini-critic security finding:\n // recreating user symlinks in our mirror creates a confused-deputy\n // vector — a previously prompt-injected process could place\n // `~/.claude/<X>` → `/some/sensitive/file`, our walker would mirror\n // it, and any subsequent write to `<mirror>/<X>` (by us or by\n // Claude Code) would follow the symlink and overwrite the target.\n // Snapshot-copy semantics make symlink preservation moot anyway:\n // a snapshot is a point-in-time content copy, and a symlink\n // recreated in the mirror points at exactly the same target as\n // the original would have — the user-side symlink is sufficient.\n // If a user has a legitimate need for a symlink to be visible\n // through the proxy session, they can create the equivalent\n // symlink in their `~/.claude/` directly and it'll be reachable\n // — they just won't see it in our mirror dir.\n consola.debug(`mirrorDirRecursive: skipping symlink ${childSource} (security policy)`)\n return\n }\n if (stats.isDirectory()) {\n await withSlot(() => fs.mkdir(childTarget, { recursive: true }))\n await mirrorDirRecursive(sourceDir, targetDir, childRel, withSlot)\n return\n }\n if (stats.isFile()) {\n // mtime-based skip — only copy if source is newer than target.\n let needsCopy = true\n try {\n const targetStat = await withSlot(() => fs.lstat(childTarget))\n if (targetStat.isFile() && targetStat.mtimeMs >= stats.mtimeMs) {\n needsCopy = false\n }\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") {\n consola.debug(`mirrorDirRecursive: lstat target ${childTarget}:`, err)\n }\n }\n if (!needsCopy) return\n try {\n await withSlot(() =>\n fs.copyFile(childSource, childTarget, fs.constants.COPYFILE_FICLONE),\n )\n } catch (err) {\n consola.debug(`mirrorDirRecursive: copy ${childSource} → ${childTarget}:`, err)\n }\n return\n }\n // Skip other inode types (sockets, devices, fifos) silently.\n }),\n )\n}\n\n/**\n * Create or refresh a directory symlink `<mirrorDir>/<name>` →\n * `<sourceDir>/<name>` (i.e. `~/.local/share/github-router/claude-config/<X>`\n * → `~/.claude/<X>`). Idempotent and concurrent-safe.\n *\n * Behavior depending on what's already at `<mirrorDir>/<name>`:\n * - Symlink with the correct target → no-op.\n * - Symlink with the wrong target → replace atomically.\n * - Empty real directory (legacy mirror leftover with no proxy-session\n * writes accumulated yet) → `rmdir` and replace with the symlink.\n * Safe by definition: `fs.rmdir` only succeeds on empty dirs (POSIX),\n * so there is nothing to lose. Smooths the upgrade path for users\n * whose legacy mirror dirs were never written to.\n * - Non-empty real directory or regular file → loud-warn and skip.\n * Auto-deleting would destroy proxy-session writes from the prior\n * version. The user is told the exact path and remediation.\n * - ENOENT → create symlink atomically.\n *\n * Atomic-creation: symlinks are first written at a unique side-path\n * (`<mirrorDir>/<name>.tmp.<pid>.<8 hex>`) and then `fs.rename()`d into\n * place. POSIX `rename` is atomic and replaces an existing symlink in\n * a single step, so two concurrent `github-router claude` startups can't\n * race to `EEXIST` — the loser's rename just overwrites the winner's\n * symlink with an identical one. Gemini-critic 3-lab-review finding.\n *\n * Pre-creates `~/.claude/<name>/` as a real directory if missing so\n * Claude Code's writes through the symlink don't fail with ENOENT.\n */\nasync function ensureSharedSymlink(\n name: string,\n sourceDir: string,\n mirrorDir: string,\n): Promise<void> {\n const sourcePath = path.join(sourceDir, name)\n const mirrorPath = path.join(mirrorDir, name)\n\n // 1. Ensure the source directory exists. Without this, Claude Code's\n // writes through the symlink (e.g. `projects/<hash>/foo.jsonl`)\n // fail with ENOENT on the parent dir.\n try {\n await fs.mkdir(sourcePath, { recursive: true })\n } catch (err) {\n // Escalated from debug → warn per the CLAUDE.md \"smoking gun\"\n // rule (consistent with the symlink and rename catches below):\n // if the source dir cannot be created (e.g. a stray regular file\n // sitting at `~/.claude/projects`, perms blocking mkdir on a\n // corp-managed Windows box, OneDrive cloud-only reparse point),\n // ensureSharedSymlink returns without creating a junction. The\n // spawned Claude Code child then writes to the REAL `~/.claude`\n // while the proxy reads from the mirror — exactly the split-brain\n // pattern this whole function exists to prevent. Silent debug-log\n // hid this from us once already; warn so the user sees the cause.\n consola.warn(\n `ensureSharedSymlink(${name}): cannot mkdir source ${sourcePath}:`,\n err,\n )\n return\n }\n\n // 2. Inspect the mirror-side slot.\n let existing: Awaited<ReturnType<typeof fs.lstat>> | null = null\n try {\n existing = await fs.lstat(mirrorPath)\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") {\n // Escalated from debug → warn per the CLAUDE.md \"smoking gun\"\n // rule (consistent with the other fs catches in this function):\n // ENOENT is the only expected-and-benign failure mode here\n // (slot doesn't exist yet — falls through to create). Any other\n // lstat failure (EACCES, ELOOP, EIO from a sketchy reparse\n // point) means we bail without creating the junction, which\n // silently leaves the proxy and child diverged. A visible warn\n // surfaces the root cause instead of a mysteriously missing\n // junction.\n consola.warn(\n `ensureSharedSymlink(${name}): cannot lstat ${mirrorPath}:`,\n err,\n )\n return\n }\n }\n\n if (existing?.isSymbolicLink()) {\n // Resolve both sides to their canonical absolute paths and compare.\n // We use `fs.realpath` rather than the raw `fs.readlink()` output\n // because Windows junctions resolve via readlink to `\\\\?\\`-prefixed\n // device-namespace paths (e.g. `\\\\?\\C:\\Users\\foo\\.claude\\projects`)\n // while we wrote the plain absolute `sourcePath` (e.g.\n // `C:\\Users\\foo\\.claude\\projects`) with `fs.symlink`. A literal\n // `===` on the raw readlink output never matched on Windows, so\n // the fast path silently failed and every startup tore down +\n // recreated all 9 SHARED junctions — masked locally because NTFS\n // File System Tunneling forges the creation timestamp for a name\n // deleted and recreated within 15 s (the per-startup churn was\n // real, the ctime-stable assertion was a false negative). The\n // realpath comparison canonicalizes both forms to the same string\n // on POSIX and Windows alike, and as a bonus handles drive-letter\n // casing / trailing-slash differences too. The extra two syscalls\n // per slot are negligible at proxy startup (runs once per launch).\n //\n // CRITICAL: sourceReal and currentReal are NOT treated symmetrically.\n // If `sourceReal` is null (we just mkdir'd it above, but realpath\n // failed — OneDrive cloud-only reparse point, EACCES on parent,\n // EXDEV mount oddity), we WARN AND RETURN rather than fall through.\n // Falling through would do unlink+symlink+rename with the same\n // failing realpath next launch — silent every-startup churn, the\n // exact bug class round-3 G2 fixed in a different code path.\n // `currentReal === null` is benign (broken/wrong slot — replace).\n const sourceReal = await fs.realpath(sourcePath).catch(() => null)\n if (sourceReal === null) {\n consola.warn(\n `ensureSharedSymlink(${name}): cannot resolve source ${sourcePath} ` +\n `— skipping junction creation to avoid silent every-startup churn. ` +\n `Inspect the source dir's permissions / OneDrive sync state and re-launch.`,\n )\n return\n }\n const currentReal = await fs.realpath(mirrorPath).catch(() => null)\n if (currentReal !== null && currentReal === sourceReal) {\n return\n }\n // Wrong target (or unresolvable mirror) — fall through to the\n // atomic-rename replace path.\n } else if (existing?.isDirectory()) {\n // Legacy real directory at the slot. Try `fs.rmdir` — on POSIX it\n // succeeds ONLY if the directory is empty, so there's nothing to\n // lose. If it's non-empty (ENOTEMPTY) or any other failure occurs,\n // fall back to the warn-and-skip path so we never auto-clobber\n // user data.\n try {\n await fs.rmdir(mirrorPath)\n // Empty dir reaped — fall through to the atomic-rename create path.\n } catch (err) {\n consola.warn(\n `ensureClaudeConfigMirror: ${mirrorPath} is a non-empty real directory ` +\n `from an older github-router version; refusing to clobber. ` +\n `If you want chat-history continuity for \"${name}\", move its ` +\n `contents into ${sourcePath}/ then delete ${mirrorPath}; the ` +\n `mirror will create a symlink (junction on Windows) on next launch. ` +\n `(rmdir error: ${(err as NodeJS.ErrnoException).code ?? \"unknown\"})`,\n )\n return\n }\n } else if (existing) {\n // Regular file (or special inode like a socket) — never auto-clobber.\n consola.warn(\n `ensureClaudeConfigMirror: ${mirrorPath} is a regular file at a ` +\n `SHARED symlink slot; refusing to clobber. Inspect and remove ` +\n `manually if safe; the mirror will create a symlink on next launch.`,\n )\n return\n }\n\n // 3. Atomic-rename creation: symlink to a unique temp path, then\n // rename over the slot. `fs.rename` replaces existing symlinks\n // atomically on POSIX and is safe against concurrent racers.\n // On Windows, MoveFileEx with MOVEFILE_REPLACE_EXISTING does NOT\n // replace an existing directory or junction destination\n // (npm/cli#9021), so when the slot already holds a wrong-target\n // junction we must explicitly unlink it first. The sub-millisecond\n // window of no-link is acceptable: ensureClaudeConfigMirror is\n // idempotent under concurrency and only runs at proxy startup,\n // before any spawned Claude Code child has been launched.\n const tempPath = `${mirrorPath}.tmp.${process.pid}.${randomBytes(4).toString(\"hex\")}`\n try {\n await fs.symlink(\n sourcePath,\n tempPath,\n process.platform === \"win32\" ? \"junction\" : \"dir\",\n )\n } catch (err) {\n // Escalated from debug → warn per the CLAUDE.md \"smoking gun\" rule:\n // the rule applies to ALL fs catches in this function, not just the\n // rename one. The temp path is per-pid + 8-hex random so EEXIST is\n // essentially impossible — any failure here (EPERM on Windows\n // without DevMode, EXDEV cross-volume, ENOSPC, …) is a real\n // operational problem the user needs to see.\n consola.warn(\n `ensureSharedSymlink(${name}): symlink ${tempPath} failed:`,\n err,\n )\n return\n }\n if (process.platform === \"win32\" && existing?.isSymbolicLink()) {\n // Windows-only: clear the wrong-target junction so the rename\n // below can land. Best-effort — if a concurrent racer already\n // unlinked it, the rename succeeds as a CREATE; if a concurrent\n // racer already replaced it with a fresh junction, the rename\n // hits the catch below and we surface a warn.\n await fs.unlink(mirrorPath).catch(() => {})\n }\n try {\n await fs.rename(tempPath, mirrorPath)\n } catch (err) {\n // Escalated from debug → warn per the CLAUDE.md \"smoking gun\"\n // rule (consistent with the fs.symlink catch above): a silent\n // debug log here previously hid the Windows rename-replace bug\n // (junction-over-junction MoveFileEx EPERM). Post-fix, rename\n // failures should be rare and visible.\n consola.warn(\n `ensureSharedSymlink(${name}): rename ${tempPath} → ${mirrorPath} failed:`,\n err,\n )\n await fs.unlink(tempPath).catch(() => {})\n }\n}\n\nasync function ensureFile(filePath: string): Promise<void> {\n try {\n await fs.access(filePath, fs.constants.W_OK)\n } catch {\n await fs.writeFile(filePath, \"\")\n await fs.chmod(filePath, 0o600)\n }\n}\n\nasync function chmodIfPossible(target: string, mode: number): Promise<void> {\n if (process.platform === \"win32\") return // Windows chmod is no-op-ish\n try {\n await fs.chmod(target, mode)\n } catch (err) {\n consola.debug(`chmod ${target} ${mode.toString(8)} failed:`, err)\n }\n}\n\n/**\n * Write a runtime tempfile securely.\n *\n * - Mode `0o600` so other local users (multi-tenant boxes, shared\n * dev containers) can't read the per-launch nonce or runtime URL.\n * - `flag: \"wx\"` (O_CREAT | O_EXCL | O_WRONLY) refuses to overwrite\n * an existing path. POSIX open(2) with O_EXCL also rejects\n * pre-placed symlinks, killing the symlink-clobber attack vector.\n * - The caller's responsibility to pick a path NOT yet in use.\n * We intentionally do NOT pre-unlink: an `lstat` + `unlink` +\n * `open(O_EXCL)` sequence still has a TOCTOU window where an\n * attacker can drop a symlink between unlink and open. Letting\n * `wx` fail is the safer behavior — surfaces the conflict\n * instead of silently following.\n */\nexport async function writeRuntimeFileSecure(\n filePath: string,\n content: string,\n): Promise<void> {\n await fs.writeFile(filePath, content, { mode: 0o600, flag: \"wx\" })\n}\n\n/**\n * Write the ai-or-die artifact creds (base/token/sessionId/insecureTLS) to a\n * mode-600 file in the per-launch mirror so the `internal-artifact-open` hook can\n * authenticate: AIORDIE_TOKEN is stripped from the spawned child env, and passing\n * a token via argv would leak it to `ps`. Insecure-TLS mirrors the artifact\n * client's loopback-only fail-closed policy. Best-effort; atomic temp+rename.\n */\nexport async function writeArtifactCredsToMirror(\n insecureTLS: boolean,\n env: NodeJS.ProcessEnv = process.env,\n): Promise<void> {\n const baseUrl = env.AIORDIE_BASE_URL\n const token = env.AIORDIE_TOKEN\n const sessionId = env.AIORDIE_SESSION_ID\n if (!baseUrl || !token || !sessionId) return\n const target = path.join(PATHS.CLAUDE_CONFIG_DIR, \".aiordie-artifact.json\")\n const tempPath = `${target}.${process.pid}.tmp`\n const body = JSON.stringify({ baseUrl, token, sessionId, insecureTLS }) + \"\\n\"\n await fs.writeFile(tempPath, body, { mode: 0o600, flag: \"wx\" }).catch(async (err) => {\n if ((err as NodeJS.ErrnoException).code === \"EEXIST\") return\n throw err\n })\n await fs.rename(tempPath, target).catch(() => {})\n await chmodIfPossible(target, 0o600)\n}\n\n/**\n * Sweep stale runtime tempfiles. Removes files whose embedded PID is no\n * longer a live process. A proxy crash (`kill -9`, OS reboot) leaves\n * orphans that would otherwise accumulate forever — and worse, a stale\n * config pointing at a now-recycled port could route MCP traffic to\n * whatever process bound that port next.\n *\n * Naming convention: `peer-mcp-<pid>.json` and `peer-agents-<pid>.json`.\n * Files not matching either pattern are left alone — this directory\n * is shared with future runtime artifacts.\n *\n * We deliberately do NOT age-prune files whose PID is alive. A\n * legitimately long-running proxy can have a tempfile older than any\n * arbitrary threshold; deleting it out from under the live process\n * breaks the spawned Claude Code child's MCP/agent wiring with no clean\n * recovery. PID-wraparound risk is mitigated by (a) PID reuse on Linux\n * being slow under typical loads, and (b) the file is only consulted by\n * github-router itself — an unrelated process that inherits the PID\n * never reads it.\n */\nexport async function sweepStaleRuntimeFiles(): Promise<void> {\n const dir = PATHS.CLAUDE_RUNTIME_DIR\n let entries: Array<string>\n try {\n entries = await fs.readdir(dir)\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return\n throw err\n }\n\n for (const name of entries) {\n // Match both legacy `peer-mcp-<pid>.json` and current\n // `peer-mcp-<pid>-<rand>.json` filenames so we can clean up either.\n const match = /^peer-(?:mcp|agents)-(\\d+)(?:-[0-9a-f]+)?\\.json$/.exec(name)\n if (!match) continue\n const pid = Number.parseInt(match[1], 10)\n const filePath = path.join(dir, name)\n\n if (isPidAlive(pid)) continue\n\n await fs.unlink(filePath).catch(() => {\n // already gone or unreadable, fine\n })\n }\n}\n\nfunction isPidAlive(pid: number): boolean {\n if (!Number.isInteger(pid) || pid <= 0) return false\n try {\n // signal 0 = check existence without delivering a signal. EPERM\n // means the process exists but we can't signal it (which is still\n // \"alive\" for our purposes); ESRCH means it's gone.\n process.kill(pid, 0)\n return true\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code\n if (code === \"EPERM\") return true\n return false\n }\n}\n\n/**\n * Sweep stale peer-* subagent .md files from the router-owned\n * `CLAUDE_CONFIG_DIR/agents/`. Phase 2.5 writes one .md per peer agent\n * into Claude Code's agents directory (now our config dir's `agents/`\n * subdir, since `getClaudeCodeEnvVars` points `CLAUDE_CONFIG_DIR` at\n * `PATHS.CLAUDE_CONFIG_DIR`) so they appear in Claude Code's Task\n * `subagent_type` enum. Files are named `peer-<pid>-<rand>-<agentName>.md`\n * so this sweep can drop orphans from crashed prior proxy sessions\n * without touching the user's own .md files (which were copied into\n * the same dir during `ensureClaudeConfigMirror`).\n *\n * Same liveness rule as `sweepStaleRuntimeFiles`: only delete when the\n * file's embedded PID is no longer alive. Live PIDs keep their files —\n * a long-running proxy doesn't lose its agent registrations.\n *\n * Regex tightening (Phase 2.6, codex-critic + gemini-critic 2-lab finding):\n * the original sweep regex `^peer-(\\d+)(?:-[0-9a-f]+)?-.+\\.md$` was too\n * permissive — a user-authored `peer-12345-meeting-notes.md` matches\n * (`12345` = \"PID\", `-meeting-notes` = trailing `.+`) and would be\n * silently unlinked when 12345 happens to be a dead PID (overwhelmingly\n * likely). Tightened to require BOTH the 8-hex-char random suffix AND\n * an exact-match persona name suffix, eliminating the risk for any\n * realistic user filename.\n *\n * SCOPE IS THE MIRROR ONLY, deliberately. This deletes on filename shape plus\n * PID liveness, and neither establishes ownership: a user who copied a generated\n * file to customize it would have a file this cannot distinguish from ours. That\n * ambiguity is acceptable inside the router-owned mirror, where the worst case\n * is dropping a snapshot that the next launch regenerates. It would NOT be\n * acceptable in the user's real `~/.claude/agents/`, where the same false\n * positive is permanent data loss, which is exactly why `agents` is MIRRORED\n * rather than SHARED (see `policyFor`). Orphans a pre-mirror release left in the\n * user's directory are handled non-destructively at the copy step in\n * `mirrorDirRecursive`, which skips them so they never register.\n */\nexport async function sweepStalePeerAgentMdFiles(): Promise<void> {\n const dir = path.join(PATHS.CLAUDE_CONFIG_DIR, \"agents\")\n let entries: Array<string>\n try {\n entries = await fs.readdir(dir)\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return\n throw err\n }\n for (const name of entries) {\n const match = PEER_AGENT_MD_FILENAME.exec(name)\n if (!match) continue\n const pid = Number.parseInt(match[1], 10)\n if (isPidAlive(pid)) continue\n await fs.unlink(path.join(dir, name)).catch(() => {\n // already gone or unreadable, fine\n })\n }\n}\n\n/**\n * Strict regex matching only files this proxy writes:\n * peer-<pid>-<8 hex>-<exact persona/coordinator/native/dispatcher name>.md\n * The name allowlist is the load-bearing protection against deleting user\n * files. Update this list whenever a new persona is added to `PERSONAS_READ` /\n * `PERSONAS_WRITE` in `peer-mcp-personas.ts`, a new coordinator-style or native\n * subagent is added in `codex-mcp-config.ts`, or a new `worker-*` dispatcher mode\n * is added in `worker-dispatch.ts` (a drift test in tests/worker-dispatch pins\n * that the `ALL_DISPATCHER_AGENT_NAMES` are all covered here).\n *\n * NAMES ARE ADDED HERE, NEVER REMOVED — this alternation is a permanent superset\n * of every agent name the proxy has ever shipped. A name dropped on a rename\n * stops matching, so `.md` files left behind by a crashed session bearing the old\n * name are never swept: they survive every later launch and load as ghost\n * subagents with a stale prompt and a stale model. That outlives any release\n * window, so \"keep the old name for one version\" is not sufficient. `debugger`\n * and `qa-engineer` are retained for exactly this reason after being renamed to\n * `brainstorm` and `reviewer`.\n *\n * `general-purpose|Explore|Plan` are the serve-only `BUILTIN_SUBAGENT_DEFINITIONS`,\n * which are written through the same `peer-<suffix>-<name>.md` path and were\n * previously absent here, so their stale files never got reaped. Note the capitals:\n * the alternation is case-sensitive and those two are capitalized at the source.\n */\nexport const PEER_AGENT_MD_FILENAME =\n /^peer-(\\d+)-[0-9a-f]{8}-(?:codex-critic|codex-reviewer|gemini-critic|gemini-reviewer|opus-critic|sol-critic|luna-reviewer|grok-critic|grok-reviewer|codex-implementer|peer-review-coordinator|implementer-fast|implementer|planner|reviewer|reviewer-fast|brainstorm|scout|scribe|general-purpose-fast|critic|generic-fast|generic-cheap|generic|debugger|qa-engineer|general-purpose|Explore|Plan|worker-explore|worker-implement|worker-review|worker-plan|worker-test|worker-browse)\\.md$/\n\n/**\n * Strict regex matching only per-launch claude-config mirror dirs this\n * proxy creates: `<pid>-<8 hex>`. Anchored to the entire entry name so\n * user-authored siblings under `<appDir>/claude-config/` (if any) are\n * untouchable. The PID prefix is what `sweepStaleClaudeConfigMirrors`\n * keys off; the 8-hex random suffix matches `randomBytes(4)` exactly\n * (no `?` — files created by a different shape are not ours).\n */\nconst CLAUDE_CONFIG_MIRROR_DIR = /^(\\d+)-[0-9a-f]{8}$/\n\n/**\n * Sweep stale per-launch CLAUDE_CONFIG_DIR mirrors left behind by\n * crashed prior proxy sessions. Symmetric to `sweepStalePeerAgentMdFiles`\n * — same liveness rule (only delete when the embedded PID is dead),\n * same strict regex (the dir-name allowlist is the load-bearing\n * protection against deleting user-authored siblings).\n *\n * Scans `<appDir>/claude-config/` (the parent of the per-launch dirs).\n * Each entry whose name matches `<pid>-<8 hex>` AND whose PID is no\n * longer alive is removed recursively. `fs.rm({recursive: true})`\n * walks the tree calling `unlink` on symlinks/junctions rather than\n * following them, so the SHARED junctions back to `~/.claude/<X>`\n * are removed without touching their targets.\n *\n * Tolerates missing parent dir (first-ever launch, or user wiped it).\n */\nexport async function sweepStaleClaudeConfigMirrors(): Promise<void> {\n const parent = path.join(appDir(), \"claude-config\")\n let entries: Array<string>\n try {\n entries = await fs.readdir(parent)\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return\n throw err\n }\n for (const name of entries) {\n const match = CLAUDE_CONFIG_MIRROR_DIR.exec(name)\n if (!match) continue\n const pid = Number.parseInt(match[1], 10)\n if (isPidAlive(pid)) continue\n await fs\n .rm(path.join(parent, name), { recursive: true, force: true })\n .catch((err) => {\n // Best-effort: stale-dir cleanup must never block startup.\n // Common failure modes (worth surviving silently): an EBUSY/EPERM\n // on Windows if a leftover handle is still open, or a stray\n // root-owned file inside the dir from a previous run with\n // different permissions.\n consola.debug(\n `sweepStaleClaudeConfigMirrors: cannot rm ${name}:`,\n err,\n )\n })\n }\n}\n\n/**\n * Remove THIS launch's per-launch CLAUDE_CONFIG_DIR on shutdown.\n * Best-effort: a failure here must not block process exit (the caller\n * wraps this in a `.catch`-equivalent via `launchChild`'s onShutdown\n * try/catch). Symmetric to `writePeerMcpRuntimeFiles`'s `cleanup()`:\n * we own this dir for the lifetime of the proxy, so removing it on\n * normal shutdown is correct; the boot-time sweep handles the\n * abnormal-exit case.\n *\n * `fs.rm({recursive: true})` removes SHARED junctions via unlink\n * (does NOT follow them into the user's real `~/.claude/<X>`).\n */\nexport async function removeOwnClaudeConfigMirror(): Promise<void> {\n const dir = PATHS.CLAUDE_CONFIG_DIR\n await fs.rm(dir, { recursive: true, force: true }).catch((err) => {\n consola.debug(`removeOwnClaudeConfigMirror: rm ${dir} skipped:`, err)\n })\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"serve-DPRZX-2j.js","names":["fs","fsSync","fs"],"sources":["../src/lib/serve/cloudcli.ts","../src/lib/serve/devtunnel.ts","../src/lib/serve/enhancements.ts","../src/lib/serve/provider-facade.ts","../src/lib/serve/reverse-proxy.ts","../src/serve.ts"],"sourcesContent":["import { spawn, type ChildProcess } from \"node:child_process\"\nimport { randomBytes } from \"node:crypto\"\nimport fs from \"node:fs/promises\"\nimport fsSync from \"node:fs\"\nimport http from \"node:http\"\nimport path from \"node:path\"\n\nimport consola from \"consola\"\n\nimport { runCommandCapture, resolveExecutable } from \"../exec\"\nimport { PATHS } from \"../paths\"\nimport { buildEnv } from \"../worker-agent/bash\"\n\nexport const CLOUDCLI_PACKAGE = \"@cloudcli-ai/cloudcli\"\n/** Pinned, tested known-good version. Never `@latest` (see docs/serve-control-plane.md). */\nexport const CLOUDCLI_PINNED_VERSION = \"1.36.1\"\n\n/**\n * Build the env handed to the CloudCLI child. Starts from `buildEnv()` — the\n * vetted secret-stripping ALLOWLIST used for worker bash (drops GITHUB_TOKEN,\n * GH_ROUTER_*, ANTHROPIC_AUTH_TOKEN, OPENAI_API_KEY, COPILOT_TOKEN by omission)\n * — then layers the non-secret Anthropic/Claude vars. The spawned claude still\n * authenticates because that rides the synthetic `.credentials.json` FILE in\n * CLAUDE_CONFIG_DIR, not an env token. Load-bearing: CloudCLI exposes a browser\n * terminal that can read its own env, so no secret may live there.\n */\nexport function composeCloudCliChildEnv(\n anthropicVars: Record<string, string>,\n): NodeJS.ProcessEnv {\n return { ...buildEnv(), ...anthropicVars }\n}\n\nconst SERVER_ENTRY_REL = path.join(\n \"dist-server\",\n \"server\",\n \"index.js\",\n)\n\nexport interface ResolvedCloudCli {\n /** Absolute path to the CloudCLI server entry (dist-server/server/index.js). */\n serverEntry: string\n version: string | null\n}\n\n/** Where the package's server entry lives under a given install root. */\nfunction serverEntryUnderRoot(root: string): string {\n return path.join(root, \"node_modules\", CLOUDCLI_PACKAGE, SERVER_ENTRY_REL)\n}\n\nasync function exists(p: string): Promise<boolean> {\n try {\n await fs.access(p)\n return true\n } catch {\n return false\n }\n}\n\nasync function readVersion(pkgRoot: string): Promise<string | null> {\n try {\n const raw = await fs.readFile(\n path.join(pkgRoot, \"node_modules\", CLOUDCLI_PACKAGE, \"package.json\"),\n \"utf8\",\n )\n return (JSON.parse(raw) as { version?: string }).version ?? null\n } catch {\n return null\n }\n}\n\n/**\n * Resolve the CloudCLI server entry, in order:\n * 1. an explicit `--cloudcli-path` (a package dir OR an install root),\n * 2. an existing pinned install in the router-owned dir,\n * 3. (unless `noInstall`) install the pinned version into the router-owned dir.\n * Never installs globally, never `@latest`.\n */\nexport async function resolveCloudCli(opts: {\n cliPath?: string\n noInstall: boolean\n version?: string\n}): Promise<ResolvedCloudCli> {\n const version = opts.version ?? CLOUDCLI_PINNED_VERSION\n\n // 1. explicit path — accept either a package dir or an install root.\n if (opts.cliPath) {\n const direct = path.join(opts.cliPath, SERVER_ENTRY_REL)\n const underRoot = serverEntryUnderRoot(opts.cliPath)\n if (await exists(direct)) return { serverEntry: direct, version: null }\n if (await exists(underRoot))\n return { serverEntry: underRoot, version: await readVersion(opts.cliPath) }\n throw new Error(\n `--cloudcli-path \"${opts.cliPath}\" does not contain a CloudCLI install (looked for ${SERVER_ENTRY_REL}).`,\n )\n }\n\n // 2. existing router-owned install.\n const root = PATHS.CLOUDCLI_HOME\n const entry = serverEntryUnderRoot(root)\n if (await exists(entry)) {\n return { serverEntry: entry, version: await readVersion(root) }\n }\n\n // 3. install the pinned version locally into the router-owned dir.\n if (opts.noInstall) {\n throw new Error(\n `CloudCLI is not installed and --no-install was given. Install it with:\\n npm i --prefix \"${root}\" ${CLOUDCLI_PACKAGE}@${version}`,\n )\n }\n await installCloudCli(root, version)\n if (!(await exists(entry))) {\n throw new Error(\n `CloudCLI install completed but the server entry is missing (${entry}).`,\n )\n }\n return { serverEntry: entry, version: await readVersion(root) }\n}\n\nasync function installCloudCli(root: string, version: string): Promise<void> {\n const npm = resolveExecutable(\"npm\")\n if (!npm) {\n throw new Error(\n \"npm not found on PATH — cannot install CloudCLI. Install Node.js/npm, or pass --cloudcli-path to an existing install.\",\n )\n }\n await fs.mkdir(root, { recursive: true, mode: 0o700 })\n // A local package.json so `npm i` installs into <root>/node_modules\n // instead of walking up to a parent project.\n const pkgJson = path.join(root, \"package.json\")\n if (!(await exists(pkgJson))) {\n await fs.writeFile(\n pkgJson,\n JSON.stringify({ name: \"gh-router-cloudcli-host\", private: true }, null, 2),\n )\n }\n consola.info(\n `Installing ${CLOUDCLI_PACKAGE}@${version} (one-time, ~a minute; builds native deps)…`,\n )\n const { code, stderr } = await runCommandCapture(\n [\n npm,\n \"install\",\n `${CLOUDCLI_PACKAGE}@${version}`,\n \"--prefix\",\n root,\n \"--no-audit\",\n \"--no-fund\",\n \"--loglevel=error\",\n ],\n { timeoutMs: 10 * 60_000 },\n )\n if (code !== 0) {\n throw new Error(\n `CloudCLI install failed (npm exit ${code}). This often means no network, a corporate proxy, or missing Windows build tools for native deps.\\nInstall manually then re-run with --cloudcli-path:\\n npm i --prefix \"${root}\" ${CLOUDCLI_PACKAGE}@${version}\\n${stderr.slice(0, 400)}`,\n )\n }\n}\n\nexport interface CloudCliProcess {\n child: ChildProcess\n port: number\n databasePath: string\n}\n\n/** Spawn CloudCLI on a loopback port with the given (already-filtered) env. */\nexport function spawnCloudCli(opts: {\n serverEntry: string\n port: number\n env: NodeJS.ProcessEnv\n}): CloudCliProcess {\n const databasePath = path.join(PATHS.CLOUDCLI_HOME, \"auth.db\")\n const node = resolveExecutable(\"node\") ?? process.execPath\n // Diagnostic opt-in: route CloudCLI's stdout+stderr to a log file so its\n // internal errors (SDK/DB/session-create) are inspectable. Default stays\n // \"ignore\" (CloudCLI is noisy and its logs aren't user-facing).\n const logPath = process.env.GH_ROUTER_CLOUDCLI_LOG\n let stdio: \"ignore\" | [\"ignore\", number, number] = \"ignore\"\n if (logPath) {\n try {\n const fd = fsSync.openSync(logPath, \"a\")\n stdio = [\"ignore\", fd, fd]\n } catch {\n /* unwritable log path — fall back to ignore */\n }\n }\n const child = spawn(node, [opts.serverEntry], {\n env: {\n ...opts.env,\n SERVER_PORT: String(opts.port),\n HOST: \"127.0.0.1\", // loopback only — never 0.0.0.0\n DATABASE_PATH: databasePath, // router-owned, isolated from a user's standalone CloudCLI\n // Do NOT set VITE_IS_PLATFORM: it disarms server auth and still shows a\n // login wall in the OSS bundle. Keep JWT auth on; auto-login is done by\n // the reverse proxy injecting the token.\n VITE_IS_PLATFORM: \"\",\n },\n stdio,\n windowsHide: true,\n })\n return { child, port: opts.port, databasePath }\n}\n\n// ---- health + JWT --------------------------------------------------------\n\nfunction req(\n port: number,\n method: \"GET\" | \"POST\",\n urlPath: string,\n body?: unknown,\n): Promise<{ status: number; body: string }> {\n return new Promise((resolve, reject) => {\n const data = body === undefined ? undefined : JSON.stringify(body)\n const r = http.request(\n {\n host: \"127.0.0.1\",\n port,\n path: urlPath,\n method,\n headers: data\n ? { \"content-type\": \"application/json\", \"content-length\": Buffer.byteLength(data) }\n : {},\n },\n (res) => {\n let b = \"\"\n res.on(\"data\", (d) => (b += d))\n res.on(\"end\", () => resolve({ status: res.statusCode ?? 0, body: b }))\n },\n )\n r.on(\"error\", reject)\n if (data) r.write(data)\n r.end()\n })\n}\n\nconst sleep = (ms: number) => new Promise((r) => setTimeout(r, ms))\n\n/** Poll /api/auth/status until the server answers. Returns `needsSetup`. */\nexport async function waitForCloudCliReady(\n port: number,\n timeoutMs = 60_000,\n): Promise<{ needsSetup: boolean }> {\n const deadline = Date.now() + timeoutMs\n while (Date.now() < deadline) {\n try {\n const r = await req(port, \"GET\", \"/api/auth/status\")\n if (r.status === 200) {\n const parsed = JSON.parse(r.body) as { needsSetup?: boolean }\n return { needsSetup: parsed.needsSetup === true }\n }\n } catch {\n // not up yet\n }\n await sleep(1000)\n }\n throw new Error(`CloudCLI did not become ready within ${timeoutMs}ms`)\n}\n\nconst secretFile = () => path.join(PATHS.CLOUDCLI_HOME, \".serve-secret.json\")\n\nasync function loadPassword(username: string): Promise<string | null> {\n try {\n const raw = await fs.readFile(secretFile(), \"utf8\")\n const parsed = JSON.parse(raw) as { username?: string; password?: string }\n if (parsed.username === username && parsed.password) return parsed.password\n } catch {\n // no file yet\n }\n return null\n}\n\nasync function persistPassword(username: string, password: string): Promise<void> {\n // mode 0o600 + a 0o700 parent dir. On Windows the mode bits are ignored, but\n // the dir lives under the per-user home, which is user-ACL'd by default; the\n // password is a low-value local login for a loopback-only, Origin-gated app.\n await fs.mkdir(PATHS.CLOUDCLI_HOME, { recursive: true, mode: 0o700 })\n await fs.writeFile(\n secretFile(),\n JSON.stringify({ username, password }),\n { mode: 0o600 },\n )\n}\n\n/**\n * Ensure a CloudCLI user exists and return a valid JWT. On a fresh (isolated)\n * DB we register (persisting the password only AFTER success); on an existing\n * DB we log in with the persisted password.\n */\nexport async function mintJwt(\n port: number,\n username: string,\n needsSetup: boolean,\n): Promise<string> {\n const existing = await loadPassword(username)\n if (needsSetup) {\n const password = existing ?? randomBytes(24).toString(\"base64url\")\n const r = await req(port, \"POST\", \"/api/auth/register\", { username, password })\n if (r.status === 200) {\n const token = (JSON.parse(r.body) as { token?: string }).token\n if (token) {\n await persistPassword(username, password)\n return token\n }\n }\n // Fall through to login (e.g. a race created the user first).\n }\n const password = existing\n if (!password) {\n throw new Error(\n `CloudCLI already has a user but no stored password for \"${username}\". Delete ${path.join(PATHS.CLOUDCLI_HOME, \"auth.db\")} and retry.`,\n )\n }\n const r = await req(port, \"POST\", \"/api/auth/login\", { username, password })\n if (r.status === 200) {\n const token = (JSON.parse(r.body) as { token?: string }).token\n if (token) return token\n }\n throw new Error(\n `Could not obtain a CloudCLI session token (status ${r.status}). If a stale isolated DB exists, delete ${path.join(PATHS.CLOUDCLI_HOME, \"auth.db\")} and retry.`,\n )\n}\n","import { spawn } from \"node:child_process\"\nimport { createHash } from \"node:crypto\"\nimport os from \"node:os\"\n\nimport { killChildProcessTree, resolveExecutable, runCommandCapture } from \"../exec\"\n\n// Label stamped on every tunnel `serve --tunnel` creates, so our tunnels are\n// identifiable and sweepable (`devtunnel list -l github-router-serve`). Before\n// this, serve minted an anonymous, unlabeled tunnel per launch that was never\n// deleted — they accumulated server-side against the hard\n// `TunnelsPerUserPerCluster` (10) cap until new tunnels were denied.\nexport const SERVE_TUNNEL_LABEL = \"github-router-serve\"\n\n// A per-machine label so reuse/sweep scopes to THIS host and never touches a\n// tunnel another machine (sharing the same dev-tunnels account) owns. Hashed so\n// an arbitrary hostname maps to a label-charset-safe token.\nexport function serveTunnelMachineLabel(hostname: string = os.hostname()): string {\n return `ghr-machine-${createHash(\"sha256\").update(hostname).digest(\"hex\").slice(0, 12)}`\n}\n\n/**\n * Stable, deterministic tunnel ID for THIS machine, so `serve --tunnel` REUSES\n * one tunnel across launches → a stable, bookmarkable public URL. Derived from a\n * hash of the hostname (valid tunnel-id charset: lowercase alnum + hyphens,\n * bounded length). NOTE: the hosting URL's subdomain is a devtunnels-assigned\n * token, NOT this id — the id only anchors find/reuse; the URL is stable because\n * the same tunnel is re-hosted, not because the id appears in it.\n */\nexport function serveTunnelId(hostname: string = os.hostname()): string {\n return `ghr-serve-${createHash(\"sha256\").update(hostname).digest(\"hex\").slice(0, 12)}`\n}\n\n/**\n * Reconcile a reused tunnel's ports to exactly `[desired]`: which existing ports\n * to delete and whether the desired port must be created. Pure (unit-tested).\n * This is why reuse works across `--port` changes — `devtunnel host <id>\n * -p <newport>` fails (\"Batch update of ports is not supported\"), but per-port\n * `port delete` + `port create` does not.\n */\nexport function portsToReconcile(\n currentPorts: number[],\n desired: number,\n): { toDelete: number[]; toCreate: number | null } {\n return {\n toDelete: currentPorts.filter((p) => p !== desired),\n toCreate: currentPorts.includes(desired) ? null : desired,\n }\n}\n\nexport interface TunnelInfo {\n tunnelId: string\n labels?: string[]\n hostConnections?: number\n}\n\n/**\n * The IDs of our own idle tunnels for this machine to sweep — EXCLUDING the\n * stable reused tunnel (`exceptId`), which we keep and re-host. Pure\n * (unit-tested). A tunnel with a live host connection (a concurrent serve\n * instance) is never swept. Cleans up pre-migration random-named tunnels + any\n * crash-orphaned duplicates while preserving the one stable tunnel.\n */\nexport function serveTunnelIdsToSweep(\n tunnels: TunnelInfo[],\n machineLabel: string,\n exceptId?: string,\n): string[] {\n return tunnels\n .filter((t) => (t.labels ?? []).includes(machineLabel) && !t.hostConnections)\n .map((t) => t.tunnelId)\n .filter((id) => !exceptId || id.split(\".\")[0] !== exceptId)\n}\n\n/** List the tunnels serve owns (label-filtered). Best-effort: [] on any error. */\nasync function listServeTunnels(cli: string): Promise<TunnelInfo[]> {\n try {\n const { stdout, code } = await runCommandCapture(\n [cli, \"list\", \"-l\", SERVE_TUNNEL_LABEL, \"-j\"],\n { timeoutMs: 15_000 },\n )\n if (code !== 0) return []\n const parsed = JSON.parse(stdout) as { tunnels?: TunnelInfo[] }\n return Array.isArray(parsed?.tunnels) ? parsed.tunnels : []\n } catch {\n return []\n }\n}\n\n/** Best-effort delete of a single tunnel by full id (e.g. `fancy-fog-x.inc1`). */\nasync function deleteTunnel(cli: string, tunnelId: string): Promise<void> {\n try {\n await runCommandCapture([cli, \"delete\", tunnelId, \"-f\"], { timeoutMs: 15_000 })\n } catch {\n /* best-effort — a stale delete failing must not block hosting */\n }\n}\n\n/** Current port numbers configured on a tunnel. Best-effort: [] on any error. */\nasync function getTunnelPorts(cli: string, tunnelId: string): Promise<number[]> {\n try {\n const { stdout, code } = await runCommandCapture(\n [cli, \"port\", \"list\", tunnelId, \"-j\"],\n { timeoutMs: 15_000 },\n )\n if (code !== 0) return []\n const parsed = JSON.parse(stdout) as { ports?: Array<{ portNumber?: number }> }\n return (parsed?.ports ?? [])\n .map((p) => p.portNumber)\n .filter((n): n is number => typeof n === \"number\")\n } catch {\n return []\n }\n}\n\n/**\n * Ensure the stable per-machine tunnel exists (create it labeled if absent).\n * Returns true when the stable tunnel is present + owned by us (safe to reuse),\n * false to fall back to the anonymous-labeled host path. `existing` is the\n * pre-fetched label-scoped list (so we don't re-list).\n */\nasync function ensureStableTunnel(\n cli: string,\n stableId: string,\n machineLabel: string,\n existing: TunnelInfo[],\n): Promise<boolean> {\n const present = existing.some((t) => t.tunnelId.split(\".\")[0] === stableId)\n if (present) return true\n try {\n // `create` errors (\"Conflict\") if the id is already taken — but we only get\n // here when our label-scoped list did NOT contain it, so a conflict means a\n // DIFFERENT owner holds the id: fall back rather than reuse a foreign tunnel.\n const { code } = await runCommandCapture(\n [cli, \"create\", stableId, \"-l\", SERVE_TUNNEL_LABEL, \"-l\", machineLabel,\n \"-d\", \"github-router-serve-control-plane\"],\n { timeoutMs: 20_000 },\n )\n return code === 0\n } catch {\n return false\n }\n}\n\n/** Reconcile the tunnel's ports to exactly `[port]` (delete stale, create missing). */\nasync function reconcileTunnelPorts(cli: string, tunnelId: string, port: number): Promise<void> {\n const { toDelete, toCreate } = portsToReconcile(await getTunnelPorts(cli, tunnelId), port)\n for (const p of toDelete) {\n await runCommandCapture([cli, \"port\", \"delete\", tunnelId, \"-p\", String(p)], { timeoutMs: 15_000 }).catch(() => {})\n }\n if (toCreate != null) {\n await runCommandCapture([cli, \"port\", \"create\", tunnelId, \"-p\", String(toCreate)], { timeoutMs: 15_000 }).catch(() => {})\n }\n}\n\n\n// Matches the browser URL devtunnel prints, e.g.\n// Hosting port 5454 at https://l3rs99qw-5454.usw2.devtunnels.ms/\nconst DEVTUNNEL_URL_RE =\n /https:\\/\\/[a-z0-9][a-z0-9-]*(?:\\.[a-z0-9-]+)*\\.devtunnels\\.ms\\/?/i\n\nexport type DevtunnelFailureKind =\n | \"not-installed\"\n | \"not-logged-in\"\n | \"timeout\"\n | \"exited\"\n | \"spawn\"\n\nexport class DevtunnelError extends Error {\n readonly kind: DevtunnelFailureKind\n constructor(kind: DevtunnelFailureKind, message: string) {\n super(message)\n this.name = \"DevtunnelError\"\n this.kind = kind\n }\n}\n\nexport interface DevtunnelHandle {\n url: string\n stop: () => void\n}\n\n/** Absolute path to the `devtunnel` CLI, or null if not on PATH. */\nexport function resolveDevtunnelCli(): string | null {\n return resolveExecutable(\"devtunnel\")\n}\n\n/** Best-effort login check via `devtunnel user show`. */\nexport async function isDevtunnelLoggedIn(cli: string): Promise<boolean> {\n try {\n const { stdout, stderr, code } = await runCommandCapture(\n [cli, \"user\", \"show\"],\n { timeoutMs: 15_000 },\n )\n const out = `${stdout}\\n${stderr}`\n if (/not logged in/i.test(out)) return false\n if (/logged in as/i.test(out)) return true\n return code === 0\n } catch {\n return false\n }\n}\n\n/** Extract the dev-tunnel browser URL from CLI output. Exported for tests. */\nexport function parseDevtunnelUrl(text: string): string | null {\n const m = text.match(DEVTUNNEL_URL_RE)\n return m ? m[0].replace(/\\/+$/, \"\") : null\n}\n\n/**\n * Spawn `devtunnel <args>` and resolve once it prints its public URL. Shared by\n * the stable-reuse and anonymous-labeled host paths. Rejects (DevtunnelError) on\n * timeout, early exit, an auth error in the output, or a spawn failure.\n */\nfunction hostTunnelProcess(cli: string, args: string[], timeoutMs: number): Promise<DevtunnelHandle> {\n const child = spawn(cli, args, { stdio: [\"ignore\", \"pipe\", \"pipe\"], windowsHide: true })\n return new Promise<DevtunnelHandle>((resolve, reject) => {\n let settled = false\n const stop = () => {\n // Tree-kill: the devtunnel process spawns a helper and a plain\n // child.kill() leaves the tunnel up (verified). taskkill /T /F on\n // Windows, process-group SIGTERM on POSIX.\n try {\n killChildProcessTree(child, { detachedGroup: process.platform !== \"win32\" })\n } catch {\n try {\n child.kill()\n } catch {\n /* already gone */\n }\n }\n }\n const finish = (fn: () => void) => {\n if (settled) return\n settled = true\n clearTimeout(timer)\n fn()\n }\n const timer = setTimeout(\n () =>\n finish(() => {\n stop()\n reject(new DevtunnelError(\"timeout\", \"dev tunnel did not report a URL in time\"))\n }),\n timeoutMs,\n )\n const onData = (buf: Buffer) => {\n const text = String(buf)\n const url = parseDevtunnelUrl(text)\n if (url) {\n finish(() => resolve({ url, stop }))\n return\n }\n if (/not logged in|login (required|expired)|unauthorized/i.test(text)) {\n finish(() => {\n stop()\n reject(new DevtunnelError(\"not-logged-in\", \"dev tunnel authentication failed; run `devtunnel user login`\"))\n })\n }\n }\n child.stdout?.on(\"data\", onData)\n child.stderr?.on(\"data\", onData)\n child.once(\"error\", (err) => finish(() => reject(new DevtunnelError(\"spawn\", err.message))))\n child.once(\"exit\", (code) =>\n finish(() =>\n reject(new DevtunnelError(\"exited\", `devtunnel exited (code ${code}) before printing a URL`)),\n ),\n )\n })\n}\n\n/**\n * Host an AUTHENTICATED (never anonymous) dev tunnel forwarding `port`, resolving\n * once its public URL is printed. NEVER `--allow-anonymous` (owner-only access).\n *\n * REUSES a stable per-machine tunnel (`serveTunnelId`) across launches → a\n * STABLE, bookmarkable public URL. Each launch: ensure the stable tunnel exists\n * (create it labeled if absent), reconcile its ports to exactly `[port]` via\n * per-port delete/create (`host <id> -p <newport>` can't change ports — it 400s\n * \"Batch update of ports is not supported\"), sweep our OTHER idle labeled tunnels\n * (pre-migration random-named ones / crash orphans), then `host <id>`. The URL's\n * subdomain is a devtunnels-assigned token (not the id), but it's stable because\n * the SAME tunnel is re-hosted every launch.\n *\n * If any stable-path step fails (id already taken by another owner, CLI hiccup),\n * falls back to hosting a fresh anonymous-labeled tunnel (the prior behavior) so\n * `--tunnel` always works — just without a stable URL that launch.\n */\nexport async function startDevtunnel(\n port: number,\n opts: { timeoutMs?: number } = {},\n): Promise<DevtunnelHandle> {\n const cli = resolveDevtunnelCli()\n if (!cli) {\n throw new DevtunnelError(\n \"not-installed\",\n \"The `devtunnel` CLI is not installed. Install it (winget install Microsoft.devtunnel / brew install --cask devtunnel) then run `devtunnel user login`.\",\n )\n }\n if (!(await isDevtunnelLoggedIn(cli))) {\n throw new DevtunnelError(\n \"not-logged-in\",\n \"Not signed in to dev tunnels. Run `devtunnel user login` (or `devtunnel user login -g` for GitHub) first.\",\n )\n }\n\n const timeoutMs = opts.timeoutMs ?? 40_000\n const machineLabel = serveTunnelMachineLabel()\n const stableId = serveTunnelId()\n\n // Stable-reuse path (best-effort; any failure falls through to anonymous host).\n try {\n const owned = await listServeTunnels(cli)\n if (await ensureStableTunnel(cli, stableId, machineLabel, owned)) {\n await reconcileTunnelPorts(cli, stableId, port)\n // Sweep our OTHER idle labeled tunnels but KEEP the stable one.\n for (const id of serveTunnelIdsToSweep(owned, machineLabel, stableId)) {\n await deleteTunnel(cli, id)\n }\n return await hostTunnelProcess(cli, [\"host\", stableId], timeoutMs)\n }\n } catch {\n /* fall through to the anonymous-labeled host path */\n }\n\n // Fallback: sweep our idle labeled tunnels, then host a fresh anonymous one.\n // Labels are SEPARATE `-l` flags — the service rejects a label containing a\n // space (`must match '[\\w-=]{1,50}'`), so they can't be joined into one arg.\n try {\n for (const id of serveTunnelIdsToSweep(await listServeTunnels(cli), machineLabel)) {\n await deleteTunnel(cli, id)\n }\n } catch {\n /* best-effort sweep — never blocks hosting */\n }\n return await hostTunnelProcess(\n cli,\n [\"host\", \"-p\", String(port), \"-l\", SERVE_TUNNEL_LABEL, \"-l\", machineLabel, \"-d\", \"github-router-serve-control-plane\"],\n timeoutMs,\n )\n}\n","import path from \"node:path\"\n\nimport consola from \"consola\"\n\nimport { injectAttributionSuppressionIntoSettingsFile } from \"../attribution-settings\"\nimport {\n BUILTIN_SUBAGENT_DEFINITIONS,\n injectPeerMcpIntoMirror,\n resolveGroupKeysFromMirror,\n workersKeyOf,\n writePeerMcpRuntimeFiles,\n} from \"../codex-mcp-config\"\nimport {\n appendPeerAwarenessToMirroredClaudeMd,\n appendToolbeltAwarenessToMirroredClaudeMd,\n buildOperatingDefaultsDirective,\n type NativeAgentAvailability,\n prependOperatingDefaultsToMirroredClaudeMd,\n prependStyleDirectiveToMirroredClaudeMd,\n} from \"../claude-md-injection\"\nimport { type SelfInvocation } from \"../hook-launcher/self-invocation\"\nimport { INJECTED_SKILLS, writeInjectedSkill } from \"../injected-skills\"\nimport {\n configureServeDefaultPermissionMode,\n injectAllowRules,\n planModeAllowRules,\n sanitizeServeSettingsEnv,\n} from \"../mcp-permissions-settings\"\nimport {\n agentToolsEnabled,\n browseAgentEnabled,\n browserCompoundToolsEnabled,\n browserToolsEnabled,\n fleetToolsEnabled,\n geminiAvailable,\n nativeSubagentModel,\n brainstormModel,\n reviewerModel,\n reviewerFastModel,\n resolveGeminiReviewModel,\n scoutModel,\n scribeModel,\n implementerFastModel,\n generalPurposeFastModel,\n standInToolEnabled,\n workerToolsEnabled,\n} from \"../mcp-capabilities\"\nimport { buildPlanReviewHookCommand, planReviewEnabled } from \"../orchestration/plan-review-hook\"\nimport { buildPromptSubmitHookCommand } from \"../orchestration/prompt-submit-hook\"\nimport { injectStopHookIntoSettingsFile } from \"../orchestration/stop-gate-hook\"\nimport { PATHS } from \"../paths\"\nimport {\n buildPeerAwarenessSnippet,\n enumerateInjectedMcpToolNames,\n type McpGroup,\n} from \"../peer-mcp-personas\"\nimport { state } from \"../state\"\nimport { availableToolCommands, buildToolbeltAwareness, toolbeltEnabled } from \"../toolbelt\"\nimport {\n activeDispatchModes,\n buildWorkerGuardHookCommand,\n guardToolMatcher,\n} from \"../worker-dispatch\"\n\nexport interface ServeEnhancementsHandle {\n cleanup: () => Promise<void>\n nonce?: string\n /** Exact `mcp__<key>__<tool>` names of every injected MCP tool — seeded into\n * the reverse-proxy's `localStorage['claude-settings'].allowedTools` so\n * CloudCLI's `canUseTool` auto-approves them in PLAN mode. */\n mcpToolNames?: string[]\n}\n\nexport interface ServeEnhancementOpts {\n /** Stable invocation baked into every persisted hook/helper command. */\n selfInvocation: SelfInvocation\n /** Route Codex personas through a local `codex mcp-server` (stdio) when the backend resolved to \"cli\". */\n codexCli?: boolean\n /** True when the control plane is reachable beyond loopback (a --tunnel or --public-url is active). Gates the highest-blast-radius capabilities OFF by default: the server-side browser MCP (a session-hijack / SSRF / cloud-metadata primitive when reachable remotely) and first-mate (mints a repo+workflow GitHub write token). */\n tunnelExposed?: boolean\n /** Operator opt-in to expose the browser MCP over the tunnel despite the risk. */\n browseOverTunnel?: boolean\n /** Operator opt-in to expose first-mate over the tunnel despite the risk. */\n agentsOverTunnel?: boolean\n /** Operator opt-in to expose the fleet MCP over the tunnel despite the risk\n * (fleet drives remote coding sessions with the operator's stored fleet\n * credentials — an equal-or-higher blast radius than the browser MCP). */\n fleetOverTunnel?: boolean\n}\n\nconst NOOP: ServeEnhancementsHandle = { cleanup: async () => {} }\n\nexport function resolveServeNativeAgentOptions() {\n const models = {\n nativeSubagentModel: nativeSubagentModel(),\n reviewerModel: reviewerModel(),\n reviewerFastModel: reviewerFastModel(),\n brainstormModel: brainstormModel(),\n scoutModel: scoutModel(),\n scribeModel: scribeModel(),\n implementerFastModel: implementerFastModel(),\n generalPurposeFastModel: generalPurposeFastModel(),\n }\n const nativeAvailability: NativeAgentAvailability = {\n scoutAvailable: models.scoutModel != null,\n implementerFastAvailable: models.implementerFastModel != null,\n reviewerFastAvailable: models.reviewerFastModel != null,\n generalPurposeFastAvailable: models.generalPurposeFastModel != null,\n }\n return { models, nativeAvailability }\n}\n\n/**\n * Wire the github-router enhancement layer into the router-owned\n * CLAUDE_CONFIG_DIR mirror so a CloudCLI-spawned Claude session gets the same\n * tools `github-router claude` provides:\n * - the scoped MCP servers (peers / search / orchestrate, plus workers /\n * decide / browser / fleet / first-mate when their gate passes) — the\n * SDK-spawned claude reads them from the mirror's `.claude.json`,\n * - the peer-critic / worker / implementer subagents (`.md` files written into\n * the mirror's `agents/`),\n * - the gh-* skills (research / orchestrate / floor-keeper / worker).\n *\n * Best-effort: on any failure Claude still works, just without the extras.\n * Must run AFTER `ensureClaudeConfigMirror()` and BEFORE CloudCLI is spawned.\n * Tab-specific bits (ai-or-die hooks, artifact review) are intentionally left\n * out — they don't apply to the serve control plane.\n */\nexport async function provisionServeEnhancements(\n serverUrl: string,\n opts: ServeEnhancementOpts,\n): Promise<ServeEnhancementsHandle> {\n try {\n const tunnelExposed = opts.tunnelExposed === true\n const browseAllowed = browserToolsEnabled() && (!tunnelExposed || opts.browseOverTunnel === true)\n const firstMateAllowed = agentToolsEnabled() && (!tunnelExposed || opts.agentsOverTunnel === true)\n const fleetAllowed = fleetToolsEnabled() && (!tunnelExposed || opts.fleetOverTunnel === true)\n\n const enabledGroups: McpGroup[] = [\"peers\", \"search\", \"orchestrate\"]\n if (workerToolsEnabled()) enabledGroups.push(\"workers\")\n if (standInToolEnabled()) enabledGroups.push(\"decide\")\n if (browseAllowed) enabledGroups.push(\"browser\")\n if (fleetAllowed) enabledGroups.push(\"fleet\")\n if (firstMateAllowed) enabledGroups.push(\"first-mate\")\n\n const gem = geminiAvailable()\n const { keys: groupKeys } = await resolveGroupKeysFromMirror(enabledGroups)\n // Resolved once and reused by the `.md` generation, the awareness snippet,\n // and the operating-defaults directive, so the three surfaces cannot\n // disagree about which conditionally-emitted natives exist.\n const { models: nativeModels, nativeAvailability } =\n resolveServeNativeAgentOptions()\n\n const runtime = await writePeerMcpRuntimeFiles(serverUrl, {\n codexCli: opts.codexCli === true,\n selfInvocation: opts.selfInvocation,\n geminiAvailable: gem,\n geminiModel: resolveGeminiReviewModel(),\n groupKeys,\n workerToolsAvailable: workerToolsEnabled(),\n browseAvailable: browseAllowed && browseAgentEnabled(),\n ...nativeModels,\n // Serve-only: register Claude Code's built-in Explore/Plan/general-purpose\n // subagents (the Agent SDK doesn't) so the model's habitual Agent() calls\n // resolve. Never passed by `github-router claude` (would shadow the CLI's\n // native built-ins).\n builtinSubagents: BUILTIN_SUBAGENT_DEFINITIONS,\n })\n // The proxy's /mcp handler authorizes tool calls against this per-launch\n // nonce (the same value baked into the mirrored mcpServers Authorization).\n state.peerMcpNonce = runtime.nonce\n\n const injected = await injectPeerMcpIntoMirror(serverUrl, {\n codexCli: opts.codexCli === true,\n selfInvocation: opts.selfInvocation,\n geminiAvailable: gem,\n groupKeys,\n nonce: runtime.nonce,\n })\n\n const peerSnippet = buildPeerAwarenessSnippet({\n codexCli: opts.codexCli === true,\n geminiAvailable: gem,\n geminiModel: resolveGeminiReviewModel(),\n workerToolsAvailable: workerToolsEnabled(),\n standInAvailable: standInToolEnabled(),\n browseAvailable: browseAllowed,\n compoundBrowseAvailable: browseAllowed && browserCompoundToolsEnabled(),\n powerBrowseAvailable: browseAllowed && state.powerBrowseEnabled,\n agentToolsAvailable: firstMateAllowed,\n ...nativeAvailability,\n groupKeys,\n })\n await appendPeerAwarenessToMirroredClaudeMd(peerSnippet).catch((err) =>\n consola.warn(`Peer-awareness CLAUDE.md append failed: ${String(err)}`),\n )\n await prependStyleDirectiveToMirroredClaudeMd().catch((err) =>\n consola.warn(`Style-directive CLAUDE.md prepend failed: ${String(err)}`),\n )\n await prependOperatingDefaultsToMirroredClaudeMd(\n buildOperatingDefaultsDirective(nativeAvailability),\n ).catch((err) =>\n consola.warn(`Operating-defaults CLAUDE.md prepend failed: ${String(err)}`),\n )\n if (toolbeltEnabled()) {\n const toolbeltLine = buildToolbeltAwareness(availableToolCommands())\n if (toolbeltLine) {\n await appendToolbeltAwarenessToMirroredClaudeMd(toolbeltLine).catch((err) =>\n consola.warn(`Toolbelt CLAUDE.md append failed: ${String(err)}`),\n )\n }\n }\n\n // gh-* skills, minus the operator/tab-specific ones (first-mate, artifact).\n let skillsWritten = 0\n for (const s of INJECTED_SKILLS) {\n if (s.name.startsWith(\"gh-first-mate\") || s.name === \"gh-artifact-review\") {\n continue\n }\n const r = await writeInjectedSkill(s.name, s.md).catch(() => ({\n written: false,\n }))\n if (r.written) skillsWritten++\n }\n\n const settingsPath = path.join(PATHS.CLAUDE_CONFIG_DIR, \"settings.json\")\n\n // Suppress AI attribution in the mirror's settings.json (no-op if the user\n // already configured it).\n await injectAttributionSuppressionIntoSettingsFile(settingsPath).catch(() => {})\n\n // Strip serve-inappropriate CLAUDE_CODE_* env keys the user may have set in\n // their real settings.json — notably CLAUDE_CODE_COORDINATOR_MODE, which\n // CloudCLI applies via settingSources and which strips the single serve chat\n // agent down to delegation-only tools (a direct Glob/Read/Bash then fails\n // \"not enabled in this context\"). UNCONDITIONAL: a working toolset is needed\n // even when GH_ROUTER_SERVE_NO_AUTO_APPROVE=1 keeps permission prompts on.\n const strip = await sanitizeServeSettingsEnv(settingsPath).catch((err) => {\n consola.warn(`Could not sanitize serve settings env: ${String(err)}`)\n return { removed: [] as string[] }\n })\n if (strip.removed.length) {\n consola.info(`Serve: removed ${strip.removed.join(\", \")} from the mirror settings (single-agent surface).`)\n }\n\n // Serve's permission model: \"seamless routine + decisions to the user\".\n // Set the mirror's default permission mode to \"default\" (NON-bypass) so\n // CloudCLI's canUseTool callback stays live — routine tools pre-approved via\n // the seeded allow-list run with no prompt, while AskUserQuestion/ExitPlanMode\n // (never allow-listed) reach the human as real widgets. Bypass would skip\n // canUseTool and auto-answer those two. Opt out with GH_ROUTER_SERVE_NO_AUTO_APPROVE=1\n // (then CloudCLI prompts on every not-yet-approved tool).\n if (process.env.GH_ROUTER_SERVE_NO_AUTO_APPROVE !== \"1\") {\n await configureServeDefaultPermissionMode(settingsPath).catch((err) =>\n consola.warn(`Could not set serve permission defaults: ${String(err)}`),\n )\n }\n\n // Auto-approve github-router's injected MCP servers PLUS the native read-only\n // research tools (WebSearch/WebFetch) via allow rules (MCP on the resolved\n // keys). Redundant under the bypass default above, but load-bearing the moment\n // a serve user switches the composer to PLAN mode: plan mode re-gates tools,\n // and this is exactly the research surface wanted while planning. Runs AFTER\n // the bypass config so the entries survive its allow-list clear. Uses the\n // authoritative injected-server list (`injected.serversAdded` — includes the\n // `codex-cli` stdio server), falling back to the resolved keys + codex-cli on\n // collision.\n const injectedServerKeys = injected.ok\n ? injected.serversAdded\n : [\n ...Object.values(groupKeys).filter((k): k is string => Boolean(k)),\n ...(opts.codexCli === true ? [\"codex-cli\"] : []),\n ]\n await injectAllowRules(settingsPath, planModeAllowRules(injectedServerKeys)).catch((err) =>\n consola.warn(`Could not auto-approve injected tools: ${String(err)}`),\n )\n\n if (workerToolsEnabled()) {\n const promptCmd = buildPromptSubmitHookCommand(opts.selfInvocation)\n await injectStopHookIntoSettingsFile(settingsPath, promptCmd, \"UserPromptSubmit\", 45).catch(\n (err) => consola.warn(`Could not register the UserPromptSubmit hook: ${String(err)}`),\n )\n\n if (!injected.ok) {\n consola.warn(\n \"Workers non-blocking guard NOT registered: subagent MCP injection fell back to parent-only, so worker-* dispatchers cannot reach the workers server. Raw worker tools remain usable on the main thread.\",\n )\n } else if (process.env.GH_ROUTER_DISABLE_WORKER_GUARD !== \"1\") {\n const workersKey = workersKeyOf(groupKeys)\n const modes = activeDispatchModes({ browse: browseAllowed && browseAgentEnabled() })\n const cmd = buildWorkerGuardHookCommand(\n opts.selfInvocation,\n workersKey,\n modes,\n )\n const matcher = guardToolMatcher(workersKey, modes)\n await injectStopHookIntoSettingsFile(settingsPath, cmd, \"PreToolUse\", 10, matcher).catch(\n (err) => consola.warn(`Could not register the workers PreToolUse guard hook: ${String(err)}`),\n )\n } else {\n consola.info(\n \"Workers non-blocking guard disabled via GH_ROUTER_DISABLE_WORKER_GUARD=1; raw worker tools remain callable on the main thread.\",\n )\n }\n }\n\n if (planReviewEnabled()) {\n try {\n const command = buildPlanReviewHookCommand(opts.selfInvocation)\n // Advisory PostToolUse(ExitPlanMode) hook: non-blocking; findings surface on the next prompt.\n // Verified (scripts/verify-plan-mode-cloudcli.mjs): ExitPlanMode runs as a real tool under\n // CloudCLI's SDK path, and settingSources=['project','user','local'] loads this mirror\n // settings.json hook, so PostToolUse(ExitPlanMode) fires on approval+execution.\n await injectStopHookIntoSettingsFile(settingsPath, command, \"PostToolUse\", undefined, \"ExitPlanMode\")\n } catch (err) {\n consola.warn(`Could not register the advisory plan-review hook: ${String(err)}`)\n }\n }\n\n const servers = injected.ok\n ? injected.serversAdded.join(\", \")\n : \"parent-only (user mcpServers collision)\"\n consola.info(\n `github-router tools wired into Claude sessions: MCP [${servers}], ${runtime.personas.length} subagents, ${skillsWritten} skills.`,\n )\n return {\n cleanup: runtime.cleanup,\n nonce: runtime.nonce,\n mcpToolNames: enumerateInjectedMcpToolNames(groupKeys, { codexCli: opts.codexCli === true }),\n }\n } catch (err) {\n consola.warn(\n `Could not wire the github-router MCP/skills layer: ${\n err instanceof Error ? err.message : String(err)\n }. Claude still works, without the extra tools.`,\n )\n return NOOP\n }\n}\n","import fs from \"node:fs/promises\"\nimport path from \"node:path\"\n\nimport type { Model, ModelsResponse } from \"~/services/copilot/get-models\"\n\nexport interface ProviderFacadeContext {\n getModels: () => ModelsResponse | undefined\n defaultModel: string\n claudeConfigDir: string\n}\n\nexport type ProviderFacadeKind = \"models\" | \"mcp\" | \"skills\" | \"commands\" | \"auth\"\n\ntype JsonObject = Record<string, unknown>\n\nfunction isObject(value: unknown): value is JsonObject {\n return value !== null && typeof value === \"object\" && !Array.isArray(value)\n}\n\nfunction envelopedData(value: unknown): JsonObject | null {\n if (!isObject(value) || !isObject(value.data)) return null\n return value.data\n}\n\nfunction cloneObject<T extends JsonObject>(value: T): JsonObject {\n return { ...value }\n}\n\n/**\n * Requests serve answers DIRECTLY (never forwarded to CloudCLI) with a canned\n * JSON body. `POST /api/system/update` is CloudCLI's \"update available\" button,\n * which runs a GLOBAL `npm install -g @cloudcli-ai/cloudcli@latest` in the user's\n * home dir (`server/index.js`) — mutating their global npm and never applying to\n * serve's pinned, router-owned install. Blocking it prevents that surprise\n * mutation and tells the user how updates actually work. HTTP 200 + `success:\n * false` so the UI neither runs the update nor throws an error toast. Returns\n * null for anything not blocked.\n */\nexport function facadeBlockedRequest(\n method: string,\n pathname: string,\n): { status: number; json: JsonObject } | null {\n if (method.toUpperCase() === \"POST\" && pathname === \"/api/system/update\") {\n return {\n status: 200,\n json: {\n success: false,\n managed: true,\n message:\n \"CloudCLI is managed by github-router (a pinned, router-owned install). \"\n + \"To update, run `npm i -g github-router@latest`, then restart `github-router serve`.\",\n },\n }\n }\n return null\n}\n\nexport function facadeInterceptKind(\n method: string,\n pathname: string,\n): ProviderFacadeKind | null {\n const normalizedMethod = method.toUpperCase()\n if (normalizedMethod === \"POST\" && pathname === \"/api/commands/list\") {\n return \"commands\"\n }\n if (normalizedMethod !== \"GET\") return null\n\n switch (pathname) {\n case \"/api/providers/claude/models\":\n return \"models\"\n case \"/api/providers/claude/mcp/servers\":\n return \"mcp\"\n case \"/api/providers/claude/skills\":\n return \"skills\"\n case \"/api/providers/claude/auth/status\":\n return \"auth\"\n default:\n return null\n }\n}\n\nexport async function rewriteProviderResponse(\n kind: ProviderFacadeKind,\n upstreamJson: unknown,\n ctx: ProviderFacadeContext,\n query: URLSearchParams,\n): Promise<unknown | null> {\n try {\n switch (kind) {\n case \"models\":\n return rewriteModels(upstreamJson, ctx)\n case \"mcp\":\n return rewriteMcp(upstreamJson, ctx, query)\n case \"skills\":\n return rewriteSkills(upstreamJson, ctx)\n case \"commands\":\n return rewriteCommands(upstreamJson, ctx)\n case \"auth\":\n return rewriteAuth(upstreamJson)\n }\n } catch {\n return null\n }\n}\n\nfunction rewriteModels(upstreamJson: unknown, ctx: ProviderFacadeContext): unknown | null {\n if (!isObject(upstreamJson)) return null\n const data = envelopedData(upstreamJson)\n if (!data || !isObject(data.models) || data.cache === undefined) return null\n\n const catalog = ctx.getModels()\n const models = catalog?.data\n if (!Array.isArray(models) || models.length === 0) return null\n\n const options = models\n .filter(isPickerModel)\n .map((m) => {\n const option: { value: string; label: string; description: string; effort?: JsonObject } = {\n value: m.id,\n label: m.name || m.id,\n description: `${m.vendor} · ${m.capabilities.family}`,\n }\n // Emit the effort selector the CloudCLI UI + `resolveClaudeEffort` need:\n // `effort.values` is `[{value}]` and the server maps it back with\n // `.map(v => v.value)` to validate a client-picked effort before passing\n // it to the binary. Derived from the Copilot catalog's per-model\n // `reasoning_effort` allowlist; omitted for non-reasoning models (no\n // selector shown). Default to `high` when supported (matches the shim's\n // default), else the model's top tier.\n const efforts = m.capabilities.supports?.reasoning_effort\n if (Array.isArray(efforts) && efforts.length > 0) {\n option.effort = {\n default: efforts.includes(\"high\") ? \"high\" : efforts[efforts.length - 1],\n values: efforts.map((e) => ({ value: e })),\n }\n }\n return option\n })\n if (options.length === 0) return null\n\n const values = new Set(options.map((o) => o.value))\n const upstreamDefault = typeof data.models.DEFAULT === \"string\" ? data.models.DEFAULT : undefined\n const DEFAULT = values.has(ctx.defaultModel)\n ? ctx.defaultModel\n : (options[0]?.value ?? upstreamDefault)\n\n const nextData = cloneObject(data)\n nextData.models = { OPTIONS: options, DEFAULT }\n return { ...upstreamJson, data: nextData }\n}\n\nfunction isPickerModel(model: unknown): model is Model {\n return isObject(model)\n && typeof model.id === \"string\"\n && model.model_picker_enabled !== false\n && typeof model.vendor === \"string\"\n && (model.name === undefined || typeof model.name === \"string\")\n && isObject(model.capabilities)\n && typeof model.capabilities.family === \"string\"\n}\n\nasync function rewriteMcp(\n upstreamJson: unknown,\n ctx: ProviderFacadeContext,\n query: URLSearchParams,\n): Promise<unknown | null> {\n if (!isObject(upstreamJson) || query.get(\"scope\") !== \"user\") return null\n const data = envelopedData(upstreamJson)\n if (!data || !Array.isArray(data.servers)) return null\n\n const mirror = await readJson(path.join(ctx.claudeConfigDir, \".claude.json\"))\n if (!isObject(mirror) || !isObject(mirror.mcpServers)) return null\n\n const existing = new Set(\n data.servers\n .filter(isObject)\n .map((s) => s.name)\n .filter((name): name is string => typeof name === \"string\"),\n )\n const additions: JsonObject[] = []\n for (const [name, entry] of Object.entries(mirror.mcpServers)) {\n if (existing.has(name) || !isObject(entry)) continue\n const transport = entry.type === \"http\" ? \"http\" : \"stdio\"\n const server: JsonObject = {\n provider: \"claude\",\n name,\n scope: \"user\",\n transport,\n }\n if (typeof entry.url === \"string\") server.url = entry.url\n additions.push(server)\n existing.add(name)\n }\n if (additions.length === 0) return null\n\n const nextData = cloneObject(data)\n nextData.servers = [...data.servers, ...additions]\n return { ...upstreamJson, data: nextData }\n}\n\nasync function rewriteSkills(\n upstreamJson: unknown,\n ctx: ProviderFacadeContext,\n): Promise<unknown | null> {\n if (!isObject(upstreamJson)) return null\n const data = envelopedData(upstreamJson)\n if (!data || !Array.isArray(data.skills)) return null\n\n const skills = await readSkillDirs(path.join(ctx.claudeConfigDir, \"skills\"))\n if (skills.length === 0) return null\n\n const existing = new Set(\n data.skills\n .filter(isObject)\n .map((s) => s.command)\n .filter((command): command is string => typeof command === \"string\"),\n )\n const additions = skills\n .filter((skill) => !existing.has(skill.name))\n .map((skill) => ({\n provider: \"claude\",\n name: skill.name,\n description: skill.description,\n command: skill.name,\n scope: \"user\",\n // sourcePath intentionally omitted — the UI dispatches on `command`/`name`,\n // and an absolute mirror path would leak the home dir / username to the browser.\n }))\n if (additions.length === 0) return null\n\n const nextData = cloneObject(data)\n nextData.skills = [...data.skills, ...additions]\n return { ...upstreamJson, data: nextData }\n}\n\nasync function rewriteCommands(\n upstreamJson: unknown,\n ctx: ProviderFacadeContext,\n): Promise<unknown | null> {\n if (!isObject(upstreamJson) || !Array.isArray(upstreamJson.custom)) return null\n\n const dirs = [\n path.join(ctx.claudeConfigDir, \"commands\"),\n path.join(ctx.claudeConfigDir, \".claude\", \"commands\"),\n ]\n const commandFiles = (await Promise.all(dirs.map(readCommandFiles))).flat()\n if (commandFiles.length === 0) return null\n\n const existing = new Set(\n upstreamJson.custom\n .filter(isObject)\n .map((c) => c.name)\n .filter((name): name is string => typeof name === \"string\"),\n )\n const additions = commandFiles\n .filter((command) => !existing.has(command.name))\n .map((command) => ({\n name: command.name,\n // Relative path only — an absolute mirror path would leak the home dir /\n // username to the browser. The UI dispatches on `name`.\n path: command.relativePath,\n relativePath: command.relativePath,\n description: command.description,\n namespace: \"user\",\n metadata: { type: \"custom\" },\n }))\n if (additions.length === 0) return null\n\n const custom = [...upstreamJson.custom, ...additions]\n return {\n ...upstreamJson,\n custom,\n count: countCommands(upstreamJson.builtIn) + custom.length,\n }\n}\n\nfunction rewriteAuth(upstreamJson: unknown): unknown | null {\n if (!isObject(upstreamJson)) return null\n const data = envelopedData(upstreamJson)\n if (!data) return null\n\n return {\n ...upstreamJson,\n data: {\n ...data,\n authenticated: true,\n installed: true,\n },\n }\n}\n\nasync function readJson(filePath: string): Promise<unknown | null> {\n try {\n return JSON.parse(await fs.readFile(filePath, \"utf8\"))\n } catch {\n return null\n }\n}\n\nasync function readSkillDirs(skillsDir: string): Promise<Array<{ name: string; description: string; sourcePath: string }>> {\n try {\n const entries = await fs.readdir(skillsDir, { withFileTypes: true })\n const skills = await Promise.all(\n entries\n .filter((entry) => entry.isDirectory() && entry.name.startsWith(\"gh-\"))\n .map(async (entry) => {\n const sourcePath = path.join(skillsDir, entry.name, \"SKILL.md\")\n let description = \"\"\n try {\n const md = await fs.readFile(sourcePath, \"utf8\")\n description = parseFrontmatterField(md, \"description\") ?? \"\"\n } catch {\n // Keep the skill visible even if only the directory is readable.\n }\n const name = entry.name\n return { name, description, sourcePath }\n }),\n )\n return skills\n } catch {\n return []\n }\n}\n\nasync function readCommandFiles(commandsDir: string): Promise<Array<{ name: string; relativePath: string; filePath: string; description: string }>> {\n try {\n const stat = await fs.stat(commandsDir)\n if (!stat.isDirectory()) return []\n } catch {\n return []\n }\n return readCommandFilesRecursive(commandsDir, commandsDir)\n}\n\nasync function readCommandFilesRecursive(\n root: string,\n dir: string,\n): Promise<Array<{ name: string; relativePath: string; filePath: string; description: string }>> {\n let entries\n try {\n entries = await fs.readdir(dir, { withFileTypes: true })\n } catch {\n return []\n }\n\n const out: Array<{ name: string; relativePath: string; filePath: string; description: string }> = []\n for (const entry of entries) {\n const filePath = path.join(dir, entry.name)\n if (entry.isDirectory()) {\n out.push(...await readCommandFilesRecursive(root, filePath))\n continue\n }\n if (!entry.isFile() || !entry.name.endsWith(\".md\")) continue\n const relativePath = normalizeRelative(path.relative(root, filePath))\n const name = relativePath.replace(/\\.md$/i, \"\")\n let description = \"\"\n try {\n const md = await fs.readFile(filePath, \"utf8\")\n description = parseFrontmatterField(md, \"description\") ?? firstMarkdownLine(md)\n } catch {\n // A listed file that disappears mid-read should not break the UI.\n }\n out.push({ name, relativePath, filePath, description })\n }\n return out\n}\n\nfunction parseFrontmatterField(markdown: string, field: string): string | null {\n if (!markdown.startsWith(\"---\")) return null\n const end = markdown.indexOf(\"\\n---\", 3)\n if (end < 0) return null\n const frontmatter = markdown.slice(3, end).split(/\\r?\\n/)\n for (const line of frontmatter) {\n const match = line.match(/^([A-Za-z0-9_-]+):\\s*(.*)$/)\n if (!match || match[1] !== field) continue\n return match[2].replace(/^['\"]|['\"]$/g, \"\").trim()\n }\n return null\n}\n\nfunction firstMarkdownLine(markdown: string): string {\n const body = markdown.startsWith(\"---\")\n ? markdown.slice(markdown.indexOf(\"\\n---\", 3) + 4)\n : markdown\n for (const line of body.split(/\\r?\\n/)) {\n const trimmed = line.trim()\n if (trimmed) return trimmed.replace(/^#+\\s*/, \"\")\n }\n return \"\"\n}\n\nfunction normalizeRelative(value: string): string {\n return value.split(path.sep).join(\"/\")\n}\n\nfunction countCommands(builtIn: unknown): number {\n return Array.isArray(builtIn) ? builtIn.length : 0\n}\n","import http from \"node:http\"\nimport type { AddressInfo, Socket } from \"node:net\"\n\nimport { getPackageVersion } from \"../version\"\n\n/**\n * Loopback-only identity endpoint served directly by the reverse proxy (never\n * forwarded to CloudCLI). A second `github-router serve` probes this on the\n * intended port to detect an already-running instance (single-instance guard),\n * so it can attach/refuse instead of starting a duplicate CloudCLI. Carries no\n * secret — just a service marker + version + origin.\n */\nexport const SERVE_IDENTITY_PATH = \"/__github-router-serve__\"\nexport const SERVE_IDENTITY_SERVICE = \"github-router-serve\"\n\n/**\n * Standalone HTTP + WebSocket reverse proxy that fronts a locally-running\n * CloudCLI instance under github-router's own origin. It is deliberately NOT\n * routed through the srvx/Hono app — a separate lightweight proxy avoids\n * entangling CloudCLI's raw WebSocket upgrades (`/ws` chat, `/shell` node-pty\n * terminal) with the model-API server.\n *\n * Security responsibilities CloudCLI's OSS server does not handle itself:\n * 1. Injects `localStorage['auth-token']` into the served `index.html` so the\n * SPA boots already-authenticated (true zero-login).\n * 2. Rejects foreign `Host` (DNS-rebinding defense — an `Origin`-only check\n * still lets a rebound `attacker.com → 127.0.0.1` navigation fetch the\n * token-injected HTML) AND foreign `Origin` on requests + WS upgrades\n * (CloudCLI performs NO Origin check on `/ws` or `/shell`).\n */\nexport interface ReverseProxyOptions {\n targetHost: string\n targetPort: number\n bindHost: string\n bindPort: number\n /** JWT injected into the SPA so it boots authenticated. */\n authToken: string\n /**\n * Seed CloudCLI's `claude-settings` localStorage with `skipPermissions:false`\n * and an `allowedTools` allow-list (routine built-ins + injected `mcp__*`) so\n * routine tools auto-run with no prompt while `AskUserQuestion`/`ExitPlanMode`\n * still reach the user (serve default; disabled by GH_ROUTER_SERVE_NO_AUTO_APPROVE=1).\n */\n seedToolSettings?: boolean\n /** Exact tool names to seed into the `claude-settings` `allowedTools`\n * auto-approve list (only used when `seedToolSettings`) — the routine\n * built-ins plus every exact `mcp__<key>__<tool>` name. MUST NOT include\n * `AskUserQuestion`/`ExitPlanMode`, or they would auto-resolve instead of\n * reaching the user. */\n seedAllowedTools?: string[]\n providerFacade?: {\n kindFor: (method: string, pathname: string) => string | null\n rewrite: (\n kind: string,\n upstreamJson: unknown,\n query: URLSearchParams,\n ) => Promise<unknown | null>\n /**\n * Requests answered DIRECTLY with a canned JSON body, never forwarded to\n * CloudCLI (e.g. `POST /api/system/update`, which would run a global npm\n * install). Returns null to forward normally.\n */\n blockedRequest?: (\n method: string,\n pathname: string,\n ) => { status: number; json: unknown } | null\n }\n /**\n * Extra exact `host:port` values to accept beyond loopback (e.g. a specific\n * dev-tunnel host `abc-5454.usw2.devtunnels.ms`). Used for remote access.\n */\n extraAllowedHosts?: string[]\n /** Extra exact origins to accept (e.g. `https://abc-5454.usw2.devtunnels.ms`). */\n extraAllowedOrigins?: string[]\n /**\n * Accept ANY `*.devtunnels.ms` host + `https://*.devtunnels.ms` origin. A\n * convenience for Microsoft dev tunnels when the exact URL isn't pinned. Only\n * the user's own authenticated tunnel actually forwards to this loopback port,\n * so this does not weaken the same-user loopback trust boundary.\n */\n allowDevtunnelHosts?: boolean\n}\n\nexport interface ReverseProxyHandle {\n url: string\n port: number\n close: () => Promise<void>\n}\n\nconst HEAD_CLOSE = \"</head>\"\n\n/** CloudCLI's localStorage key for per-tool chat permission settings. */\nconst CLAUDE_SETTINGS_KEY = \"claude-settings\"\n\n/**\n * The two interaction tools CloudCLI routes to the human (its\n * `TOOLS_REQUIRING_INTERACTION`). They must NEVER appear in the seeded\n * `allowedTools` — an exact-name match there would auto-approve them and defeat\n * the question widget / plan card. Stripped defensively at the seed chokepoint\n * so no upstream list (mcp names, a future caller) can reintroduce them.\n */\nconst NEVER_AUTO_APPROVE = new Set([\"AskUserQuestion\", \"ExitPlanMode\"])\n\n/**\n * Embed the token as a JS string literal. JSON.stringify escapes quotes and\n * backslashes; we additionally neutralize `<` so the value can never break out\n * of the surrounding <script> element regardless of contents.\n *\n * When `seedToolSettings` is set, also seed CloudCLI's `claude-settings`\n * localStorage with `{allowedTools:<routine+mcp>, disallowedTools:[], skipPermissions:false}`.\n * This is serve's \"seamless routine + decisions to the user\" model: `canUseTool`\n * stays LIVE (skipPermissions:false, NOT bypass), routine tools in `allowedTools`\n * auto-run with no prompt, and `AskUserQuestion`/`ExitPlanMode` — deliberately\n * NOT in the list and hard-cased by CloudCLI into `TOOLS_REQUIRING_INTERACTION` —\n * always reach the human as a real question widget / plan Approve-Reject card.\n * `skipPermissions:true` (bypass) would skip `canUseTool` entirely and silently\n * auto-answer those two, which is exactly what we are fixing.\n */\nfunction buildInjection(\n token: string,\n seedToolSettings = false,\n seedAllowedTools: string[] = [],\n): string {\n const safe = JSON.stringify(token).replace(/</g, \"\\\\u003c\")\n let body = `localStorage.setItem('auth-token',${safe});`\n if (seedToolSettings) {\n const settings = JSON.stringify({\n // CloudCLI's per-tool AUTO-APPROVE list — feeds the Agent-SDK `canUseTool`\n // fast-path (EXACT-name match, no `mcp__<server>` wildcard) AND\n // `sdkOptions.allowedTools`. We seed the routine built-ins + every exact\n // `mcp__<key>__<tool>` name so ordinary work never prompts. The two\n // interaction tools are filtered out unconditionally (NEVER_AUTO_APPROVE)\n // so they always reach the user, no matter what the caller passed.\n allowedTools: seedAllowedTools.filter((t) => !NEVER_AUTO_APPROVE.has(t)),\n disallowedTools: [],\n // NON-bypass: keep canUseTool live so AskUserQuestion/ExitPlanMode reach\n // the user. Bypass would auto-resolve them on a generated answer.\n skipPermissions: false,\n })\n const safeSettings = JSON.stringify(settings).replace(/</g, \"\\\\u003c\")\n body += `localStorage.setItem('${CLAUDE_SETTINGS_KEY}',${safeSettings});`\n }\n return `<script>try{${body}}catch(e){}</script>`\n}\n\nfunction headerLines(headers: http.IncomingHttpHeaders): string {\n return Object.entries(headers)\n .map(([k, v]) => `${k}: ${Array.isArray(v) ? v.join(\", \") : v}`)\n .join(\"\\r\\n\")\n}\n\nexport async function startReverseProxy(\n opts: ReverseProxyOptions,\n): Promise<ReverseProxyHandle> {\n const { targetHost, targetPort, bindHost, bindPort, authToken } = opts\n const ownOrigin = `http://${bindHost}:${bindPort}`\n const injection = buildInjection(authToken, opts.seedToolSettings === true, opts.seedAllowedTools ?? [])\n\n const allowedHosts = new Set([\n `127.0.0.1:${bindPort}`,\n `localhost:${bindPort}`,\n `[::1]:${bindPort}`,\n ...(opts.extraAllowedHosts ?? []).map((h) => h.toLowerCase()),\n ])\n // Loopback-only subset — the single-instance identity probe always comes from\n // 127.0.0.1, so the identity endpoint is answered only for a loopback Host\n // (never a dev-tunnel host), keeping the version off the remote surface.\n const loopbackHosts = new Set([\n `127.0.0.1:${bindPort}`,\n `localhost:${bindPort}`,\n `[::1]:${bindPort}`,\n ])\n const allowedOrigins = new Set([\n `http://127.0.0.1:${bindPort}`,\n `http://localhost:${bindPort}`,\n `http://[::1]:${bindPort}`,\n ...(opts.extraAllowedOrigins ?? []).map((o) => o.toLowerCase()),\n ])\n // `(^|\\.)devtunnels.ms$` matches `x.devtunnels.ms` but not `evildevtunnels.ms`\n // / `evil-devtunnels.ms` (the char before must be start-of-string or a dot).\n const DEVTUNNEL_RE = /(^|\\.)devtunnels\\.ms$/i\n const isDevtunnelHost = (host: string): boolean =>\n !!opts.allowDevtunnelHosts && DEVTUNNEL_RE.test(host.replace(/:\\d+$/, \"\"))\n const isDevtunnelOrigin = (origin: string): boolean => {\n if (!opts.allowDevtunnelHosts) return false\n try {\n const u = new URL(origin)\n return u.protocol === \"https:\" && DEVTUNNEL_RE.test(u.hostname)\n } catch {\n return false\n }\n }\n // Host defends against DNS-rebinding; Origin defends against cross-origin\n // browser access. A missing Origin (plain navigation / non-browser) is only\n // allowed once the Host has already been confirmed allowed.\n const hostAllowed = (host?: string): boolean =>\n !!host\n && (allowedHosts.has(host.toLowerCase()) || isDevtunnelHost(host.toLowerCase()))\n const originAllowed = (origin?: string): boolean =>\n !origin\n || allowedOrigins.has(origin.toLowerCase())\n || isDevtunnelOrigin(origin)\n\n // Track hijacked upgrade sockets so close() can destroy them — http.Server\n // .close() otherwise waits for them forever (shutdown hang).\n const liveSockets = new Set<Socket>()\n\n const server = http.createServer((clientReq, clientRes) => {\n clientReq.on(\"error\", () => clientRes.destroy())\n clientRes.on(\"error\", () => clientReq.destroy())\n\n if (!hostAllowed(clientReq.headers.host) || !originAllowed(clientReq.headers.origin)) {\n clientRes.writeHead(403, { \"content-type\": \"text/plain\" })\n clientRes.end(\"Forbidden\")\n return\n }\n\n // Single-instance identity probe — answered directly, never proxied to\n // CloudCLI. Loopback Host only (the probe is always local); carries no\n // secret and no attacker-usable URL.\n if ((clientReq.url ?? \"\").split(\"?\")[0] === SERVE_IDENTITY_PATH) {\n if (!loopbackHosts.has(String(clientReq.headers.host ?? \"\").toLowerCase())) {\n clientRes.writeHead(404, { \"content-type\": \"text/plain\" })\n clientRes.end(\"Not found\")\n return\n }\n clientRes.writeHead(200, { \"content-type\": \"application/json\" })\n clientRes.end(\n JSON.stringify({\n service: SERVE_IDENTITY_SERVICE,\n version: getPackageVersion(),\n }),\n )\n return\n }\n\n const requestUrl = new URL(clientReq.url ?? \"/\", ownOrigin)\n\n // Requests serve answers directly (never forwarded) — e.g. CloudCLI's\n // system-update button, which would run a global `npm i -g` mutation.\n const blocked = opts.providerFacade?.blockedRequest?.(\n clientReq.method ?? \"GET\",\n requestUrl.pathname,\n )\n if (blocked) {\n clientReq.resume() // drain the request body so the socket can be reused\n clientRes.writeHead(blocked.status, { \"content-type\": \"application/json\" })\n clientRes.end(JSON.stringify(blocked.json))\n return\n }\n\n const facadeKind = opts.providerFacade?.kindFor(\n clientReq.method ?? \"GET\",\n requestUrl.pathname,\n ) ?? null\n\n // Drop accept-encoding so HTML comes back uncompressed and stays\n // injectable; façade JSON rewrites also need original bytes, not gzip/br.\n const headers = { ...clientReq.headers }\n delete headers[\"accept-encoding\"]\n\n const proxyReq = http.request(\n {\n host: targetHost,\n port: targetPort,\n path: clientReq.url,\n method: clientReq.method,\n headers,\n },\n (proxyRes) => {\n proxyRes.on(\"error\", () => clientRes.destroy())\n const contentType = String(proxyRes.headers[\"content-type\"] ?? \"\")\n if (facadeKind) {\n const chunks: Buffer[] = []\n proxyRes.on(\"data\", (d) => chunks.push(Buffer.from(d)))\n proxyRes.on(\"end\", () => {\n const original = Buffer.concat(chunks)\n const sendOriginal = () => {\n // We buffered the whole body, so send it as a fixed buffer and let\n // Node re-frame — any upstream chunked/encoded framing is now wrong.\n const outHeaders = { ...proxyRes.headers }\n delete outHeaders[\"content-length\"]\n delete outHeaders[\"content-encoding\"]\n delete outHeaders[\"transfer-encoding\"]\n clientRes.writeHead(proxyRes.statusCode ?? 502, outHeaders)\n clientRes.end(original)\n }\n if (proxyRes.statusCode !== 200) {\n sendOriginal()\n return\n }\n void (async () => {\n try {\n const parsed = JSON.parse(original.toString(\"utf8\"))\n const rewritten = await opts.providerFacade?.rewrite(\n facadeKind,\n parsed,\n requestUrl.searchParams,\n )\n if (rewritten == null) {\n sendOriginal()\n return\n }\n const outHeaders = { ...proxyRes.headers }\n // We send a fresh JSON body, so any upstream length/encoding\n // framing is now wrong — drop it and let Node re-frame.\n delete outHeaders[\"content-length\"]\n delete outHeaders[\"content-encoding\"]\n delete outHeaders[\"transfer-encoding\"]\n outHeaders[\"content-type\"] = \"application/json\"\n clientRes.writeHead(200, outHeaders)\n clientRes.end(JSON.stringify(rewritten))\n } catch {\n sendOriginal()\n }\n })()\n })\n } else if (contentType.includes(\"text/html\")) {\n const chunks: Buffer[] = []\n proxyRes.on(\"data\", (d) => chunks.push(Buffer.from(d)))\n proxyRes.on(\"end\", () => {\n let html = Buffer.concat(chunks).toString(\"utf8\")\n html = html.includes(HEAD_CLOSE)\n ? html.replace(HEAD_CLOSE, injection + HEAD_CLOSE)\n : injection + html\n const outHeaders = { ...proxyRes.headers }\n // We send a fresh fixed buffer, so any length/encoding framing the\n // upstream set is now wrong — drop it and let Node re-frame.\n delete outHeaders[\"content-length\"]\n delete outHeaders[\"content-encoding\"]\n delete outHeaders[\"transfer-encoding\"]\n clientRes.writeHead(proxyRes.statusCode ?? 502, outHeaders)\n clientRes.end(html)\n })\n } else {\n clientRes.writeHead(proxyRes.statusCode ?? 502, proxyRes.headers)\n proxyRes.pipe(clientRes)\n }\n },\n )\n proxyReq.on(\"error\", (err) => {\n if (!clientRes.headersSent)\n clientRes.writeHead(502, { \"content-type\": \"text/plain\" })\n clientRes.end(`Bad gateway: ${err.message}`)\n })\n clientReq.pipe(proxyReq)\n })\n\n server.on(\"upgrade\", (clientReq, clientSock: Socket, clientHead) => {\n // Attach error handling + track the socket IMMEDIATELY, before any async\n // window (a client reset during the upstream handshake must not crash us).\n clientSock.on(\"error\", () => clientSock.destroy())\n liveSockets.add(clientSock)\n clientSock.once(\"close\", () => liveSockets.delete(clientSock))\n\n if (!hostAllowed(clientReq.headers.host) || !originAllowed(clientReq.headers.origin)) {\n clientSock.destroy()\n return\n }\n\n const proxyReq = http.request({\n host: targetHost,\n port: targetPort,\n path: clientReq.url,\n method: clientReq.method,\n headers: clientReq.headers,\n })\n proxyReq.on(\"upgrade\", (proxyRes, proxySock, proxyHead) => {\n clientSock.write(\n `HTTP/1.1 ${proxyRes.statusCode} ${proxyRes.statusMessage}\\r\\n${headerLines(\n proxyRes.headers,\n )}\\r\\n\\r\\n`,\n )\n if (clientHead && clientHead.length) proxySock.write(clientHead)\n if (proxyHead && proxyHead.length) proxySock.unshift(proxyHead)\n proxySock.pipe(clientSock)\n clientSock.pipe(proxySock)\n const cleanup = () => {\n proxySock.destroy()\n clientSock.destroy()\n }\n // Tear down on BOTH error and normal close, on either side (else the\n // paired socket leaks on every page refresh / terminal close).\n clientSock.on(\"error\", cleanup)\n proxySock.on(\"error\", cleanup)\n clientSock.on(\"close\", cleanup)\n proxySock.on(\"close\", cleanup)\n })\n // CloudCLI answered the upgrade with an ordinary response (e.g. 401/404 for\n // a missing/invalid JWT). Relay it and close so the browser WS doesn't hang.\n proxyReq.on(\"response\", (proxyRes) => {\n try {\n clientSock.write(\n `HTTP/1.1 ${proxyRes.statusCode} ${proxyRes.statusMessage}\\r\\n${headerLines(\n proxyRes.headers,\n )}\\r\\n\\r\\n`,\n )\n } catch {\n /* socket already gone */\n }\n proxyRes.on(\"data\", (d) => {\n clientSock.write(d as Buffer)\n })\n proxyRes.on(\"end\", () => clientSock.end())\n proxyRes.on(\"error\", () => clientSock.destroy())\n })\n proxyReq.on(\"error\", () => clientSock.destroy())\n proxyReq.end()\n })\n\n await new Promise<void>((resolve, reject) => {\n const onError = (err: Error) => reject(err)\n server.once(\"error\", onError)\n server.listen(bindPort, bindHost, () => {\n server.removeListener(\"error\", onError)\n resolve()\n })\n })\n\n // Defense-in-depth: refuse to run if we somehow bound a non-loopback address.\n const addr = server.address() as AddressInfo | null\n if (addr && addr.address !== \"127.0.0.1\" && addr.address !== \"::1\") {\n await new Promise<void>((r) => server.close(() => r()))\n throw new Error(\n `reverse proxy refused non-loopback bind (got ${addr.address})`,\n )\n }\n\n return {\n url: ownOrigin,\n port: bindPort,\n close: () =>\n new Promise<void>((resolve) => {\n for (const s of liveSockets) s.destroy()\n liveSockets.clear()\n const t = setTimeout(resolve, 1500) // never let shutdown hang\n server.close(() => {\n clearTimeout(t)\n resolve()\n })\n }),\n }\n}\n\n/** Exported for unit tests. */\nexport const __test = { buildInjection, HEAD_CLOSE }\n","import { spawn } from \"node:child_process\"\nimport http from \"node:http\"\nimport type { AddressInfo } from \"node:net\"\n\nimport { defineCommand } from \"citty\"\nimport consola from \"consola\"\n\nimport { provisionBrowserAssets } from \"./lib/browser-mcp/provision\"\nimport { provisionAndIndexColbert } from \"./lib/colbert\"\nimport { resolveCodexCliBackend } from \"./lib/codex-mcp-config\"\nimport { killChildProcessTree } from \"./lib/exec\"\nimport { startKeepAwake, stopKeepAwake } from \"./lib/keep-awake\"\nimport {\n hookLauncherDegradedWarning,\n resolveSelfInvocation,\n} from \"./lib/hook-launcher/self-invocation\"\nimport { getCodexVersion } from \"./lib/launch\"\nimport { browserToolsEnabled } from \"./lib/mcp-capabilities\"\nimport { SEAMLESS_BUILTIN_TOOLS } from \"./lib/mcp-permissions-settings\"\nimport { ensureClaudeConfigMirror, PATHS, removeOwnClaudeConfigMirror } from \"./lib/paths\"\nimport {\n DEFAULT_CLAUDE_MODEL_FALLBACKS,\n pickClaudeDefault,\n resolveLeadSlugArg,\n} from \"./lib/port\"\nimport {\n getClaudeCodeEnvVars,\n parseSharedArgs,\n setupAndServe,\n sharedServerArgs,\n} from \"./lib/server-setup\"\nimport {\n composeCloudCliChildEnv,\n mintJwt,\n resolveCloudCli,\n spawnCloudCli,\n waitForCloudCliReady,\n} from \"./lib/serve/cloudcli\"\nimport { DevtunnelError, startDevtunnel } from \"./lib/serve/devtunnel\"\nimport { provisionServeEnhancements } from \"./lib/serve/enhancements\"\nimport { facadeBlockedRequest, facadeInterceptKind, rewriteProviderResponse } from \"./lib/serve/provider-facade\"\nimport {\n SERVE_IDENTITY_PATH,\n SERVE_IDENTITY_SERVICE,\n startReverseProxy,\n} from \"./lib/serve/reverse-proxy\"\nimport { runSelfUpdate } from \"./lib/self-update\"\nimport { state } from \"./lib/state\"\nimport { provisionToolbelt } from \"./lib/toolbelt/provision\"\nimport { resolveModel } from \"./lib/utils\"\nimport { getGitHubUser } from \"./services/github/get-user\"\n\nasync function getFreePort(): Promise<number> {\n return new Promise((resolve, reject) => {\n const s = http.createServer()\n s.once(\"error\", reject)\n s.listen(0, \"127.0.0.1\", () => {\n const port = (s.address() as AddressInfo).port\n s.close(() => resolve(port))\n })\n })\n}\n\n/** Default user-facing control-plane port when `--port` is not given. */\nconst DEFAULT_SERVE_PORT = 5454\n\nfunction isPortFree(port: number): Promise<boolean> {\n return new Promise((resolve) => {\n const s = http.createServer()\n s.once(\"error\", () => resolve(false))\n s.listen(port, \"127.0.0.1\", () => s.close(() => resolve(true)))\n })\n}\n\n/**\n * Probe a loopback port for an already-running `github-router serve` (its\n * identity endpoint). Returns true when the port hosts OUR serve, else false\n * (free, or a foreign process). `serve` is designed to run ONCE per machine\n * (a single machine-wide control plane for all repos), so a second launch\n * attaches to the running one instead of spawning a duplicate CloudCLI +\n * contending on the shared auth DB. We intentionally do NOT trust any URL the\n * probed service reports — a local squatter could forge the identity marker, so\n * the caller only ever opens a locally-constructed loopback origin.\n */\nfunction probeExistingServe(port: number): Promise<boolean> {\n return new Promise((resolve) => {\n const req = http.get(\n {\n host: \"127.0.0.1\",\n port,\n path: SERVE_IDENTITY_PATH,\n headers: { host: `127.0.0.1:${port}` },\n timeout: 1500,\n },\n (res) => {\n if (res.statusCode !== 200) {\n res.resume()\n resolve(false)\n return\n }\n const chunks: Buffer[] = []\n res.on(\"data\", (d) => chunks.push(Buffer.from(d)))\n res.on(\"end\", () => {\n try {\n const body = JSON.parse(Buffer.concat(chunks).toString(\"utf8\")) as {\n service?: string\n }\n resolve(body.service === SERVE_IDENTITY_SERVICE)\n } catch {\n resolve(false)\n }\n })\n res.on(\"error\", () => resolve(false))\n },\n )\n req.on(\"timeout\", () => req.destroy())\n req.on(\"error\", () => resolve(false))\n })\n}\n\n/**\n * Resolve the user-facing port: honor an explicit `--port`; otherwise use 5454\n * when free, else fall back to a random free port. (Single-instance detection —\n * an already-running github-router serve on the intended port — is handled by\n * the earlier `probeExistingServe` attach/refuse in `run`.)\n */\nasync function resolveServePort(requested?: number): Promise<number> {\n if (requested != null) return requested\n if (await isPortFree(DEFAULT_SERVE_PORT)) return DEFAULT_SERVE_PORT\n const fallback = await getFreePort()\n consola.info(`Port ${DEFAULT_SERVE_PORT} is in use; using ${fallback} instead.`)\n return fallback\n}\n\nfunction openBrowser(url: string): void {\n try {\n if (process.platform === \"win32\") {\n spawn(\"cmd\", [\"/c\", \"start\", \"\", url], {\n detached: true,\n stdio: \"ignore\",\n windowsHide: true,\n }).unref()\n } else if (process.platform === \"darwin\") {\n spawn(\"open\", [url], { detached: true, stdio: \"ignore\" }).unref()\n } else {\n spawn(\"xdg-open\", [url], { detached: true, stdio: \"ignore\" }).unref()\n }\n } catch {\n // best-effort; the URL is printed too\n }\n}\n\nexport const serve = defineCommand({\n meta: {\n name: \"serve\",\n description:\n \"Serve a browser control plane (CloudCLI) with Claude routed through the proxy\",\n },\n args: {\n ...sharedServerArgs,\n model: {\n alias: \"m\",\n type: \"string\",\n description: \"Override the default Claude model\",\n },\n \"cloudcli-path\": {\n type: \"string\",\n description: \"Use an existing CloudCLI install instead of the managed one\",\n },\n \"cloudcli-version\": {\n type: \"string\",\n description: \"Override the pinned CloudCLI version to install\",\n },\n \"codex-cli\": {\n type: \"boolean\",\n default: false,\n description: \"Route Codex personas through a local `codex mcp-server` (requires codex 0.129+; falls back to HTTP if missing)\",\n },\n \"browse-over-tunnel\": {\n type: \"boolean\",\n default: false,\n description: \"Expose the server-side browser MCP even when tunnel-exposed (session-hijack / SSRF risk — only with fully-trusted tunnel access)\",\n },\n \"agents-over-tunnel\": {\n type: \"boolean\",\n default: false,\n description: \"Expose first-mate (mints a repo+workflow GitHub write token) even when tunnel-exposed (only with fully-trusted tunnel access)\",\n },\n \"fleet-over-tunnel\": {\n type: \"boolean\",\n default: false,\n description: \"Expose the fleet MCP (drives remote coding sessions with your fleet credentials) even when tunnel-exposed (only with fully-trusted tunnel access)\",\n },\n \"no-install\": {\n type: \"boolean\",\n default: false,\n description: \"Do not auto-install CloudCLI; require an existing install\",\n },\n \"no-open\": {\n type: \"boolean\",\n default: false,\n description: \"Do not open the browser automatically\",\n },\n \"public-url\": {\n type: \"string\",\n description:\n \"Public URL(s) this is reachable at (e.g. a dev-tunnel https URL), comma-separated — allowlists their host+origin for remote access\",\n },\n tunnel: {\n type: \"boolean\",\n default: false,\n description:\n \"Auto-create + host an AUTHENTICATED (never anonymous) Microsoft dev tunnel for the serve port and print its public URL, so you can reach the control plane from anywhere. Requires the `devtunnel` CLI + `devtunnel user login`.\",\n },\n },\n async run({ args }) {\n state.serveMode = true\n\n if (process.versions.bun) {\n consola.warn(\n \"`serve` should run under Node.js. Bun's node:http upgrade sockets cannot relay the CloudCLI terminal/chat WebSockets; run the installed `github-router` binary (Node).\",\n )\n }\n\n const parsed = parseSharedArgs(args as unknown as Record<string, unknown>)\n\n // Single-instance guard: `serve` is a machine-wide control plane meant to run\n // ONCE per machine (work on any repo through its file explorer / sessions). If\n // one is already running on the intended port, attach to it (open + exit)\n // instead of spawning a second CloudCLI that would contend on the shared auth\n // DB. Only triggers for OUR serve; a foreign process on the port falls through\n // to the random-port fallback in resolveServePort.\n const intendedPort = parsed.port ?? DEFAULT_SERVE_PORT\n if (await probeExistingServe(intendedPort)) {\n // Construct the URL locally — never trust a URL the probed service reports\n // (a local squatter could forge the identity marker into an open-browser).\n const existingUrl = `http://127.0.0.1:${intendedPort}`\n consola.box(\n `github-router control plane already running\\n\\n${existingUrl}\\n\\nOpening it — stop that instance first if you want a fresh one.`,\n )\n if (args[\"no-open\"] !== true) openBrowser(existingUrl)\n return\n }\n\n // 1. github-router proxy — internal (random port). The USER-facing port is\n // the reverse proxy below, so force this one random regardless of --port.\n const { server, serverUrl } = await setupAndServe({\n ...parsed,\n port: undefined,\n silent: true,\n })\n\n // 2. provision the router-owned CLAUDE_CONFIG_DIR (synthetic creds +\n // onboarding-skip) so CloudCLI's SDK-spawned claude authenticates\n // through the proxy. Hard-fail on error (mirrors src/claude.ts).\n try {\n await ensureClaudeConfigMirror()\n } catch (err) {\n consola.error(\n `Failed to provision CLAUDE_CONFIG_DIR mirror: ${\n err instanceof Error ? err.message : String(err)\n }`,\n )\n process.exit(1)\n }\n\n // Best-effort self-update (detached, applies next launch). Runs after the\n // server is listening so the bounded probe can't delay it.\n void runSelfUpdate({ selfUpdate: args[\"self-update\"] !== false })\n\n // Best-effort ColBERT semantic-search provision. ON by default; never blocks\n // launch. `skipCwdIndex`: serve is machine-wide, so the launch cwd is usually\n // NOT a repo the user works on — per-workspace on-demand indexing (kicked by\n // the first search for a given repo) covers real queries instead.\n void provisionAndIndexColbert({ skipCwdIndex: true })\n\n // Best-effort LLM toolbelt materialization. The mirror awareness line is\n // written by provisionServeEnhancements below, matching `github-router claude`.\n void provisionToolbelt().catch((err) =>\n consola.debug(\"Toolbelt provisioning failed:\", err),\n )\n\n // Best-effort: keep the machine awake while the proxy/CloudCLI session runs.\n // Released via stopKeepAwake() in the shutdown chain below and by the module's\n // own self-registered signal/exit reaper.\n startKeepAwake()\n\n // Best-effort: materialize the browser extension + bridge into the stable\n // app-dir when browser tools are enabled.\n if (browserToolsEnabled()) {\n void provisionBrowserAssets().catch((err) =>\n consola.debug(\"Browser extension provisioning failed:\", err),\n )\n }\n\n // Same lead-slug resolution the `claude` subcommand uses, via the shared\n // helper rather than a second copy of the branching. The copy that used to\n // live here had drifted twice: it never learned the `-m fast` alias, and\n // its fallback walk assigned the bare constant, silently dropping the\n // `[1m]` accounting on a family that is 1M-capable. Re-deriving the\n // fallback through `pickClaudeDefault` is what `src/claude.ts` already\n // does, for that exact reason.\n let chosenSlug = resolveLeadSlugArg(args.model as string | undefined)\n if (!(args.model as string | undefined)?.trim() && state.models) {\n const inCache = (slug: string): boolean =>\n state.models?.data.some((m) => m.id === resolveModel(slug)) ?? false\n if (!inCache(chosenSlug)) {\n for (const fb of DEFAULT_CLAUDE_MODEL_FALLBACKS) {\n if (inCache(fb)) {\n const fallbackSlug = pickClaudeDefault(\n fb.replace(/^claude-opus-/, \"\"),\n )\n consola.info(\n `Default model \"${chosenSlug}\" not in your Copilot model list; falling back to \"${fallbackSlug}\".`,\n )\n chosenSlug = fallbackSlug\n break\n }\n }\n }\n }\n\n const requestedCli = args[\"codex-cli\"] === true\n const backend = resolveCodexCliBackend({\n requested: requestedCli,\n codexInfo: requestedCli ? getCodexVersion() : null,\n })\n const tunnelExposed =\n args.tunnel === true\n || String(args[\"public-url\"] ?? \"\")\n .split(\",\")\n .map((s) => s.trim())\n .filter(Boolean).length > 0\n if (tunnelExposed && browserToolsEnabled() && args[\"browse-over-tunnel\"] !== true) {\n consola.info(\n \"Browser MCP is disabled over the tunnel (session-hijack/SSRF risk). Pass --browse-over-tunnel to enable it for fully-trusted access.\",\n )\n }\n if (tunnelExposed && state.agentsEnabled && args[\"agents-over-tunnel\"] !== true) {\n consola.info(\n \"first-mate is disabled over the tunnel (it mints a GitHub write token). Pass --agents-over-tunnel to enable it for fully-trusted access.\",\n )\n }\n if (tunnelExposed && state.fleetEnabled && args[\"fleet-over-tunnel\"] !== true) {\n consola.info(\n \"Fleet MCP is disabled over the tunnel (it drives remote coding sessions with your fleet credentials). Pass --fleet-over-tunnel to enable it for fully-trusted access.\",\n )\n }\n\n // 2b. wire the github-router enhancement layer (MCP servers + peer/worker\n // subagents + gh-* skills) into the mirror so CloudCLI-spawned Claude\n // sessions get the same tools `github-router claude` provides. Must run\n // after the mirror exists and before CloudCLI spawns claude. Resolve the\n // self-invocation first so no persisted command races launcher publication.\n const selfInvocation = await resolveSelfInvocation()\n {\n const warning = hookLauncherDegradedWarning(selfInvocation)\n if (warning) process.stderr.write(`${warning}\\n`)\n }\n const enhancements = await provisionServeEnhancements(serverUrl, {\n selfInvocation,\n codexCli: backend === \"cli\",\n tunnelExposed,\n browseOverTunnel: args[\"browse-over-tunnel\"] === true,\n agentsOverTunnel: args[\"agents-over-tunnel\"] === true,\n fleetOverTunnel: args[\"fleet-over-tunnel\"] === true,\n })\n\n // 3. filtered child env: the vetted secret-stripping allowlist (buildEnv,\n // drops GITHUB_TOKEN/GH_ROUTER_*/ANTHROPIC_AUTH_TOKEN/OPENAI_API_KEY/\n // COPILOT_TOKEN) plus the non-secret ANTHROPIC_BASE_URL/CLAUDE_CONFIG_DIR\n // /model vars. Auth rides the synthetic .credentials.json FILE, not env.\n const anthropicVars = getClaudeCodeEnvVars(serverUrl, chosenSlug)\n // CLAUDE_CODE_FORK_SUBAGENT silently no-ops under CloudCLI's headless\n // `claude --print` path (the binary's Z8() precondition disables forking\n // without an interactive session), so setting it here is dead weight that\n // would mislead the model into assuming forked subagents inherit full\n // context. Drop it for serve. (getClaudeCodeEnvVars sets it for the\n // interactive `claude` path, where forking works.)\n delete anthropicVars.CLAUDE_CODE_FORK_SUBAGENT\n if (enhancements.nonce) {\n anthropicVars.GH_ROUTER_HOOK_MCP_URL = serverUrl\n anthropicVars.GH_ROUTER_HOOK_NONCE = enhancements.nonce\n }\n const childEnv: NodeJS.ProcessEnv = composeCloudCliChildEnv(anthropicVars)\n // Placeholder only: CloudCLI's Claude provider checks this env var to mark\n // Claude as connected. Claude still authenticates through the proxy mirror.\n childEnv.ANTHROPIC_API_KEY = \"sk-ant-github-router-proxy-placeholder\"\n // CloudCLI's cloned server defines CLAUDE_FALLBACK_MODELS as an in-code object,\n // and the searched server tree has no process.env.CLAUDE_FALLBACK_MODELS reader.\n // Do not set an env var here unless CloudCLI adds a documented string format.\n\n // 4. resolve/install + spawn CloudCLI on a loopback port.\n let cloudcli\n try {\n cloudcli = await resolveCloudCli({\n cliPath: args[\"cloudcli-path\"] as string | undefined,\n noInstall: args[\"no-install\"] === true,\n version: args[\"cloudcli-version\"] as string | undefined,\n })\n } catch (err) {\n consola.error(err instanceof Error ? err.message : String(err))\n process.exit(1)\n }\n\n const ccPort = await getFreePort()\n const cc = spawnCloudCli({\n serverEntry: cloudcli.serverEntry,\n port: ccPort,\n env: childEnv,\n })\n\n // 5. shutdown wiring (we don't use launchChild — its kind union is closed).\n let shuttingDown = false\n let reverseClose: (() => Promise<void>) | null = null\n let tunnelStop: (() => void) | null = null\n const shutdown = async (): Promise<void> => {\n if (shuttingDown) return\n shuttingDown = true\n if (tunnelStop) {\n try {\n tunnelStop()\n } catch {\n /* best-effort */\n }\n }\n try {\n killChildProcessTree(cc.child, {\n detachedGroup: process.platform !== \"win32\",\n })\n } catch {\n /* best-effort */\n }\n if (reverseClose) await reverseClose().catch(() => {})\n try {\n ;(server as unknown as { close?: () => void }).close?.()\n } catch {\n /* best-effort */\n }\n await stopKeepAwake().catch(() => {})\n await enhancements.cleanup().catch(() => {})\n await removeOwnClaudeConfigMirror().catch(() => {})\n }\n // `shutdown()` must never strand the process. `.then(exit)` alone skips the\n // exit on a REJECTION, so a teardown failure leaves a process that was\n // explicitly trying to die running forever — the worst possible outcome on\n // a shutdown path. `.finally` guarantees the exit either way, and the code\n // is preserved because an operator's script reads it.\n const exitAfterShutdown = (code: number) => {\n void shutdown()\n .catch((err: unknown) => {\n consola.error(\"Shutdown failed; exiting anyway:\", err)\n })\n .finally(() => process.exit(code))\n }\n process.once(\"SIGINT\", () => exitAfterShutdown(0))\n process.once(\"SIGTERM\", () => exitAfterShutdown(0))\n cc.child.once(\"exit\", (code) => {\n if (!shuttingDown) {\n consola.error(`CloudCLI exited unexpectedly (code ${code}).`)\n exitAfterShutdown(1)\n }\n })\n cc.child.once(\"error\", (err) => {\n // spawn failure (ENOENT/EACCES) emits 'error', not 'exit' — an unhandled\n // 'error' crashes the process and bypasses shutdown, orphaning the mirror.\n if (!shuttingDown) {\n consola.error(`CloudCLI failed to start: ${err.message}`)\n exitAfterShutdown(1)\n }\n })\n\n // 6. wait for CloudCLI, seed the user from the GitHub identity, mint a JWT.\n let ready\n try {\n ready = await waitForCloudCliReady(ccPort)\n } catch (err) {\n consola.error(err instanceof Error ? err.message : String(err))\n await shutdown()\n process.exit(1)\n }\n\n let username = \"github-router\"\n try {\n username = (await getGitHubUser()).login\n } catch {\n // fall back to a generic control-plane user; not fatal\n }\n\n let token: string\n try {\n token = await mintJwt(ccPort, username, ready.needsSetup)\n } catch (err) {\n consola.error(err instanceof Error ? err.message : String(err))\n await shutdown()\n process.exit(1)\n }\n\n // 7. resolve remote-access allowlist (dev tunnels) + the user-facing port.\n const publicUrls = String(args[\"public-url\"] ?? \"\")\n .split(\",\")\n .map((s) => s.trim())\n .filter(Boolean)\n const extraAllowedHosts: string[] = []\n const extraAllowedOrigins: string[] = []\n for (const u of publicUrls) {\n try {\n const url = new URL(u)\n extraAllowedHosts.push(url.host)\n extraAllowedOrigins.push(url.origin)\n } catch {\n consola.warn(`Ignoring invalid --public-url \"${u}\"`)\n }\n }\n const tunnelMode = args.tunnel === true\n if (extraAllowedHosts.length || tunnelMode) {\n consola.warn(\n \"Remote access enabled: anyone who reaches this through the tunnel AND passes its authentication gets FULL control-plane access — including a shell on this machine. The tunnel is authenticated (never anonymous); only grant access to people you fully trust.\",\n )\n }\n\n const servePort = await resolveServePort(parsed.port)\n const providerFacade = {\n kindFor: facadeInterceptKind,\n blockedRequest: facadeBlockedRequest,\n rewrite: (kind: string, json: unknown, query: URLSearchParams) =>\n rewriteProviderResponse(\n kind as Parameters<typeof rewriteProviderResponse>[0],\n json,\n {\n getModels: () => state.models,\n // Resolve to the Copilot catalog slug so it matches the picker\n // OPTIONS values (chosenSlug may be a dashed/`[1m]`-bracketed Anthropic\n // slug; the OPTIONS values are catalog ids). Otherwise DEFAULT misses\n // and falls back to the first option.\n defaultModel: resolveModel(chosenSlug),\n claudeConfigDir: PATHS.CLAUDE_CONFIG_DIR,\n },\n query,\n ),\n }\n let reverse\n try {\n reverse = await startReverseProxy({\n targetHost: \"127.0.0.1\",\n targetPort: ccPort,\n bindHost: \"127.0.0.1\",\n bindPort: servePort,\n authToken: token,\n seedToolSettings: process.env.GH_ROUTER_SERVE_NO_AUTO_APPROVE !== \"1\",\n // Routine built-ins + every injected mcp tool name → auto-approve; the\n // two interaction tools (AskUserQuestion/ExitPlanMode) are excluded so\n // they reach the user (SEAMLESS_BUILTIN_TOOLS never lists them).\n seedAllowedTools: [...SEAMLESS_BUILTIN_TOOLS, ...(enhancements.mcpToolNames ?? [])],\n extraAllowedHosts,\n extraAllowedOrigins,\n allowDevtunnelHosts: tunnelMode,\n providerFacade,\n })\n } catch (err) {\n consola.error(\n `Failed to bind the control-plane port ${servePort}: ${\n err instanceof Error ? err.message : String(err)\n }. Is it already in use? Pass --port <n>.`,\n )\n await shutdown()\n process.exit(1)\n }\n reverseClose = reverse.close\n\n // 8. optionally auto-create + host an AUTHENTICATED dev tunnel for remote\n // access. Non-fatal: on any failure keep serving locally (and the\n // *.devtunnels.ms allowlist stays, so a manual `devtunnel host` works).\n let tunnelUrl: string | null = null\n if (tunnelMode) {\n consola.info(\"Creating an authenticated dev tunnel…\")\n try {\n const tunnel = await startDevtunnel(servePort)\n tunnelUrl = tunnel.url\n tunnelStop = tunnel.stop\n } catch (err) {\n const msg = err instanceof DevtunnelError ? err.message : String(err)\n consola.warn(\n `Could not auto-host a dev tunnel: ${msg}\\nServing locally only. To host manually once fixed: devtunnel host -p ${servePort}`,\n )\n }\n }\n\n // 9. ready.\n const publicLines = [\n ...publicUrls.map((u) => `remote: ${u}`),\n ...(tunnelUrl ? [`remote (tunnel): ${tunnelUrl} (stable — bookmark it; same URL every launch)`] : []),\n ]\n const publicBlock = publicLines.length ? `\\n${publicLines.join(\"\\n\")}` : \"\"\n consola.box(\n `🖥️ github-router control plane\\n\\nlocal: ${reverse.url}${publicBlock}\\n\\nsigned in as ${username} · model ${chosenSlug}\\nCtrl+C to stop`,\n )\n if (args[\"no-open\"] !== true) openBrowser(reverse.url)\n },\n})\n"],"mappings":"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"}
|