@schoolai/shipyard 3.7.0 → 3.8.0-rc.20260529.0

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.
Files changed (104) hide show
  1. package/dist/{auth-SS7LV5XK.js → auth-EXHO3AG5.js} +4 -4
  2. package/dist/capability-detector-worker.js +142 -0
  3. package/dist/capability-detector-worker.js.map +1 -0
  4. package/dist/{chunk-DKMDBOFU.js → chunk-2CNIEBKO.js} +21 -11
  5. package/dist/chunk-2CNIEBKO.js.map +1 -0
  6. package/dist/chunk-4T2OQAVL.js +51 -0
  7. package/dist/chunk-4T2OQAVL.js.map +1 -0
  8. package/dist/chunk-5ER6ZHA2.js +46 -0
  9. package/dist/chunk-5ER6ZHA2.js.map +1 -0
  10. package/dist/chunk-7LSEE26O.js +24227 -0
  11. package/dist/chunk-7LSEE26O.js.map +1 -0
  12. package/dist/{chunk-7AHRFPAL.js → chunk-7YOU7MBN.js} +183 -17
  13. package/dist/chunk-7YOU7MBN.js.map +1 -0
  14. package/dist/chunk-CMGJGK6R.js +382 -0
  15. package/dist/chunk-CMGJGK6R.js.map +1 -0
  16. package/dist/{chunk-VPMN47TL.js → chunk-CNR7O5YH.js} +1 -2
  17. package/dist/{chunk-2J3WSIAF.js → chunk-EF2DAODF.js} +18 -3
  18. package/dist/chunk-EF2DAODF.js.map +1 -0
  19. package/dist/chunk-HQ43PHOH.js +1203 -0
  20. package/dist/chunk-HQ43PHOH.js.map +1 -0
  21. package/dist/chunk-KITSAHTX.js +134 -0
  22. package/dist/chunk-KITSAHTX.js.map +1 -0
  23. package/dist/chunk-LESHN5J5.js +6898 -0
  24. package/dist/chunk-LESHN5J5.js.map +1 -0
  25. package/dist/{chunk-LW2MS4T5.js → chunk-LMJFHKRD.js} +15 -12
  26. package/dist/chunk-LMJFHKRD.js.map +1 -0
  27. package/dist/{chunk-SNYEQHUK.js → chunk-NACJENDW.js} +14 -21
  28. package/dist/chunk-NACJENDW.js.map +1 -0
  29. package/dist/{chunk-IISLTKYY.js → chunk-TU63KZFW.js} +2 -2
  30. package/dist/chunk-TX6DK4PK.js +186 -0
  31. package/dist/chunk-TX6DK4PK.js.map +1 -0
  32. package/dist/chunk-UQVXWOPT.js +48 -0
  33. package/dist/chunk-UQVXWOPT.js.map +1 -0
  34. package/dist/{chunk-3MNPDCO5.js → chunk-WBB4XHLH.js} +139 -140
  35. package/dist/chunk-WBB4XHLH.js.map +1 -0
  36. package/dist/chunk-X3MULCV5.js +11 -0
  37. package/dist/chunk-X3MULCV5.js.map +1 -0
  38. package/dist/chunk-YZ3Z3ZYI.js +787 -0
  39. package/dist/chunk-YZ3Z3ZYI.js.map +1 -0
  40. package/dist/{chunk-2UN5AR7V.js → chunk-ZAOPND5G.js} +2 -2
  41. package/dist/chunk-ZFKJAYAN.js +542 -0
  42. package/dist/chunk-ZFKJAYAN.js.map +1 -0
  43. package/dist/cursor-hook-shim.js +316 -0
  44. package/dist/cursor-hook-shim.js.map +1 -0
  45. package/dist/cursor-runner.js +358 -0
  46. package/dist/cursor-runner.js.map +1 -0
  47. package/dist/electron-utility.js +111 -0
  48. package/dist/electron-utility.js.map +1 -0
  49. package/dist/git-pool-V73Q53NX.js +18 -0
  50. package/dist/{git-repo-VRT57DGC.js → git-repo-TN3VZXQV.js} +9 -6
  51. package/dist/index.js +12 -12
  52. package/dist/index.js.map +1 -1
  53. package/dist/{logger-GQCSLSZH.js → logger-QHPTO22N.js} +4 -4
  54. package/dist/login-Q7SZI7JJ.js +20 -0
  55. package/dist/{logout-VUNCW5B2.js → logout-O4AVMO5S.js} +6 -6
  56. package/dist/mcp-servers-F64M5T4I.js +24 -0
  57. package/dist/{roi-Y3MX5UW4.js → roi-EYDLPOCS.js} +5 -5
  58. package/dist/rss-worker.js +159 -0
  59. package/dist/rss-worker.js.map +1 -0
  60. package/dist/{serve-O53FNK64.js → serve-6A7RJWEF.js} +89862 -102999
  61. package/dist/{serve-O53FNK64.js.map → serve-6A7RJWEF.js.map} +1 -1
  62. package/dist/skills-ZHEPSBHW.js +11 -0
  63. package/dist/{start-IDFDHRD6.js → start-YGYYIK53.js} +229 -27
  64. package/dist/start-YGYYIK53.js.map +1 -0
  65. package/dist/vault-crypto-BKDOA65F.js +13 -0
  66. package/dist/vault-crypto-BKDOA65F.js.map +1 -0
  67. package/dist/worker.js +6 -3
  68. package/dist/worker.js.map +1 -1
  69. package/package.json +17 -10
  70. package/dist/chunk-2J3WSIAF.js.map +0 -1
  71. package/dist/chunk-3MNPDCO5.js.map +0 -1
  72. package/dist/chunk-66OBOZ3X.js +0 -79
  73. package/dist/chunk-66OBOZ3X.js.map +0 -1
  74. package/dist/chunk-7AHRFPAL.js.map +0 -1
  75. package/dist/chunk-DKMDBOFU.js.map +0 -1
  76. package/dist/chunk-L2WQMPWS.js +0 -666
  77. package/dist/chunk-L2WQMPWS.js.map +0 -1
  78. package/dist/chunk-LW2MS4T5.js.map +0 -1
  79. package/dist/chunk-PI77CUEP.js +0 -49
  80. package/dist/chunk-PI77CUEP.js.map +0 -1
  81. package/dist/chunk-RXI4637N.js +0 -395
  82. package/dist/chunk-RXI4637N.js.map +0 -1
  83. package/dist/chunk-SNYEQHUK.js.map +0 -1
  84. package/dist/chunk-VBPHGPBR.js +0 -126
  85. package/dist/chunk-VBPHGPBR.js.map +0 -1
  86. package/dist/index.d.ts +0 -2
  87. package/dist/login-L4BBPUYO.js +0 -20
  88. package/dist/mcp-servers-MXS5VAWI.js +0 -18
  89. package/dist/shell-V36EX2IJ.js +0 -27
  90. package/dist/skills-GPGRNV4R.js +0 -9
  91. package/dist/start-IDFDHRD6.js.map +0 -1
  92. package/dist/worker.d.ts +0 -49
  93. /package/dist/{auth-SS7LV5XK.js.map → auth-EXHO3AG5.js.map} +0 -0
  94. /package/dist/{chunk-VPMN47TL.js.map → chunk-CNR7O5YH.js.map} +0 -0
  95. /package/dist/{chunk-IISLTKYY.js.map → chunk-TU63KZFW.js.map} +0 -0
  96. /package/dist/{chunk-2UN5AR7V.js.map → chunk-ZAOPND5G.js.map} +0 -0
  97. /package/dist/{git-repo-VRT57DGC.js.map → git-pool-V73Q53NX.js.map} +0 -0
  98. /package/dist/{logger-GQCSLSZH.js.map → git-repo-TN3VZXQV.js.map} +0 -0
  99. /package/dist/{login-L4BBPUYO.js.map → logger-QHPTO22N.js.map} +0 -0
  100. /package/dist/{mcp-servers-MXS5VAWI.js.map → login-Q7SZI7JJ.js.map} +0 -0
  101. /package/dist/{logout-VUNCW5B2.js.map → logout-O4AVMO5S.js.map} +0 -0
  102. /package/dist/{shell-V36EX2IJ.js.map → mcp-servers-F64M5T4I.js.map} +0 -0
  103. /package/dist/{roi-Y3MX5UW4.js.map → roi-EYDLPOCS.js.map} +0 -0
  104. /package/dist/{skills-GPGRNV4R.js.map → skills-ZHEPSBHW.js.map} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/shared/capabilities/mcp-servers.ts","../src/shared/mcp/claude-code-credentials.ts","../src/shared/mcp/schemas.ts","../src/shared/mcp/resolve-servers.ts","../src/shared/auth/anthropic-credentials.ts","../src/shared/capabilities/runtime/catalog.ts","../src/shared/capabilities/account-integrations.ts"],"sourcesContent":["import { readFile, stat } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { basename, join, resolve } from 'node:path';\nimport type { AnthropicAuthMethod } from '@shipyard/loro-schema';\nimport type { GitRepoInfo, MCPServerInfo, MCPServerSource } from '@shipyard/session';\nimport { parse as parseToml } from 'toml';\nimport { z } from 'zod';\nimport { logger } from '../logger.js';\nimport { readClaudeCodeCredentials } from '../mcp/claude-code-credentials.js';\nimport { VAULT_REF_PATTERN } from '../mcp/resolve-servers.js';\nimport { fetchClaudeAiIntegrations } from './account-integrations.js';\nimport {\n CLAUDE_CODE_RUNTIME_ID,\n CODEX_RUNTIME_ID,\n getRuntimeDisplayName,\n} from './runtime/catalog.js';\n\nconst MCPStdioEntrySchema = z\n .object({\n type: z.literal('stdio').optional(),\n command: z.string(),\n args: z.array(z.string()).optional(),\n env: z.record(z.string(), z.string()).optional(),\n enabled: z.boolean().optional(),\n })\n .passthrough();\n\nconst MCPOAuthConfigSchema = z\n .object({\n clientId: z.string().optional(),\n callbackPort: z.number().optional(),\n })\n .passthrough();\n\nconst MCPHttpEntrySchema = z\n .object({\n type: z.literal('http'),\n url: z.string(),\n headers: z.record(z.string(), z.string()).optional(),\n oauth: MCPOAuthConfigSchema.optional(),\n enabled: z.boolean().optional(),\n })\n .passthrough();\n\nconst MCPSSEEntrySchema = z\n .object({\n type: z.literal('sse'),\n url: z.string(),\n headers: z.record(z.string(), z.string()).optional(),\n enabled: z.boolean().optional(),\n })\n .passthrough();\n\nconst MCPServerEntrySchema = z.union([MCPStdioEntrySchema, MCPHttpEntrySchema, MCPSSEEntrySchema]);\nconst CodexEnvVarSchema = z.union([\n z.string(),\n z\n .object({\n name: z.string(),\n source: z.string().optional(),\n })\n .passthrough(),\n]);\nconst CodexMCPStdioEntrySchema = z\n .object({\n command: z.string(),\n args: z.array(z.string()).optional(),\n env: z.record(z.string(), z.string()).optional(),\n env_vars: z.array(CodexEnvVarSchema).optional(),\n enabled: z.boolean().optional(),\n })\n .passthrough();\nconst CodexMCPHttpEntrySchema = z\n .object({\n url: z.string(),\n http_headers: z.record(z.string(), z.string()).optional(),\n env_http_headers: z.record(z.string(), z.string()).optional(),\n bearer_token_env_var: z.string().optional(),\n enabled: z.boolean().optional(),\n })\n .passthrough();\n\n/**\n * Pure decision: should `fetchClaudeAiIntegrations` (the claude.ai Connectors\n * fetch) actually run for this user?\n *\n * Only the claude.ai OAuth method has access to that endpoint. `null` or\n * `undefined` (preference unknown — pre-backfill or caller didn't plumb the\n * value through) returns true to preserve the legacy behaviour for OAuth\n * users during the rollout window before the boot backfill writes a real\n * value. After backfill, `null` should be vanishingly rare.\n */\nexport function shouldFetchClaudeAiIntegrations(\n preferredAuth: AnthropicAuthMethod | null | undefined\n): boolean {\n if (preferredAuth == null) return true;\n return preferredAuth === 'claude-ai';\n}\n\nconst SECRET_PATTERNS = /^(sk-|ghp_|gho_|glpat-|xoxb-|xoxp-|Bearer\\s|token\\s)/i;\nconst SECRET_FLAGS = new Set(['--api-key', '--token', '--secret', '--password', '--key', '-k']);\n\nexport function redactArgs(args: string[]): string[] {\n return args.map((arg, i) => {\n if (SECRET_PATTERNS.test(arg)) return '***';\n const prevArg = i > 0 ? args[i - 1] : undefined;\n if (prevArg && SECRET_FLAGS.has(prevArg)) return '***';\n const eqIdx = arg.indexOf('=');\n if (eqIdx > 0 && SECRET_FLAGS.has(arg.slice(0, eqIdx))) {\n return `${arg.slice(0, eqIdx + 1)}***`;\n }\n return arg;\n });\n}\n\n/**\n * Redact all environment variable values. Env vars passed to MCP servers\n * frequently contain secrets (API keys, tokens) and should never be\n * exposed to the browser.\n */\nexport function redactEnv(env: Record<string, string>): Record<string, string> {\n const result: Record<string, string> = {};\n for (const key of Object.keys(env)) {\n result[key] = '***';\n }\n return result;\n}\n\ntype MCPStdioEntry = z.infer<typeof MCPStdioEntrySchema>;\ntype MCPHttpEntry = z.infer<typeof MCPHttpEntrySchema>;\ntype MCPSSEEntry = z.infer<typeof MCPSSEEntrySchema>;\ntype CodexMCPStdioEntry = z.infer<typeof CodexMCPStdioEntrySchema>;\ntype CodexMCPHttpEntry = z.infer<typeof CodexMCPHttpEntrySchema>;\ntype MCPSourceMetadata = Pick<MCPServerInfo, 'runtimeId' | 'sourceLabel' | 'sourcePath'>;\ninterface ParsedConfigResult {\n servers: MCPServerInfo[];\n invalid: InvalidEntry[];\n}\ninterface DetectedServerCandidate {\n server: MCPServerInfo;\n priority: number;\n}\n\nfunction isHttpEntry(data: MCPStdioEntry | MCPHttpEntry | MCPSSEEntry): data is MCPHttpEntry {\n return 'type' in data && data.type === 'http';\n}\n\nfunction isSSEEntry(data: MCPStdioEntry | MCPHttpEntry | MCPSSEEntry): data is MCPSSEEntry {\n return 'type' in data && data.type === 'sse';\n}\n\nfunction entryToServerInfo(\n name: string,\n data: MCPStdioEntry | MCPHttpEntry | MCPSSEEntry,\n source: MCPServerSource,\n metadata: MCPSourceMetadata = {}\n): MCPServerInfo {\n if (isHttpEntry(data)) {\n return {\n name,\n type: 'http',\n runtimeId: metadata.runtimeId,\n url: data.url,\n headers: data.headers,\n oauth: data.oauth,\n enabled: data.enabled ?? true,\n source,\n sourceLabel: metadata.sourceLabel,\n sourcePath: metadata.sourcePath,\n authStatus: 'unknown' as const,\n };\n }\n if (isSSEEntry(data)) {\n return {\n name,\n type: 'sse',\n runtimeId: metadata.runtimeId,\n url: data.url,\n headers: data.headers,\n enabled: data.enabled ?? true,\n source,\n sourceLabel: metadata.sourceLabel,\n sourcePath: metadata.sourcePath,\n authStatus: 'unknown' as const,\n };\n }\n return {\n name,\n type: 'stdio',\n runtimeId: metadata.runtimeId,\n command: data.command,\n args: data.args,\n env: data.env,\n enabled: data.enabled ?? true,\n source,\n sourceLabel: metadata.sourceLabel,\n sourcePath: metadata.sourcePath,\n authStatus: 'unknown' as const,\n };\n}\n\nfunction codexEnvVarsToPlaceholders(\n envVars: CodexMCPStdioEntry['env_vars']\n): Record<string, string> | undefined {\n if (!envVars || envVars.length === 0) return undefined;\n const result: Record<string, string> = {};\n for (const envVar of envVars) {\n const name = typeof envVar === 'string' ? envVar : envVar.name;\n result[name] = `\\${${name}}`;\n }\n return result;\n}\n\nfunction codexHttpHeadersToPlaceholders(\n envHeaders: CodexMCPHttpEntry['env_http_headers']\n): Record<string, string> | undefined {\n if (!envHeaders) return undefined;\n const result: Record<string, string> = {};\n for (const [header, envVar] of Object.entries(envHeaders)) {\n result[header] = `\\${${envVar}}`;\n }\n return result;\n}\n\nfunction codexEntryToServerInfo(\n name: string,\n data: CodexMCPStdioEntry | CodexMCPHttpEntry,\n source: MCPServerSource,\n metadata: MCPSourceMetadata\n): MCPServerInfo {\n if (isCodexStdioEntry(data)) {\n return {\n name,\n type: 'stdio',\n runtimeId: metadata.runtimeId,\n command: data.command,\n args: data.args,\n env: {\n ...(data.env ?? {}),\n ...(codexEnvVarsToPlaceholders(data.env_vars) ?? {}),\n },\n enabled: data.enabled ?? true,\n source,\n sourceLabel: metadata.sourceLabel,\n sourcePath: metadata.sourcePath,\n authStatus: 'unknown' as const,\n };\n }\n\n const headers = {\n ...(data.http_headers ?? {}),\n ...(codexHttpHeadersToPlaceholders(data.env_http_headers) ?? {}),\n };\n if (data.bearer_token_env_var) {\n headers.Authorization = `Bearer \\${${data.bearer_token_env_var}}`;\n }\n\n return {\n name,\n type: 'http',\n runtimeId: metadata.runtimeId,\n url: data.url,\n headers,\n enabled: data.enabled ?? true,\n source,\n sourceLabel: metadata.sourceLabel,\n sourcePath: metadata.sourcePath,\n authStatus: 'unknown' as const,\n };\n}\n\nfunction isCodexStdioEntry(\n data: CodexMCPStdioEntry | CodexMCPHttpEntry\n): data is CodexMCPStdioEntry {\n return isObjectRecord(data) && typeof data.command === 'string';\n}\n\ntype Log = (entry: { event: string; [key: string]: unknown }) => void;\n\nfunction isObjectRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction parseTomlRecord(raw: string): Record<string, unknown> {\n const parsed = parseToml(raw);\n return isObjectRecord(parsed) ? parsed : {};\n}\n\n/**\n * Path-aware: `settings.json` / `settings.local.json` only carry MCP servers\n * under the explicit `mcpServers` wrapper — their top-level keys are Claude\n * Code config (`hooks`, `permissions`, `env`, `statusLine`, `enabledPlugins`,\n * `spinnerVerbs`, ...) that will never validate as MCP entries. Falling\n * through to iterate them produced 100k+ `mcp_config_entry_invalid` warnings\n * per 15min in #3264. Other files (`.mcp.json`, `~/.claude.json`) keep the\n * fallthrough — `.mcp.json` puts servers at the top level by spec.\n *\n * #3293 follow-on: even with the path-aware fix, files that DO have an\n * `mcpServers` wrapper containing malformed entries (e.g. mis-keyed\n * `hooks`-style records pasted into `mcpServers`) still produced per-entry\n * warning events on every scan. With 80+ worktrees × multiple scans per\n * minute × 6-10 invalid entries per file, the daemon's event loop saturated\n * (29k warnings / 9min, worst freeze 5.8s).\n *\n * Two defenses now applied at this chokepoint:\n * 1. **mtime cache** — stat() the file first; skip the entire read+parse+\n * validate cycle when `stat.mtimeMs` matches the prior validation's\n * cached value. Stable malformed configs across many worktrees produce\n * ~99% cache hits per scan cycle.\n * 2. **Aggregated + throttled warnings** — invalid entries are accumulated\n * into a single `config_entry_invalid` event per file per scan (with\n * `invalidCount` and the first 3 error messages). The warn-level\n * emission is throttled to once per 60s per file; intermediate\n * re-validations log at `debug` level so the firestorm cannot recur but\n * debug-level observability is preserved. Bad entries are still\n * REJECTED — only the log shape changes.\n */\nconst WARN_THROTTLE_MS = 60_000;\n\ninterface ConfigCacheEntry {\n mtime: number;\n result: MCPServerInfo[];\n}\n\ninterface InvalidEntry {\n name: string;\n error: string;\n}\n\nconst validationCache = new Map<string, ConfigCacheEntry>();\nconst lastWarnAtByPath = new Map<string, number>();\n\n/**\n * Clear the in-process mtime cache + warn-throttle map. Test-only — production\n * relies on natural eviction via mtime change. Exported so unit tests can\n * isolate cases without resorting to module-reload tricks.\n */\nexport function resetMCPConfigCaches(): void {\n validationCache.clear();\n lastWarnAtByPath.clear();\n}\n\nfunction validateJsonEntries(\n entries: Record<string, unknown>,\n source: MCPServerSource,\n metadata: MCPSourceMetadata\n): ParsedConfigResult {\n const servers: MCPServerInfo[] = [];\n const invalid: InvalidEntry[] = [];\n for (const [name, value] of Object.entries(entries)) {\n if (name === 'mcpServers') continue;\n if (typeof value !== 'object' || value === null || Array.isArray(value)) continue;\n const result = MCPServerEntrySchema.safeParse(value);\n if (!result.success) {\n invalid.push({ name, error: result.error.message });\n continue;\n }\n servers.push(entryToServerInfo(name, result.data, source, metadata));\n }\n return { servers, invalid };\n}\n\nfunction validateCodexEntries(\n entries: Record<string, unknown>,\n source: MCPServerSource,\n metadata: MCPSourceMetadata\n): ParsedConfigResult {\n const servers: MCPServerInfo[] = [];\n const invalid: InvalidEntry[] = [];\n for (const [name, value] of Object.entries(entries)) {\n if (!isObjectRecord(value)) continue;\n const result =\n 'command' in value\n ? CodexMCPStdioEntrySchema.safeParse(value)\n : 'url' in value\n ? CodexMCPHttpEntrySchema.safeParse(value)\n : { success: false as const, error: { message: 'invalid transport' } };\n if (!result.success) {\n invalid.push({ name, error: result.error.message });\n continue;\n }\n servers.push(codexEntryToServerInfo(name, result.data, source, metadata));\n }\n return { servers, invalid };\n}\n\nfunction emitAggregateInvalid(\n filePath: string,\n source: MCPServerSource,\n invalid: InvalidEntry[],\n log: Log,\n now: number\n): void {\n const payload = {\n event: 'config_entry_invalid' as const,\n source,\n filePath,\n invalidCount: invalid.length,\n errors: invalid.slice(0, 3),\n };\n const last = lastWarnAtByPath.get(filePath) ?? 0;\n if (now - last >= WARN_THROTTLE_MS) {\n log(payload);\n lastWarnAtByPath.set(filePath, now);\n return;\n }\n /**\n * Within the per-file 60s throttle window — emit at debug level so the\n * firestorm cannot recur via the WARN-routed log adapter, but the event\n * is still observable for diagnostics with debug logging enabled.\n */\n logger.debug(payload, payload.event);\n}\n\nasync function readCachedMcpConfig(\n filePath: string,\n source: MCPServerSource,\n log: Log,\n parse: (raw: string) => ParsedConfigResult,\n /** Test seam: defaults to `Date.now`. */\n now: () => number = Date.now\n): Promise<MCPServerInfo[]> {\n let mtime: number;\n try {\n const st = await stat(filePath);\n mtime = st.mtimeMs;\n } catch {\n /**\n * File doesn't exist (or unreadable stat). Don't cache — a future scan\n * might race a file-create and we want to pick it up. Cheap fs call\n * with no read/parse/validate work, so unrolling the cache is fine.\n */\n return [];\n }\n\n const cached = validationCache.get(filePath);\n if (cached && cached.mtime === mtime) {\n return cached.result;\n }\n\n let servers: MCPServerInfo[] = [];\n try {\n const raw = await readFile(filePath, 'utf-8');\n const parsed = parse(raw);\n servers = parsed.servers;\n if (parsed.invalid.length > 0) {\n emitAggregateInvalid(filePath, source, parsed.invalid, log, now());\n }\n } catch {\n /**\n * Read/parse failure — cache an empty result keyed on the failing\n * mtime so repeated scans don't keep re-reading the same broken file\n * until the user touches it. Mirrors the cache-on-success branch.\n */\n servers = [];\n }\n\n validationCache.set(filePath, { mtime, result: servers });\n return servers;\n}\n\nexport async function readMCPConfig(\n filePath: string,\n source: MCPServerSource,\n log: Log,\n metadataOrNow: MCPSourceMetadata | (() => number) = {},\n /** Test seam: defaults to `Date.now`. */\n now: () => number = Date.now\n): Promise<MCPServerInfo[]> {\n const metadata =\n typeof metadataOrNow === 'function' ? ({} satisfies MCPSourceMetadata) : metadataOrNow;\n const effectiveNow = typeof metadataOrNow === 'function' ? metadataOrNow : now;\n return readCachedMcpConfig(\n filePath,\n source,\n log,\n (raw) => {\n // eslint-disable-next-line no-restricted-syntax -- JSON.parse returns unknown; validated per-entry below\n const json = JSON.parse(raw) as Record<string, unknown>;\n\n const fileName = basename(filePath);\n const isSettingsFile = fileName === 'settings.json' || fileName === 'settings.local.json';\n\n let entries: Record<string, unknown> | null;\n if (isObjectRecord(json.mcpServers)) {\n entries = json.mcpServers;\n } else if (isSettingsFile) {\n entries = null;\n } else {\n entries = json;\n }\n\n return entries === null\n ? { servers: [], invalid: [] }\n : validateJsonEntries(entries, source, metadata);\n },\n effectiveNow\n );\n}\n\nasync function readCodexMCPConfig(\n filePath: string,\n source: MCPServerSource,\n log: Log,\n metadata: MCPSourceMetadata,\n now: () => number = Date.now\n): Promise<MCPServerInfo[]> {\n return readCachedMcpConfig(\n filePath,\n source,\n log,\n (raw) => {\n const parsed = parseTomlRecord(raw);\n return isObjectRecord(parsed.mcp_servers)\n ? validateCodexEntries(parsed.mcp_servers, source, metadata)\n : { servers: [], invalid: [] };\n },\n now\n );\n}\n\nasync function readPluginMCPServers(log: Log): Promise<MCPServerInfo[]> {\n const servers: MCPServerInfo[] = [];\n try {\n const settingsPath = join(homedir(), '.claude', 'settings.json');\n const settingsRaw = await readFile(settingsPath, 'utf-8');\n // eslint-disable-next-line no-restricted-syntax -- JSON.parse returns unknown; checking enabledPlugins field only\n const settings = JSON.parse(settingsRaw) as { enabledPlugins?: Record<string, boolean> };\n if (!settings.enabledPlugins) return [];\n\n const installedPath = join(homedir(), '.claude', 'plugins', 'installed_plugins.json');\n const installedRaw = await readFile(installedPath, 'utf-8');\n // eslint-disable-next-line no-restricted-syntax -- JSON.parse returns unknown; checking plugins field only\n const installed = JSON.parse(installedRaw) as {\n plugins?: Record<string, Array<{ installPath?: string }>>;\n };\n if (!installed.plugins) return [];\n\n for (const [pluginId, enabled] of Object.entries(settings.enabledPlugins)) {\n if (!enabled) continue;\n const installs = installed.plugins[pluginId];\n if (!installs || installs.length === 0) continue;\n const installPath = installs[0]?.installPath;\n if (!installPath) continue;\n\n const mcpJsonPath = join(installPath, '.mcp.json');\n const pluginServers = await readMCPConfig(mcpJsonPath, 'plugin', log, {\n sourceLabel: 'Plugin',\n sourcePath: '~/.claude/plugins/',\n });\n servers.push(...pluginServers);\n }\n } catch {}\n return servers;\n}\n\nconst CodexMarketplaceEntrySchema = z.object({\n source: z.string(),\n source_type: z.string().optional(),\n});\n\nconst CodexMarketplacesSchema = z.record(z.string(), CodexMarketplaceEntrySchema.passthrough());\n\nconst CodexPluginEntrySchema = z.object({\n enabled: z.boolean().optional(),\n});\n\nconst CodexPluginsSchema = z.record(z.string(), CodexPluginEntrySchema.passthrough());\n\nconst CodexConfigTomlSchema = z.object({\n mcp_servers: z.record(z.string(), z.unknown()).optional(),\n marketplaces: CodexMarketplacesSchema.optional(),\n plugins: CodexPluginsSchema.optional(),\n});\n\ntype CodexConfigToml = z.infer<typeof CodexConfigTomlSchema>;\n\nconst MarketplaceJsonPluginSourceSchema = z.object({\n source: z.string(),\n path: z.string(),\n});\n\nconst MarketplaceJsonPluginSchema = z.object({\n name: z.string(),\n source: MarketplaceJsonPluginSourceSchema.optional(),\n});\n\nconst MarketplaceJsonSchema = z.object({\n name: z.string().optional(),\n plugins: z.array(MarketplaceJsonPluginSchema),\n});\n\nconst CodexPluginManifestSchema = z.object({\n mcpServers: z.string().optional(),\n skills: z.string().optional(),\n});\n\nconst CodexPluginMcpServersFileSchema = z.object({\n mcpServers: z.record(z.string(), z.unknown()).optional(),\n});\n\nasync function readCodexTomlConfig(configPath: string): Promise<CodexConfigToml | null> {\n try {\n const raw = await readFile(configPath, 'utf-8');\n const parsed = parseTomlRecord(raw);\n const result = CodexConfigTomlSchema.safeParse(parsed);\n return result.success ? result.data : null;\n } catch {\n return null;\n }\n}\n\nasync function readMarketplaceJson(\n marketplaceSourceDir: string\n): Promise<z.infer<typeof MarketplaceJsonSchema> | null> {\n const candidates = [\n join(marketplaceSourceDir, '.agents', 'plugins', 'marketplace.json'),\n join(marketplaceSourceDir, 'marketplace.json'),\n ];\n for (const candidate of candidates) {\n try {\n const raw = await readFile(candidate, 'utf-8');\n // eslint-disable-next-line no-restricted-syntax -- JSON.parse returns unknown; validated by schema\n const parsed = JSON.parse(raw) as unknown;\n const result = MarketplaceJsonSchema.safeParse(parsed);\n if (result.success) return result.data;\n } catch {}\n }\n return null;\n}\n\n/**\n * Read MCP servers from a Codex plugin's manifest and mcp file.\n * Returns an empty array (and logs) if any step fails — one bad plugin\n * must not block the rest of detection.\n */\nasync function readCodexPluginManifestMcps(\n pluginRoot: string,\n pluginId: string,\n marketplaceName: string,\n log: Log\n): Promise<MCPServerInfo[]> {\n const manifestPath = join(pluginRoot, '.codex-plugin', 'plugin.json');\n try {\n const raw = await readFile(manifestPath, 'utf-8');\n // eslint-disable-next-line no-restricted-syntax -- JSON.parse returns unknown; validated by schema\n const parsed = JSON.parse(raw) as unknown;\n const manifestResult = CodexPluginManifestSchema.safeParse(parsed);\n if (!manifestResult.success) {\n log({\n event: 'codex_plugin_manifest_invalid',\n pluginId,\n marketplaceName,\n error: manifestResult.error.message,\n });\n return [];\n }\n const manifest = manifestResult.data;\n if (!manifest.mcpServers) return [];\n\n const mcpFilePath = resolve(pluginRoot, manifest.mcpServers);\n try {\n const mcpRaw = await readFile(mcpFilePath, 'utf-8');\n // eslint-disable-next-line no-restricted-syntax -- JSON.parse returns unknown; validated by schema\n const mcpParsed = JSON.parse(mcpRaw) as unknown;\n const mcpResult = CodexPluginMcpServersFileSchema.safeParse(mcpParsed);\n if (!mcpResult.success || !mcpResult.data.mcpServers) return [];\n\n const sourceLabel = `Codex Plugin (${pluginId})`;\n const sourcePath = `~/.codex/plugins/${pluginId}`;\n const { servers, invalid } = validateCodexEntries(mcpResult.data.mcpServers, 'plugin', {\n runtimeId: CODEX_RUNTIME_ID,\n sourceLabel,\n sourcePath,\n });\n if (invalid.length > 0) {\n log({\n event: 'codex_plugin_mcp_entries_invalid',\n pluginId,\n marketplaceName,\n invalidCount: invalid.length,\n errors: invalid.slice(0, 3),\n });\n }\n return servers;\n } catch {\n log({ event: 'codex_plugin_mcp_file_unreadable', pluginId, marketplaceName, mcpFilePath });\n return [];\n }\n } catch {\n /**\n * Plugin manifest missing — not an error, just not configured.\n * Absence of `.codex-plugin/plugin.json` is the common case for repos\n * that don't declare Codex plugins, so this catch is intentionally silent.\n */\n return [];\n }\n}\n\n/**\n * Detect MCP servers declared by Codex marketplace plugins.\n *\n * Reads `configToml.marketplaces` → finds each marketplace's `marketplace.json`\n * → resolves local plugin paths → reads each plugin's `.codex-plugin/plugin.json`\n * → reads the `mcpServers` file referenced by the manifest.\n *\n * Only processes plugins where `configToml.plugins[\"pluginName@marketplaceName\"]?.enabled`\n * is `true` (or absent — omitted entry means enabled by default in Codex).\n *\n * Errors in any single plugin are logged and skipped; a bad plugin never\n * blocks the rest of detection.\n */\nexport async function detectCodexPluginMcps(\n codexHome: string,\n configToml: CodexConfigToml,\n log: Log\n): Promise<MCPServerInfo[]> {\n const marketplaces = configToml.marketplaces;\n if (!marketplaces || Object.keys(marketplaces).length === 0) return [];\n\n const pluginEnabledMap = configToml.plugins ?? {};\n const allServers: MCPServerInfo[] = [];\n\n await Promise.all(\n Object.entries(marketplaces).map(async ([marketplaceName, marketplaceEntry]) => {\n if (marketplaceEntry.source_type && marketplaceEntry.source_type !== 'local') return;\n\n const marketplaceSourceDir = marketplaceEntry.source;\n if (!marketplaceSourceDir) return;\n\n const marketplaceJson = await readMarketplaceJson(marketplaceSourceDir);\n if (!marketplaceJson) {\n log({ event: 'codex_marketplace_json_missing', marketplaceName, marketplaceSourceDir });\n return;\n }\n\n await Promise.all(\n marketplaceJson.plugins.map(async (plugin) => {\n if (!plugin.source || plugin.source.source !== 'local') return;\n\n const pluginId = `${plugin.name}@${marketplaceName}`;\n const pluginEnabledEntry = pluginEnabledMap[pluginId];\n /** Codex treats absence of a plugins entry as enabled; only skip if explicitly disabled. */\n if (pluginEnabledEntry?.enabled === false) return;\n\n const pluginRoot = resolve(marketplaceSourceDir, plugin.source.path);\n const servers = await readCodexPluginManifestMcps(\n pluginRoot,\n pluginId,\n marketplaceName,\n log\n );\n allServers.push(...servers);\n })\n );\n })\n );\n\n if (allServers.length > 0) {\n log({ event: 'codex_plugin_mcps_detected', count: allServers.length, codexHome });\n }\n\n return allServers;\n}\n\nfunction pushCandidates(\n target: DetectedServerCandidate[],\n servers: MCPServerInfo[],\n priority: number\n): void {\n for (const server of servers) {\n target.push({ server, priority });\n }\n}\n\n/**\n * Check Claude Code's credential store for servers that still have\n * `authStatus === 'unknown'` after checking Shipyard's own token store.\n * If Claude Code has a valid (non-expired) token, mark the server as authenticated.\n */\nasync function resolveClaudeCodeAuthStatuses(servers: Map<string, MCPServerInfo>): Promise<void> {\n for (const server of servers.values()) {\n if (server.authStatus !== 'unknown') continue;\n if (server.type === 'stdio') continue;\n const ccCreds = await readClaudeCodeCredentials(server.name, server.url ?? '');\n if (ccCreds?.accessToken && ccCreds.expiresAt && ccCreds.expiresAt > Date.now()) {\n server.authStatus = 'authenticated';\n }\n }\n}\n\nfunction resolveStdioAuthStatus(\n server: MCPServerInfo,\n tokens: Record<string, import('../mcp/token-store.js').MCPOAuthToken>,\n tokenStore: import('../mcp/token-store.js').MCPTokenStore\n): void {\n if (!server.env) return;\n for (const value of Object.values(server.env)) {\n const match = VAULT_REF_PATTERN.exec(value);\n if (!match) continue;\n const vaultKey = match[1] ?? '';\n const token = tokens[vaultKey];\n if (token) {\n server.authStatus = tokenStore.isExpired(token) ? 'unauthenticated' : 'authenticated';\n return;\n }\n }\n}\n\nasync function resolveAuthStatuses(\n servers: Map<string, MCPServerInfo>,\n tokenStore: import('../mcp/token-store.js').MCPTokenStore\n): Promise<void> {\n const tokens = await tokenStore.getAllTokens();\n for (const server of servers.values()) {\n if (server.type === 'stdio') {\n resolveStdioAuthStatus(server, tokens, tokenStore);\n continue;\n }\n const token = tokens[server.name];\n if (token) {\n server.authStatus = tokenStore.isExpired(token) ? 'unauthenticated' : 'authenticated';\n }\n }\n\n await resolveClaudeCodeAuthStatuses(servers);\n}\n\n/**\n * Detect MCP servers across user/project/plugin/claude.ai sources.\n *\n * `lastKnown` preserves the previously-detected MCP server list when the\n * detection itself throws (rare — readMCPConfig swallows its own errors —\n * but defense in depth against future regressions and unhandled rejections\n * inside `fetchClaudeAiIntegrations`). Mirror of the `lastKnownAgents`\n * pattern in `agents.ts`.\n *\n * Note: under normal operation, errors are swallowed inside the per-file\n * readers, so this function reaches the dedupe step with a possibly-empty\n * but well-shaped `allServers`. The `try/catch` here is the chokepoint that\n * catches anything escaping that — particularly account-integrations\n * fetches that hit the network.\n */\nexport async function detectMCPServers(\n environments: GitRepoInfo[],\n tokenStore: import('../mcp/token-store.js').MCPTokenStore | undefined,\n log: Log,\n lastKnown: MCPServerInfo[] | undefined,\n activeRuntimeId: string | undefined,\n /**\n * Gates the `fetchClaudeAiIntegrations` network fetch (Connectors page on\n * claude.ai). The fetch only succeeds with claude.ai OAuth tokens; for\n * console / api-key / SSO users it 401s and floods logs every refresh tick.\n *\n * Skipped when the user's chosen method is anything other than 'claude-ai'.\n * `null`/`undefined` (preference unknown — not yet backfilled) keeps the\n * current behaviour of attempting the fetch so OAuth users still get their\n * connectors before the boot-time backfill lands.\n */\n preferredAuth?: AnthropicAuthMethod | null,\n /**\n * Incremental scope: when set, only the project-level files at\n * `changedCwd` are re-scanned. Project entries from all OTHER environments\n * are reused from `lastKnown`. The global slices (user / plugin /\n * claude.ai) are still re-scanned in full because they're cheap and\n * commonly change between refreshes.\n *\n * Wired through `refreshMcpServers` from `buildCwdScopedPatch` so the\n * worktree-creation cwd refresh skips the O(envs) project scan that\n * dominated the 1.3-2.5s scoped-refresh budget on a 180-worktree machine.\n * When unset (production default for full refresh and capability\n * watcher), behaviour matches the pre-incremental path.\n */\n changedCwd?: string\n): Promise<MCPServerInfo[]> {\n try {\n return await detectMCPServersInner(\n environments,\n tokenStore,\n log,\n activeRuntimeId,\n preferredAuth,\n changedCwd,\n lastKnown\n );\n } catch (err) {\n /**\n * Preserve the original \"always resolves\" contract: inner readers\n * already swallow per-file errors and return [], so this catch only\n * fires for unhandled rejections (network failure in\n * `fetchClaudeAiIntegrations`, future regressions). Returning\n * lastKnown if available, otherwise [] — never rethrow, because the\n * Promise.all in detectCapabilities would tear down the whole refresh\n * for a single slice's failure.\n */\n const { logger } = await import('../logger.js');\n if (lastKnown && lastKnown.length > 0) {\n logger.warn(\n { err, lastKnownCount: lastKnown.length },\n 'detectMCPServers threw — preserving lastKnown'\n );\n return lastKnown;\n }\n logger.debug({ err }, 'detectMCPServers threw with no lastKnown — returning []');\n return [];\n }\n}\n\n/** Build a display label like \"Claude Code User\" or \"Codex Local\" for a runtime-owned config scope. */\nfunction runtimeScopeLabel(runtimeId: string, scope: string): string {\n return `${getRuntimeDisplayName(runtimeId)} ${scope}`;\n}\n\n/**\n * Priority used when injecting `lastKnown` entries into the candidate pool\n * for the incremental cwd path. Project-tier sources are mapped to the\n * same priority a fresh per-env scan would assign so the dedupe matches\n * the non-incremental output for environments we deliberately skipped.\n *\n * Globals (claudeai/plugin/user) keep their original priorities; their\n * fresh re-scan ALWAYS runs in the incremental path, so the lastKnown\n * entry is overwritten via `>=` regardless of exact mapping. Mapping is\n * still provided for correctness when `lastKnown` includes a global\n * source that the fresh scan happens to miss (e.g. transient claude.ai\n * fetch failure).\n */\nconst SOURCE_PRIORITY_FOR_LASTKNOWN: Record<MCPServerSource, number> = {\n claudeai: 10,\n plugin: 20,\n user: 30,\n project: 50,\n local: 60,\n 'mcp-json': 70,\n};\n\n/**\n * Seed the candidate pool with prior-known entries for the incremental\n * scan path. Must be called BEFORE any fresh-scan candidates are pushed\n * so the fresh entries dedupe-win via the `>=` priority comparison in\n * the dedupe loop.\n */\nfunction seedLastKnownCandidates(\n target: DetectedServerCandidate[],\n lastKnown: MCPServerInfo[] | undefined\n): void {\n if (!lastKnown) return;\n for (const server of lastKnown) {\n target.push({ server, priority: SOURCE_PRIORITY_FOR_LASTKNOWN[server.source] });\n }\n}\n\n/**\n * Read the four project-tier config files for a single environment in\n * parallel. Extracted from `detectMCPServersInner` so that function stays\n * under the cognitive-complexity ceiling — the per-file metadata blocks\n * are unwieldy inline.\n */\nfunction readEnvProjectMcpFiles(\n env: GitRepoInfo,\n log: Log\n): Promise<[MCPServerInfo[], MCPServerInfo[], MCPServerInfo[], MCPServerInfo[]]> {\n const projectPath = join(env.path, '.claude', 'settings.json');\n const localPath = join(env.path, '.claude', 'settings.local.json');\n const codexProjectPath = join(env.path, '.codex', 'config.toml');\n const mcpJsonPath = join(env.path, '.mcp.json');\n return Promise.all([\n readMCPConfig(projectPath, 'project', log, {\n runtimeId: CLAUDE_CODE_RUNTIME_ID,\n sourceLabel: runtimeScopeLabel(CLAUDE_CODE_RUNTIME_ID, 'Project'),\n sourcePath: '.claude/settings.json',\n }),\n readMCPConfig(localPath, 'local', log, {\n runtimeId: CLAUDE_CODE_RUNTIME_ID,\n sourceLabel: runtimeScopeLabel(CLAUDE_CODE_RUNTIME_ID, 'Local'),\n sourcePath: '.claude/settings.local.json',\n }),\n readCodexMCPConfig(codexProjectPath, 'project', log, {\n runtimeId: CODEX_RUNTIME_ID,\n sourceLabel: 'Codex Project',\n sourcePath: '.codex/config.toml',\n }),\n readMCPConfig(mcpJsonPath, 'mcp-json', log, {\n sourceLabel: 'Project',\n sourcePath: '.mcp.json',\n }),\n ]);\n}\n\nasync function detectMCPServersInner(\n environments: GitRepoInfo[],\n tokenStore: import('../mcp/token-store.js').MCPTokenStore | undefined,\n log: Log,\n activeRuntimeId: string | undefined,\n preferredAuth: AnthropicAuthMethod | null | undefined,\n changedCwd: string | undefined,\n lastKnown: MCPServerInfo[] | undefined\n): Promise<MCPServerInfo[]> {\n const allCandidates: DetectedServerCandidate[] = [];\n\n /**\n * Incremental path: seed the candidate pool with prior-known entries so\n * project-tier servers from environments we won't re-scan persist.\n *\n * Note: project-tier removals at `changedCwd` are NOT propagated here —\n * seeded entries for that path survive if the fresh scan doesn't produce\n * a same-named replacement. This is safe because `changedCwd` is only\n * supplied on worktree **creation** (new path, no prior servers to remove).\n * Removals from existing paths arrive via `capability_watcher_mcp` which\n * calls `detectMCPServers` without `changedCwd` (full rescan).\n */\n if (changedCwd) seedLastKnownCandidates(allCandidates, lastKnown);\n\n const userSettingsPath = join(homedir(), '.claude', 'settings.json');\n const userLocalSettingsPath = join(homedir(), '.claude', 'settings.local.json');\n const userCodexConfigPath = join(homedir(), '.codex', 'config.toml');\n const userMcpJsonPath = join(homedir(), '.mcp.json');\n const userClaudeJsonPath = join(homedir(), '.claude.json');\n const codexHome = join(homedir(), '.codex');\n\n /**\n * Only the claude.ai OAuth flow exposes the Connectors page that\n * `fetchClaudeAiIntegrations` reads. Console / api-key / SSO / cloud-provider\n * users would 401 and produce log noise on every refresh tick. `null` (not\n * yet backfilled) and `undefined` (caller didn't plumb through) keep the\n * legacy behaviour to preserve OAuth users' connectors during rollout.\n */\n const fetchClaudeAi = shouldFetchClaudeAiIntegrations(preferredAuth);\n const claudeAiPromise: Promise<MCPServerInfo[]> = fetchClaudeAi\n ? fetchClaudeAiIntegrations(log)\n : Promise.resolve([]);\n if (!fetchClaudeAi) {\n log({ event: 'claudeai_integrations_skipped_by_method', method: preferredAuth ?? null });\n }\n\n /**\n * Read the Codex config once so we can pass it to both the MCP reader and\n * the plugin-marketplace traversal without hitting disk twice.\n */\n const codexUserConfig = await readCodexTomlConfig(userCodexConfigPath);\n\n const [\n userServers,\n userLocalServers,\n userClaudeJsonServers,\n userCodexServers,\n userMcpJsonServers,\n pluginServers,\n codexPluginServers,\n claudeAiServers,\n ] = await Promise.all([\n readMCPConfig(userSettingsPath, 'user', log, {\n runtimeId: CLAUDE_CODE_RUNTIME_ID,\n sourceLabel: runtimeScopeLabel(CLAUDE_CODE_RUNTIME_ID, 'User'),\n sourcePath: '~/.claude/settings.json',\n }),\n readMCPConfig(userLocalSettingsPath, 'user', log, {\n runtimeId: CLAUDE_CODE_RUNTIME_ID,\n sourceLabel: runtimeScopeLabel(CLAUDE_CODE_RUNTIME_ID, 'Local'),\n sourcePath: '~/.claude/settings.local.json',\n }),\n readMCPConfig(userClaudeJsonPath, 'user', log, {\n runtimeId: CLAUDE_CODE_RUNTIME_ID,\n sourceLabel: runtimeScopeLabel(CLAUDE_CODE_RUNTIME_ID, 'User'),\n sourcePath: '~/.claude.json',\n }),\n readCodexMCPConfig(userCodexConfigPath, 'user', log, {\n runtimeId: 'codex',\n sourceLabel: 'Codex User',\n sourcePath: '~/.codex/config.toml',\n }),\n readMCPConfig(userMcpJsonPath, 'user', log, {\n sourceLabel: 'User',\n sourcePath: '~/.mcp.json',\n }),\n readPluginMCPServers(log),\n codexUserConfig ? detectCodexPluginMcps(codexHome, codexUserConfig, log) : Promise.resolve([]),\n claudeAiPromise,\n ]);\n pushCandidates(allCandidates, claudeAiServers, 10);\n pushCandidates(allCandidates, pluginServers, 20);\n pushCandidates(allCandidates, codexPluginServers, 25);\n pushCandidates(allCandidates, userServers, 30);\n pushCandidates(allCandidates, userLocalServers, 31);\n pushCandidates(allCandidates, userClaudeJsonServers, 32);\n pushCandidates(allCandidates, userCodexServers, 33);\n pushCandidates(allCandidates, userMcpJsonServers, 40);\n\n /**\n * Incremental path: only the env at `changedCwd` is re-scanned. All other\n * environments' project-tier entries come from the `lastKnown` seed above.\n */\n const envsToScan = changedCwd\n ? environments.filter((env) => env.path === changedCwd)\n : environments;\n const envResults = await Promise.all(envsToScan.map((env) => readEnvProjectMcpFiles(env, log)));\n for (const [projectServers, localServers, codexProjectServers, mcpJsonServers] of envResults) {\n pushCandidates(allCandidates, projectServers, 50);\n pushCandidates(allCandidates, localServers, 60);\n pushCandidates(allCandidates, codexProjectServers, 65);\n pushCandidates(allCandidates, mcpJsonServers, 70);\n }\n\n const deduped = new Map<string, DetectedServerCandidate>();\n for (const candidate of allCandidates) {\n if (\n activeRuntimeId &&\n candidate.server.runtimeId &&\n candidate.server.runtimeId !== activeRuntimeId\n ) {\n continue;\n }\n const existing = deduped.get(candidate.server.name);\n if (!existing || candidate.priority >= existing.priority) {\n deduped.set(candidate.server.name, candidate);\n }\n }\n\n if (tokenStore) {\n await resolveAuthStatuses(\n new Map([...deduped.entries()].map(([name, candidate]) => [name, candidate.server])),\n tokenStore\n );\n }\n\n return [...deduped.values()].map((candidate) => candidate.server);\n}\n","import { execFile as execFileCb } from 'node:child_process';\nimport { readFile } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { promisify } from 'node:util';\nimport type { OAuthDiscoveryState } from '@modelcontextprotocol/sdk/client/auth.js';\nimport { z } from 'zod';\n\nimport { logger } from '../logger.js';\nimport { DiscoveryStateSchema } from './schemas.js';\n\nconst execFile = promisify(execFileCb);\n\n/**\n * Represents a single OAuth entry from Claude Code's credential store.\n *\n * Claude Code v2.1.81+ stores credentials in macOS Keychain under\n * service name \"Claude Code-credentials\". Older versions used\n * `~/.claude/.credentials.json`.\n *\n * The format is owned by Claude Code and may change without notice.\n * All fields are optional to tolerate schema drift.\n */\nexport interface ClaudeCodeOAuthEntry {\n serverName: string;\n serverUrl: string;\n clientId?: string;\n clientSecret?: string;\n accessToken?: string;\n refreshToken?: string;\n expiresAt?: number;\n discoveryState?: OAuthDiscoveryState;\n}\n\n/**\n * Schema for a single entry in `mcpOAuth`. We use `.passthrough()` so\n * unknown future fields are preserved.\n *\n * CC stores `null` for cleared fields (e.g., `accessToken: null` after\n * logout). Zod's `.optional()` only accepts `undefined`, not `null`,\n * so we must use `.nullable().optional()` to tolerate both.\n */\nconst ClaudeCodeOAuthEntrySchema = z\n .object({\n serverName: z.string().nullable().optional(),\n serverUrl: z.string().nullable().optional(),\n clientId: z.string().nullable().optional(),\n clientSecret: z.string().nullable().optional(),\n accessToken: z.string().nullable().optional(),\n refreshToken: z.string().nullable().optional(),\n expiresAt: z.number().nullable().optional(),\n discoveryState: DiscoveryStateSchema.nullable().optional(),\n })\n .passthrough();\n\nconst CredentialsFileSchema = z\n .object({\n mcpOAuth: z.record(z.string(), z.unknown()).optional(),\n })\n .passthrough();\n\nconst CREDENTIALS_PATH = join(homedir(), '.claude', '.credentials.json');\nconst KEYCHAIN_SERVICE = 'Claude Code-credentials';\n\nconst PluginMcpJsonSchema = z.record(\n z.string(),\n z\n .object({\n url: z.string().optional(),\n oauth: z.object({ clientId: z.string(), callbackPort: z.number().optional() }).optional(),\n })\n .passthrough()\n);\n\n/**\n * Read OAuth clientIds from CC plugin `.mcp.json` files.\n * Returns a Map from server URL to clientId.\n */\nasync function extractClientIdsFromPlugin(\n installPath: string,\n result: Map<string, string>\n): Promise<void> {\n const raw = await readFile(join(installPath, '.mcp.json'), 'utf-8');\n const json: unknown = JSON.parse(raw);\n const outer = z.record(z.string(), z.unknown()).safeParse(json);\n if (!outer.success) return;\n const servers = outer.data.mcpServers ?? outer.data;\n const validated = PluginMcpJsonSchema.safeParse(servers);\n if (!validated.success) return;\n\n for (const config of Object.values(validated.data)) {\n if (config.url && config.oauth?.clientId) {\n result.set(config.url, config.oauth.clientId);\n }\n }\n}\n\nexport async function readPluginClientIds(): Promise<Map<string, string>> {\n const result = new Map<string, string>();\n try {\n const installedPath = join(homedir(), '.claude', 'plugins', 'installed_plugins.json');\n const InstalledPluginsSchema = z.object({\n plugins: z\n .record(z.string(), z.array(z.object({ installPath: z.string().optional() })))\n .optional(),\n });\n const parsed = InstalledPluginsSchema.safeParse(\n JSON.parse(await readFile(installedPath, 'utf-8'))\n );\n if (!parsed.success || !parsed.data.plugins) return result;\n\n for (const installs of Object.values(parsed.data.plugins)) {\n const installPath = installs?.[0]?.installPath;\n if (!installPath) continue;\n await extractClientIdsFromPlugin(installPath, result).catch(() => {});\n }\n } catch {}\n return result;\n}\n\n/**\n * Attempt to read Claude Code's credential blob from macOS Keychain.\n * Returns the parsed JSON object or null if unavailable.\n */\nasync function readKeychainCredentials(): Promise<Record<string, unknown> | null> {\n if (process.platform !== 'darwin') return null;\n\n try {\n const { stdout } = await execFile(\n 'security',\n ['find-generic-password', '-s', KEYCHAIN_SERVICE, '-w'],\n { timeout: 5_000 }\n );\n const result = z.record(z.string(), z.unknown()).safeParse(JSON.parse(stdout.trim()));\n return result.success ? result.data : null;\n } catch {\n return null;\n }\n}\n\n/**\n * Search a parsed credentials object for the best MCP OAuth entry\n * matching the given `serverUrl`. Prefers entries with an access token;\n * separately gathers clientId from any matching entry.\n *\n * CC stores entries under both bare (`slack`) and plugin-prefixed\n * (`plugin:slack:slack`) keys. The prefixed entries typically have\n * tokens while the bare entries may only have clientId. We merge\n * across all matching entries to get the most complete picture.\n */\n/** Scan a flat mcpOAuth record for entries matching serverUrl. */\nfunction scanEntries(\n mcpOAuth: Record<string, unknown>,\n serverUrl: string\n): {\n bestWithToken: z.infer<typeof ClaudeCodeOAuthEntrySchema> | null;\n clientId?: string;\n clientSecret?: string;\n discoveryState?: OAuthDiscoveryState;\n} {\n let bestWithToken: z.infer<typeof ClaudeCodeOAuthEntrySchema> | null = null;\n let clientId: string | undefined;\n let clientSecret: string | undefined;\n let discoveryState: OAuthDiscoveryState | undefined;\n\n for (const value of Object.values(mcpOAuth)) {\n const entryResult = ClaudeCodeOAuthEntrySchema.safeParse(value);\n if (!entryResult.success) continue;\n\n const entry = entryResult.data;\n if (entry.serverUrl !== serverUrl) continue;\n\n if (entry.clientId && !clientId) {\n clientId = entry.clientId;\n clientSecret = entry.clientSecret || undefined;\n }\n if (entry.discoveryState && !discoveryState) {\n discoveryState = entry.discoveryState;\n }\n if (entry.accessToken && !bestWithToken) {\n bestWithToken = entry;\n }\n }\n\n return { bestWithToken, clientId, clientSecret, discoveryState };\n}\n\nfunction findMatchingEntry(\n credentialsData: Record<string, unknown>,\n serverName: string,\n serverUrl: string\n): ClaudeCodeOAuthEntry | null {\n const fileResult = CredentialsFileSchema.safeParse(credentialsData);\n if (!fileResult.success) return null;\n\n const mcpOAuth = fileResult.data.mcpOAuth;\n if (!mcpOAuth) return null;\n\n const { bestWithToken, clientId, clientSecret, discoveryState } = scanEntries(\n mcpOAuth,\n serverUrl\n );\n if (!bestWithToken && !clientId) return null;\n\n return {\n serverName: bestWithToken?.serverName ?? serverName,\n serverUrl,\n clientId: bestWithToken?.clientId || clientId,\n clientSecret: bestWithToken?.clientSecret || clientSecret || undefined,\n accessToken: bestWithToken?.accessToken || undefined,\n refreshToken: bestWithToken?.refreshToken || undefined,\n expiresAt: bestWithToken?.expiresAt ?? undefined,\n discoveryState: bestWithToken?.discoveryState ?? discoveryState ?? undefined,\n };\n}\n\n/**\n * Search Claude Code's credential store for an MCP OAuth entry\n * matching the given `serverUrl`.\n *\n * Tries macOS Keychain first (Claude Code v2.1.81+), then falls back\n * to the legacy file at `~/.claude/.credentials.json`.\n *\n * Keys in the store use hashes we cannot reproduce, so we match\n * by `serverUrl` instead. Returns the first entry whose `serverUrl`\n * matches and that has a non-empty `clientId`.\n */\nexport async function readClaudeCodeCredentials(\n serverName: string,\n serverUrl: string\n): Promise<ClaudeCodeOAuthEntry | null> {\n if (!serverUrl) return null;\n\n try {\n const keychainData = await readKeychainCredentials();\n if (keychainData) {\n const entry = findMatchingEntry(keychainData, serverName, serverUrl);\n if (entry) {\n logger.debug({ serverUrl }, 'Found Claude Code credentials in macOS Keychain');\n return entry;\n }\n }\n } catch (err: unknown) {\n logger.debug({ err }, 'Failed to read Claude Code credentials from Keychain');\n }\n\n try {\n const raw = await readFile(CREDENTIALS_PATH, 'utf-8');\n const fileResult = CredentialsFileSchema.safeParse(JSON.parse(raw));\n if (fileResult.success) {\n const entry = findMatchingEntry(fileResult.data, serverName, serverUrl);\n if (entry) {\n logger.debug({ serverUrl }, 'Found Claude Code credentials in legacy file');\n return entry;\n }\n }\n\n return null;\n } catch (err: unknown) {\n logger.debug({ err }, 'Failed to read Claude Code credentials from file');\n return null;\n }\n}\n","import {\n OAuthMetadataSchema,\n OAuthProtectedResourceMetadataSchema,\n OpenIdProviderDiscoveryMetadataSchema,\n} from '@modelcontextprotocol/sdk/shared/auth.js';\nimport { z } from 'zod';\n\nexport const AuthorizationServerMetadataSchema = z.union([\n OAuthMetadataSchema,\n OpenIdProviderDiscoveryMetadataSchema,\n]);\n\nexport const DiscoveryStateSchema = z\n .object({\n authorizationServerUrl: z.string(),\n authorizationServerMetadata: AuthorizationServerMetadataSchema.optional(),\n resourceMetadata: OAuthProtectedResourceMetadataSchema.optional(),\n resourceMetadataUrl: z.string().optional(),\n })\n .passthrough();\n","import type { McpServerConfig } from '@anthropic-ai/claude-agent-sdk';\nimport type { MCPServerInfo } from '@shipyard/session';\n\nimport type { MCPTokenStore } from './token-store.js';\n\nexport const ENV_VAR_PATTERN = /\\$\\{([^}]+)\\}/g;\nexport const VAULT_REF_PATTERN = /^\\$\\{vault:([^}]+)\\}$/;\n\nexport function interpolateEnvVars(headers: Record<string, string>): Record<string, string> {\n const result: Record<string, string> = {};\n for (const [key, value] of Object.entries(headers)) {\n result[key] = value.replace(\n ENV_VAR_PATTERN,\n (_, varName: string) => process.env[varName] ?? ''\n );\n }\n return result;\n}\n\n/**\n * Pass through user-configured static headers only. OAuth tokens for HTTP\n * servers are managed by the SDK subprocess via CC's Keychain — injecting\n * Shipyard's own tokens here would conflict with the SDK's OAuth transport.\n * Token store injection is only used for stdio env vars (resolveStdioEnv).\n */\nexport function resolveHeaders(server: MCPServerInfo): Record<string, string> | undefined {\n if (!server.headers) return undefined;\n return interpolateEnvVars(server.headers);\n}\n\n/**\n * Shared MCP sources (`runtimeId` unset) apply to every runtime. Runtime-\n * specific sources apply only to the matching runtime.\n */\nexport function isMcpServerCompatibleWithRuntime(\n server: MCPServerInfo,\n runtimeId: string | undefined\n): boolean {\n if (!runtimeId) return true;\n return server.runtimeId === undefined || server.runtimeId === runtimeId;\n}\n\nfunction resolveVaultRefEnvValue(\n key: string,\n value: string,\n store: MCPTokenStore,\n log?: (entry: { event: string; [key: string]: unknown }) => void\n): string | undefined {\n const match = VAULT_REF_PATTERN.exec(value);\n if (!match) return undefined;\n\n const vaultKey = match[1] ?? '';\n const token = store.getTokenSync(vaultKey);\n if (token && !store.isExpired(token)) {\n log?.({\n event: 'vault_ref_resolved',\n envKey: key,\n vaultKey,\n tokenType: token.tokenType,\n hasExpiry: token.expiresAt !== undefined,\n });\n return token.accessToken;\n }\n\n log?.({\n event: 'vault_ref_unresolved',\n envKey: key,\n vaultKey,\n tokenFound: token !== null,\n tokenExpired: token ? store.isExpired(token) : false,\n });\n return value;\n}\n\nexport function resolveStdioEnv(\n env: Record<string, string> | undefined,\n store?: MCPTokenStore,\n log?: (entry: { event: string; [key: string]: unknown }) => void\n): Record<string, string> | undefined {\n if (!env) return undefined;\n if (!store) return env;\n\n const result: Record<string, string> = {};\n let changed = false;\n\n for (const [key, value] of Object.entries(env)) {\n const vaultResolved = resolveVaultRefEnvValue(key, value, store, log);\n if (vaultResolved !== undefined) {\n result[key] = vaultResolved;\n if (vaultResolved !== value) {\n changed = true;\n }\n continue;\n }\n const interpolated = value.replace(\n ENV_VAR_PATTERN,\n (_, varName: string) => process.env[varName] ?? ''\n );\n result[key] = interpolated;\n if (interpolated !== value) {\n changed = true;\n }\n }\n\n return changed ? result : env;\n}\n\nfunction toResolvedMcpServerConfig(\n server: MCPServerInfo,\n mcpTokenStore?: MCPTokenStore\n): McpServerConfig | null {\n if (server.type === 'http' && server.url) {\n return {\n type: 'http',\n url: server.url,\n headers: resolveHeaders(server),\n };\n }\n if (server.type === 'sse' && server.url) {\n return {\n type: 'sse',\n url: server.url,\n headers: resolveHeaders(server),\n };\n }\n if (server.command) {\n return {\n command: server.command,\n args: server.args ?? undefined,\n env: resolveStdioEnv(server.env, mcpTokenStore),\n };\n }\n return null;\n}\n\nexport function resolveEnabledMcpServers(\n overrides: Array<{ name: string; enabled: boolean }> | undefined,\n available: MCPServerInfo[] | undefined,\n mcpTokenStore?: MCPTokenStore,\n runtimeId?: string\n): Record<string, McpServerConfig> | undefined {\n if (!overrides || !available) return undefined;\n const enabledNames = new Set(overrides.filter((o) => o.enabled).map((o) => o.name));\n if (enabledNames.size === 0) return {};\n const result: Record<string, McpServerConfig> = {};\n for (const server of available) {\n if (!isMcpServerCompatibleWithRuntime(server, runtimeId)) continue;\n if (!enabledNames.has(server.name)) continue;\n const config = toResolvedMcpServerConfig(server, mcpTokenStore);\n if (config) result[server.name] = config;\n }\n return result;\n}\n","import { execFile as execFileCb } from 'node:child_process';\nimport { promisify } from 'node:util';\n\nimport { logger } from '../logger.js';\n\nconst execFile = promisify(execFileCb);\n\nexport type AnthropicCredentialsLog = (entry: {\n event: string;\n reason?: string;\n [key: string]: unknown;\n}) => void;\n\nconst defaultLog: AnthropicCredentialsLog = (entry) => logger.debug(entry, entry.event);\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nasync function readKeychainEntry(service: string): Promise<string | null> {\n if (process.platform !== 'darwin') return null;\n try {\n const { stdout } = await execFile('security', ['find-generic-password', '-s', service, '-w'], {\n timeout: 5_000,\n });\n const value = stdout.trim();\n return value || null;\n } catch {\n return null;\n }\n}\n\n/**\n * Read the OAuth access token from the `Claude Code-credentials` Keychain entry,\n * stored as `claudeAiOauth.accessToken` by `claude setup-token` / `claude auth login`.\n *\n * This is the canonical token for talking to claude.ai-hosted services\n * (e.g. `https://api.anthropic.com/v1/mcp_servers`) on behalf of an OAuth-authed user.\n */\nexport async function readKeychainOAuthToken(\n log: AnthropicCredentialsLog = defaultLog\n): Promise<string | null> {\n if (process.platform !== 'darwin') {\n log({ event: 'anthropic_keychain_read_skipped', reason: 'non_darwin' });\n return null;\n }\n const raw = await readKeychainEntry('Claude Code-credentials');\n if (!raw) {\n log({ event: 'anthropic_keychain_oauth_missing', reason: 'no_credentials_entry' });\n return null;\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n log({ event: 'anthropic_keychain_oauth_corrupt', reason: 'json_parse_error' });\n return null;\n }\n if (!isRecord(parsed)) {\n log({ event: 'anthropic_keychain_oauth_corrupt', reason: 'json_parse_error' });\n return null;\n }\n const oauth = parsed.claudeAiOauth;\n if (!isRecord(oauth)) {\n log({ event: 'anthropic_keychain_oauth_missing', reason: 'no_oauth_field' });\n return null;\n }\n const token = oauth.accessToken;\n if (typeof token !== 'string' || token.length === 0) {\n log({ event: 'anthropic_keychain_oauth_missing', reason: 'no_oauth_field' });\n return null;\n }\n return token;\n}\n\n/**\n * Read the managed API key from the `Claude Code` Keychain entry —\n * the value stored by `claude login` for direct Messages API calls\n * (paired with the billing header prefix).\n */\nexport async function readKeychainManagedKey(): Promise<string | null> {\n const value = await readKeychainEntry('Claude Code');\n if (!value || !value.startsWith('sk-')) return null;\n return value;\n}\n","/**\n * Runtime descriptor catalog — knows what runtimes exist, their display\n * names, and how to install them. Auth-shape mapping lives in the\n * sibling `auth-adapters.ts` module (single source of truth keyed by\n * profile id) so this file stays focused on the *runtime* concern.\n */\n\nexport const CLAUDE_CODE_RUNTIME_ID = 'claude-code' as const;\nexport const CODEX_RUNTIME_ID = 'codex' as const;\nexport const CURSOR_RUNTIME_ID = 'cursor' as const;\n\nexport interface RuntimeInstallCommand {\n command: string;\n args: string[];\n}\n\nexport interface RuntimeDescriptor {\n runtimeId: string;\n providerId: string;\n displayName: string;\n buildInstallCommand(platform: NodeJS.Platform): RuntimeInstallCommand | null;\n}\n\nconst CLAUDE_RUNTIME: RuntimeDescriptor = {\n runtimeId: CLAUDE_CODE_RUNTIME_ID,\n providerId: 'anthropic',\n displayName: 'Claude Code',\n buildInstallCommand(platform) {\n if (platform === 'win32') {\n return {\n command: 'powershell',\n args: ['-Command', 'irm https://claude.ai/install.ps1 | iex'],\n };\n }\n return {\n command: 'bash',\n args: ['-c', 'curl -fsSL https://claude.ai/install.sh | bash'],\n };\n },\n};\n\nconst CODEX_RUNTIME: RuntimeDescriptor = {\n runtimeId: CODEX_RUNTIME_ID,\n providerId: 'openai',\n displayName: 'Codex',\n buildInstallCommand(platform) {\n return {\n command: platform === 'win32' ? 'npm.cmd' : 'npm',\n args: ['install', '-g', '@openai/codex'],\n };\n },\n};\n\nconst CURSOR_RUNTIME: RuntimeDescriptor = {\n runtimeId: CURSOR_RUNTIME_ID,\n providerId: 'cursor',\n displayName: 'Cursor',\n buildInstallCommand() {\n /** Cursor ships bundled with Shipyard via `@cursor/sdk` — no external install path. */\n return null;\n },\n};\n\nconst RUNTIME_DESCRIPTORS = new Map<string, RuntimeDescriptor>([\n [CLAUDE_RUNTIME.runtimeId, CLAUDE_RUNTIME],\n [CODEX_RUNTIME.runtimeId, CODEX_RUNTIME],\n [CURSOR_RUNTIME.runtimeId, CURSOR_RUNTIME],\n]);\n\nexport function getRuntimeDescriptor(runtimeId: string): RuntimeDescriptor | null {\n return RUNTIME_DESCRIPTORS.get(runtimeId) ?? null;\n}\n\nexport function getRuntimeDisplayName(runtimeId: string): string {\n const known = getRuntimeDescriptor(runtimeId);\n if (known) return known.displayName;\n return runtimeId\n .split(/[-_]/)\n .map((word) => word.charAt(0).toUpperCase() + word.slice(1))\n .join(' ');\n}\n\n/**\n * `buildRuntimeAuthMap` is intentionally re-exported via the parent\n * `shared/capabilities/index.ts` barrel rather than here — re-exporting\n * from `./auth-adapters.js` at the bottom of this module would create a\n * circular import (`auth-adapters.ts` imports the runtime-id constants\n * declared above). Callers should import from `../index.js` or from\n * `./auth-adapters.js` directly.\n */\n","import type { MCPServerInfo } from '@shipyard/session';\n\nimport { readKeychainOAuthToken } from '../auth/anthropic-credentials.js';\nimport { CLAUDE_CODE_RUNTIME_ID } from './runtime/catalog.js';\n\ntype Log = (entry: { event: string; [key: string]: unknown }) => void;\n\nconst CLAUDEAI_INTEGRATIONS_URL = 'https://api.anthropic.com/v1/mcp_servers?limit=1000';\nconst ERROR_BODY_MAX_CHARS = 256;\n\ntype FetchFailureClass = 'http_4xx' | 'http_5xx' | 'http_other' | 'network' | 'timeout' | 'parse';\n\n/**\n * Minimal shape we expect from the Anthropic MCP servers API response.\n * Hand-rolled instead of Zod to keep best-effort parsing lightweight.\n */\nexport function parseClaudeAiServers(\n data: unknown,\n log?: Log\n): Array<{ name: string; url: string }> {\n if (typeof data !== 'object' || data === null) return [];\n // eslint-disable-next-line no-restricted-syntax -- JSON.parse returns unknown; manually validated below\n const obj = data as Record<string, unknown>;\n if (!Array.isArray(obj.data)) return [];\n const results: Array<{ name: string; url: string }> = [];\n let legacyNameCount = 0;\n for (const entry of obj.data) {\n if (typeof entry !== 'object' || entry === null) continue;\n // eslint-disable-next-line no-restricted-syntax -- narrowing unknown entry fields\n const rec = entry as Record<string, unknown>;\n if (typeof rec.url !== 'string') continue;\n /**\n * Anthropic's API returns `display_name` on MCP server records. We prefer\n * that field but fall back to `name` for forward-compat in case the API\n * ever switches back, and so that synthetic test fixtures using the\n * legacy shape continue to parse.\n */\n if (typeof rec.display_name === 'string') {\n results.push({ name: rec.display_name, url: rec.url });\n } else if (typeof rec.name === 'string') {\n legacyNameCount += 1;\n results.push({ name: rec.name, url: rec.url });\n }\n }\n if (legacyNameCount > 0 && log) {\n log({\n event: 'claudeai_integrations_legacy_name_fallback',\n count: legacyNameCount,\n });\n }\n return results;\n}\n\nfunction classifyHttpStatus(status: number): FetchFailureClass {\n if (status >= 400 && status < 500) return 'http_4xx';\n if (status >= 500 && status < 600) return 'http_5xx';\n return 'http_other';\n}\n\nfunction classifyException(err: unknown): FetchFailureClass {\n if (err instanceof Error) {\n if (err.name === 'AbortError') return 'timeout';\n if (err.name === 'SyntaxError') return 'parse';\n }\n return 'network';\n}\n\n/**\n * Best-effort tail-readers for the failure body. We do NOT log Authorization\n * headers or other request metadata — the URL is a stable public endpoint and\n * the body is the model's own error envelope, so neither carries the user's\n * OAuth token. Truncated to bound log payload size.\n */\nasync function readErrorBody(response: Response): Promise<string | null> {\n try {\n const text = await response.text();\n if (text.length === 0) return null;\n return text.length <= ERROR_BODY_MAX_CHARS ? text : text.slice(0, ERROR_BODY_MAX_CHARS);\n } catch {\n return null;\n }\n}\n\n/**\n * Fetch MCP server integrations from the user's Claude.ai account.\n * Best-effort: returns [] on any failure.\n */\nexport async function fetchClaudeAiIntegrations(log: Log): Promise<MCPServerInfo[]> {\n const url = CLAUDEAI_INTEGRATIONS_URL;\n try {\n const token = await readKeychainOAuthToken(log);\n if (!token) {\n log({ event: 'claudeai_integrations_skipped', reason: 'no_oauth_token' });\n return [];\n }\n\n const controller = new AbortController();\n /**\n * 5s cap on the claude.ai Connectors fetch. Anthropic's API normally\n * responds in <500ms; 10s was generous slack that singlehandedly added\n * up to 10s of boot stall when the API hung. 5s is the same bound used\n * by the sibling auth-status timeout. See issue #3267.\n */\n const timeout = setTimeout(() => controller.abort(), 5_000);\n\n try {\n const response = await fetch(url, {\n headers: {\n Authorization: `Bearer ${token}`,\n 'anthropic-version': '2023-06-01',\n 'anthropic-beta': 'mcp-servers-2025-12-04',\n },\n signal: controller.signal,\n });\n\n if (!response.ok) {\n const errorBody = await readErrorBody(response);\n log({\n event: 'claudeai_integrations_fetch_failed',\n url,\n status: response.status,\n errorClass: classifyHttpStatus(response.status),\n errorBody,\n });\n return [];\n }\n\n const body: unknown = await response.json();\n const servers = parseClaudeAiServers(body, log);\n\n return servers.map((s) => ({\n name: s.name,\n type: 'http' as const,\n runtimeId: CLAUDE_CODE_RUNTIME_ID,\n url: s.url,\n enabled: true,\n source: 'claudeai' as const,\n authStatus: 'unknown' as const,\n }));\n } finally {\n clearTimeout(timeout);\n }\n } catch (err: unknown) {\n log({\n event: 'claudeai_integrations_fetch_failed',\n url,\n status: null,\n errorClass: classifyException(err),\n errorBody: null,\n error: err instanceof Error ? err.message : String(err),\n });\n return [];\n }\n}\n"],"mappings":"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","names":["readFile","homedir","join","execFileCb","promisify","execFile","promisify","execFileCb","readFile","join","homedir","logger"]}
@@ -0,0 +1,134 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ external_exports
4
+ } from "./chunk-CNR7O5YH.js";
5
+
6
+ // src/shared/env.ts
7
+ import { homedir } from "os";
8
+ import { join } from "path";
9
+ process.env.CLAUDE_CODE_ENABLE_FINE_GRAINED_TOOL_STREAMING = "1";
10
+ process.env.CLAUDE_CODE_ENABLE_TASKS = "1";
11
+ function isDevMode() {
12
+ return process.env.SHIPYARD_DEV === "1" || process.env.SHIPYARD_DEV === "true";
13
+ }
14
+ function isVanillaAgentMode() {
15
+ return process.env.SHIPYARD_VANILLA_AGENT === "1" || process.env.SHIPYARD_VANILLA_AGENT === "true";
16
+ }
17
+ function shipyardDirName() {
18
+ return isDevMode() ? ".shipyard-dev" : ".shipyard";
19
+ }
20
+ function getShipyardHome() {
21
+ if (process.env.SHIPYARD_HOME) return process.env.SHIPYARD_HOME;
22
+ return join(homedir(), shipyardDirName());
23
+ }
24
+ var EnvSchema = external_exports.object({
25
+ ANTHROPIC_API_KEY: external_exports.string().min(1).optional(),
26
+ SHIPYARD_DEV: external_exports.enum(["1", "true", "0", "false", ""]).optional().transform((v) => v === "1" || v === "true"),
27
+ SHIPYARD_DAEMON_CHILD: external_exports.enum(["1", "true", "0", "false", ""]).optional().transform((v) => v === "1" || v === "true"),
28
+ SHIPYARD_DATA_DIR: external_exports.string().optional().transform((v) => v ?? `~/${shipyardDirName()}/data`),
29
+ LOG_LEVEL: external_exports.enum(["debug", "info", "warn", "error"]).default("warn"),
30
+ SHIPYARD_SIGNALING_URL: external_exports.string().url().optional(),
31
+ SHIPYARD_METRICS_WORKER_URL: external_exports.string().url().default("https://shipyard-metrics.jacob-191.workers.dev"),
32
+ SHIPYARD_TELEMETRY: external_exports.enum(["1", "true", "0", "false", ""]).optional().transform((v) => v !== "0" && v !== "false"),
33
+ SHIPYARD_USER_TOKEN: external_exports.string().optional(),
34
+ SHIPYARD_USER_ID: external_exports.string().optional(),
35
+ SHIPYARD_MACHINE_ID: external_exports.string().optional(),
36
+ SHIPYARD_MACHINE_NAME: external_exports.string().optional(),
37
+ SHIPYARD_USER_DISPLAY_NAME: external_exports.string().optional(),
38
+ SHIPYARD_HOME: external_exports.string().optional(),
39
+ SHIPYARD_WORKTREE_NAME: external_exports.string().optional(),
40
+ /**
41
+ * Electron utility transport mode. Absent for the CLI daemon; set by
42
+ * apps/electron/src/main/daemon-host.ts when the daemon runs as an Electron
43
+ * utility process.
44
+ */
45
+ SHIPYARD_TRANSPORT: external_exports.enum(["electron-port"]).optional(),
46
+ /**
47
+ * Build artifact hosting this daemon. Electron disables the daemon's npm
48
+ * self-updater because electron-updater owns app replacement.
49
+ */
50
+ SHIPYARD_ARTIFACT: external_exports.enum(["electron"]).optional(),
51
+ SHIPYARD_VANILLA_AGENT: external_exports.enum(["1", "true", "0", "false", ""]).optional().transform((v) => v === "1" || v === "true"),
52
+ SHIPYARD_STALL_PROFILER_ENABLED: external_exports.enum(["1", "true", "0", "false", ""]).optional().transform((v) => v !== "0" && v !== "false"),
53
+ /**
54
+ * Event-loop stall threshold (ms). Once `histogram.max` crosses this,
55
+ * the profiler starts a CPU profile. Raised from 250ms to 5000ms in
56
+ * May 2026 to prevent the profiler from amplifying the stalls it was
57
+ * trying to measure — V8 inspector `post()` itself consumed ~493ms of
58
+ * a measured 800ms capture window. At 5s only severe stalls are profiled.
59
+ * Override via `SHIPYARD_STALL_PROFILER_THRESHOLD_MS`.
60
+ */
61
+ SHIPYARD_STALL_PROFILER_THRESHOLD_MS: external_exports.coerce.number().int().positive().default(5e3),
62
+ /**
63
+ * How long to sample the V8 CPU profile after a stall is detected.
64
+ * Default 250ms — long enough for the post-stall queue drain and the
65
+ * cascading sibling stall (if any) to land in the same profile, but
66
+ * short enough that the capture window does not itself amplify the
67
+ * stall on the same event loop. Lowered from 1000 → 250 in May 2026
68
+ * after profile evidence showed `#capture` consumed ~1s of inclusive
69
+ * time inside a 3.5s stall window — the profiler was contributing
70
+ * to the stalls it was trying to measure once #3204 dropped the
71
+ * threshold from 500ms to 250ms.
72
+ */
73
+ SHIPYARD_STALL_PROFILER_PROFILE_DURATION_MS: external_exports.coerce.number().int().positive().default(250),
74
+ /**
75
+ * Cascading-stall suppression window. After one capture, subsequent
76
+ * stalls within this window log `stall_profile_suppressed` instead of
77
+ * starting a new profile. Raised from 30s to 300s (5min) in May 2026
78
+ * to prevent capture storms — at 30s a recurring stall every 60s would
79
+ * trigger 2 captures/min, each adding hundreds of ms of inspector overhead.
80
+ */
81
+ SHIPYARD_STALL_PROFILER_MIN_INTERVAL_MS: external_exports.coerce.number().int().nonnegative().default(3e5),
82
+ /**
83
+ * Supervisor wedge detection: if the daemon child sends no IPC heartbeats
84
+ * for this many ms, the supervisor declares the event loop wedged, sends
85
+ * SIGUSR2 to capture a CPU profile (the inspector runs off the JS loop),
86
+ * waits 2s, then SIGKILLs and respawns via the normal abnormal-exit flow.
87
+ * Generous default — 30s tolerates GC pauses and other legitimate sync
88
+ * blocks well outside human-noticeable territory.
89
+ */
90
+ SHIPYARD_WEDGE_TIMEOUT_MS: external_exports.coerce.number().int().positive().default(3e4),
91
+ /**
92
+ * Daemon child IPC heartbeat interval. Must be smaller than
93
+ * SHIPYARD_WEDGE_TIMEOUT_MS by a comfortable margin so a single missed
94
+ * heartbeat doesn't trip the wedge detector.
95
+ */
96
+ SHIPYARD_HEARTBEAT_INTERVAL_MS: external_exports.coerce.number().int().positive().default(2e3),
97
+ /**
98
+ * When set to `'electron-safe-storage'`, the vault-key-manager wraps the
99
+ * master key via Electron's safeStorage API (macOS Keychain on macOS,
100
+ * Secret Service on Linux). Set by daemon-host.ts in the Electron main
101
+ * process. When absent, the daemon falls back to the existing AES-256-GCM
102
+ * file-based path (CLI daemon path — unchanged).
103
+ */
104
+ SHIPYARD_VAULT_BACKEND: external_exports.enum(["electron-safe-storage"]).optional(),
105
+ /**
106
+ * TTL (ms) for the per-repo worktree list cache. Browser polls at 4-7/sec;
107
+ * this collapses redundant git subprocess invocations to 1 per TTL window.
108
+ * Set to 0 to disable caching (useful for tests that need fresh results).
109
+ */
110
+ SHIPYARD_WORKTREE_LIST_TTL_MS: external_exports.coerce.number().int().min(0).default(3e4)
111
+ });
112
+ function validateEnv() {
113
+ return EnvSchema.parse(process.env);
114
+ }
115
+ function getStallProfilerConfig() {
116
+ const env = validateEnv();
117
+ const explicitlySet = process.env.SHIPYARD_STALL_PROFILER_ENABLED !== void 0 && process.env.SHIPYARD_STALL_PROFILER_ENABLED !== "";
118
+ const enabled = explicitlySet ? env.SHIPYARD_STALL_PROFILER_ENABLED : env.SHIPYARD_ARTIFACT !== "electron";
119
+ return {
120
+ enabled,
121
+ thresholdMs: env.SHIPYARD_STALL_PROFILER_THRESHOLD_MS,
122
+ captureMs: env.SHIPYARD_STALL_PROFILER_PROFILE_DURATION_MS,
123
+ rateLimitMs: env.SHIPYARD_STALL_PROFILER_MIN_INTERVAL_MS
124
+ };
125
+ }
126
+
127
+ export {
128
+ isDevMode,
129
+ isVanillaAgentMode,
130
+ getShipyardHome,
131
+ validateEnv,
132
+ getStallProfilerConfig
133
+ };
134
+ //# sourceMappingURL=chunk-KITSAHTX.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/shared/env.ts"],"sourcesContent":["import { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { z } from 'zod';\n\n/**\n * Enable fine-grained tool streaming so MCP tool inputs stream token-by-token\n * via input_json_delta events instead of being buffered as complete JSON.\n * Set early so the subprocess inherits it.\n */\nprocess.env.CLAUDE_CODE_ENABLE_FINE_GRAINED_TOOL_STREAMING = '1';\n\n/**\n * Enable the Task system (TaskCreate/TaskGet/TaskList/TaskUpdate) which provides\n * structured task management with dependency tracking (blocks/blockedBy).\n * Replaces TodoWrite — when enabled, CC only has TaskCreate (not TodoWrite).\n */\nprocess.env.CLAUDE_CODE_ENABLE_TASKS = '1';\n\nexport function isDevMode(): boolean {\n return process.env.SHIPYARD_DEV === '1' || process.env.SHIPYARD_DEV === 'true';\n}\n\n/**\n * When enabled, the agent runs with only Shipyard builtins (system prompt,\n * comment + visualize MCP servers) and no user/plugin MCP servers.\n * Useful for testing the harness in isolation without external interference.\n *\n * Set `SHIPYARD_VANILLA_AGENT=1` to activate.\n */\nexport function isVanillaAgentMode(): boolean {\n return (\n process.env.SHIPYARD_VANILLA_AGENT === '1' || process.env.SHIPYARD_VANILLA_AGENT === 'true'\n );\n}\n\n/**\n * Auto-profiler that captures a V8 CPU profile when the event loop stalls\n * past a threshold. Default ON — explicit `0`/`false` disables. Production\n * kill-switch lives in this env var so we can ship the feature broadly and\n * disable on a specific host without redeploying.\n */\nexport function isStallProfilerEnabled(): boolean {\n const v = process.env.SHIPYARD_STALL_PROFILER_ENABLED;\n return v !== '0' && v !== 'false';\n}\n\nfunction shipyardDirName(): string {\n return isDevMode() ? '.shipyard-dev' : '.shipyard';\n}\n\nexport function getShipyardHome(): string {\n if (process.env.SHIPYARD_HOME) return process.env.SHIPYARD_HOME;\n return join(homedir(), shipyardDirName());\n}\n\nexport const EnvSchema = z.object({\n ANTHROPIC_API_KEY: z.string().min(1).optional(),\n SHIPYARD_DEV: z\n .enum(['1', 'true', '0', 'false', ''])\n .optional()\n .transform((v) => v === '1' || v === 'true'),\n SHIPYARD_DAEMON_CHILD: z\n .enum(['1', 'true', '0', 'false', ''])\n .optional()\n .transform((v) => v === '1' || v === 'true'),\n SHIPYARD_DATA_DIR: z\n .string()\n .optional()\n .transform((v) => v ?? `~/${shipyardDirName()}/data`),\n LOG_LEVEL: z.enum(['debug', 'info', 'warn', 'error']).default('warn'),\n SHIPYARD_SIGNALING_URL: z.string().url().optional(),\n SHIPYARD_METRICS_WORKER_URL: z\n .string()\n .url()\n .default('https://shipyard-metrics.jacob-191.workers.dev'),\n SHIPYARD_TELEMETRY: z\n .enum(['1', 'true', '0', 'false', ''])\n .optional()\n .transform((v) => v !== '0' && v !== 'false'),\n SHIPYARD_USER_TOKEN: z.string().optional(),\n SHIPYARD_USER_ID: z.string().optional(),\n SHIPYARD_MACHINE_ID: z.string().optional(),\n SHIPYARD_MACHINE_NAME: z.string().optional(),\n SHIPYARD_USER_DISPLAY_NAME: z.string().optional(),\n SHIPYARD_HOME: z.string().optional(),\n SHIPYARD_WORKTREE_NAME: z.string().optional(),\n /**\n * Electron utility transport mode. Absent for the CLI daemon; set by\n * apps/electron/src/main/daemon-host.ts when the daemon runs as an Electron\n * utility process.\n */\n SHIPYARD_TRANSPORT: z.enum(['electron-port']).optional(),\n /**\n * Build artifact hosting this daemon. Electron disables the daemon's npm\n * self-updater because electron-updater owns app replacement.\n */\n SHIPYARD_ARTIFACT: z.enum(['electron']).optional(),\n SHIPYARD_VANILLA_AGENT: z\n .enum(['1', 'true', '0', 'false', ''])\n .optional()\n .transform((v) => v === '1' || v === 'true'),\n SHIPYARD_STALL_PROFILER_ENABLED: z\n .enum(['1', 'true', '0', 'false', ''])\n .optional()\n .transform((v) => v !== '0' && v !== 'false'),\n /**\n * Event-loop stall threshold (ms). Once `histogram.max` crosses this,\n * the profiler starts a CPU profile. Raised from 250ms to 5000ms in\n * May 2026 to prevent the profiler from amplifying the stalls it was\n * trying to measure — V8 inspector `post()` itself consumed ~493ms of\n * a measured 800ms capture window. At 5s only severe stalls are profiled.\n * Override via `SHIPYARD_STALL_PROFILER_THRESHOLD_MS`.\n */\n SHIPYARD_STALL_PROFILER_THRESHOLD_MS: z.coerce.number().int().positive().default(5_000),\n /**\n * How long to sample the V8 CPU profile after a stall is detected.\n * Default 250ms — long enough for the post-stall queue drain and the\n * cascading sibling stall (if any) to land in the same profile, but\n * short enough that the capture window does not itself amplify the\n * stall on the same event loop. Lowered from 1000 → 250 in May 2026\n * after profile evidence showed `#capture` consumed ~1s of inclusive\n * time inside a 3.5s stall window — the profiler was contributing\n * to the stalls it was trying to measure once #3204 dropped the\n * threshold from 500ms to 250ms.\n */\n SHIPYARD_STALL_PROFILER_PROFILE_DURATION_MS: z.coerce.number().int().positive().default(250),\n /**\n * Cascading-stall suppression window. After one capture, subsequent\n * stalls within this window log `stall_profile_suppressed` instead of\n * starting a new profile. Raised from 30s to 300s (5min) in May 2026\n * to prevent capture storms — at 30s a recurring stall every 60s would\n * trigger 2 captures/min, each adding hundreds of ms of inspector overhead.\n */\n SHIPYARD_STALL_PROFILER_MIN_INTERVAL_MS: z.coerce.number().int().nonnegative().default(300_000),\n /**\n * Supervisor wedge detection: if the daemon child sends no IPC heartbeats\n * for this many ms, the supervisor declares the event loop wedged, sends\n * SIGUSR2 to capture a CPU profile (the inspector runs off the JS loop),\n * waits 2s, then SIGKILLs and respawns via the normal abnormal-exit flow.\n * Generous default — 30s tolerates GC pauses and other legitimate sync\n * blocks well outside human-noticeable territory.\n */\n SHIPYARD_WEDGE_TIMEOUT_MS: z.coerce.number().int().positive().default(30_000),\n /**\n * Daemon child IPC heartbeat interval. Must be smaller than\n * SHIPYARD_WEDGE_TIMEOUT_MS by a comfortable margin so a single missed\n * heartbeat doesn't trip the wedge detector.\n */\n SHIPYARD_HEARTBEAT_INTERVAL_MS: z.coerce.number().int().positive().default(2_000),\n /**\n * When set to `'electron-safe-storage'`, the vault-key-manager wraps the\n * master key via Electron's safeStorage API (macOS Keychain on macOS,\n * Secret Service on Linux). Set by daemon-host.ts in the Electron main\n * process. When absent, the daemon falls back to the existing AES-256-GCM\n * file-based path (CLI daemon path — unchanged).\n */\n SHIPYARD_VAULT_BACKEND: z.enum(['electron-safe-storage']).optional(),\n /**\n * TTL (ms) for the per-repo worktree list cache. Browser polls at 4-7/sec;\n * this collapses redundant git subprocess invocations to 1 per TTL window.\n * Set to 0 to disable caching (useful for tests that need fresh results).\n */\n SHIPYARD_WORKTREE_LIST_TTL_MS: z.coerce.number().int().min(0).default(30_000),\n});\n\nexport type Env = z.infer<typeof EnvSchema>;\n\nexport function validateEnv(): Env {\n return EnvSchema.parse(process.env);\n}\n\n/**\n * Resolved profiler tuning derived from the validated env. Re-parses the\n * schema so a typo in one of the *_MS vars (e.g. `THRESHOLD_MS=abc`) fails\n * here too — boot already failed in `validateEnv()` (start.ts / serve.ts\n * call it), this second parse is cheap and keeps the wiring side free of\n * `process.env.X` reads at the call site.\n */\nexport interface StallProfilerConfig {\n enabled: boolean;\n thresholdMs: number;\n captureMs: number;\n rateLimitMs: number;\n}\n\nexport function getStallProfilerConfig(): StallProfilerConfig {\n const env = validateEnv();\n\n /**\n * Default OFF in packaged Electron when `SHIPYARD_STALL_PROFILER_ENABLED`\n * is unset. Rationale: the V8 inspector `post()` blocks the event loop\n * during capture — in a 2,959ms stall, `node:inspector:117 post` consumed\n * 493.5ms of 800ms captured, i.e. the profiler was profiling itself.\n * Trade: lose production stall visibility in packaged builds in exchange\n * for not amplifying stalls for users. Dev sessions and CLI daemon keep\n * profiling enabled by default. Override with `SHIPYARD_STALL_PROFILER_ENABLED=1`.\n *\n * `env.SHIPYARD_STALL_PROFILER_ENABLED` is `true` when the env var is\n * absent (the Zod transform treats missing/empty as truthy). We detect\n * \"explicitly set\" vs \"defaulted\" by reading `process.env` directly here\n * — the schema transform already validated it, so this read is safe.\n */\n const explicitlySet =\n process.env.SHIPYARD_STALL_PROFILER_ENABLED !== undefined &&\n process.env.SHIPYARD_STALL_PROFILER_ENABLED !== '';\n\n const enabled = explicitlySet\n ? env.SHIPYARD_STALL_PROFILER_ENABLED\n : env.SHIPYARD_ARTIFACT !== 'electron';\n\n return {\n enabled,\n thresholdMs: env.SHIPYARD_STALL_PROFILER_THRESHOLD_MS,\n captureMs: env.SHIPYARD_STALL_PROFILER_PROFILE_DURATION_MS,\n rateLimitMs: env.SHIPYARD_STALL_PROFILER_MIN_INTERVAL_MS,\n };\n}\n"],"mappings":"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","names":[]}