patchwork-os 0.2.0-alpha.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 (721) hide show
  1. package/LICENSE +21 -0
  2. package/README.bridge.md +352 -0
  3. package/README.md +72 -0
  4. package/deploy/README.md +172 -0
  5. package/deploy/bootstrap-new-vps.sh +364 -0
  6. package/deploy/claude-ide-bridge.service.template +67 -0
  7. package/deploy/claude-ide-bridge@.service +31 -0
  8. package/deploy/ecosystem.config.js.example +36 -0
  9. package/deploy/install-vps-service.sh +240 -0
  10. package/deploy/nginx-claude-bridge.conf.template +129 -0
  11. package/dist/activityLog.d.ts +112 -0
  12. package/dist/activityLog.js +399 -0
  13. package/dist/activityLog.js.map +1 -0
  14. package/dist/activityTypes.d.ts +28 -0
  15. package/dist/activityTypes.js +9 -0
  16. package/dist/activityTypes.js.map +1 -0
  17. package/dist/adapters/base.d.ts +78 -0
  18. package/dist/adapters/base.js +14 -0
  19. package/dist/adapters/base.js.map +1 -0
  20. package/dist/adapters/claude.d.ts +18 -0
  21. package/dist/adapters/claude.js +276 -0
  22. package/dist/adapters/claude.js.map +1 -0
  23. package/dist/adapters/gemini.d.ts +17 -0
  24. package/dist/adapters/gemini.js +218 -0
  25. package/dist/adapters/gemini.js.map +1 -0
  26. package/dist/adapters/grok.d.ts +7 -0
  27. package/dist/adapters/grok.js +21 -0
  28. package/dist/adapters/grok.js.map +1 -0
  29. package/dist/adapters/index.d.ts +5 -0
  30. package/dist/adapters/index.js +37 -0
  31. package/dist/adapters/index.js.map +1 -0
  32. package/dist/adapters/local.d.ts +7 -0
  33. package/dist/adapters/local.js +22 -0
  34. package/dist/adapters/local.js.map +1 -0
  35. package/dist/adapters/openai.d.ts +22 -0
  36. package/dist/adapters/openai.js +284 -0
  37. package/dist/adapters/openai.js.map +1 -0
  38. package/dist/adapters/sse.d.ts +13 -0
  39. package/dist/adapters/sse.js +58 -0
  40. package/dist/adapters/sse.js.map +1 -0
  41. package/dist/analyticsAggregator.d.ts +28 -0
  42. package/dist/analyticsAggregator.js +133 -0
  43. package/dist/analyticsAggregator.js.map +1 -0
  44. package/dist/analyticsPrefs.d.ts +9 -0
  45. package/dist/analyticsPrefs.js +50 -0
  46. package/dist/analyticsPrefs.js.map +1 -0
  47. package/dist/analyticsSend.d.ts +12 -0
  48. package/dist/analyticsSend.js +34 -0
  49. package/dist/analyticsSend.js.map +1 -0
  50. package/dist/approvalHttp.d.ts +46 -0
  51. package/dist/approvalHttp.js +370 -0
  52. package/dist/approvalHttp.js.map +1 -0
  53. package/dist/approvalQueue.d.ts +49 -0
  54. package/dist/approvalQueue.js +84 -0
  55. package/dist/approvalQueue.js.map +1 -0
  56. package/dist/automation.d.ts +675 -0
  57. package/dist/automation.js +1038 -0
  58. package/dist/automation.js.map +1 -0
  59. package/dist/bridge.d.ts +85 -0
  60. package/dist/bridge.js +1535 -0
  61. package/dist/bridge.js.map +1 -0
  62. package/dist/bridgeLockDiscovery.d.ts +11 -0
  63. package/dist/bridgeLockDiscovery.js +49 -0
  64. package/dist/bridgeLockDiscovery.js.map +1 -0
  65. package/dist/bridgeToken.d.ts +22 -0
  66. package/dist/bridgeToken.js +114 -0
  67. package/dist/bridgeToken.js.map +1 -0
  68. package/dist/bridgeToolsRules.d.ts +20 -0
  69. package/dist/bridgeToolsRules.js +79 -0
  70. package/dist/bridgeToolsRules.js.map +1 -0
  71. package/dist/ccPermissions.d.ts +59 -0
  72. package/dist/ccPermissions.js +163 -0
  73. package/dist/ccPermissions.js.map +1 -0
  74. package/dist/claudeDriver.d.ts +129 -0
  75. package/dist/claudeDriver.js +459 -0
  76. package/dist/claudeDriver.js.map +1 -0
  77. package/dist/claudeMdPatch.d.ts +29 -0
  78. package/dist/claudeMdPatch.js +164 -0
  79. package/dist/claudeMdPatch.js.map +1 -0
  80. package/dist/claudeOrchestrator.d.ts +171 -0
  81. package/dist/claudeOrchestrator.js +591 -0
  82. package/dist/claudeOrchestrator.js.map +1 -0
  83. package/dist/commands/install.d.ts +1 -0
  84. package/dist/commands/install.js +158 -0
  85. package/dist/commands/install.js.map +1 -0
  86. package/dist/commands/marketplace.d.ts +11 -0
  87. package/dist/commands/marketplace.js +120 -0
  88. package/dist/commands/marketplace.js.map +1 -0
  89. package/dist/commands/patchworkInit.d.ts +14 -0
  90. package/dist/commands/patchworkInit.js +155 -0
  91. package/dist/commands/patchworkInit.js.map +1 -0
  92. package/dist/commands/task.d.ts +14 -0
  93. package/dist/commands/task.js +289 -0
  94. package/dist/commands/task.js.map +1 -0
  95. package/dist/commands/tokenEfficiency.d.ts +9 -0
  96. package/dist/commands/tokenEfficiency.js +211 -0
  97. package/dist/commands/tokenEfficiency.js.map +1 -0
  98. package/dist/commands/tools.d.ts +28 -0
  99. package/dist/commands/tools.js +326 -0
  100. package/dist/commands/tools.js.map +1 -0
  101. package/dist/commitIssueLinkLog.d.ts +77 -0
  102. package/dist/commitIssueLinkLog.js +142 -0
  103. package/dist/commitIssueLinkLog.js.map +1 -0
  104. package/dist/companions/registry.d.ts +12 -0
  105. package/dist/companions/registry.js +71 -0
  106. package/dist/companions/registry.js.map +1 -0
  107. package/dist/config.d.ts +105 -0
  108. package/dist/config.js +720 -0
  109. package/dist/config.js.map +1 -0
  110. package/dist/crypto.d.ts +16 -0
  111. package/dist/crypto.js +34 -0
  112. package/dist/crypto.js.map +1 -0
  113. package/dist/dashboard.d.ts +12 -0
  114. package/dist/dashboard.js +149 -0
  115. package/dist/dashboard.js.map +1 -0
  116. package/dist/decisionTraceLog.d.ts +77 -0
  117. package/dist/decisionTraceLog.js +147 -0
  118. package/dist/decisionTraceLog.js.map +1 -0
  119. package/dist/errors.d.ts +32 -0
  120. package/dist/errors.js +34 -0
  121. package/dist/errors.js.map +1 -0
  122. package/dist/extensionClient.d.ts +279 -0
  123. package/dist/extensionClient.js +1253 -0
  124. package/dist/extensionClient.js.map +1 -0
  125. package/dist/fileLock.d.ts +36 -0
  126. package/dist/fileLock.js +121 -0
  127. package/dist/fileLock.js.map +1 -0
  128. package/dist/fp/activityAnalytics.d.ts +39 -0
  129. package/dist/fp/activityAnalytics.js +111 -0
  130. package/dist/fp/activityAnalytics.js.map +1 -0
  131. package/dist/fp/async.d.ts +48 -0
  132. package/dist/fp/async.js +60 -0
  133. package/dist/fp/async.js.map +1 -0
  134. package/dist/fp/automationInterpreter.d.ts +37 -0
  135. package/dist/fp/automationInterpreter.js +523 -0
  136. package/dist/fp/automationInterpreter.js.map +1 -0
  137. package/dist/fp/automationProgram.d.ts +89 -0
  138. package/dist/fp/automationProgram.js +29 -0
  139. package/dist/fp/automationProgram.js.map +1 -0
  140. package/dist/fp/automationState.d.ts +135 -0
  141. package/dist/fp/automationState.js +206 -0
  142. package/dist/fp/automationState.js.map +1 -0
  143. package/dist/fp/automationUtils.d.ts +31 -0
  144. package/dist/fp/automationUtils.js +61 -0
  145. package/dist/fp/automationUtils.js.map +1 -0
  146. package/dist/fp/brandedTypes.d.ts +32 -0
  147. package/dist/fp/brandedTypes.js +41 -0
  148. package/dist/fp/brandedTypes.js.map +1 -0
  149. package/dist/fp/commandDescription.d.ts +18 -0
  150. package/dist/fp/commandDescription.js +125 -0
  151. package/dist/fp/commandDescription.js.map +1 -0
  152. package/dist/fp/extensionSnapshot.d.ts +10 -0
  153. package/dist/fp/extensionSnapshot.js +14 -0
  154. package/dist/fp/extensionSnapshot.js.map +1 -0
  155. package/dist/fp/index.d.ts +8 -0
  156. package/dist/fp/index.js +9 -0
  157. package/dist/fp/index.js.map +1 -0
  158. package/dist/fp/interpreterContext.d.ts +69 -0
  159. package/dist/fp/interpreterContext.js +56 -0
  160. package/dist/fp/interpreterContext.js.map +1 -0
  161. package/dist/fp/policyParser.d.ts +16 -0
  162. package/dist/fp/policyParser.js +334 -0
  163. package/dist/fp/policyParser.js.map +1 -0
  164. package/dist/fp/result.d.ts +38 -0
  165. package/dist/fp/result.js +57 -0
  166. package/dist/fp/result.js.map +1 -0
  167. package/dist/fp/tokenBucket.d.ts +27 -0
  168. package/dist/fp/tokenBucket.js +36 -0
  169. package/dist/fp/tokenBucket.js.map +1 -0
  170. package/dist/index.d.ts +2 -0
  171. package/dist/index.js +1465 -0
  172. package/dist/index.js.map +1 -0
  173. package/dist/instructionsUtils.d.ts +17 -0
  174. package/dist/instructionsUtils.js +38 -0
  175. package/dist/instructionsUtils.js.map +1 -0
  176. package/dist/lockfile.d.ts +16 -0
  177. package/dist/lockfile.js +172 -0
  178. package/dist/lockfile.js.map +1 -0
  179. package/dist/logger.d.ts +16 -0
  180. package/dist/logger.js +68 -0
  181. package/dist/logger.js.map +1 -0
  182. package/dist/oauth.d.ts +105 -0
  183. package/dist/oauth.js +880 -0
  184. package/dist/oauth.js.map +1 -0
  185. package/dist/orchestrator/childBridgeClient.d.ts +33 -0
  186. package/dist/orchestrator/childBridgeClient.js +321 -0
  187. package/dist/orchestrator/childBridgeClient.js.map +1 -0
  188. package/dist/orchestrator/childBridgeRegistry.d.ts +67 -0
  189. package/dist/orchestrator/childBridgeRegistry.js +297 -0
  190. package/dist/orchestrator/childBridgeRegistry.js.map +1 -0
  191. package/dist/orchestrator/index.d.ts +3 -0
  192. package/dist/orchestrator/index.js +3 -0
  193. package/dist/orchestrator/index.js.map +1 -0
  194. package/dist/orchestrator/orchestratorBridge.d.ts +32 -0
  195. package/dist/orchestrator/orchestratorBridge.js +412 -0
  196. package/dist/orchestrator/orchestratorBridge.js.map +1 -0
  197. package/dist/orchestrator/orchestratorConfig.d.ts +11 -0
  198. package/dist/orchestrator/orchestratorConfig.js +85 -0
  199. package/dist/orchestrator/orchestratorConfig.js.map +1 -0
  200. package/dist/orchestrator/orchestratorTools.d.ts +16 -0
  201. package/dist/orchestrator/orchestratorTools.js +272 -0
  202. package/dist/orchestrator/orchestratorTools.js.map +1 -0
  203. package/dist/patchworkCli.d.ts +15 -0
  204. package/dist/patchworkCli.js +41 -0
  205. package/dist/patchworkCli.js.map +1 -0
  206. package/dist/patchworkConfig.d.ts +28 -0
  207. package/dist/patchworkConfig.js +30 -0
  208. package/dist/patchworkConfig.js.map +1 -0
  209. package/dist/plugin.d.ts +106 -0
  210. package/dist/plugin.js +31 -0
  211. package/dist/plugin.js.map +1 -0
  212. package/dist/pluginLoader.d.ts +44 -0
  213. package/dist/pluginLoader.js +357 -0
  214. package/dist/pluginLoader.js.map +1 -0
  215. package/dist/pluginWatcher.d.ts +24 -0
  216. package/dist/pluginWatcher.js +139 -0
  217. package/dist/pluginWatcher.js.map +1 -0
  218. package/dist/preToolUseHook.d.ts +10 -0
  219. package/dist/preToolUseHook.js +57 -0
  220. package/dist/preToolUseHook.js.map +1 -0
  221. package/dist/probe.d.ts +35 -0
  222. package/dist/probe.js +143 -0
  223. package/dist/probe.js.map +1 -0
  224. package/dist/prompts.d.ts +27 -0
  225. package/dist/prompts.js +1680 -0
  226. package/dist/prompts.js.map +1 -0
  227. package/dist/quickTaskPresets.d.ts +64 -0
  228. package/dist/quickTaskPresets.js +156 -0
  229. package/dist/quickTaskPresets.js.map +1 -0
  230. package/dist/recipes/compiler.d.ts +44 -0
  231. package/dist/recipes/compiler.js +140 -0
  232. package/dist/recipes/compiler.js.map +1 -0
  233. package/dist/recipes/installer.d.ts +25 -0
  234. package/dist/recipes/installer.js +62 -0
  235. package/dist/recipes/installer.js.map +1 -0
  236. package/dist/recipes/parser.d.ts +18 -0
  237. package/dist/recipes/parser.js +160 -0
  238. package/dist/recipes/parser.js.map +1 -0
  239. package/dist/recipes/scheduler.d.ts +45 -0
  240. package/dist/recipes/scheduler.js +110 -0
  241. package/dist/recipes/scheduler.js.map +1 -0
  242. package/dist/recipes/schema.d.ts +71 -0
  243. package/dist/recipes/schema.js +11 -0
  244. package/dist/recipes/schema.js.map +1 -0
  245. package/dist/recipesHttp.d.ts +63 -0
  246. package/dist/recipesHttp.js +183 -0
  247. package/dist/recipesHttp.js.map +1 -0
  248. package/dist/resources.d.ts +33 -0
  249. package/dist/resources.js +266 -0
  250. package/dist/resources.js.map +1 -0
  251. package/dist/riskTier.d.ts +40 -0
  252. package/dist/riskTier.js +142 -0
  253. package/dist/riskTier.js.map +1 -0
  254. package/dist/runLog.d.ts +90 -0
  255. package/dist/runLog.js +143 -0
  256. package/dist/runLog.js.map +1 -0
  257. package/dist/server.d.ts +160 -0
  258. package/dist/server.js +1244 -0
  259. package/dist/server.js.map +1 -0
  260. package/dist/sessionCheckpoint.d.ts +37 -0
  261. package/dist/sessionCheckpoint.js +123 -0
  262. package/dist/sessionCheckpoint.js.map +1 -0
  263. package/dist/streamableHttp.d.ts +86 -0
  264. package/dist/streamableHttp.js +702 -0
  265. package/dist/streamableHttp.js.map +1 -0
  266. package/dist/telemetry.d.ts +18 -0
  267. package/dist/telemetry.js +95 -0
  268. package/dist/telemetry.js.map +1 -0
  269. package/dist/tools/activityLog.d.ts +140 -0
  270. package/dist/tools/activityLog.js +204 -0
  271. package/dist/tools/activityLog.js.map +1 -0
  272. package/dist/tools/auditDependencies.d.ts +67 -0
  273. package/dist/tools/auditDependencies.js +298 -0
  274. package/dist/tools/auditDependencies.js.map +1 -0
  275. package/dist/tools/batchLsp.d.ts +262 -0
  276. package/dist/tools/batchLsp.js +328 -0
  277. package/dist/tools/batchLsp.js.map +1 -0
  278. package/dist/tools/blame-utils.d.ts +30 -0
  279. package/dist/tools/blame-utils.js +60 -0
  280. package/dist/tools/blame-utils.js.map +1 -0
  281. package/dist/tools/bridgeDoctor.d.ts +78 -0
  282. package/dist/tools/bridgeDoctor.js +542 -0
  283. package/dist/tools/bridgeDoctor.js.map +1 -0
  284. package/dist/tools/bridgeStatus.d.ts +122 -0
  285. package/dist/tools/bridgeStatus.js +250 -0
  286. package/dist/tools/bridgeStatus.js.map +1 -0
  287. package/dist/tools/cancelClaudeTask.d.ts +48 -0
  288. package/dist/tools/cancelClaudeTask.js +56 -0
  289. package/dist/tools/cancelClaudeTask.js.map +1 -0
  290. package/dist/tools/checkDocumentDirty.d.ts +56 -0
  291. package/dist/tools/checkDocumentDirty.js +74 -0
  292. package/dist/tools/checkDocumentDirty.js.map +1 -0
  293. package/dist/tools/clipboard.d.ts +80 -0
  294. package/dist/tools/clipboard.js +211 -0
  295. package/dist/tools/clipboard.js.map +1 -0
  296. package/dist/tools/closeTabs.d.ts +84 -0
  297. package/dist/tools/closeTabs.js +97 -0
  298. package/dist/tools/closeTabs.js.map +1 -0
  299. package/dist/tools/codeLens.d.ts +50 -0
  300. package/dist/tools/codeLens.js +47 -0
  301. package/dist/tools/codeLens.js.map +1 -0
  302. package/dist/tools/contextBundle.d.ts +75 -0
  303. package/dist/tools/contextBundle.js +218 -0
  304. package/dist/tools/contextBundle.js.map +1 -0
  305. package/dist/tools/createIssueFromAIComment.d.ts +75 -0
  306. package/dist/tools/createIssueFromAIComment.js +119 -0
  307. package/dist/tools/createIssueFromAIComment.js.map +1 -0
  308. package/dist/tools/ctxGetTaskContext.d.ts +103 -0
  309. package/dist/tools/ctxGetTaskContext.js +274 -0
  310. package/dist/tools/ctxGetTaskContext.js.map +1 -0
  311. package/dist/tools/ctxQueryTraces.d.ts +142 -0
  312. package/dist/tools/ctxQueryTraces.js +194 -0
  313. package/dist/tools/ctxQueryTraces.js.map +1 -0
  314. package/dist/tools/ctxSaveTrace.d.ts +87 -0
  315. package/dist/tools/ctxSaveTrace.js +94 -0
  316. package/dist/tools/ctxSaveTrace.js.map +1 -0
  317. package/dist/tools/debug.d.ts +206 -0
  318. package/dist/tools/debug.js +234 -0
  319. package/dist/tools/debug.js.map +1 -0
  320. package/dist/tools/decorations.d.ts +130 -0
  321. package/dist/tools/decorations.js +160 -0
  322. package/dist/tools/decorations.js.map +1 -0
  323. package/dist/tools/detectUnusedCode.d.ts +78 -0
  324. package/dist/tools/detectUnusedCode.js +173 -0
  325. package/dist/tools/detectUnusedCode.js.map +1 -0
  326. package/dist/tools/documentLinks.d.ts +62 -0
  327. package/dist/tools/documentLinks.js +55 -0
  328. package/dist/tools/documentLinks.js.map +1 -0
  329. package/dist/tools/editText.d.ts +108 -0
  330. package/dist/tools/editText.js +318 -0
  331. package/dist/tools/editText.js.map +1 -0
  332. package/dist/tools/enrichCommit.d.ts +89 -0
  333. package/dist/tools/enrichCommit.js +201 -0
  334. package/dist/tools/enrichCommit.js.map +1 -0
  335. package/dist/tools/enrichStackTrace.d.ts +121 -0
  336. package/dist/tools/enrichStackTrace.js +194 -0
  337. package/dist/tools/enrichStackTrace.js.map +1 -0
  338. package/dist/tools/explainDiagnostic.d.ts +137 -0
  339. package/dist/tools/explainDiagnostic.js +230 -0
  340. package/dist/tools/explainDiagnostic.js.map +1 -0
  341. package/dist/tools/explainSymbol.d.ts +119 -0
  342. package/dist/tools/explainSymbol.js +177 -0
  343. package/dist/tools/explainSymbol.js.map +1 -0
  344. package/dist/tools/fileOperations.d.ts +186 -0
  345. package/dist/tools/fileOperations.js +330 -0
  346. package/dist/tools/fileOperations.js.map +1 -0
  347. package/dist/tools/fileWatcher.d.ts +107 -0
  348. package/dist/tools/fileWatcher.js +121 -0
  349. package/dist/tools/fileWatcher.js.map +1 -0
  350. package/dist/tools/findFiles.d.ts +65 -0
  351. package/dist/tools/findFiles.js +142 -0
  352. package/dist/tools/findFiles.js.map +1 -0
  353. package/dist/tools/findRelatedTests.d.ts +83 -0
  354. package/dist/tools/findRelatedTests.js +196 -0
  355. package/dist/tools/findRelatedTests.js.map +1 -0
  356. package/dist/tools/fixAllLintErrors.d.ts +66 -0
  357. package/dist/tools/fixAllLintErrors.js +128 -0
  358. package/dist/tools/fixAllLintErrors.js.map +1 -0
  359. package/dist/tools/foldingRanges.d.ts +50 -0
  360. package/dist/tools/foldingRanges.js +51 -0
  361. package/dist/tools/foldingRanges.js.map +1 -0
  362. package/dist/tools/formatAndSave.d.ts +57 -0
  363. package/dist/tools/formatAndSave.js +87 -0
  364. package/dist/tools/formatAndSave.js.map +1 -0
  365. package/dist/tools/formatDocument.d.ts +61 -0
  366. package/dist/tools/formatDocument.js +144 -0
  367. package/dist/tools/formatDocument.js.map +1 -0
  368. package/dist/tools/generateAPIDocumentation.d.ts +62 -0
  369. package/dist/tools/generateAPIDocumentation.js +249 -0
  370. package/dist/tools/generateAPIDocumentation.js.map +1 -0
  371. package/dist/tools/generateTests.d.ts +75 -0
  372. package/dist/tools/generateTests.js +226 -0
  373. package/dist/tools/generateTests.js.map +1 -0
  374. package/dist/tools/getAIComments.d.ts +79 -0
  375. package/dist/tools/getAIComments.js +93 -0
  376. package/dist/tools/getAIComments.js.map +1 -0
  377. package/dist/tools/getAnalyticsReport.d.ts +102 -0
  378. package/dist/tools/getAnalyticsReport.js +137 -0
  379. package/dist/tools/getAnalyticsReport.js.map +1 -0
  380. package/dist/tools/getArchitectureContext.d.ts +85 -0
  381. package/dist/tools/getArchitectureContext.js +135 -0
  382. package/dist/tools/getArchitectureContext.js.map +1 -0
  383. package/dist/tools/getBufferContent.d.ts +80 -0
  384. package/dist/tools/getBufferContent.js +207 -0
  385. package/dist/tools/getBufferContent.js.map +1 -0
  386. package/dist/tools/getChangeImpact.d.ts +76 -0
  387. package/dist/tools/getChangeImpact.js +184 -0
  388. package/dist/tools/getChangeImpact.js.map +1 -0
  389. package/dist/tools/getClaudeTaskStatus.d.ts +87 -0
  390. package/dist/tools/getClaudeTaskStatus.js +89 -0
  391. package/dist/tools/getClaudeTaskStatus.js.map +1 -0
  392. package/dist/tools/getCodeCoverage.d.ts +86 -0
  393. package/dist/tools/getCodeCoverage.js +237 -0
  394. package/dist/tools/getCodeCoverage.js.map +1 -0
  395. package/dist/tools/getCommitsForIssue.d.ts +98 -0
  396. package/dist/tools/getCommitsForIssue.js +106 -0
  397. package/dist/tools/getCommitsForIssue.js.map +1 -0
  398. package/dist/tools/getCurrentSelection.d.ts +123 -0
  399. package/dist/tools/getCurrentSelection.js +113 -0
  400. package/dist/tools/getCurrentSelection.js.map +1 -0
  401. package/dist/tools/getDebugState.d.ts +140 -0
  402. package/dist/tools/getDebugState.js +109 -0
  403. package/dist/tools/getDebugState.js.map +1 -0
  404. package/dist/tools/getDependencyTree.d.ts +59 -0
  405. package/dist/tools/getDependencyTree.js +207 -0
  406. package/dist/tools/getDependencyTree.js.map +1 -0
  407. package/dist/tools/getDiagnostics.d.ts +108 -0
  408. package/dist/tools/getDiagnostics.js +371 -0
  409. package/dist/tools/getDiagnostics.js.map +1 -0
  410. package/dist/tools/getDiffFromHandoff.d.ts +89 -0
  411. package/dist/tools/getDiffFromHandoff.js +163 -0
  412. package/dist/tools/getDiffFromHandoff.js.map +1 -0
  413. package/dist/tools/getDocumentSymbols.d.ts +74 -0
  414. package/dist/tools/getDocumentSymbols.js +177 -0
  415. package/dist/tools/getDocumentSymbols.js.map +1 -0
  416. package/dist/tools/getFileTree.d.ts +66 -0
  417. package/dist/tools/getFileTree.js +131 -0
  418. package/dist/tools/getFileTree.js.map +1 -0
  419. package/dist/tools/getGitDiff.d.ts +50 -0
  420. package/dist/tools/getGitDiff.js +73 -0
  421. package/dist/tools/getGitDiff.js.map +1 -0
  422. package/dist/tools/getGitHotspots.d.ts +88 -0
  423. package/dist/tools/getGitHotspots.js +145 -0
  424. package/dist/tools/getGitHotspots.js.map +1 -0
  425. package/dist/tools/getGitLog.d.ts +62 -0
  426. package/dist/tools/getGitLog.js +87 -0
  427. package/dist/tools/getGitLog.js.map +1 -0
  428. package/dist/tools/getGitStatus.d.ts +72 -0
  429. package/dist/tools/getGitStatus.js +126 -0
  430. package/dist/tools/getGitStatus.js.map +1 -0
  431. package/dist/tools/getImportTree.d.ts +73 -0
  432. package/dist/tools/getImportTree.js +223 -0
  433. package/dist/tools/getImportTree.js.map +1 -0
  434. package/dist/tools/getImportedSignatures.d.ts +62 -0
  435. package/dist/tools/getImportedSignatures.js +255 -0
  436. package/dist/tools/getImportedSignatures.js.map +1 -0
  437. package/dist/tools/getOpenEditors.d.ts +62 -0
  438. package/dist/tools/getOpenEditors.js +126 -0
  439. package/dist/tools/getOpenEditors.js.map +1 -0
  440. package/dist/tools/getPRTemplate.d.ts +68 -0
  441. package/dist/tools/getPRTemplate.js +187 -0
  442. package/dist/tools/getPRTemplate.js.map +1 -0
  443. package/dist/tools/getProjectContext.d.ts +114 -0
  444. package/dist/tools/getProjectContext.js +344 -0
  445. package/dist/tools/getProjectContext.js.map +1 -0
  446. package/dist/tools/getProjectInfo.d.ts +51 -0
  447. package/dist/tools/getProjectInfo.js +325 -0
  448. package/dist/tools/getProjectInfo.js.map +1 -0
  449. package/dist/tools/getSecurityAdvisories.d.ts +105 -0
  450. package/dist/tools/getSecurityAdvisories.js +472 -0
  451. package/dist/tools/getSecurityAdvisories.js.map +1 -0
  452. package/dist/tools/getSessionUsage.d.ts +58 -0
  453. package/dist/tools/getSessionUsage.js +57 -0
  454. package/dist/tools/getSessionUsage.js.map +1 -0
  455. package/dist/tools/getSymbolHistory.d.ts +157 -0
  456. package/dist/tools/getSymbolHistory.js +256 -0
  457. package/dist/tools/getSymbolHistory.js.map +1 -0
  458. package/dist/tools/getToolCapabilities.d.ts +69 -0
  459. package/dist/tools/getToolCapabilities.js +298 -0
  460. package/dist/tools/getToolCapabilities.js.map +1 -0
  461. package/dist/tools/getTypeSignature.d.ts +70 -0
  462. package/dist/tools/getTypeSignature.js +132 -0
  463. package/dist/tools/getTypeSignature.js.map +1 -0
  464. package/dist/tools/getWorkspaceFolders.d.ts +58 -0
  465. package/dist/tools/getWorkspaceFolders.js +69 -0
  466. package/dist/tools/getWorkspaceFolders.js.map +1 -0
  467. package/dist/tools/getWorkspaceSettings.d.ts +44 -0
  468. package/dist/tools/getWorkspaceSettings.js +70 -0
  469. package/dist/tools/getWorkspaceSettings.js.map +1 -0
  470. package/dist/tools/git-utils.d.ts +16 -0
  471. package/dist/tools/git-utils.js +46 -0
  472. package/dist/tools/git-utils.js.map +1 -0
  473. package/dist/tools/gitHistory.d.ts +110 -0
  474. package/dist/tools/gitHistory.js +167 -0
  475. package/dist/tools/gitHistory.js.map +1 -0
  476. package/dist/tools/gitWrite.d.ts +612 -0
  477. package/dist/tools/gitWrite.js +983 -0
  478. package/dist/tools/gitWrite.js.map +1 -0
  479. package/dist/tools/github/actions.d.ts +152 -0
  480. package/dist/tools/github/actions.js +195 -0
  481. package/dist/tools/github/actions.js.map +1 -0
  482. package/dist/tools/github/index.d.ts +3 -0
  483. package/dist/tools/github/index.js +4 -0
  484. package/dist/tools/github/index.js.map +1 -0
  485. package/dist/tools/github/issues.d.ts +281 -0
  486. package/dist/tools/github/issues.js +340 -0
  487. package/dist/tools/github/issues.js.map +1 -0
  488. package/dist/tools/github/pr.d.ts +433 -0
  489. package/dist/tools/github/pr.js +588 -0
  490. package/dist/tools/github/pr.js.map +1 -0
  491. package/dist/tools/github/shared.d.ts +4 -0
  492. package/dist/tools/github/shared.js +12 -0
  493. package/dist/tools/github/shared.js.map +1 -0
  494. package/dist/tools/handoffNote.d.ts +106 -0
  495. package/dist/tools/handoffNote.js +232 -0
  496. package/dist/tools/handoffNote.js.map +1 -0
  497. package/dist/tools/headless/lspClient.d.ts +26 -0
  498. package/dist/tools/headless/lspClient.js +221 -0
  499. package/dist/tools/headless/lspClient.js.map +1 -0
  500. package/dist/tools/headless/lspFallback.d.ts +28 -0
  501. package/dist/tools/headless/lspFallback.js +122 -0
  502. package/dist/tools/headless/lspFallback.js.map +1 -0
  503. package/dist/tools/hoverAtCursor.d.ts +54 -0
  504. package/dist/tools/hoverAtCursor.js +68 -0
  505. package/dist/tools/hoverAtCursor.js.map +1 -0
  506. package/dist/tools/httpClient.d.ts +141 -0
  507. package/dist/tools/httpClient.js +486 -0
  508. package/dist/tools/httpClient.js.map +1 -0
  509. package/dist/tools/index.d.ts +49 -0
  510. package/dist/tools/index.js +672 -0
  511. package/dist/tools/index.js.map +1 -0
  512. package/dist/tools/inlayHints.d.ts +81 -0
  513. package/dist/tools/inlayHints.js +76 -0
  514. package/dist/tools/inlayHints.js.map +1 -0
  515. package/dist/tools/issueRefs.d.ts +14 -0
  516. package/dist/tools/issueRefs.js +27 -0
  517. package/dist/tools/issueRefs.js.map +1 -0
  518. package/dist/tools/jumpToFirstError.d.ts +63 -0
  519. package/dist/tools/jumpToFirstError.js +124 -0
  520. package/dist/tools/jumpToFirstError.js.map +1 -0
  521. package/dist/tools/launchQuickTask.d.ts +76 -0
  522. package/dist/tools/launchQuickTask.js +170 -0
  523. package/dist/tools/launchQuickTask.js.map +1 -0
  524. package/dist/tools/linters/biome.d.ts +2 -0
  525. package/dist/tools/linters/biome.js +44 -0
  526. package/dist/tools/linters/biome.js.map +1 -0
  527. package/dist/tools/linters/cargo.d.ts +2 -0
  528. package/dist/tools/linters/cargo.js +45 -0
  529. package/dist/tools/linters/cargo.js.map +1 -0
  530. package/dist/tools/linters/eslint.d.ts +2 -0
  531. package/dist/tools/linters/eslint.js +59 -0
  532. package/dist/tools/linters/eslint.js.map +1 -0
  533. package/dist/tools/linters/govet.d.ts +2 -0
  534. package/dist/tools/linters/govet.js +37 -0
  535. package/dist/tools/linters/govet.js.map +1 -0
  536. package/dist/tools/linters/pyright.d.ts +2 -0
  537. package/dist/tools/linters/pyright.js +34 -0
  538. package/dist/tools/linters/pyright.js.map +1 -0
  539. package/dist/tools/linters/ruff.d.ts +2 -0
  540. package/dist/tools/linters/ruff.js +30 -0
  541. package/dist/tools/linters/ruff.js.map +1 -0
  542. package/dist/tools/linters/types.d.ts +16 -0
  543. package/dist/tools/linters/types.js +2 -0
  544. package/dist/tools/linters/types.js.map +1 -0
  545. package/dist/tools/linters/typescript.d.ts +2 -0
  546. package/dist/tools/linters/typescript.js +38 -0
  547. package/dist/tools/linters/typescript.js.map +1 -0
  548. package/dist/tools/listClaudeTasks.d.ts +84 -0
  549. package/dist/tools/listClaudeTasks.js +88 -0
  550. package/dist/tools/listClaudeTasks.js.map +1 -0
  551. package/dist/tools/listTerminals.d.ts +55 -0
  552. package/dist/tools/listTerminals.js +78 -0
  553. package/dist/tools/listTerminals.js.map +1 -0
  554. package/dist/tools/lsp.d.ts +1086 -0
  555. package/dist/tools/lsp.js +1339 -0
  556. package/dist/tools/lsp.js.map +1 -0
  557. package/dist/tools/navigateToSymbolByName.d.ts +56 -0
  558. package/dist/tools/navigateToSymbolByName.js +170 -0
  559. package/dist/tools/navigateToSymbolByName.js.map +1 -0
  560. package/dist/tools/openDiff.d.ts +66 -0
  561. package/dist/tools/openDiff.js +126 -0
  562. package/dist/tools/openDiff.js.map +1 -0
  563. package/dist/tools/openFile.d.ts +69 -0
  564. package/dist/tools/openFile.js +129 -0
  565. package/dist/tools/openFile.js.map +1 -0
  566. package/dist/tools/openInBrowser.d.ts +55 -0
  567. package/dist/tools/openInBrowser.js +129 -0
  568. package/dist/tools/openInBrowser.js.map +1 -0
  569. package/dist/tools/organizeImports.d.ts +56 -0
  570. package/dist/tools/organizeImports.js +115 -0
  571. package/dist/tools/organizeImports.js.map +1 -0
  572. package/dist/tools/performanceReport.d.ts +133 -0
  573. package/dist/tools/performanceReport.js +218 -0
  574. package/dist/tools/performanceReport.js.map +1 -0
  575. package/dist/tools/planPersistence.d.ts +306 -0
  576. package/dist/tools/planPersistence.js +485 -0
  577. package/dist/tools/planPersistence.js.map +1 -0
  578. package/dist/tools/previewEdit.d.ts +107 -0
  579. package/dist/tools/previewEdit.js +270 -0
  580. package/dist/tools/previewEdit.js.map +1 -0
  581. package/dist/tools/recentTracesDigest.d.ts +35 -0
  582. package/dist/tools/recentTracesDigest.js +98 -0
  583. package/dist/tools/recentTracesDigest.js.map +1 -0
  584. package/dist/tools/refactorAnalyze.d.ts +78 -0
  585. package/dist/tools/refactorAnalyze.js +141 -0
  586. package/dist/tools/refactorAnalyze.js.map +1 -0
  587. package/dist/tools/refactorExtractFunction.d.ts +52 -0
  588. package/dist/tools/refactorExtractFunction.js +121 -0
  589. package/dist/tools/refactorExtractFunction.js.map +1 -0
  590. package/dist/tools/refactorPreview.d.ts +75 -0
  591. package/dist/tools/refactorPreview.js +93 -0
  592. package/dist/tools/refactorPreview.js.map +1 -0
  593. package/dist/tools/replaceBlock.d.ts +62 -0
  594. package/dist/tools/replaceBlock.js +125 -0
  595. package/dist/tools/replaceBlock.js.map +1 -0
  596. package/dist/tools/resumeClaudeTask.d.ts +75 -0
  597. package/dist/tools/resumeClaudeTask.js +149 -0
  598. package/dist/tools/resumeClaudeTask.js.map +1 -0
  599. package/dist/tools/runClaudeTask.d.ts +97 -0
  600. package/dist/tools/runClaudeTask.js +224 -0
  601. package/dist/tools/runClaudeTask.js.map +1 -0
  602. package/dist/tools/runCommand.d.ts +82 -0
  603. package/dist/tools/runCommand.js +101 -0
  604. package/dist/tools/runCommand.js.map +1 -0
  605. package/dist/tools/runTests.d.ts +146 -0
  606. package/dist/tools/runTests.js +315 -0
  607. package/dist/tools/runTests.js.map +1 -0
  608. package/dist/tools/saveDocument.d.ts +50 -0
  609. package/dist/tools/saveDocument.js +73 -0
  610. package/dist/tools/saveDocument.js.map +1 -0
  611. package/dist/tools/screenshot.d.ts +23 -0
  612. package/dist/tools/screenshot.js +43 -0
  613. package/dist/tools/screenshot.js.map +1 -0
  614. package/dist/tools/screenshotAndAnnotate.d.ts +103 -0
  615. package/dist/tools/screenshotAndAnnotate.js +192 -0
  616. package/dist/tools/screenshotAndAnnotate.js.map +1 -0
  617. package/dist/tools/searchAndReplace.d.ts +108 -0
  618. package/dist/tools/searchAndReplace.js +281 -0
  619. package/dist/tools/searchAndReplace.js.map +1 -0
  620. package/dist/tools/searchTools.d.ts +61 -0
  621. package/dist/tools/searchTools.js +85 -0
  622. package/dist/tools/searchTools.js.map +1 -0
  623. package/dist/tools/searchWorkspace.d.ts +99 -0
  624. package/dist/tools/searchWorkspace.js +189 -0
  625. package/dist/tools/searchWorkspace.js.map +1 -0
  626. package/dist/tools/selectionRanges.d.ts +58 -0
  627. package/dist/tools/selectionRanges.js +61 -0
  628. package/dist/tools/selectionRanges.js.map +1 -0
  629. package/dist/tools/semanticTokens.d.ts +87 -0
  630. package/dist/tools/semanticTokens.js +86 -0
  631. package/dist/tools/semanticTokens.js.map +1 -0
  632. package/dist/tools/setActiveWorkspaceFolder.d.ts +41 -0
  633. package/dist/tools/setActiveWorkspaceFolder.js +38 -0
  634. package/dist/tools/setActiveWorkspaceFolder.js.map +1 -0
  635. package/dist/tools/signatureHelp.d.ts +86 -0
  636. package/dist/tools/signatureHelp.js +79 -0
  637. package/dist/tools/signatureHelp.js.map +1 -0
  638. package/dist/tools/spawnWorkspace.d.ts +103 -0
  639. package/dist/tools/spawnWorkspace.js +268 -0
  640. package/dist/tools/spawnWorkspace.js.map +1 -0
  641. package/dist/tools/stackTraceParser.d.ts +43 -0
  642. package/dist/tools/stackTraceParser.js +139 -0
  643. package/dist/tools/stackTraceParser.js.map +1 -0
  644. package/dist/tools/terminal.d.ts +352 -0
  645. package/dist/tools/terminal.js +670 -0
  646. package/dist/tools/terminal.js.map +1 -0
  647. package/dist/tools/testRunners/cargoTest.d.ts +2 -0
  648. package/dist/tools/testRunners/cargoTest.js +129 -0
  649. package/dist/tools/testRunners/cargoTest.js.map +1 -0
  650. package/dist/tools/testRunners/goTest.d.ts +2 -0
  651. package/dist/tools/testRunners/goTest.js +108 -0
  652. package/dist/tools/testRunners/goTest.js.map +1 -0
  653. package/dist/tools/testRunners/pytest.d.ts +2 -0
  654. package/dist/tools/testRunners/pytest.js +135 -0
  655. package/dist/tools/testRunners/pytest.js.map +1 -0
  656. package/dist/tools/testRunners/types.d.ts +18 -0
  657. package/dist/tools/testRunners/types.js +2 -0
  658. package/dist/tools/testRunners/types.js.map +1 -0
  659. package/dist/tools/testRunners/vitestJest.d.ts +3 -0
  660. package/dist/tools/testRunners/vitestJest.js +215 -0
  661. package/dist/tools/testRunners/vitestJest.js.map +1 -0
  662. package/dist/tools/testTraceToSource.d.ts +80 -0
  663. package/dist/tools/testTraceToSource.js +206 -0
  664. package/dist/tools/testTraceToSource.js.map +1 -0
  665. package/dist/tools/transaction.d.ts +243 -0
  666. package/dist/tools/transaction.js +309 -0
  667. package/dist/tools/transaction.js.map +1 -0
  668. package/dist/tools/typeHierarchy.d.ts +77 -0
  669. package/dist/tools/typeHierarchy.js +86 -0
  670. package/dist/tools/typeHierarchy.js.map +1 -0
  671. package/dist/tools/utils.d.ts +124 -0
  672. package/dist/tools/utils.js +566 -0
  673. package/dist/tools/utils.js.map +1 -0
  674. package/dist/tools/vscodeCommands.d.ts +90 -0
  675. package/dist/tools/vscodeCommands.js +112 -0
  676. package/dist/tools/vscodeCommands.js.map +1 -0
  677. package/dist/tools/vscodeTasks.d.ts +102 -0
  678. package/dist/tools/vscodeTasks.js +110 -0
  679. package/dist/tools/vscodeTasks.js.map +1 -0
  680. package/dist/tools/watchDiagnostics.d.ts +64 -0
  681. package/dist/tools/watchDiagnostics.js +270 -0
  682. package/dist/tools/watchDiagnostics.js.map +1 -0
  683. package/dist/tools/workspaceSettings.d.ts +57 -0
  684. package/dist/tools/workspaceSettings.js +80 -0
  685. package/dist/tools/workspaceSettings.js.map +1 -0
  686. package/dist/transport.d.ts +207 -0
  687. package/dist/transport.js +1272 -0
  688. package/dist/transport.js.map +1 -0
  689. package/dist/version.d.ts +13 -0
  690. package/dist/version.js +31 -0
  691. package/dist/version.js.map +1 -0
  692. package/dist/wsUtils.d.ts +8 -0
  693. package/dist/wsUtils.js +54 -0
  694. package/dist/wsUtils.js.map +1 -0
  695. package/package.json +118 -0
  696. package/scripts/gen-claude-desktop-config.sh +124 -0
  697. package/scripts/gen-mcp-config.sh +390 -0
  698. package/scripts/install-extension.sh +106 -0
  699. package/scripts/mcp-stdio-shim.cjs +482 -0
  700. package/scripts/postinstall.mjs +68 -0
  701. package/scripts/start-all.sh +502 -0
  702. package/scripts/start-orchestrator.sh +186 -0
  703. package/scripts/start-remote.sh +126 -0
  704. package/scripts/start-vps.sh +116 -0
  705. package/templates/CLAUDE.bridge.md +125 -0
  706. package/templates/automation-policies/security-first.json +46 -0
  707. package/templates/automation-policies/strict-lint.json +41 -0
  708. package/templates/automation-policies/test-driven.json +54 -0
  709. package/templates/automation-policy.example.json +105 -0
  710. package/templates/bridge-tools.md +111 -0
  711. package/templates/dispatch-context.md +33 -0
  712. package/templates/managed-agent/code-review-agent.md +50 -0
  713. package/templates/managed-agent/managed-agent-mcp.json +102 -0
  714. package/templates/recipes/ambient-journal.yaml +11 -0
  715. package/templates/recipes/daily-status.yaml +21 -0
  716. package/templates/recipes/lint-on-save.yaml +13 -0
  717. package/templates/recipes/stale-branches.yaml +18 -0
  718. package/templates/recipes/watch-failing-tests.yaml +15 -0
  719. package/templates/scheduled-tasks/dependency-audit/SKILL.md +77 -0
  720. package/templates/scheduled-tasks/health-check/SKILL.md +73 -0
  721. package/templates/scheduled-tasks/nightly-review/SKILL.md +69 -0
package/dist/oauth.js ADDED
@@ -0,0 +1,880 @@
1
+ /**
2
+ * OAuth 2.0 Authorization Server for claude-ide-bridge.
3
+ *
4
+ * Implements the MCP OAuth 2.0 profile required for authenticated remote servers:
5
+ * - RFC 8414 Authorization Server Metadata (/.well-known/oauth-authorization-server)
6
+ * - RFC 6749 Authorization Code Grant with PKCE (S256, RFC 7636)
7
+ * - RFC 7009 Token Revocation (/oauth/revoke)
8
+ *
9
+ * Design
10
+ * All state is in-memory. The bridge's static bearer token is the resource owner
11
+ * credential: only someone who knows it can open an OAuth flow via the approval page.
12
+ * Issued access tokens are opaque base64url strings stored in a TTL map.
13
+ * resolveBearerToken() is called by server.ts to admit OAuth-issued tokens alongside
14
+ * the static bridge token (backward compat).
15
+ * Refresh tokens are not issued.
16
+ *
17
+ * Security
18
+ * PKCE S256 mandatory. Auth codes single-use, 5 min TTL. Access tokens 24 h TTL.
19
+ * All string comparisons via crypto.timingSafeEqual. HTML output attribute-escaped.
20
+ */
21
+ import crypto from "node:crypto";
22
+ import fs from "node:fs";
23
+ import path from "node:path";
24
+ import { URL } from "node:url";
25
+ import { timingSafeStringEqual } from "./crypto.js";
26
+ // ── CIMD SSRF guard ───────────────────────────────────────────────────────────
27
+ /**
28
+ * Blocks private/loopback hostnames for CIMD fetches.
29
+ * CIMD URLs must be public HTTPS — rejecting private addresses prevents
30
+ * SSRF via a crafted client_id URL.
31
+ */
32
+ function isPrivateCimdHost(hostname) {
33
+ const h = hostname.toLowerCase().replace(/^\[|]$/g, ""); // strip IPv6 brackets
34
+ if (h === "localhost" ||
35
+ h.startsWith("127.") ||
36
+ h.startsWith("10.") ||
37
+ h.startsWith("192.168.") ||
38
+ h === "::1" ||
39
+ h.startsWith("fc") ||
40
+ h.startsWith("fd") ||
41
+ h.startsWith("169.254."))
42
+ return true;
43
+ // 172.16.0.0/12
44
+ const m = /^172\.(\d+)\./.exec(h);
45
+ if (m && Number(m[1]) >= 16 && Number(m[1]) <= 31)
46
+ return true;
47
+ return false;
48
+ }
49
+ // ── Constants ─────────────────────────────────────────────────────────────────
50
+ const CODE_TTL_MS = 5 * 60 * 1_000; // 5 min
51
+ const TOKEN_TTL_MS = 24 * 60 * 60 * 1_000; // 24 hours
52
+ const CLIENT_TTL_MS = 7 * 24 * 60 * 60 * 1_000; // 7 days — GC registered clients after a week
53
+ const DEFAULT_SCOPE = "mcp";
54
+ const SUPPORTED_SCOPES = ["mcp"];
55
+ // ── OAuthServerImpl ───────────────────────────────────────────────────────────
56
+ export class OAuthServerImpl {
57
+ bridgeToken;
58
+ issuerUrl;
59
+ authCodes = new Map();
60
+ accessTokens = new Map();
61
+ /** client_id → { redirectUris, issuedAt } (populated by handleRegister) */
62
+ registeredClients = new Map();
63
+ /** Per-IP registration rate limit: IP → { count, windowStart } */
64
+ registerIpCounts = new Map();
65
+ static REGISTER_IP_MAX = 10; // max registrations per IP per minute
66
+ static REGISTER_IP_WINDOW_MS = 60 * 1_000;
67
+ gcTimer;
68
+ /** CSRF nonces: flowId → { nonce, clientId, expiresAt } */
69
+ csrfNonces = new Map();
70
+ static CSRF_TTL_MS = 10 * 60 * 1_000; // 10 minutes
71
+ /**
72
+ * CIMD cache: client_id URL → { redirectUris, fetchedAt }
73
+ * Short TTL (5 min) — avoids re-fetching on every authorize request while
74
+ * staying fresh enough for clients that rotate their metadata.
75
+ */
76
+ cimdCache = new Map();
77
+ static CIMD_CACHE_TTL_MS = 5 * 60 * 1_000; // 5 min
78
+ static CIMD_MAX_BYTES = 8_192; // 8 KB max for metadata doc
79
+ /** Path to the persisted token file; null when persistence is disabled. */
80
+ tokenStorePath;
81
+ tokenTtlMs;
82
+ /**
83
+ * Tokens loaded from disk on startup: SHA-256(token) → AccessToken.
84
+ * In-memory issued tokens use `accessTokens` (raw key). Both are checked
85
+ * in resolveBearerToken — disk tokens are promoted to accessTokens on first use.
86
+ */
87
+ hashedTokens = new Map();
88
+ persistTimer = null;
89
+ constructor(bridgeToken, issuerUrl, opts) {
90
+ this.bridgeToken = bridgeToken;
91
+ this.issuerUrl = issuerUrl.replace(/\/$/, "");
92
+ this.tokenTtlMs = opts?.tokenTtlMs ?? TOKEN_TTL_MS;
93
+ this.tokenStorePath = opts?.configDir
94
+ ? path.join(opts.configDir, "ide", "oauth-tokens.json")
95
+ : null;
96
+ this.gcTimer = setInterval(() => {
97
+ const now = Date.now();
98
+ for (const [k, v] of this.authCodes)
99
+ if (v.expiresAt < now)
100
+ this.authCodes.delete(k);
101
+ for (const [k, v] of this.accessTokens)
102
+ if (v.expiresAt < now)
103
+ this.accessTokens.delete(k);
104
+ for (const [k, v] of this.registeredClients)
105
+ if (now - v.issuedAt > CLIENT_TTL_MS)
106
+ this.registeredClients.delete(k);
107
+ for (const [k, v] of this.csrfNonces)
108
+ if (v.expiresAt < now)
109
+ this.csrfNonces.delete(k);
110
+ for (const [k, v] of this.registerIpCounts)
111
+ if (now - v.windowStart > OAuthServerImpl.REGISTER_IP_WINDOW_MS)
112
+ this.registerIpCounts.delete(k);
113
+ for (const [k, v] of this.cimdCache)
114
+ if (now - v.fetchedAt > OAuthServerImpl.CIMD_CACHE_TTL_MS)
115
+ this.cimdCache.delete(k);
116
+ }, 10 * 60 * 1_000);
117
+ this.gcTimer.unref();
118
+ this.loadTokens();
119
+ }
120
+ destroy() {
121
+ // Flush any pending debounced persist before shutdown
122
+ if (this.persistTimer !== null) {
123
+ clearTimeout(this.persistTimer);
124
+ this.persistTimer = null;
125
+ this.persistTokens();
126
+ }
127
+ clearInterval(this.gcTimer);
128
+ }
129
+ // ── RFC 8414 discovery ────────────────────────────────────────────────────
130
+ handleDiscovery(res) {
131
+ this.sendJson(res, 200, {
132
+ issuer: this.issuerUrl,
133
+ authorization_endpoint: `${this.issuerUrl}/oauth/authorize`,
134
+ token_endpoint: `${this.issuerUrl}/oauth/token`,
135
+ revocation_endpoint: `${this.issuerUrl}/oauth/revoke`,
136
+ registration_endpoint: `${this.issuerUrl}/oauth/register`,
137
+ response_types_supported: ["code"],
138
+ grant_types_supported: ["authorization_code"],
139
+ code_challenge_methods_supported: ["S256"],
140
+ token_endpoint_auth_methods_supported: ["none"],
141
+ scopes_supported: SUPPORTED_SCOPES,
142
+ });
143
+ }
144
+ // ── RFC 7591 Dynamic Client Registration ──────────────────────────────────
145
+ async handleRegister(req, res) {
146
+ if (req.method !== "POST") {
147
+ res.writeHead(405, { Allow: "POST" });
148
+ res.end("Method Not Allowed");
149
+ return;
150
+ }
151
+ let body = {};
152
+ try {
153
+ const raw = await new Promise((resolve, reject) => {
154
+ let data = "";
155
+ const onData = (c) => {
156
+ data += c.toString();
157
+ if (data.length > 8192) {
158
+ req.removeListener("data", onData);
159
+ req.removeListener("end", onEnd);
160
+ req.destroy();
161
+ reject(new Error("too large"));
162
+ }
163
+ };
164
+ const onEnd = () => resolve(data);
165
+ req.on("data", onData);
166
+ req.on("end", onEnd);
167
+ req.on("error", reject);
168
+ });
169
+ body = raw ? JSON.parse(raw) : {};
170
+ }
171
+ catch {
172
+ this.sendJson(res, 400, { error: "invalid_client_metadata" });
173
+ return;
174
+ }
175
+ const redirectUris = body.redirect_uris;
176
+ if (!Array.isArray(redirectUris) || redirectUris.length === 0) {
177
+ this.sendJson(res, 400, { error: "invalid_redirect_uri" });
178
+ return;
179
+ }
180
+ // Validate each redirect_uri is a well-formed absolute HTTPS (or localhost) URL
181
+ for (const uri of redirectUris) {
182
+ if (typeof uri !== "string") {
183
+ this.sendJson(res, 400, { error: "invalid_redirect_uri" });
184
+ return;
185
+ }
186
+ try {
187
+ const u = new URL(uri);
188
+ const isLocalhost = u.hostname === "localhost" || u.hostname === "127.0.0.1";
189
+ if (!isLocalhost && u.protocol !== "https:") {
190
+ this.sendJson(res, 400, { error: "invalid_redirect_uri" });
191
+ return;
192
+ }
193
+ }
194
+ catch {
195
+ this.sendJson(res, 400, { error: "invalid_redirect_uri" });
196
+ return;
197
+ }
198
+ }
199
+ // Validate scope if provided
200
+ if (body.scope !== undefined) {
201
+ const requestedScopes = String(body.scope).split(" ");
202
+ for (const s of requestedScopes) {
203
+ if (!SUPPORTED_SCOPES.includes(s)) {
204
+ this.sendJson(res, 400, {
205
+ error: "invalid_client_metadata",
206
+ error_description: `unsupported scope: ${s}`,
207
+ });
208
+ return;
209
+ }
210
+ }
211
+ }
212
+ // Per-IP rate limit: max 10 registrations per minute per IP
213
+ const remoteIp = (req.socket?.remoteAddress ?? "unknown").slice(0, 64);
214
+ const now = Date.now();
215
+ const ipEntry = this.registerIpCounts.get(remoteIp);
216
+ if (ipEntry &&
217
+ now - ipEntry.windowStart < OAuthServerImpl.REGISTER_IP_WINDOW_MS) {
218
+ ipEntry.count++;
219
+ if (ipEntry.count > OAuthServerImpl.REGISTER_IP_MAX) {
220
+ this.sendJson(res, 429, {
221
+ error: "too_many_requests",
222
+ error_description: "per-IP client registration limit reached",
223
+ });
224
+ return;
225
+ }
226
+ }
227
+ else {
228
+ this.registerIpCounts.set(remoteIp, { count: 1, windowStart: now });
229
+ }
230
+ // Cap registered clients to prevent memory exhaustion via pre-auth DoS.
231
+ // /oauth/register requires no bearer token, so any caller can POST freely.
232
+ if (this.registeredClients.size >= 500) {
233
+ this.sendJson(res, 429, {
234
+ error: "too_many_requests",
235
+ error_description: "client registration limit reached",
236
+ });
237
+ return;
238
+ }
239
+ // Public clients only — no client secret issued
240
+ const clientId = this.randomToken(16);
241
+ this.registeredClients.set(clientId, {
242
+ redirectUris: redirectUris,
243
+ issuedAt: Date.now(),
244
+ });
245
+ this.sendJson(res, 201, {
246
+ client_id: clientId,
247
+ client_id_issued_at: Math.floor(Date.now() / 1000),
248
+ redirect_uris: redirectUris,
249
+ grant_types: ["authorization_code"],
250
+ response_types: ["code"],
251
+ token_endpoint_auth_method: "none",
252
+ ...(body.client_name
253
+ ? {
254
+ client_name: typeof body.client_name === "string"
255
+ ? body.client_name.replace(/[\x00-\x1f\x7f]/g, "").slice(0, 128)
256
+ : undefined,
257
+ }
258
+ : {}),
259
+ });
260
+ }
261
+ // ── Authorization endpoint ────────────────────────────────────────────────
262
+ async handleAuthorize(req, res) {
263
+ const method = req.method ?? "GET";
264
+ if (method === "GET") {
265
+ await this.authorizeGet(req, res);
266
+ }
267
+ else if (method === "POST") {
268
+ await this.authorizePost(req, res);
269
+ }
270
+ else {
271
+ res.writeHead(405, { "Content-Type": "text/plain", Allow: "GET, POST" });
272
+ res.end("Method Not Allowed");
273
+ }
274
+ }
275
+ async authorizeGet(req, res) {
276
+ const url = new URL(req.url ?? "/", this.issuerUrl);
277
+ const { error, clientId, redirectUri, codeChallenge, scope, state } = await this.parseAuthorizeParams(url);
278
+ if (error) {
279
+ res.writeHead(400, { "Content-Type": "text/plain" });
280
+ res.end(error);
281
+ return;
282
+ }
283
+ // Generate CSRF nonce keyed by a random flowId (not client_id) so concurrent
284
+ // authorization flows for the same client_id cannot overwrite each other's nonce.
285
+ const csrfNonce = crypto.randomBytes(16).toString("hex");
286
+ const flowId = crypto.randomBytes(8).toString("hex");
287
+ this.csrfNonces.set(flowId, {
288
+ nonce: csrfNonce,
289
+ clientId: clientId,
290
+ expiresAt: Date.now() + OAuthServerImpl.CSRF_TTL_MS,
291
+ });
292
+ res.writeHead(200, {
293
+ "Content-Type": "text/html; charset=utf-8",
294
+ "Content-Security-Policy": "default-src 'none'; style-src 'unsafe-inline'; form-action 'self'; frame-ancestors 'none'",
295
+ "X-Frame-Options": "DENY",
296
+ });
297
+ res.end(this.approvalPage({
298
+ clientId: clientId,
299
+ redirectUri: redirectUri,
300
+ codeChallenge: codeChallenge,
301
+ scope: scope ?? DEFAULT_SCOPE,
302
+ state: state ?? "",
303
+ csrfNonce,
304
+ flowId,
305
+ }));
306
+ }
307
+ async authorizePost(req, res) {
308
+ let body;
309
+ try {
310
+ body = await this.readBody(req);
311
+ }
312
+ catch {
313
+ res.writeHead(400, { "Content-Type": "text/plain" });
314
+ res.end("request body too large");
315
+ return;
316
+ }
317
+ const action = body.get("action");
318
+ const clientId = body.get("client_id") ?? "";
319
+ const redirectUri = body.get("redirect_uri") ?? "";
320
+ const codeChallenge = body.get("code_challenge") ?? "";
321
+ const scope = body.get("scope") ?? DEFAULT_SCOPE;
322
+ const state = body.get("state") ?? "";
323
+ const csrfNonce = body.get("csrf_nonce") ?? "";
324
+ const flowId = body.get("flow_id") ?? "";
325
+ if (!clientId || !redirectUri || !codeChallenge) {
326
+ res.writeHead(400, { "Content-Type": "text/plain" });
327
+ res.end("missing parameters");
328
+ return;
329
+ }
330
+ // Verify CSRF nonce before any further processing.
331
+ // Look up by flowId (not clientId) to prevent concurrent-flow nonce collision attacks.
332
+ const storedCsrf = this.csrfNonces.get(flowId);
333
+ if (!storedCsrf ||
334
+ storedCsrf.expiresAt < Date.now() ||
335
+ storedCsrf.clientId !== clientId ||
336
+ !timingSafeStringEqual(csrfNonce, storedCsrf.nonce)) {
337
+ res.writeHead(403, { "Content-Type": "text/plain" });
338
+ res.end("invalid or expired CSRF token");
339
+ return;
340
+ }
341
+ // Consume the nonce (one-time use)
342
+ this.csrfNonces.delete(flowId);
343
+ // Validate redirect_uri against registered URIs to prevent open redirect
344
+ const registered = this.registeredClients.get(clientId);
345
+ if (!registered?.redirectUris.includes(redirectUri)) {
346
+ res.writeHead(400, { "Content-Type": "text/plain" });
347
+ res.end("invalid redirect_uri");
348
+ return;
349
+ }
350
+ if (action === "deny") {
351
+ const u = new URL(redirectUri);
352
+ u.searchParams.set("error", "access_denied");
353
+ if (state)
354
+ u.searchParams.set("state", state);
355
+ res.writeHead(302, { Location: u.toString() });
356
+ res.end();
357
+ return;
358
+ }
359
+ if (action !== "approve") {
360
+ res.writeHead(400, { "Content-Type": "text/plain" });
361
+ res.end("invalid action");
362
+ return;
363
+ }
364
+ // Verify bridge token on approve
365
+ const presentedToken = body.get("bridge_token") ?? "";
366
+ if (!timingSafeStringEqual(presentedToken, this.bridgeToken)) {
367
+ // Issue a fresh flowId + nonce for the retry so the form remains usable.
368
+ const retryCsrfNonce = crypto.randomBytes(16).toString("hex");
369
+ const retryFlowId = crypto.randomBytes(8).toString("hex");
370
+ this.csrfNonces.set(retryFlowId, {
371
+ nonce: retryCsrfNonce,
372
+ clientId,
373
+ expiresAt: Date.now() + OAuthServerImpl.CSRF_TTL_MS,
374
+ });
375
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
376
+ res.end(this.approvalPage({
377
+ clientId,
378
+ redirectUri,
379
+ codeChallenge,
380
+ scope,
381
+ state,
382
+ tokenError: true,
383
+ csrfNonce: retryCsrfNonce,
384
+ flowId: retryFlowId,
385
+ }));
386
+ return;
387
+ }
388
+ const code = this.randomToken(32);
389
+ this.authCodes.set(code, {
390
+ clientId,
391
+ redirectUri,
392
+ codeChallenge,
393
+ scope,
394
+ expiresAt: Date.now() + CODE_TTL_MS,
395
+ used: false,
396
+ });
397
+ const dest = new URL(redirectUri);
398
+ dest.searchParams.set("code", code);
399
+ if (state)
400
+ dest.searchParams.set("state", state);
401
+ res.writeHead(302, { Location: dest.toString() });
402
+ res.end();
403
+ }
404
+ // ── Token endpoint ────────────────────────────────────────────────────────
405
+ async handleToken(req, res) {
406
+ let body;
407
+ try {
408
+ body = await this.readBody(req);
409
+ }
410
+ catch {
411
+ this.sendError(res, 400, "invalid_request");
412
+ return;
413
+ }
414
+ if (body.get("grant_type") !== "authorization_code") {
415
+ this.sendError(res, 400, "unsupported_grant_type");
416
+ return;
417
+ }
418
+ const code = body.get("code") ?? "";
419
+ const redirectUri = body.get("redirect_uri") ?? "";
420
+ const clientId = body.get("client_id") ?? "";
421
+ const verifier = body.get("code_verifier") ?? "";
422
+ if (!code || !redirectUri || !clientId || !verifier) {
423
+ this.sendError(res, 400, "invalid_request", "missing required parameters");
424
+ return;
425
+ }
426
+ const record = this.authCodes.get(code);
427
+ if (!record) {
428
+ this.sendError(res, 400, "invalid_grant", "authorization code not found or expired");
429
+ return;
430
+ }
431
+ if (record.used) {
432
+ this.sendError(res, 400, "invalid_grant", "authorization code already used");
433
+ return;
434
+ }
435
+ if (record.expiresAt < Date.now()) {
436
+ this.authCodes.delete(code);
437
+ this.sendError(res, 400, "invalid_grant", "authorization code expired");
438
+ return;
439
+ }
440
+ if (!timingSafeStringEqual(record.clientId, clientId)) {
441
+ this.sendError(res, 400, "invalid_grant", "client_id mismatch");
442
+ return;
443
+ }
444
+ if (!timingSafeStringEqual(record.redirectUri, redirectUri)) {
445
+ this.sendError(res, 400, "invalid_grant", "redirect_uri mismatch");
446
+ return;
447
+ }
448
+ if (!this.pkceVerify(verifier, record.codeChallenge)) {
449
+ this.sendError(res, 400, "invalid_grant", "code_verifier mismatch");
450
+ return;
451
+ }
452
+ // RFC 6749 §4.1.2: delete the auth code immediately on use.
453
+ // A missing code naturally rejects replay attempts (the !record check above).
454
+ this.authCodes.delete(code);
455
+ const accessToken = this.randomToken(32);
456
+ this.accessTokens.set(accessToken, {
457
+ clientId,
458
+ scope: record.scope,
459
+ expiresAt: Date.now() + this.tokenTtlMs,
460
+ });
461
+ this.schedulePersist();
462
+ this.sendJson(res, 200, {
463
+ access_token: accessToken,
464
+ token_type: "Bearer",
465
+ expires_in: Math.floor(this.tokenTtlMs / 1_000),
466
+ scope: record.scope,
467
+ });
468
+ }
469
+ // ── Revocation endpoint (RFC 7009) ────────────────────────────────────────
470
+ async handleRevoke(req, res) {
471
+ try {
472
+ const body = await this.readBody(req);
473
+ const token = body.get("token");
474
+ if (token) {
475
+ this.accessTokens.delete(token);
476
+ this.authCodes.delete(token);
477
+ // Also remove from hashed (disk-persisted) tokens
478
+ const hash = this.hashToken(token);
479
+ this.hashedTokens.delete(hash);
480
+ this.schedulePersist();
481
+ }
482
+ }
483
+ catch {
484
+ // RFC 7009: always 200
485
+ }
486
+ res.writeHead(200, {
487
+ "Content-Type": "application/json",
488
+ "Cache-Control": "no-store",
489
+ });
490
+ res.end("{}");
491
+ }
492
+ // ── Bearer resolution (called by server.ts) ───────────────────────────────
493
+ resolveBearerToken(token) {
494
+ const record = this.lookupToken(token);
495
+ if (!record)
496
+ return null;
497
+ return this.bridgeToken;
498
+ }
499
+ resolveBearerScope(token) {
500
+ const record = this.lookupToken(token);
501
+ if (!record)
502
+ return null;
503
+ return record.scope;
504
+ }
505
+ /**
506
+ * Lookup a bearer token in memory first, then fall back to disk-loaded hashed
507
+ * tokens. Promotes disk tokens to the in-memory map on first use for fast
508
+ * subsequent lookups.
509
+ */
510
+ lookupToken(token) {
511
+ // 1. Check in-memory map (tokens issued this session)
512
+ const inMemory = this.accessTokens.get(token);
513
+ if (inMemory) {
514
+ if (inMemory.expiresAt < Date.now()) {
515
+ this.accessTokens.delete(token);
516
+ return null;
517
+ }
518
+ return inMemory;
519
+ }
520
+ // 2. Check disk-loaded hashed tokens (tokens issued before last restart)
521
+ const hash = this.hashToken(token);
522
+ const fromDisk = this.hashedTokens.get(hash);
523
+ if (fromDisk) {
524
+ if (fromDisk.expiresAt < Date.now()) {
525
+ this.hashedTokens.delete(hash);
526
+ return null;
527
+ }
528
+ // Promote to in-memory for fast subsequent lookups
529
+ this.accessTokens.set(token, fromDisk);
530
+ this.hashedTokens.delete(hash);
531
+ return fromDisk;
532
+ }
533
+ return null;
534
+ }
535
+ // ── Token persistence helpers ─────────────────────────────────────────────
536
+ hashToken(token) {
537
+ return crypto.createHash("sha256").update(token).digest("hex");
538
+ }
539
+ loadTokens() {
540
+ if (!this.tokenStorePath)
541
+ return;
542
+ try {
543
+ const raw = fs.readFileSync(this.tokenStorePath, "utf8");
544
+ const parsed = JSON.parse(raw);
545
+ if (parsed.version !== 1 || typeof parsed.tokens !== "object")
546
+ return;
547
+ const entries = Object.entries(parsed.tokens);
548
+ // Cap: refuse to load a file with suspiciously many entries to prevent DoS
549
+ if (entries.length > 10_000) {
550
+ console.warn("[claude-ide-bridge] oauth-tokens.json contains >10,000 entries — skipping load");
551
+ return;
552
+ }
553
+ const now = Date.now();
554
+ for (const [hash, entry] of entries) {
555
+ // Validate each field before trusting the persisted data
556
+ if (typeof entry.clientId !== "string" ||
557
+ typeof entry.scope !== "string" ||
558
+ typeof entry.expiresAt !== "number" ||
559
+ !Number.isFinite(entry.expiresAt) ||
560
+ entry.expiresAt <= 0 ||
561
+ !SUPPORTED_SCOPES.some((s) => entry.scope === s ||
562
+ entry.scope
563
+ .split(" ")
564
+ .every((tok) => SUPPORTED_SCOPES.includes(tok)))) {
565
+ continue; // skip invalid entries
566
+ }
567
+ if (entry.expiresAt > now) {
568
+ this.hashedTokens.set(hash, {
569
+ clientId: entry.clientId,
570
+ scope: entry.scope,
571
+ expiresAt: entry.expiresAt,
572
+ });
573
+ }
574
+ }
575
+ }
576
+ catch {
577
+ // Missing or corrupt file — start fresh
578
+ }
579
+ }
580
+ persistTokens() {
581
+ if (!this.tokenStorePath)
582
+ return;
583
+ try {
584
+ const now = Date.now();
585
+ const tokens = {};
586
+ // Persist current in-memory tokens (keyed by hash).
587
+ // Invariant: a promoted disk token exists ONLY in `accessTokens` — it was
588
+ // deleted from `hashedTokens` at promotion time. Do not write it from both
589
+ // maps or revocation tracking breaks.
590
+ for (const [rawToken, record] of this.accessTokens) {
591
+ if (record.expiresAt > now) {
592
+ tokens[this.hashToken(rawToken)] = {
593
+ clientId: record.clientId,
594
+ scope: record.scope,
595
+ expiresAt: record.expiresAt,
596
+ };
597
+ }
598
+ }
599
+ // Persist still-valid disk tokens that have not yet been promoted.
600
+ // The `!(hash in tokens)` guard is a safety net — promoted tokens should
601
+ // already be absent from `hashedTokens`, but the check prevents any
602
+ // double-write if that invariant is ever violated.
603
+ for (const [hash, record] of this.hashedTokens) {
604
+ if (record.expiresAt > now && !(hash in tokens)) {
605
+ tokens[hash] = {
606
+ clientId: record.clientId,
607
+ scope: record.scope,
608
+ expiresAt: record.expiresAt,
609
+ };
610
+ }
611
+ }
612
+ const data = JSON.stringify({ version: 1, tokens }, null, 2);
613
+ const tmpPath = `${this.tokenStorePath}.tmp`;
614
+ const dir = path.dirname(this.tokenStorePath);
615
+ if (!fs.existsSync(dir))
616
+ fs.mkdirSync(dir, { recursive: true });
617
+ fs.writeFileSync(tmpPath, data, { mode: 0o600 });
618
+ fs.renameSync(tmpPath, this.tokenStorePath);
619
+ fs.chmodSync(this.tokenStorePath, 0o600);
620
+ }
621
+ catch {
622
+ // Best-effort — never block operation
623
+ }
624
+ }
625
+ schedulePersist() {
626
+ if (!this.tokenStorePath)
627
+ return;
628
+ if (this.persistTimer !== null)
629
+ clearTimeout(this.persistTimer);
630
+ this.persistTimer = setTimeout(() => {
631
+ this.persistTimer = null;
632
+ this.persistTokens();
633
+ }, 500).unref(); // unref so a pending flush doesn't prevent process exit
634
+ }
635
+ // ── Private helpers ───────────────────────────────────────────────────────
636
+ randomToken(bytes) {
637
+ return crypto.randomBytes(bytes).toString("base64url");
638
+ }
639
+ pkceVerify(verifier, challenge) {
640
+ const hash = crypto
641
+ .createHash("sha256")
642
+ .update(verifier)
643
+ .digest("base64url");
644
+ return timingSafeStringEqual(hash, challenge);
645
+ }
646
+ readBody(req) {
647
+ return new Promise((resolve, reject) => {
648
+ let data = "";
649
+ const onData = (chunk) => {
650
+ data += chunk.toString();
651
+ if (data.length > 8_192) {
652
+ req.removeListener("data", onData);
653
+ req.removeListener("end", onEnd);
654
+ req.destroy();
655
+ reject(new Error("Request body too large"));
656
+ }
657
+ };
658
+ const onEnd = () => resolve(new URLSearchParams(data));
659
+ req.on("data", onData);
660
+ req.on("end", onEnd);
661
+ req.on("error", reject);
662
+ });
663
+ }
664
+ sendJson(res, status, body) {
665
+ res.writeHead(status, {
666
+ "Content-Type": "application/json",
667
+ "Cache-Control": "no-store",
668
+ Pragma: "no-cache",
669
+ });
670
+ res.end(JSON.stringify(body));
671
+ }
672
+ sendError(res, status, error, description) {
673
+ this.sendJson(res, status, {
674
+ error,
675
+ ...(description ? { error_description: description } : {}),
676
+ });
677
+ }
678
+ /**
679
+ * Fetch and cache a Client ID Metadata Document (CIMD / SEP-991).
680
+ * Called when client_id is an HTTPS URL instead of an opaque registered ID.
681
+ * Returns the redirect_uris from the document, or null on any error.
682
+ *
683
+ * Security: only public HTTPS URLs are allowed (isPrivateCimdHost blocks
684
+ * RFC 1918 / loopback). Response size capped at CIMD_MAX_BYTES.
685
+ */
686
+ async fetchCimd(clientIdUrl) {
687
+ const cached = this.cimdCache.get(clientIdUrl);
688
+ if (cached &&
689
+ Date.now() - cached.fetchedAt < OAuthServerImpl.CIMD_CACHE_TTL_MS) {
690
+ return cached.redirectUris;
691
+ }
692
+ let parsed;
693
+ try {
694
+ parsed = new URL(clientIdUrl);
695
+ }
696
+ catch {
697
+ return null;
698
+ }
699
+ if (parsed.protocol !== "https:")
700
+ return null;
701
+ if (isPrivateCimdHost(parsed.hostname))
702
+ return null;
703
+ try {
704
+ const controller = new AbortController();
705
+ const timeout = setTimeout(() => controller.abort(), 5_000);
706
+ let body;
707
+ try {
708
+ // No redirects — CIMD metadata documents must be served directly at
709
+ // the registered client_id URL. Following Location headers from an
710
+ // attacker-controlled server could bypass the isPrivateCimdHost() guard
711
+ // regardless of per-hop re-validation.
712
+ const resp = await fetch(clientIdUrl, {
713
+ signal: controller.signal,
714
+ headers: { Accept: "application/json" },
715
+ redirect: "error",
716
+ });
717
+ if (!resp.ok)
718
+ return null;
719
+ // Stream with size cap to prevent OOM
720
+ const reader = resp.body?.getReader();
721
+ if (!reader)
722
+ return null;
723
+ const chunks = [];
724
+ let total = 0;
725
+ while (true) {
726
+ const { done, value } = await reader.read();
727
+ if (done)
728
+ break;
729
+ total += value.length;
730
+ if (total > OAuthServerImpl.CIMD_MAX_BYTES) {
731
+ reader.cancel().catch(() => { });
732
+ return null;
733
+ }
734
+ chunks.push(value);
735
+ }
736
+ body = Buffer.concat(chunks).toString("utf8");
737
+ }
738
+ finally {
739
+ clearTimeout(timeout);
740
+ }
741
+ const doc = JSON.parse(body);
742
+ const uris = doc.redirect_uris;
743
+ if (!Array.isArray(uris) || uris.length === 0)
744
+ return null;
745
+ const redirectUris = uris.filter((u) => typeof u === "string");
746
+ if (redirectUris.length === 0)
747
+ return null;
748
+ this.cimdCache.set(clientIdUrl, { redirectUris, fetchedAt: Date.now() });
749
+ return redirectUris;
750
+ }
751
+ catch {
752
+ return null;
753
+ }
754
+ }
755
+ async parseAuthorizeParams(url) {
756
+ const responseType = url.searchParams.get("response_type");
757
+ const clientId = url.searchParams.get("client_id");
758
+ const redirectUri = url.searchParams.get("redirect_uri");
759
+ const codeChallenge = url.searchParams.get("code_challenge");
760
+ const codeChallengeMethod = url.searchParams.get("code_challenge_method");
761
+ const state = url.searchParams.get("state");
762
+ if (responseType !== "code")
763
+ return { error: "unsupported_response_type" };
764
+ if (!clientId || !redirectUri || !codeChallenge)
765
+ return { error: "invalid_request" };
766
+ if (codeChallengeMethod !== "S256")
767
+ return { error: "invalid_request" };
768
+ // CIMD: if client_id is an HTTPS URL, fetch its metadata document to get
769
+ // redirect_uris dynamically (SEP-991 / Claude Code v2.1.81+).
770
+ // Otherwise fall back to the pre-registered client map.
771
+ let allowedRedirectUris;
772
+ if (clientId.startsWith("https://")) {
773
+ const cimdUris = await this.fetchCimd(clientId);
774
+ if (!cimdUris)
775
+ return { error: "invalid_client" };
776
+ allowedRedirectUris = cimdUris;
777
+ // Register the client dynamically so the POST handler can look it up
778
+ if (!this.registeredClients.has(clientId)) {
779
+ this.registeredClients.set(clientId, {
780
+ redirectUris: cimdUris,
781
+ issuedAt: Date.now(),
782
+ });
783
+ }
784
+ }
785
+ else {
786
+ const registered = this.registeredClients.get(clientId);
787
+ allowedRedirectUris = registered?.redirectUris;
788
+ }
789
+ if (!allowedRedirectUris?.includes(redirectUri)) {
790
+ return { error: "invalid_redirect_uri" };
791
+ }
792
+ return {
793
+ clientId,
794
+ redirectUri,
795
+ codeChallenge,
796
+ scope: url.searchParams.get("scope") ?? DEFAULT_SCOPE,
797
+ state: state ?? "",
798
+ };
799
+ }
800
+ // ── Approval page HTML ────────────────────────────────────────────────────
801
+ approvalPage(opts) {
802
+ const e = (s) => s
803
+ .replace(/&/g, "&amp;")
804
+ .replace(/</g, "&lt;")
805
+ .replace(/>/g, "&gt;")
806
+ .replace(/"/g, "&quot;")
807
+ .replace(/'/g, "&#39;");
808
+ return `<!DOCTYPE html>
809
+ <html lang="en">
810
+ <head>
811
+ <meta charset="UTF-8">
812
+ <meta name="viewport" content="width=device-width,initial-scale=1">
813
+ <title>Authorize \u2014 Claude IDE Bridge</title>
814
+ <style>
815
+ *,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
816
+ body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;
817
+ background:#0f1117;color:#e2e8f0;display:flex;align-items:center;
818
+ justify-content:center;min-height:100vh;padding:2rem}
819
+ .card{background:#1a1d27;border:1px solid #2d3148;border-radius:12px;
820
+ padding:2rem;max-width:420px;width:100%;box-shadow:0 4px 32px rgba(0,0,0,.4)}
821
+ .logo{font-size:1.5rem;font-weight:700;color:#818cf8;margin-bottom:1.5rem}
822
+ h1{font-size:1.1rem;margin-bottom:.5rem}
823
+ .client{font-size:.9rem;color:#94a3b8;margin-bottom:1.5rem;word-break:break-all}
824
+ .scope{background:#12141e;border:1px solid #2d3148;border-radius:8px;
825
+ padding:1rem;margin-bottom:1.5rem;font-size:.875rem;color:#94a3b8}
826
+ .scope strong{color:#e2e8f0;display:block;margin-bottom:.5rem}
827
+ .item::before{content:"\u2713 ";color:#34d399}
828
+ .token-field{margin-bottom:1.25rem}
829
+ .token-field label{display:block;font-size:.8rem;color:#94a3b8;margin-bottom:.4rem}
830
+ .token-field input{width:100%;padding:.5rem .75rem;background:#12141e;border:1px solid #2d3148;
831
+ border-radius:6px;color:#e2e8f0;font-size:.875rem;font-family:monospace}
832
+ .token-field input.err{border-color:#f87171}
833
+ .token-err{color:#f87171;font-size:.8rem;margin-top:.3rem}
834
+ .actions{display:flex;gap:.75rem}
835
+ button{flex:1;padding:.65rem 1rem;border:none;border-radius:8px;
836
+ font-size:.95rem;font-weight:600;cursor:pointer;transition:opacity .15s}
837
+ button:hover{opacity:.85}
838
+ .approve{background:#818cf8;color:#0f1117}
839
+ .deny{background:#2d3148;color:#94a3b8}
840
+ footer{margin-top:1.25rem;font-size:.75rem;color:#475569;text-align:center}
841
+ </style>
842
+ </head>
843
+ <body>
844
+ <div class="card">
845
+ <div class="logo">\u29ed Claude IDE Bridge</div>
846
+ <h1>Authorization Request</h1>
847
+ <p class="client">Client: <strong>${e(opts.clientId)}</strong></p>
848
+ <div class="scope">
849
+ <strong>Requested permissions</strong>
850
+ <div class="item">Full MCP tool access (read, write, execute)</div>
851
+ </div>
852
+ <form method="POST" action="/oauth/authorize">
853
+ <input type="hidden" name="client_id" value="${e(opts.clientId)}">
854
+ <input type="hidden" name="redirect_uri" value="${e(opts.redirectUri)}">
855
+ <input type="hidden" name="code_challenge" value="${e(opts.codeChallenge)}">
856
+ <input type="hidden" name="scope" value="${e(opts.scope)}">
857
+ <input type="hidden" name="state" value="${e(opts.state)}">
858
+ <input type="hidden" name="csrf_nonce" value="${e(opts.csrfNonce ?? "")}">
859
+ <input type="hidden" name="flow_id" value="${e(opts.flowId ?? "")}">
860
+ <div class="token-field">
861
+ <label for="bridge_token">Bridge Token</label>
862
+ <input id="bridge_token" type="password" name="bridge_token" placeholder="Paste your bridge token"
863
+ class="${opts.tokenError ? "err" : ""}" autocomplete="off" required>
864
+ ${opts.tokenError ? '<div class="token-err">Incorrect token — check your bridge token and try again.</div>' : ""}
865
+ </div>
866
+ <div class="actions">
867
+ <button class="approve" type="submit" name="action" value="approve">Authorize</button>
868
+ <button class="deny" type="submit" name="action" value="deny">Deny</button>
869
+ </div>
870
+ </form>
871
+ <footer>
872
+ Issuer: ${e(this.issuerUrl)}<br>
873
+ Only approve if you initiated this from your MCP client.
874
+ </footer>
875
+ </div>
876
+ </body>
877
+ </html>`;
878
+ }
879
+ }
880
+ //# sourceMappingURL=oauth.js.map