codebase-onboarder 0.6.0 → 0.7.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 (448) hide show
  1. package/README.md +174 -46
  2. package/bin/build-if-source.js +23 -0
  3. package/cli/commands.d.ts +26 -0
  4. package/cli/commands.js +836 -811
  5. package/cli/commands.js.map +1 -0
  6. package/cli/explorer/advanced.d.ts +14 -0
  7. package/cli/explorer/advanced.js +321 -329
  8. package/cli/explorer/advanced.js.map +1 -0
  9. package/cli/explorer/app.d.ts +1 -0
  10. package/cli/explorer/app.js +384 -313
  11. package/cli/explorer/app.js.map +1 -0
  12. package/cli/explorer/assistant.d.ts +9 -0
  13. package/cli/explorer/assistant.js +58 -0
  14. package/cli/explorer/assistant.js.map +1 -0
  15. package/cli/explorer/commands.d.ts +17 -0
  16. package/cli/explorer/commands.js +368 -302
  17. package/cli/explorer/commands.js.map +1 -0
  18. package/cli/explorer/featureViews.d.ts +124 -0
  19. package/cli/explorer/featureViews.js +179 -0
  20. package/cli/explorer/featureViews.js.map +1 -0
  21. package/cli/explorer/github.d.ts +21 -0
  22. package/cli/explorer/github.js +42 -44
  23. package/cli/explorer/github.js.map +1 -0
  24. package/cli/explorer/graphs.d.ts +7 -0
  25. package/cli/explorer/graphs.js +90 -95
  26. package/cli/explorer/graphs.js.map +1 -0
  27. package/cli/explorer/picker.d.ts +22 -0
  28. package/cli/explorer/picker.js +150 -0
  29. package/cli/explorer/picker.js.map +1 -0
  30. package/cli/explorer/report.d.ts +85 -0
  31. package/cli/explorer/report.js +60 -0
  32. package/cli/explorer/report.js.map +1 -0
  33. package/cli/explorer/session.d.ts +449 -0
  34. package/cli/explorer/session.js +143 -144
  35. package/cli/explorer/session.js.map +1 -0
  36. package/cli/explorer/views.d.ts +19 -0
  37. package/cli/explorer/views.js +465 -482
  38. package/cli/explorer/views.js.map +1 -0
  39. package/cli/explorer/wrap.d.ts +1 -0
  40. package/cli/explorer/wrap.js +28 -44
  41. package/cli/explorer/wrap.js.map +1 -0
  42. package/cli/main.d.ts +1 -0
  43. package/cli/main.js +214 -161
  44. package/cli/main.js.map +1 -0
  45. package/cli/prompt.d.ts +5 -0
  46. package/cli/prompt.js +84 -80
  47. package/cli/prompt.js.map +1 -0
  48. package/cli/scanOptions.d.ts +2 -0
  49. package/cli/scanOptions.js +11 -0
  50. package/cli/scanOptions.js.map +1 -0
  51. package/cli/ui.d.ts +33 -0
  52. package/cli/ui.js +35 -44
  53. package/cli/ui.js.map +1 -0
  54. package/cli/wizard.d.ts +187 -0
  55. package/cli/wizard.js +304 -284
  56. package/cli/wizard.js.map +1 -0
  57. package/package.json +15 -4
  58. package/public/app.d.ts +1 -0
  59. package/public/app.js +1057 -1009
  60. package/public/app.js.map +1 -0
  61. package/public/browser.d.ts +68 -0
  62. package/public/browser.js +2 -0
  63. package/public/browser.js.map +1 -0
  64. package/public/index.html +30 -13
  65. package/public/js/about.d.ts +6 -0
  66. package/public/js/about.js +152 -172
  67. package/public/js/about.js.map +1 -0
  68. package/public/js/aiDraft.d.ts +3 -0
  69. package/public/js/aiDraft.js +116 -124
  70. package/public/js/aiDraft.js.map +1 -0
  71. package/public/js/analysisPanel.d.ts +7 -0
  72. package/public/js/analysisPanel.js +166 -166
  73. package/public/js/analysisPanel.js.map +1 -0
  74. package/public/js/analysisReport.d.ts +38 -0
  75. package/public/js/analysisReport.js +105 -110
  76. package/public/js/analysisReport.js.map +1 -0
  77. package/public/js/api.d.ts +26 -0
  78. package/public/js/api.js +176 -167
  79. package/public/js/api.js.map +1 -0
  80. package/public/js/atlas.d.ts +6 -0
  81. package/public/js/atlas.js +50 -58
  82. package/public/js/atlas.js.map +1 -0
  83. package/public/js/blameView.d.ts +2 -0
  84. package/public/js/blameView.js +36 -37
  85. package/public/js/blameView.js.map +1 -0
  86. package/public/js/codeTab.d.ts +3 -0
  87. package/public/js/codeTab.js +175 -180
  88. package/public/js/codeTab.js.map +1 -0
  89. package/public/js/codeViewer.d.ts +5 -0
  90. package/public/js/codeViewer.js +162 -150
  91. package/public/js/codeViewer.js.map +1 -0
  92. package/public/js/components/FileChip.d.ts +9 -0
  93. package/public/js/components/FileChip.js +45 -49
  94. package/public/js/components/FileChip.js.map +1 -0
  95. package/public/js/components/MetricSparkline.d.ts +7 -0
  96. package/public/js/components/MetricSparkline.js +35 -44
  97. package/public/js/components/MetricSparkline.js.map +1 -0
  98. package/public/js/components/RiskBadge.d.ts +9 -0
  99. package/public/js/components/RiskBadge.js +55 -63
  100. package/public/js/components/RiskBadge.js.map +1 -0
  101. package/public/js/deepAnalysisView.d.ts +99 -0
  102. package/public/js/deepAnalysisView.js +282 -302
  103. package/public/js/deepAnalysisView.js.map +1 -0
  104. package/public/js/diagramPane.d.ts +8 -0
  105. package/public/js/diagramPane.js +133 -135
  106. package/public/js/diagramPane.js.map +1 -0
  107. package/public/js/diffView.d.ts +6 -0
  108. package/public/js/diffView.js +222 -209
  109. package/public/js/diffView.js.map +1 -0
  110. package/public/js/docsView.d.ts +2 -0
  111. package/public/js/docsView.js +441 -442
  112. package/public/js/docsView.js.map +1 -0
  113. package/public/js/dom.d.ts +189 -0
  114. package/public/js/dom.js +5 -0
  115. package/public/js/dom.js.map +1 -0
  116. package/public/js/fileSourceBrowser.d.ts +5 -0
  117. package/public/js/fileSourceBrowser.js +33 -41
  118. package/public/js/fileSourceBrowser.js.map +1 -0
  119. package/public/js/flameGraph.d.ts +5 -0
  120. package/public/js/flameGraph.js +62 -72
  121. package/public/js/flameGraph.js.map +1 -0
  122. package/public/js/forceGraph.d.ts +10 -0
  123. package/public/js/forceGraph.js +581 -556
  124. package/public/js/forceGraph.js.map +1 -0
  125. package/public/js/heatmap.d.ts +4 -0
  126. package/public/js/heatmap.js +142 -164
  127. package/public/js/heatmap.js.map +1 -0
  128. package/public/js/highlight.d.ts +2 -0
  129. package/public/js/highlight.js +116 -107
  130. package/public/js/highlight.js.map +1 -0
  131. package/public/js/html.d.ts +2 -0
  132. package/public/js/html.js +14 -44
  133. package/public/js/html.js.map +1 -0
  134. package/public/js/insightsView.d.ts +5 -0
  135. package/public/js/insightsView.js +93 -111
  136. package/public/js/insightsView.js.map +1 -0
  137. package/public/js/inspector.d.ts +37 -0
  138. package/public/js/inspector.js +416 -445
  139. package/public/js/inspector.js.map +1 -0
  140. package/public/js/llm.d.ts +113 -0
  141. package/public/js/llm.js +233 -272
  142. package/public/js/llm.js.map +1 -0
  143. package/public/js/mapView.d.ts +3 -0
  144. package/public/js/mapView.js +121 -120
  145. package/public/js/mapView.js.map +1 -0
  146. package/public/js/markdown.d.ts +3 -0
  147. package/public/js/markdown.js +84 -91
  148. package/public/js/markdown.js.map +1 -0
  149. package/public/js/mindmap.d.ts +32 -0
  150. package/public/js/mindmap.js +244 -259
  151. package/public/js/mindmap.js.map +1 -0
  152. package/public/js/mobilePanels.d.ts +5 -0
  153. package/public/js/mobilePanels.js +50 -0
  154. package/public/js/mobilePanels.js.map +1 -0
  155. package/public/js/repoFiles.d.ts +12 -0
  156. package/public/js/repoFiles.js +28 -27
  157. package/public/js/repoFiles.js.map +1 -0
  158. package/public/js/reviewView.d.ts +28 -0
  159. package/public/js/reviewView.js +256 -0
  160. package/public/js/reviewView.js.map +1 -0
  161. package/public/js/sbomView.d.ts +3 -0
  162. package/public/js/sbomView.js +59 -67
  163. package/public/js/sbomView.js.map +1 -0
  164. package/public/js/scanCache.d.ts +16 -0
  165. package/public/js/scanCache.js +67 -65
  166. package/public/js/scanCache.js.map +1 -0
  167. package/public/js/search.d.ts +7 -0
  168. package/public/js/search.js +868 -800
  169. package/public/js/search.js.map +1 -0
  170. package/public/js/searchLocal.d.ts +42 -0
  171. package/public/js/searchLocal.js +87 -0
  172. package/public/js/searchLocal.js.map +1 -0
  173. package/public/js/serverSettings.d.ts +5 -0
  174. package/public/js/serverSettings.js +153 -164
  175. package/public/js/serverSettings.js.map +1 -0
  176. package/public/js/state.d.ts +798 -0
  177. package/public/js/state.js +114 -122
  178. package/public/js/state.js.map +1 -0
  179. package/public/js/tour.d.ts +6 -0
  180. package/public/js/tour.js +5 -7
  181. package/public/js/tour.js.map +1 -0
  182. package/public/js/transitions.d.ts +2 -0
  183. package/public/js/transitions.js +9 -10
  184. package/public/js/transitions.js.map +1 -0
  185. package/public/js/tree.d.ts +12 -0
  186. package/public/js/tree.js +157 -154
  187. package/public/js/tree.js.map +1 -0
  188. package/public/js/workflowsView.d.ts +3 -0
  189. package/public/js/workflowsView.js +15 -22
  190. package/public/js/workflowsView.js.map +1 -0
  191. package/public/search.css +495 -0
  192. package/public/styles.css +163 -346
  193. package/public/workbench.css +166 -0
  194. package/server/apiAuth.d.ts +3 -0
  195. package/server/apiAuth.js +18 -20
  196. package/server/apiAuth.js.map +1 -0
  197. package/server/apiDiff.d.ts +2 -0
  198. package/server/apiDiff.js +20 -18
  199. package/server/apiDiff.js.map +1 -0
  200. package/server/apiDocs.d.ts +2 -0
  201. package/server/apiDocs.js +38 -40
  202. package/server/apiDocs.js.map +1 -0
  203. package/server/apiFile.d.ts +1 -0
  204. package/server/apiFile.js +14 -17
  205. package/server/apiFile.js.map +1 -0
  206. package/server/apiGitBlame.d.ts +2 -0
  207. package/server/apiGitBlame.js +50 -51
  208. package/server/apiGitBlame.js.map +1 -0
  209. package/server/apiMcp.d.ts +4 -0
  210. package/server/apiMcp.js +64 -66
  211. package/server/apiMcp.js.map +1 -0
  212. package/server/apiReview.d.ts +2 -0
  213. package/server/apiReview.js +45 -0
  214. package/server/apiReview.js.map +1 -0
  215. package/server/apiScan.d.ts +5 -0
  216. package/server/apiScan.js +90 -71
  217. package/server/apiScan.js.map +1 -0
  218. package/server/apiSearch.d.ts +33 -0
  219. package/server/apiSearch.js +184 -200
  220. package/server/apiSearch.js.map +1 -0
  221. package/server/apiSettings.d.ts +4 -0
  222. package/server/apiSettings.js +84 -85
  223. package/server/apiSettings.js.map +1 -0
  224. package/server/apiTools.d.ts +3 -0
  225. package/server/apiTools.js +55 -56
  226. package/server/apiTools.js.map +1 -0
  227. package/server/auth.d.ts +5 -0
  228. package/server/auth.js +54 -51
  229. package/server/auth.js.map +1 -0
  230. package/server/config.d.ts +42 -0
  231. package/server/config.js +189 -194
  232. package/server/config.js.map +1 -0
  233. package/server/daemon.d.ts +12 -0
  234. package/server/daemon.js +124 -114
  235. package/server/daemon.js.map +1 -0
  236. package/server/fileSourceNode.d.ts +2 -0
  237. package/server/fileSourceNode.js +34 -36
  238. package/server/fileSourceNode.js.map +1 -0
  239. package/server/gitClone.d.ts +9 -0
  240. package/server/gitClone.js +59 -61
  241. package/server/gitClone.js.map +1 -0
  242. package/server/gitDiff.d.ts +36 -0
  243. package/server/gitDiff.js +271 -169
  244. package/server/gitDiff.js.map +1 -0
  245. package/server/gitHistory.d.ts +12 -0
  246. package/server/gitHistory.js +80 -80
  247. package/server/gitHistory.js.map +1 -0
  248. package/server/gitRemote.d.ts +12 -0
  249. package/server/gitRemote.js +37 -38
  250. package/server/gitRemote.js.map +1 -0
  251. package/server/htmlText.d.ts +4 -0
  252. package/server/htmlText.js +16 -23
  253. package/server/htmlText.js.map +1 -0
  254. package/server/http.d.ts +5 -0
  255. package/server/http.js +27 -30
  256. package/server/http.js.map +1 -0
  257. package/server/httpGuards.d.ts +2 -0
  258. package/server/httpGuards.js +47 -43
  259. package/server/httpGuards.js.map +1 -0
  260. package/server/https.d.ts +34 -0
  261. package/server/https.js +121 -111
  262. package/server/https.js.map +1 -0
  263. package/server/index.d.ts +40 -0
  264. package/server/index.js +139 -147
  265. package/server/index.js.map +1 -0
  266. package/server/layout.d.ts +26 -0
  267. package/server/layout.js +59 -64
  268. package/server/layout.js.map +1 -0
  269. package/server/llmProxy.d.ts +3 -0
  270. package/server/llmProxy.js +137 -135
  271. package/server/llmProxy.js.map +1 -0
  272. package/server/logger.d.ts +38 -0
  273. package/server/logger.js +198 -211
  274. package/server/logger.js.map +1 -0
  275. package/server/mcp/analysis.d.ts +29 -0
  276. package/server/mcp/analysis.js +99 -120
  277. package/server/mcp/analysis.js.map +1 -0
  278. package/server/mcp/http.d.ts +9 -0
  279. package/server/mcp/http.js +128 -137
  280. package/server/mcp/http.js.map +1 -0
  281. package/server/mcp/runner.d.ts +68 -0
  282. package/server/mcp/runner.js +208 -218
  283. package/server/mcp/runner.js.map +1 -0
  284. package/server/mcp/server.d.ts +29 -0
  285. package/server/mcp/server.js +166 -179
  286. package/server/mcp/server.js.map +1 -0
  287. package/server/mcp/standalone.d.ts +1 -0
  288. package/server/mcp/standalone.js +8 -10
  289. package/server/mcp/standalone.js.map +1 -0
  290. package/server/mcp/tools.d.ts +2020 -0
  291. package/server/mcp/tools.js +523 -574
  292. package/server/mcp/tools.js.map +1 -0
  293. package/server/paths.d.ts +3 -0
  294. package/server/paths.js +17 -15
  295. package/server/paths.js.map +1 -0
  296. package/server/pidfile.d.ts +9 -0
  297. package/server/pidfile.js +43 -45
  298. package/server/pidfile.js.map +1 -0
  299. package/server/review.d.ts +5 -0
  300. package/server/review.js +35 -0
  301. package/server/review.js.map +1 -0
  302. package/server/router.d.ts +33 -0
  303. package/server/router.js +191 -197
  304. package/server/router.js.map +1 -0
  305. package/server/scanRequest.d.ts +3 -0
  306. package/server/scanRequest.js +36 -0
  307. package/server/scanRequest.js.map +1 -0
  308. package/server/searchIndex.d.ts +17 -0
  309. package/server/searchIndex.js +83 -80
  310. package/server/searchIndex.js.map +1 -0
  311. package/server/sessions.d.ts +14 -0
  312. package/server/sessions.js +62 -70
  313. package/server/sessions.js.map +1 -0
  314. package/server/startup.d.ts +118 -0
  315. package/server/startup.js +143 -148
  316. package/server/startup.js.map +1 -0
  317. package/server/static.d.ts +6 -0
  318. package/server/static.js +28 -31
  319. package/server/static.js.map +1 -0
  320. package/server/tools/install.d.ts +4 -0
  321. package/server/tools/install.js +176 -185
  322. package/server/tools/install.js.map +1 -0
  323. package/server/tools/parse.d.ts +8 -0
  324. package/server/tools/parse.js +112 -114
  325. package/server/tools/parse.js.map +1 -0
  326. package/server/tools/platform.d.ts +9 -0
  327. package/server/tools/platform.js +49 -47
  328. package/server/tools/platform.js.map +1 -0
  329. package/server/tools/registry.d.ts +137 -0
  330. package/server/tools/registry.js +173 -172
  331. package/server/tools/registry.js.map +1 -0
  332. package/server/tools/scan.d.ts +34 -0
  333. package/server/tools/scan.js +89 -98
  334. package/server/tools/scan.js.map +1 -0
  335. package/server/tools.d.ts +34 -0
  336. package/server/tools.js +94 -107
  337. package/server/tools.js.map +1 -0
  338. package/server/tunnel.d.ts +29 -0
  339. package/server/tunnel.js +61 -66
  340. package/server/tunnel.js.map +1 -0
  341. package/shared/analyzer/docs.d.ts +16 -0
  342. package/shared/analyzer/docs.js +99 -93
  343. package/shared/analyzer/docs.js.map +1 -0
  344. package/shared/analyzer/explainLocal.d.ts +4 -0
  345. package/shared/analyzer/explainLocal.js +122 -142
  346. package/shared/analyzer/explainLocal.js.map +1 -0
  347. package/shared/analyzer/github.d.ts +37 -0
  348. package/shared/analyzer/github.js +41 -43
  349. package/shared/analyzer/github.js.map +1 -0
  350. package/shared/analyzer/graph.d.ts +41 -0
  351. package/shared/analyzer/graph.js +393 -400
  352. package/shared/analyzer/graph.js.map +1 -0
  353. package/shared/analyzer/health.d.ts +33 -0
  354. package/shared/analyzer/health.js +197 -193
  355. package/shared/analyzer/health.js.map +1 -0
  356. package/shared/analyzer/history.d.ts +29 -0
  357. package/shared/analyzer/history.js +110 -111
  358. package/shared/analyzer/history.js.map +1 -0
  359. package/shared/analyzer/jsonConfig.d.ts +2 -0
  360. package/shared/analyzer/jsonConfig.js +61 -0
  361. package/shared/analyzer/jsonConfig.js.map +1 -0
  362. package/shared/analyzer/languages/csharp.d.ts +26 -0
  363. package/shared/analyzer/languages/csharp.js +83 -88
  364. package/shared/analyzer/languages/csharp.js.map +1 -0
  365. package/shared/analyzer/languages/generic.d.ts +23 -0
  366. package/shared/analyzer/languages/generic.js +94 -105
  367. package/shared/analyzer/languages/generic.js.map +1 -0
  368. package/shared/analyzer/languages/go.d.ts +24 -0
  369. package/shared/analyzer/languages/go.js +45 -54
  370. package/shared/analyzer/languages/go.js.map +1 -0
  371. package/shared/analyzer/languages/index.d.ts +6 -0
  372. package/shared/analyzer/languages/index.js +19 -20
  373. package/shared/analyzer/languages/index.js.map +1 -0
  374. package/shared/analyzer/languages/java.d.ts +20 -0
  375. package/shared/analyzer/languages/java.js +73 -78
  376. package/shared/analyzer/languages/java.js.map +1 -0
  377. package/shared/analyzer/languages/javascript.d.ts +12 -0
  378. package/shared/analyzer/languages/javascript.js +258 -209
  379. package/shared/analyzer/languages/javascript.js.map +1 -0
  380. package/shared/analyzer/languages/python.d.ts +23 -0
  381. package/shared/analyzer/languages/python.js +92 -100
  382. package/shared/analyzer/languages/python.js.map +1 -0
  383. package/shared/analyzer/languages/rust.d.ts +17 -0
  384. package/shared/analyzer/languages/rust.js +96 -98
  385. package/shared/analyzer/languages/rust.js.map +1 -0
  386. package/shared/analyzer/languages/typescript.d.ts +11 -0
  387. package/shared/analyzer/languages/typescript.js +117 -116
  388. package/shared/analyzer/languages/typescript.js.map +1 -0
  389. package/shared/analyzer/licenses.d.ts +18 -0
  390. package/shared/analyzer/licenses.js +130 -133
  391. package/shared/analyzer/licenses.js.map +1 -0
  392. package/shared/analyzer/metrics.d.ts +20 -0
  393. package/shared/analyzer/metrics.js +112 -104
  394. package/shared/analyzer/metrics.js.map +1 -0
  395. package/shared/analyzer/pathUtil.d.ts +10 -0
  396. package/shared/analyzer/pathUtil.js +29 -35
  397. package/shared/analyzer/pathUtil.js.map +1 -0
  398. package/shared/analyzer/patterns.d.ts +11 -0
  399. package/shared/analyzer/patterns.js +231 -253
  400. package/shared/analyzer/patterns.js.map +1 -0
  401. package/shared/analyzer/scan.d.ts +62 -0
  402. package/shared/analyzer/scan.js +451 -442
  403. package/shared/analyzer/scan.js.map +1 -0
  404. package/shared/analyzer/scanControl.d.ts +11 -0
  405. package/shared/analyzer/scanControl.js +62 -0
  406. package/shared/analyzer/scanControl.js.map +1 -0
  407. package/shared/analyzer/security.d.ts +18 -0
  408. package/shared/analyzer/security.js +159 -159
  409. package/shared/analyzer/security.js.map +1 -0
  410. package/shared/analyzer/services.d.ts +5 -0
  411. package/shared/analyzer/services.js +141 -138
  412. package/shared/analyzer/services.js.map +1 -0
  413. package/shared/analyzer/stack.d.ts +6 -0
  414. package/shared/analyzer/stack.js +134 -141
  415. package/shared/analyzer/stack.js.map +1 -0
  416. package/shared/analyzer/tour.d.ts +2 -0
  417. package/shared/analyzer/tour.js +45 -53
  418. package/shared/analyzer/tour.js.map +1 -0
  419. package/shared/analyzer/util.d.ts +8 -0
  420. package/shared/analyzer/util.js +130 -76
  421. package/shared/analyzer/util.js.map +1 -0
  422. package/shared/analyzer/workflows.d.ts +8 -0
  423. package/shared/analyzer/workflows.js +139 -134
  424. package/shared/analyzer/workflows.js.map +1 -0
  425. package/shared/contracts.d.ts +250 -0
  426. package/shared/contracts.js +2 -0
  427. package/shared/contracts.js.map +1 -0
  428. package/shared/diagram/aiFacts.d.ts +35 -0
  429. package/shared/diagram/aiFacts.js +102 -105
  430. package/shared/diagram/aiFacts.js.map +1 -0
  431. package/shared/diagram/aiMermaid.d.ts +5 -0
  432. package/shared/diagram/aiMermaid.js +46 -45
  433. package/shared/diagram/aiMermaid.js.map +1 -0
  434. package/shared/diagram/atlas.d.ts +16 -0
  435. package/shared/diagram/atlas.js +51 -60
  436. package/shared/diagram/atlas.js.map +1 -0
  437. package/shared/diagram/mermaid.d.ts +43 -0
  438. package/shared/diagram/mermaid.js +348 -370
  439. package/shared/diagram/mermaid.js.map +1 -0
  440. package/shared/review/contracts.d.ts +95 -0
  441. package/shared/review/contracts.js +2 -0
  442. package/shared/review/contracts.js.map +1 -0
  443. package/shared/review/review.d.ts +10 -0
  444. package/shared/review/review.js +184 -0
  445. package/shared/review/review.js.map +1 -0
  446. package/shared/search/query.d.ts +32 -0
  447. package/shared/search/query.js +395 -388
  448. package/shared/search/query.js.map +1 -0
package/server/router.js CHANGED
@@ -9,7 +9,6 @@
9
9
  // `Sec-Fetch-Site` to stop another page and DNS rebinding from driving it.
10
10
  // Self-hosted mode adds real access-key authentication: local browser requests
11
11
  // stay open, remote browsers get a signed session, and API clients use Bearer.
12
-
13
12
  import { handleDocs } from './apiDocs.js';
14
13
  import { handleFile } from './apiFile.js';
15
14
  import { handleCleanup, handleScan } from './apiScan.js';
@@ -20,145 +19,145 @@ import { serveStatic } from './static.js';
20
19
  import { handleSearch } from './apiSearch.js';
21
20
  import { handleBlame } from './apiGitBlame.js';
22
21
  import { handleDiff, handleDiffRefs } from './apiDiff.js';
22
+ import { handleReview } from './apiReview.js';
23
23
  import { handleToolsInstall, handleToolsRun, handleToolsStatus } from './apiTools.js';
24
24
  import { handleMcpStart, handleMcpStatus, handleMcpStop, handleMcpCommand } from './apiMcp.js';
25
25
  import { handleGetSettings, handleRotateAccessKey, handleUpdateSettings } from './apiSettings.js';
26
26
  import { handleAuthStatus, handleLogin, handleLogout } from './apiAuth.js';
27
27
  import { accessKeysMatch, allowedHosts, authReason, bearerToken, DEFAULT_SETTINGS } from './config.js';
28
28
  import { hasValidSession } from './auth.js';
29
-
30
29
  const ROUTES = [
31
- {
32
- method: 'GET', path: '/api/diff/refs', sameOrigin: true,
33
- run: ({ res, url }) => handleDiffRefs(res, url.searchParams.get('scan')),
34
- },
35
- {
36
- method: 'GET', path: '/api/diff', sameOrigin: true,
37
- run: ({ res, url }) => handleDiff(res, url.searchParams.get('scan'), url.searchParams),
38
- },
39
- {
40
- method: 'POST', path: '/api/search', body: true,
41
- run: ({ res, body }) => handleSearch(res, body),
42
- },
43
- {
44
- method: 'GET', path: '/api/blame', sameOrigin: true,
45
- run: ({ res, url }) => handleBlame(res, url.searchParams.get('scan'), url.searchParams.get('path')),
46
- },
47
- {
48
- method: 'POST', path: '/api/scan', body: true,
49
- run: ({ res, body, config }) => handleScan(res, body, config),
50
- },
51
- {
52
- method: 'DELETE', prefix: '/api/scan/',
53
- run: ({ res, rest }) => handleCleanup(res, decodeURIComponent(rest)),
54
- },
55
- {
56
- method: 'GET', path: '/api/file', sameOrigin: true,
57
- run: ({ res, url }) => handleFile(res, url.searchParams.get('scan'), url.searchParams.get('path')),
58
- },
59
- {
60
- // The proxy is the handler; there is no wrapper to write. It forwards the
61
- // browser's key to the endpoint the browser chose and never keeps either.
62
- method: 'POST', path: '/api/explain', body: true,
63
- run: ({ res, body }) => proxyChat(res, body),
64
- },
65
- {
66
- method: 'POST', path: '/api/doc', body: true,
67
- run: ({ res, body }) => handleDocs(res, body),
68
- },
69
- {
70
- // Reports which analyzers are installed — environment information a foreign
71
- // page has no business reading, so it is guarded like /api/file.
72
- method: 'GET', path: '/api/tools', sameOrigin: true,
73
- run: ({ res }) => handleToolsStatus(res),
74
- },
75
- {
76
- // Runs real analyzers over a scanned root. `body: true` applies the size
77
- // limit; the session check in the handler is the capability gate.
78
- method: 'POST', path: '/api/tools/run', body: true,
79
- run: ({ res, body }) => handleToolsRun(res, body),
80
- },
81
- {
82
- // Installs a missing analyzer from the GUI. Same-origin like every POST;
83
- // the tool name is validated against the registry and the install plans
84
- // are fixed data, so the request body never reaches a command line.
85
- method: 'POST', path: '/api/tools/install', body: true,
86
- run: ({ res, body }) => handleToolsInstall(res, body),
87
- },
88
- {
89
- // The MCP button's state, and the command an agent harness is configured with.
90
- // The config is not secret, but it is a path into this machine and a foreign
91
- // page has no business reading either one.
92
- method: 'GET', path: '/api/mcp', sameOrigin: true,
93
- run: ({ req, res, config }) => handleMcpStatus(req, res, config),
94
- },
95
- {
96
- method: 'GET', path: '/api/mcp/command', sameOrigin: true,
97
- run: ({ req, res, config }) => handleMcpCommand(req, res, config),
98
- },
99
- {
100
- // Starting and stopping a process is a side effect on the person's machine, so
101
- // these take a body like every other POST and are checked like every other POST.
102
- method: 'POST', path: '/api/mcp/start', body: true,
103
- run: ({ req, res, config }) => handleMcpStart(req, res, config),
104
- },
105
- {
106
- method: 'POST', path: '/api/mcp/stop', body: true,
107
- run: ({ req, res, config }) => handleMcpStop(req, res, config),
108
- },
109
- {
110
- method: 'GET', path: '/api/health',
111
- run: ({ res }) => sendJSON(res, 200, { ok: true }),
112
- },
113
- {
114
- method: 'GET', path: '/api/auth/status', sameOrigin: true,
115
- run: ({ req, res, settings }) => handleAuthStatus(req, res, settings),
116
- },
117
- {
118
- method: 'POST', path: '/api/auth/login', body: true, sameOrigin: true,
119
- run: ({ req, res, body, settings }) => handleLogin(req, res, body, settings),
120
- },
121
- {
122
- method: 'POST', path: '/api/auth/logout', body: true, sameOrigin: true,
123
- run: ({ req, res }) => handleLogout(req, res),
124
- },
125
- {
126
- // The settings drawer and the CLI read the same public shape: everything
127
- // about the configuration except the access key itself.
128
- method: 'GET', path: '/api/settings', sameOrigin: true,
129
- run: ({ req, res, config }) => handleGetSettings(req, res, config),
130
- },
131
- {
132
- // PATCH semantics over a small allow-listed key set; the validation that
133
- // rejects a bad port or a lawless mode lives in `server/config.js` and is
134
- // shared with the CLI wizard, so both surfaces enforce one schema.
135
- method: 'PUT', path: '/api/settings', body: true,
136
- run: ({ res, body, config }) => handleUpdateSettings(res, body, config),
137
- },
138
- {
139
- // The only way the key changes: generated on the server, shown once in
140
- // this response, never readable again. In self-hosted mode this endpoint
141
- // is itself behind the current key, so rotation requires possession.
142
- method: 'POST', path: '/api/settings/access-key', body: true,
143
- run: ({ req, res, config }) => handleRotateAccessKey(req, res, config),
144
- },
30
+ { method: 'POST', path: '/api/review', body: true, run: ({ res, body }) => handleReview(res, body) },
31
+ {
32
+ method: 'GET', path: '/api/diff/refs', sameOrigin: true,
33
+ run: ({ res, url }) => handleDiffRefs(res, url.searchParams.get('scan')),
34
+ },
35
+ {
36
+ method: 'GET', path: '/api/diff', sameOrigin: true,
37
+ run: ({ res, url }) => handleDiff(res, url.searchParams.get('scan'), url.searchParams),
38
+ },
39
+ {
40
+ method: 'POST', path: '/api/search', body: true,
41
+ run: ({ res, body }) => handleSearch(res, body),
42
+ },
43
+ {
44
+ method: 'GET', path: '/api/blame', sameOrigin: true,
45
+ run: ({ res, url }) => handleBlame(res, url.searchParams.get('scan'), url.searchParams.get('path')),
46
+ },
47
+ {
48
+ method: 'POST', path: '/api/scan', body: true,
49
+ run: ({ res, body, config }) => handleScan(res, body, config),
50
+ },
51
+ {
52
+ method: 'DELETE', prefix: '/api/scan/',
53
+ run: ({ res, rest }) => handleCleanup(res, decodeURIComponent(rest)),
54
+ },
55
+ {
56
+ method: 'GET', path: '/api/file', sameOrigin: true,
57
+ run: ({ res, url }) => handleFile(res, url.searchParams.get('scan'), url.searchParams.get('path')),
58
+ },
59
+ {
60
+ // The proxy is the handler; there is no wrapper to write. It forwards the
61
+ // browser's key to the endpoint the browser chose and never keeps either.
62
+ method: 'POST', path: '/api/explain', body: true,
63
+ run: ({ res, body }) => proxyChat(res, body),
64
+ },
65
+ {
66
+ method: 'POST', path: '/api/doc', body: true,
67
+ run: ({ res, body }) => handleDocs(res, body),
68
+ },
69
+ {
70
+ // Reports which analyzers are installed — environment information a foreign
71
+ // page has no business reading, so it is guarded like /api/file.
72
+ method: 'GET', path: '/api/tools', sameOrigin: true,
73
+ run: ({ res }) => handleToolsStatus(res),
74
+ },
75
+ {
76
+ // Runs real analyzers over a scanned root. `body: true` applies the size
77
+ // limit; the session check in the handler is the capability gate.
78
+ method: 'POST', path: '/api/tools/run', body: true,
79
+ run: ({ res, body }) => handleToolsRun(res, body),
80
+ },
81
+ {
82
+ // Installs a missing analyzer from the GUI. Same-origin like every POST;
83
+ // the tool name is validated against the registry and the install plans
84
+ // are fixed data, so the request body never reaches a command line.
85
+ method: 'POST', path: '/api/tools/install', body: true,
86
+ run: ({ res, body }) => handleToolsInstall(res, body),
87
+ },
88
+ {
89
+ // The MCP button's state, and the command an agent harness is configured with.
90
+ // The config is not secret, but it is a path into this machine and a foreign
91
+ // page has no business reading either one.
92
+ method: 'GET', path: '/api/mcp', sameOrigin: true,
93
+ run: ({ req, res, config }) => handleMcpStatus(req, res, config),
94
+ },
95
+ {
96
+ method: 'GET', path: '/api/mcp/command', sameOrigin: true,
97
+ run: ({ req, res, config }) => handleMcpCommand(req, res, config),
98
+ },
99
+ {
100
+ // Starting and stopping a process is a side effect on the person's machine, so
101
+ // these take a body like every other POST and are checked like every other POST.
102
+ method: 'POST', path: '/api/mcp/start', body: true,
103
+ run: ({ req, res, config }) => handleMcpStart(req, res, config),
104
+ },
105
+ {
106
+ method: 'POST', path: '/api/mcp/stop', body: true,
107
+ run: ({ req, res, config }) => handleMcpStop(req, res, config),
108
+ },
109
+ {
110
+ method: 'GET', path: '/api/health',
111
+ run: ({ res }) => sendJSON(res, 200, { ok: true }),
112
+ },
113
+ {
114
+ method: 'GET', path: '/api/auth/status', sameOrigin: true,
115
+ run: ({ req, res, settings }) => handleAuthStatus(req, res, settings),
116
+ },
117
+ {
118
+ method: 'POST', path: '/api/auth/login', body: true, sameOrigin: true,
119
+ run: ({ req, res, body, settings }) => handleLogin(req, res, body, settings),
120
+ },
121
+ {
122
+ method: 'POST', path: '/api/auth/logout', body: true, sameOrigin: true,
123
+ run: ({ req, res }) => handleLogout(req, res),
124
+ },
125
+ {
126
+ // The settings drawer and the CLI read the same public shape: everything
127
+ // about the configuration except the access key itself.
128
+ method: 'GET', path: '/api/settings', sameOrigin: true,
129
+ run: ({ req, res, config }) => handleGetSettings(req, res, config),
130
+ },
131
+ {
132
+ // PATCH semantics over a small allow-listed key set; the validation that
133
+ // rejects a bad port or a lawless mode lives in `server/config.js` and is
134
+ // shared with the CLI wizard, so both surfaces enforce one schema.
135
+ method: 'PUT', path: '/api/settings', body: true,
136
+ run: ({ res, body, config }) => handleUpdateSettings(res, body, config),
137
+ },
138
+ {
139
+ // The only way the key changes: generated on the server, shown once in
140
+ // this response, never readable again. In self-hosted mode this endpoint
141
+ // is itself behind the current key, so rotation requires possession.
142
+ method: 'POST', path: '/api/settings/access-key', body: true,
143
+ run: ({ req, res, config }) => handleRotateAccessKey(req, res, config),
144
+ },
145
145
  ];
146
-
147
146
  // The table itself, for the test that walks it. Exported read-only: the routes
148
147
  // are decided here, not assembled by whoever imports this.
149
148
  export const routes = Object.freeze(ROUTES.map((r) => Object.freeze({ ...r })));
150
-
151
149
  export function matchRoute(method, pathname) {
152
- for (const route of ROUTES) {
153
- if (route.method !== method) continue;
154
- if (route.path === pathname) return { route, rest: '' };
155
- if (route.prefix && pathname.startsWith(route.prefix)) {
156
- return { route, rest: pathname.slice(route.prefix.length) };
150
+ for (const route of ROUTES) {
151
+ if (route.method !== method)
152
+ continue;
153
+ if (route.path === pathname)
154
+ return { route, rest: '' };
155
+ if (route.prefix && pathname.startsWith(route.prefix)) {
156
+ return { route, rest: pathname.slice(route.prefix.length) };
157
+ }
157
158
  }
158
- }
159
- return null;
159
+ return null;
160
160
  }
161
-
162
161
  // `config` carries the directories the handlers need — the project root to scan
163
162
  // for the demo, and the two roots static serving maps into. Passed in rather
164
163
  // than resolved here so a test can point the server somewhere else, and so this
@@ -169,79 +168,74 @@ export function matchRoute(method, pathname) {
169
168
  // request without a restart. A bare `createServer()` (the tests) has no getter
170
169
  // and sees the local-mode defaults — never somebody's home directory.
171
170
  async function liveSettings(config) {
172
- if (typeof config?.getSettings !== 'function') return DEFAULT_SETTINGS;
173
- try {
174
- return await config.getSettings();
175
- } catch {
176
- return null; // unreadable config: fail closed, every /api call gets a 500
177
- }
171
+ if (typeof config?.getSettings !== 'function')
172
+ return DEFAULT_SETTINGS;
173
+ try {
174
+ return await config.getSettings();
175
+ }
176
+ catch {
177
+ return null; // unreadable config: fail closed, every /api call gets a 500
178
+ }
178
179
  }
179
-
180
180
  export function createRouter(config) {
181
- return async function handleRequest(req, res) {
182
- try {
183
- const url = new URL(req.url, 'http://127.0.0.1');
184
-
185
- const settings = await liveSettings(config);
186
- if (!settings) {
187
- return sendError(res, 500, 'The settings file could not be read — run `onboarder doctor` to find out why.');
188
- }
189
-
190
- const wrongHost = rebindingReason(req, allowedHosts(settings));
191
- if (wrongHost) {
192
- const scope = settings.mode === 'self-hosted'
193
- ? 'Onboarder only answers to its configured host and domain. '
194
- : 'Onboarder only answers to localhost. ';
195
- return sendError(res, 403, scope + wrongHost);
196
- }
197
-
198
- const found = matchRoute(req.method, url.pathname);
199
- const publicAuthRoute = ['/api/health', '/api/auth/status', '/api/auth/login', '/api/auth/logout'].includes(url.pathname);
200
- if (req.method !== 'GET' || found?.route.sameOrigin) {
201
- const foreign = crossOriginReason(req);
202
- if (foreign) {
203
- return sendError(res, 403, 'That request did not come from Onboarder’s own page. ' + foreign);
181
+ return async function handleRequest(req, res) {
182
+ try {
183
+ const url = new URL(req.url, 'http://127.0.0.1');
184
+ const settings = await liveSettings(config);
185
+ if (!settings) {
186
+ return sendError(res, 500, 'The settings file could not be read — run `onboarder doctor` to find out why.');
187
+ }
188
+ const wrongHost = rebindingReason(req, allowedHosts(settings));
189
+ if (wrongHost) {
190
+ const scope = settings.mode === 'self-hosted'
191
+ ? 'Onboarder only answers to its configured host and domain. '
192
+ : 'Onboarder only answers to localhost. ';
193
+ return sendError(res, 403, scope + wrongHost);
194
+ }
195
+ const found = matchRoute(req.method, url.pathname);
196
+ const publicAuthRoute = ['/api/health', '/api/auth/status', '/api/auth/login', '/api/auth/logout'].includes(url.pathname);
197
+ if (req.method !== 'GET' || found?.route.sameOrigin) {
198
+ const foreign = crossOriginReason(req);
199
+ if (foreign) {
200
+ return sendError(res, 403, 'That request did not come from Onboarder’s own page. ' + foreign);
201
+ }
202
+ }
203
+ // Bearer clients keep their existing API contract. A browser gets a signed,
204
+ // HttpOnly session from the login form instead of storing the raw key.
205
+ const bearerClient = accessKeysMatch(settings.accessKey, bearerToken(req));
206
+ const browserAuthenticated = hasValidSession(req, settings);
207
+ const authDenied = authReason(req, settings);
208
+ const locallyExempt = !authDenied;
209
+ const authenticated = bearerClient || browserAuthenticated || locallyExempt;
210
+ const remoteSelfHosted = settings.mode === 'self-hosted' && Boolean(authDenied);
211
+ if (remoteSelfHosted && !authenticated && !publicAuthRoute) {
212
+ // API callers keep a machine-readable 401. A browser navigation gets the
213
+ // themed sign-in document so the user never has to paste JSON into a tab.
214
+ const accepts = String(req.headers?.accept || '');
215
+ if (req.method === 'GET' && (url.pathname === '/' || accepts.includes('text/html'))) {
216
+ res.statusCode = 200;
217
+ return await serveStatic(res, '/login.html', config);
218
+ }
219
+ return sendError(res, 401, authDenied || 'Sign in with the Onboarder access key first.');
220
+ }
221
+ if (url.pathname === '/api/auth/logout') {
222
+ // Always clear the browser cookie, even if it had already expired.
223
+ return handleLogout(req, res);
224
+ }
225
+ if (found) {
226
+ const body = found.route.body ? await readBody(req) : null;
227
+ return await found.route.run({ req, res, url, body, rest: found.rest, config, settings });
228
+ }
229
+ if (req.method === 'GET')
230
+ return await serveStatic(res, url.pathname, config);
231
+ sendError(res, 404, 'Nothing lives at ' + url.pathname + '.');
204
232
  }
205
- }
206
-
207
- // Bearer clients keep their existing API contract. A browser gets a signed,
208
- // HttpOnly session from the login form instead of storing the raw key.
209
- const bearerClient = accessKeysMatch(settings.accessKey, bearerToken(req));
210
- const browserAuthenticated = hasValidSession(req, settings);
211
- const authDenied = authReason(req, settings);
212
- const locallyExempt = !authDenied;
213
- const authenticated = bearerClient || browserAuthenticated || locallyExempt;
214
- const remoteSelfHosted = settings.mode === 'self-hosted' && Boolean(authDenied);
215
-
216
- if (remoteSelfHosted && !authenticated && !publicAuthRoute) {
217
- // API callers keep a machine-readable 401. A browser navigation gets the
218
- // themed sign-in document so the user never has to paste JSON into a tab.
219
- const accepts = String(req.headers?.accept || '');
220
- if (req.method === 'GET' && (url.pathname === '/' || accepts.includes('text/html'))) {
221
- res.statusCode = 200;
222
- return await serveStatic(res, '/login.html', config);
233
+ catch (err) {
234
+ // The last resort. Handlers turn their own expected failures into a status
235
+ // that says something useful; anything reaching here is a bug or a broken
236
+ // request body, and the message is more use to the person than "500".
237
+ sendError(res, 500, err.message || 'Something went sideways.');
223
238
  }
224
- return sendError(res, 401, authDenied || 'Sign in with the Onboarder access key first.');
225
- }
226
-
227
- if (url.pathname === '/api/auth/logout') {
228
- // Always clear the browser cookie, even if it had already expired.
229
- return handleLogout(req, res);
230
- }
231
-
232
- if (found) {
233
- const body = found.route.body ? await readBody(req) : null;
234
- return await found.route.run({ req, res, url, body, rest: found.rest, config, settings });
235
- }
236
-
237
- if (req.method === 'GET') return await serveStatic(res, url.pathname, config);
238
-
239
- sendError(res, 404, 'Nothing lives at ' + url.pathname + '.');
240
- } catch (err) {
241
- // The last resort. Handlers turn their own expected failures into a status
242
- // that says something useful; anything reaching here is a bug or a broken
243
- // request body, and the message is more use to the person than "500".
244
- sendError(res, 500, err.message || 'Something went sideways.');
245
- }
246
- };
239
+ };
247
240
  }
241
+ //# sourceMappingURL=router.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"router.js","sourceRoot":"","sources":["../src/server/router.ts"],"names":[],"mappings":"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","sourcesContent":["import type { IncomingMessage, ServerResponse } from 'node:http';\nimport type { Settings } from '../shared/contracts.js';\n\ninterface RouteContext { req: IncomingMessage; res: ServerResponse; url: URL; body: any; rest: string; config: any; settings: Settings }\ninterface Route { method: string; path?: string; prefix?: string; body?: boolean; sameOrigin?: boolean; run(context: RouteContext): unknown }\n// The route table and the gates in front of it.\n//\n// Everything arrives here: one function decides whether a request is allowed to\n// be answered at all, then which handler answers it. The routes are a list rather\n// than a ladder of `if` statements so that the whole surface of the server is\n// visible in one screen, and everything else is a static file.\n//\n// There are three concerns. Local mode relies on `Host`, `Origin`, and\n// `Sec-Fetch-Site` to stop another page and DNS rebinding from driving it.\n// Self-hosted mode adds real access-key authentication: local browser requests\n// stay open, remote browsers get a signed session, and API clients use Bearer.\n\nimport { handleDocs } from './apiDocs.js';\nimport { handleFile } from './apiFile.js';\nimport { handleCleanup, handleScan } from './apiScan.js';\nimport { sendError, sendJSON, readBody } from './http.js';\nimport { crossOriginReason, rebindingReason } from './httpGuards.js';\nimport { proxyChat } from './llmProxy.js';\nimport { serveStatic } from './static.js';\nimport { handleSearch } from './apiSearch.js';\nimport { handleBlame } from './apiGitBlame.js';\nimport { handleDiff, handleDiffRefs } from './apiDiff.js';\nimport { handleReview } from './apiReview.js';\nimport { handleToolsInstall, handleToolsRun, handleToolsStatus } from './apiTools.js';\nimport { handleMcpStart, handleMcpStatus, handleMcpStop, handleMcpCommand } from './apiMcp.js';\nimport { handleGetSettings, handleRotateAccessKey, handleUpdateSettings } from './apiSettings.js';\nimport { handleAuthStatus, handleLogin, handleLogout } from './apiAuth.js';\nimport { accessKeysMatch, allowedHosts, authReason, bearerToken, DEFAULT_SETTINGS } from './config.js';\nimport { hasValidSession } from './auth.js';\n\nconst ROUTES: Route[] = [\n { method: 'POST', path: '/api/review', body: true, run: ({ res, body }) => handleReview(res, body) },\n {\n method: 'GET', path: '/api/diff/refs', sameOrigin: true,\n run: ({ res, url }) => handleDiffRefs(res, url.searchParams.get('scan')),\n },\n {\n method: 'GET', path: '/api/diff', sameOrigin: true,\n run: ({ res, url }) => handleDiff(res, url.searchParams.get('scan'), url.searchParams),\n },\n {\n method: 'POST', path: '/api/search', body: true,\n run: ({ res, body }) => handleSearch(res, body),\n },\n {\n method: 'GET', path: '/api/blame', sameOrigin: true,\n run: ({ res, url }) => handleBlame(res, url.searchParams.get('scan'), url.searchParams.get('path')),\n },\n {\n method: 'POST', path: '/api/scan', body: true,\n run: ({ res, body, config }) => handleScan(res, body, config),\n },\n {\n method: 'DELETE', prefix: '/api/scan/',\n run: ({ res, rest }) => handleCleanup(res, decodeURIComponent(rest)),\n },\n {\n method: 'GET', path: '/api/file', sameOrigin: true,\n run: ({ res, url }) => handleFile(res, url.searchParams.get('scan'), url.searchParams.get('path')),\n },\n {\n // The proxy is the handler; there is no wrapper to write. It forwards the\n // browser's key to the endpoint the browser chose and never keeps either.\n method: 'POST', path: '/api/explain', body: true,\n run: ({ res, body }) => proxyChat(res, body),\n },\n {\n method: 'POST', path: '/api/doc', body: true,\n run: ({ res, body }) => handleDocs(res, body),\n },\n {\n // Reports which analyzers are installed — environment information a foreign\n // page has no business reading, so it is guarded like /api/file.\n method: 'GET', path: '/api/tools', sameOrigin: true,\n run: ({ res }) => handleToolsStatus(res),\n },\n {\n // Runs real analyzers over a scanned root. `body: true` applies the size\n // limit; the session check in the handler is the capability gate.\n method: 'POST', path: '/api/tools/run', body: true,\n run: ({ res, body }) => handleToolsRun(res, body),\n },\n {\n // Installs a missing analyzer from the GUI. Same-origin like every POST;\n // the tool name is validated against the registry and the install plans\n // are fixed data, so the request body never reaches a command line.\n method: 'POST', path: '/api/tools/install', body: true,\n run: ({ res, body }) => handleToolsInstall(res, body),\n },\n {\n // The MCP button's state, and the command an agent harness is configured with.\n // The config is not secret, but it is a path into this machine and a foreign\n // page has no business reading either one.\n method: 'GET', path: '/api/mcp', sameOrigin: true,\n run: ({ req, res, config }) => handleMcpStatus(req, res, config),\n },\n {\n method: 'GET', path: '/api/mcp/command', sameOrigin: true,\n run: ({ req, res, config }) => handleMcpCommand(req, res, config),\n },\n {\n // Starting and stopping a process is a side effect on the person's machine, so\n // these take a body like every other POST and are checked like every other POST.\n method: 'POST', path: '/api/mcp/start', body: true,\n run: ({ req, res, config }) => handleMcpStart(req, res, config),\n },\n {\n method: 'POST', path: '/api/mcp/stop', body: true,\n run: ({ req, res, config }) => handleMcpStop(req, res, config),\n },\n {\n method: 'GET', path: '/api/health',\n run: ({ res }) => sendJSON(res, 200, { ok: true }),\n },\n {\n method: 'GET', path: '/api/auth/status', sameOrigin: true,\n run: ({ req, res, settings }) => handleAuthStatus(req, res, settings),\n },\n {\n method: 'POST', path: '/api/auth/login', body: true, sameOrigin: true,\n run: ({ req, res, body, settings }) => handleLogin(req, res, body, settings),\n },\n {\n method: 'POST', path: '/api/auth/logout', body: true, sameOrigin: true,\n run: ({ req, res }) => handleLogout(req, res),\n },\n {\n // The settings drawer and the CLI read the same public shape: everything\n // about the configuration except the access key itself.\n method: 'GET', path: '/api/settings', sameOrigin: true,\n run: ({ req, res, config }) => handleGetSettings(req, res, config),\n },\n {\n // PATCH semantics over a small allow-listed key set; the validation that\n // rejects a bad port or a lawless mode lives in `server/config.js` and is\n // shared with the CLI wizard, so both surfaces enforce one schema.\n method: 'PUT', path: '/api/settings', body: true,\n run: ({ res, body, config }) => handleUpdateSettings(res, body, config),\n },\n {\n // The only way the key changes: generated on the server, shown once in\n // this response, never readable again. In self-hosted mode this endpoint\n // is itself behind the current key, so rotation requires possession.\n method: 'POST', path: '/api/settings/access-key', body: true,\n run: ({ req, res, config }) => handleRotateAccessKey(req, res, config),\n },\n];\n\n// The table itself, for the test that walks it. Exported read-only: the routes\n// are decided here, not assembled by whoever imports this.\nexport const routes = Object.freeze(ROUTES.map((r) => Object.freeze({ ...r })));\n\nexport function matchRoute(method, pathname) {\n for (const route of ROUTES) {\n if (route.method !== method) continue;\n if (route.path === pathname) return { route, rest: '' };\n if (route.prefix && pathname.startsWith(route.prefix)) {\n return { route, rest: pathname.slice(route.prefix.length) };\n }\n }\n return null;\n}\n\n// `config` carries the directories the handlers need — the project root to scan\n// for the demo, and the two roots static serving maps into. Passed in rather\n// than resolved here so a test can point the server somewhere else, and so this\n// module has nothing to say about where it was installed.\n//\n// Settings arrive the same way: `config.getSettings` is read on every request,\n// so a key rotated or a domain changed through the API takes effect on the next\n// request without a restart. A bare `createServer()` (the tests) has no getter\n// and sees the local-mode defaults — never somebody's home directory.\nasync function liveSettings(config) {\n if (typeof config?.getSettings !== 'function') return DEFAULT_SETTINGS;\n try {\n return await config.getSettings();\n } catch {\n return null; // unreadable config: fail closed, every /api call gets a 500\n }\n}\n\nexport function createRouter(config) {\n return async function handleRequest(req, res) {\n try {\n const url = new URL(req.url, 'http://127.0.0.1');\n\n const settings = await liveSettings(config);\n if (!settings) {\n return sendError(res, 500, 'The settings file could not be read — run `onboarder doctor` to find out why.');\n }\n\n const wrongHost = rebindingReason(req, allowedHosts(settings));\n if (wrongHost) {\n const scope = settings.mode === 'self-hosted'\n ? 'Onboarder only answers to its configured host and domain. '\n : 'Onboarder only answers to localhost. ';\n return sendError(res, 403, scope + wrongHost);\n }\n\n const found = matchRoute(req.method, url.pathname);\n const publicAuthRoute = ['/api/health', '/api/auth/status', '/api/auth/login', '/api/auth/logout'].includes(url.pathname);\n if (req.method !== 'GET' || found?.route.sameOrigin) {\n const foreign = crossOriginReason(req);\n if (foreign) {\n return sendError(res, 403, 'That request did not come from Onboarder’s own page. ' + foreign);\n }\n }\n\n // Bearer clients keep their existing API contract. A browser gets a signed,\n // HttpOnly session from the login form instead of storing the raw key.\n const bearerClient = accessKeysMatch(settings.accessKey, bearerToken(req));\n const browserAuthenticated = hasValidSession(req, settings);\n const authDenied = authReason(req, settings);\n const locallyExempt = !authDenied;\n const authenticated = bearerClient || browserAuthenticated || locallyExempt;\n const remoteSelfHosted = settings.mode === 'self-hosted' && Boolean(authDenied);\n\n if (remoteSelfHosted && !authenticated && !publicAuthRoute) {\n // API callers keep a machine-readable 401. A browser navigation gets the\n // themed sign-in document so the user never has to paste JSON into a tab.\n const accepts = String(req.headers?.accept || '');\n if (req.method === 'GET' && (url.pathname === '/' || accepts.includes('text/html'))) {\n res.statusCode = 200;\n return await serveStatic(res, '/login.html', config);\n }\n return sendError(res, 401, authDenied || 'Sign in with the Onboarder access key first.');\n }\n\n if (url.pathname === '/api/auth/logout') {\n // Always clear the browser cookie, even if it had already expired.\n return handleLogout(req, res);\n }\n\n if (found) {\n const body = found.route.body ? await readBody(req) : null;\n return await found.route.run({ req, res, url, body, rest: found.rest, config, settings });\n }\n\n if (req.method === 'GET') return await serveStatic(res, url.pathname, config);\n\n sendError(res, 404, 'Nothing lives at ' + url.pathname + '.');\n } catch (err) {\n // The last resort. Handlers turn their own expected failures into a status\n // that says something useful; anything reaching here is a bug or a broken\n // request body, and the message is more use to the person than \"500\".\n sendError(res, 500, err.message || 'Something went sideways.');\n }\n };\n}\n"]}
@@ -0,0 +1,3 @@
1
+ import type { ScanRequest } from '../shared/contracts.js';
2
+ /** Validate untrusted JSON before touching the filesystem or cloning anything. */
3
+ export declare function parseScanRequest(value: unknown): ScanRequest;
@@ -0,0 +1,36 @@
1
+ import { normalizeScanOptions } from '../shared/analyzer/scanControl.js';
2
+ /** Validate untrusted JSON before touching the filesystem or cloning anything. */
3
+ export function parseScanRequest(value) {
4
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
5
+ throw new TypeError('Send a path, a gitUrl, or { demo: true }.');
6
+ }
7
+ const body = value;
8
+ const sources = [body.path !== undefined, body.gitUrl !== undefined, body.demo === true].filter(Boolean).length;
9
+ if (sources === 0)
10
+ throw new TypeError('Send a path, a gitUrl, or { demo: true }.');
11
+ if (sources !== 1)
12
+ throw new TypeError('Send exactly one path, gitUrl, or { demo: true }.');
13
+ const options = {};
14
+ if (body.options !== undefined) {
15
+ if (!body.options || typeof body.options !== 'object' || Array.isArray(body.options)) {
16
+ throw new TypeError('Scan options must be an object.');
17
+ }
18
+ const input = body.options;
19
+ for (const key of ['maxFiles', 'maxFileSize', 'readConcurrency']) {
20
+ if (input[key] === undefined)
21
+ continue;
22
+ if (typeof input[key] !== 'number')
23
+ throw new TypeError(`${key} must be a number.`);
24
+ options[key] = input[key];
25
+ }
26
+ normalizeScanOptions(options);
27
+ }
28
+ if (body.demo === true)
29
+ return { demo: true, options };
30
+ if (typeof body.path === 'string' && body.path.trim())
31
+ return { path: body.path.trim(), options };
32
+ if (typeof body.gitUrl === 'string' && body.gitUrl.trim())
33
+ return { gitUrl: body.gitUrl.trim(), options };
34
+ throw new TypeError('The path or gitUrl must be a non-empty string.');
35
+ }
36
+ //# sourceMappingURL=scanRequest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scanRequest.js","sourceRoot":"","sources":["../src/server/scanRequest.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAC;AAEzE,kFAAkF;AAClF,MAAM,UAAU,gBAAgB,CAAC,KAAc;IAC7C,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QAChE,MAAM,IAAI,SAAS,CAAC,2CAA2C,CAAC,CAAC;IACnE,CAAC;IACD,MAAM,IAAI,GAAG,KAAgC,CAAC;IAC9C,MAAM,OAAO,GAAG,CAAC,IAAI,CAAC,IAAI,KAAK,SAAS,EAAE,IAAI,CAAC,MAAM,KAAK,SAAS,EAAE,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC;IAChH,IAAI,OAAO,KAAK,CAAC;QAAE,MAAM,IAAI,SAAS,CAAC,2CAA2C,CAAC,CAAC;IACpF,IAAI,OAAO,KAAK,CAAC;QAAE,MAAM,IAAI,SAAS,CAAC,mDAAmD,CAAC,CAAC;IAC5F,MAAM,OAAO,GAAuB,EAAE,CAAC;IACvC,IAAI,IAAI,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;QAC/B,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,OAAO,IAAI,CAAC,OAAO,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;YACrF,MAAM,IAAI,SAAS,CAAC,iCAAiC,CAAC,CAAC;QACzD,CAAC;QACD,MAAM,KAAK,GAAG,IAAI,CAAC,OAAkC,CAAC;QACtD,KAAK,MAAM,GAAG,IAAI,CAAC,UAAU,EAAE,aAAa,EAAE,iBAAiB,CAAU,EAAE,CAAC;YAC1E,IAAI,KAAK,CAAC,GAAG,CAAC,KAAK,SAAS;gBAAE,SAAS;YACvC,IAAI,OAAO,KAAK,CAAC,GAAG,CAAC,KAAK,QAAQ;gBAAE,MAAM,IAAI,SAAS,CAAC,GAAG,GAAG,oBAAoB,CAAC,CAAC;YACpF,OAAO,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC;QAC5B,CAAC;QACD,oBAAoB,CAAC,OAAO,CAAC,CAAC;IAChC,CAAC;IACD,IAAI,IAAI,CAAC,IAAI,KAAK,IAAI;QAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;IACvD,IAAI,OAAO,IAAI,CAAC,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE;QAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,OAAO,EAAE,CAAC;IAClG,IAAI,OAAO,IAAI,CAAC,MAAM,KAAK,QAAQ,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE;QAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,OAAO,EAAE,CAAC;IAC1G,MAAM,IAAI,SAAS,CAAC,gDAAgD,CAAC,CAAC;AACxE,CAAC","sourcesContent":["import type { ScanRequest, ScanRequestOptions } from '../shared/contracts.js';\nimport { normalizeScanOptions } from '../shared/analyzer/scanControl.js';\n\n/** Validate untrusted JSON before touching the filesystem or cloning anything. */\nexport function parseScanRequest(value: unknown): ScanRequest {\n if (!value || typeof value !== 'object' || Array.isArray(value)) {\n throw new TypeError('Send a path, a gitUrl, or { demo: true }.');\n }\n const body = value as Record<string, unknown>;\n const sources = [body.path !== undefined, body.gitUrl !== undefined, body.demo === true].filter(Boolean).length;\n if (sources === 0) throw new TypeError('Send a path, a gitUrl, or { demo: true }.');\n if (sources !== 1) throw new TypeError('Send exactly one path, gitUrl, or { demo: true }.');\n const options: ScanRequestOptions = {};\n if (body.options !== undefined) {\n if (!body.options || typeof body.options !== 'object' || Array.isArray(body.options)) {\n throw new TypeError('Scan options must be an object.');\n }\n const input = body.options as Record<string, unknown>;\n for (const key of ['maxFiles', 'maxFileSize', 'readConcurrency'] as const) {\n if (input[key] === undefined) continue;\n if (typeof input[key] !== 'number') throw new TypeError(`${key} must be a number.`);\n options[key] = input[key];\n }\n normalizeScanOptions(options);\n }\n if (body.demo === true) return { demo: true, options };\n if (typeof body.path === 'string' && body.path.trim()) return { path: body.path.trim(), options };\n if (typeof body.gitUrl === 'string' && body.gitUrl.trim()) return { gitUrl: body.gitUrl.trim(), options };\n throw new TypeError('The path or gitUrl must be a non-empty string.');\n}\n"]}
@@ -0,0 +1,17 @@
1
+ export declare function buildSearchIndex(source: any, codeFiles: any, options?: Record<string, any>): Promise<{
2
+ index: Map<any, any>;
3
+ docCounts: Map<any, any>;
4
+ totalDocs: number;
5
+ totalBytes: number;
6
+ skipped: {
7
+ tooLarge: number;
8
+ overCap: number;
9
+ readFailed: number;
10
+ binary: number;
11
+ noTokens: number;
12
+ };
13
+ cap: {
14
+ maxFileBytes: number;
15
+ maxTotalBytes: number;
16
+ };
17
+ }>;