codebase-onboarder 0.5.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 +196 -47
  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 +449 -0
  8. package/cli/explorer/advanced.js.map +1 -0
  9. package/cli/explorer/app.d.ts +1 -0
  10. package/cli/explorer/app.js +396 -229
  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 +400 -162
  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 +73 -0
  23. package/cli/explorer/github.js.map +1 -0
  24. package/cli/explorer/graphs.d.ts +7 -0
  25. package/cli/explorer/graphs.js +173 -0
  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 +184 -95
  35. package/cli/explorer/session.js.map +1 -0
  36. package/cli/explorer/views.d.ts +19 -0
  37. package/cli/explorer/views.js +496 -412
  38. package/cli/explorer/views.js.map +1 -0
  39. package/cli/explorer/wrap.d.ts +1 -0
  40. package/cli/explorer/wrap.js +33 -0
  41. package/cli/explorer/wrap.js.map +1 -0
  42. package/cli/main.d.ts +1 -0
  43. package/cli/main.js +235 -138
  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 +55 -35
  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 +165 -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 +60 -0
  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 -37
  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 +79 -0
  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
@@ -6,121 +6,120 @@
6
6
  // status codes, the secret-handling rules (never read the key back out, show a
7
7
  // new one exactly once), and the restart hint — the running server bound its
8
8
  // socket at boot, so a host/port change is real but not yet in effect.
9
-
10
- import {
11
- DEFAULT_SETTINGS, generateAccessKey, normalizeSettings, publicSettings,
12
- readSettings, updateSettings, writeSettings, configPath,
13
- } from './config.js';
9
+ import { DEFAULT_SETTINGS, generateAccessKey, normalizeSettings, publicSettings, readSettings, updateSettings, writeSettings, configPath, } from './config.js';
14
10
  import { sendError, sendJSON } from './http.js';
15
11
  import { sessionCookie } from './auth.js';
16
12
  import { tunnelStatus } from './tunnel.js';
17
13
  import { httpsStatus } from './https.js';
18
-
19
14
  // Where this request's settings live. The router's config object carries the
20
15
  // path when the server was booted with one; tests and a bare `createServer()`
21
16
  // get the defaults-in-memory view instead of somebody's home directory.
22
17
  function settingsFile(config) {
23
- return config?.configPath || null;
18
+ return config?.configPath || null;
24
19
  }
25
-
26
20
  async function currentSettings(config) {
27
- if (typeof config?.getSettings === 'function') return config.getSettings();
28
- const file = settingsFile(config);
29
- if (file) return readSettings(file);
30
- return { ...DEFAULT_SETTINGS, account: { ...DEFAULT_SETTINGS.account }, tunnel: { ...DEFAULT_SETTINGS.tunnel } };
21
+ if (typeof config?.getSettings === 'function')
22
+ return config.getSettings();
23
+ const file = settingsFile(config);
24
+ if (file)
25
+ return readSettings(file);
26
+ return { ...DEFAULT_SETTINGS, account: { ...DEFAULT_SETTINGS.account }, tunnel: { ...DEFAULT_SETTINGS.tunnel } };
31
27
  }
32
-
33
28
  // The public body every read returns: settings with the secret masked, plus
34
29
  // the connection picture (URLs, tunnel status) the drawer and CLI both render.
35
30
  async function publicBody(config) {
36
- const settings = await currentSettings(config);
37
- return {
38
- settings: publicSettings(settings),
39
- configFile: settingsFile(config) || configPath(),
40
- node: process.version,
41
- tunnels: tunnelStatus(settings),
42
- https: httpsStatus(settings, settingsFile(config) || configPath()),
43
- };
31
+ const settings = await currentSettings(config);
32
+ return {
33
+ settings: publicSettings(settings),
34
+ configFile: settingsFile(config) || configPath(),
35
+ node: process.version,
36
+ tunnels: tunnelStatus(settings),
37
+ https: httpsStatus(settings, settingsFile(config) || configPath()),
38
+ };
44
39
  }
45
-
46
40
  export async function handleGetSettings(req, res, config) {
47
- try {
48
- sendJSON(res, 200, await publicBody(config));
49
- } catch (err) {
50
- sendError(res, 500, err.message || 'The settings could not be read.');
51
- }
41
+ try {
42
+ sendJSON(res, 200, await publicBody(config));
43
+ }
44
+ catch (err) {
45
+ sendError(res, 500, err.message || 'The settings could not be read.');
46
+ }
52
47
  }
53
-
54
48
  // Which changes the running server cannot absorb. Access-key and tunnel flags
55
49
  // are read live per request. Host/port are bound sockets. Domain/HTTPS change
56
50
  // the managed Caddyfile and certificate, so Caddy must validate/reload too.
57
51
  function restartRequired(config, next) {
58
- const boot = config?.boot;
59
- if (!boot) return [];
60
- const changed = [];
61
- if (next.host !== boot.host) changed.push('host');
62
- if (next.port !== boot.port) changed.push('port');
63
- if (next.domain !== boot.domain) changed.push('domain');
64
- if (next.https !== boot.https) changed.push('https');
65
- return changed;
52
+ const boot = config?.boot;
53
+ if (!boot)
54
+ return [];
55
+ const changed = [];
56
+ if (next.host !== boot.host)
57
+ changed.push('host');
58
+ if (next.port !== boot.port)
59
+ changed.push('port');
60
+ if (next.domain !== boot.domain)
61
+ changed.push('domain');
62
+ if (next.https !== boot.https)
63
+ changed.push('https');
64
+ return changed;
66
65
  }
67
-
68
66
  export async function handleUpdateSettings(res, body, config) {
69
- const file = settingsFile(config);
70
- if (!file) {
71
- return sendError(res, 400, 'This server was started without a config file, so there is nothing to save to.');
72
- }
73
- if (!body || typeof body !== 'object' || Array.isArray(body)) {
74
- return sendError(res, 400, 'Send a JSON object of settings to change.');
75
- }
76
- // Only known keys may be patched — a typo like "ports" must be a loud 400,
77
- // not a silently ignored write that the UI then claims it saved.
78
- const allowed = new Set(['mode', 'host', 'port', 'domain', 'https', 'autoOpen', 'account', 'tunnel']);
79
- const unknown = Object.keys(body).filter((k) => k !== 'accessKey' && !allowed.has(k));
80
- if (unknown.length) {
81
- return sendError(res, 400, `Unknown setting${unknown.length === 1 ? '' : 's'}: ${unknown.join(', ')}.`);
82
- }
83
- if ('accessKey' in body) {
84
- return sendError(res, 400, 'The access key is rotated, not patched — POST /api/settings/access-key.');
85
- }
86
- try {
87
- const next = await updateSettings(body, file);
88
- sendJSON(res, 200, {
89
- settings: publicSettings(next),
90
- restartRequired: restartRequired(config, next),
91
- });
92
- } catch (err) {
93
- sendError(res, 400, err.message || 'Those settings did not validate.');
94
- }
67
+ const file = settingsFile(config);
68
+ if (!file) {
69
+ return sendError(res, 400, 'This server was started without a config file, so there is nothing to save to.');
70
+ }
71
+ if (!body || typeof body !== 'object' || Array.isArray(body)) {
72
+ return sendError(res, 400, 'Send a JSON object of settings to change.');
73
+ }
74
+ // Only known keys may be patched — a typo like "ports" must be a loud 400,
75
+ // not a silently ignored write that the UI then claims it saved.
76
+ const allowed = new Set(['mode', 'host', 'port', 'domain', 'https', 'autoOpen', 'account', 'tunnel']);
77
+ const unknown = Object.keys(body).filter((k) => k !== 'accessKey' && !allowed.has(k));
78
+ if (unknown.length) {
79
+ return sendError(res, 400, `Unknown setting${unknown.length === 1 ? '' : 's'}: ${unknown.join(', ')}.`);
80
+ }
81
+ if ('accessKey' in body) {
82
+ return sendError(res, 400, 'The access key is rotated, not patched — POST /api/settings/access-key.');
83
+ }
84
+ try {
85
+ const next = await updateSettings(body, file);
86
+ sendJSON(res, 200, {
87
+ settings: publicSettings(next),
88
+ restartRequired: restartRequired(config, next),
89
+ });
90
+ }
91
+ catch (err) {
92
+ sendError(res, 400, err.message || 'Those settings did not validate.');
93
+ }
95
94
  }
96
-
97
95
  // Rotation returns the new key in the clear, once. It is never in a GET after
98
96
  // that — `hasAccessKey`/`accessKeyMasked` are all the readback there is. The
99
97
  // person copies it from this response into the devices that need it.
100
98
  export async function handleRotateAccessKey(req, res, config) {
101
- const file = settingsFile(config);
102
- if (!file) {
103
- return sendError(res, 400, 'This server was started without a config file, so there is nothing to save to.');
104
- }
105
- try {
106
- const accessKey = generateAccessKey();
107
- const current = await currentSettings(config);
108
- const next = await writeSettings({ ...current, accessKey }, file);
109
- sendJSON(res, 200, {
110
- accessKey,
111
- settings: publicSettings(next),
112
- note: 'Shown once. This browser is signed in with the new key; update every other device.',
113
- }, {
114
- 'cache-control': 'no-store',
115
- 'set-cookie': sessionCookie(req, next),
116
- });
117
- } catch (err) {
118
- sendError(res, 400, err.message || 'The key could not be rotated.');
119
- }
99
+ const file = settingsFile(config);
100
+ if (!file) {
101
+ return sendError(res, 400, 'This server was started without a config file, so there is nothing to save to.');
102
+ }
103
+ try {
104
+ const accessKey = generateAccessKey();
105
+ const current = await currentSettings(config);
106
+ const next = await writeSettings({ ...current, accessKey }, file);
107
+ sendJSON(res, 200, {
108
+ accessKey,
109
+ settings: publicSettings(next),
110
+ note: 'Shown once. This browser is signed in with the new key; update every other device.',
111
+ }, {
112
+ 'cache-control': 'no-store',
113
+ 'set-cookie': sessionCookie(req, next),
114
+ });
115
+ }
116
+ catch (err) {
117
+ sendError(res, 400, err.message || 'The key could not be rotated.');
118
+ }
120
119
  }
121
-
122
120
  // Kept exported for the tests: a patch that survives normalizeSettings is the
123
121
  // same patch the wizard and the API would both accept.
124
122
  export function validatePatch(patch) {
125
- return normalizeSettings({ ...DEFAULT_SETTINGS, ...patch });
123
+ return normalizeSettings({ ...DEFAULT_SETTINGS, ...patch });
126
124
  }
125
+ //# sourceMappingURL=apiSettings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"apiSettings.js","sourceRoot":"","sources":["../src/server/apiSettings.ts"],"names":[],"mappings":"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","sourcesContent":["// The settings endpoints: read the public shape, apply a patch, rotate the key.\n//\n// All three go through `server/config.js` for validation and the atomic 0600\n// write, so the CLI wizard, `onboarder config set`, and this API can never save\n// three dialects of the same file. What lives here is only what HTTP adds:\n// status codes, the secret-handling rules (never read the key back out, show a\n// new one exactly once), and the restart hint — the running server bound its\n// socket at boot, so a host/port change is real but not yet in effect.\n\nimport {\n DEFAULT_SETTINGS, generateAccessKey, normalizeSettings, publicSettings,\n readSettings, updateSettings, writeSettings, configPath,\n} from './config.js';\nimport { sendError, sendJSON } from './http.js';\nimport { sessionCookie } from './auth.js';\nimport { tunnelStatus } from './tunnel.js';\nimport { httpsStatus } from './https.js';\n\n// Where this request's settings live. The router's config object carries the\n// path when the server was booted with one; tests and a bare `createServer()`\n// get the defaults-in-memory view instead of somebody's home directory.\nfunction settingsFile(config) {\n return config?.configPath || null;\n}\n\nasync function currentSettings(config) {\n if (typeof config?.getSettings === 'function') return config.getSettings();\n const file = settingsFile(config);\n if (file) return readSettings(file);\n return { ...DEFAULT_SETTINGS, account: { ...DEFAULT_SETTINGS.account }, tunnel: { ...DEFAULT_SETTINGS.tunnel } };\n}\n\n// The public body every read returns: settings with the secret masked, plus\n// the connection picture (URLs, tunnel status) the drawer and CLI both render.\nasync function publicBody(config) {\n const settings = await currentSettings(config);\n return {\n settings: publicSettings(settings),\n configFile: settingsFile(config) || configPath(),\n node: process.version,\n tunnels: tunnelStatus(settings),\n https: httpsStatus(settings, settingsFile(config) || configPath()),\n };\n}\n\nexport async function handleGetSettings(req, res, config) {\n try {\n sendJSON(res, 200, await publicBody(config));\n } catch (err) {\n sendError(res, 500, err.message || 'The settings could not be read.');\n }\n}\n\n// Which changes the running server cannot absorb. Access-key and tunnel flags\n// are read live per request. Host/port are bound sockets. Domain/HTTPS change\n// the managed Caddyfile and certificate, so Caddy must validate/reload too.\nfunction restartRequired(config, next) {\n const boot = config?.boot;\n if (!boot) return [];\n const changed = [];\n if (next.host !== boot.host) changed.push('host');\n if (next.port !== boot.port) changed.push('port');\n if (next.domain !== boot.domain) changed.push('domain');\n if (next.https !== boot.https) changed.push('https');\n return changed;\n}\n\nexport async function handleUpdateSettings(res, body, config) {\n const file = settingsFile(config);\n if (!file) {\n return sendError(res, 400, 'This server was started without a config file, so there is nothing to save to.');\n }\n if (!body || typeof body !== 'object' || Array.isArray(body)) {\n return sendError(res, 400, 'Send a JSON object of settings to change.');\n }\n // Only known keys may be patched — a typo like \"ports\" must be a loud 400,\n // not a silently ignored write that the UI then claims it saved.\n const allowed = new Set(['mode', 'host', 'port', 'domain', 'https', 'autoOpen', 'account', 'tunnel']);\n const unknown = Object.keys(body).filter((k) => k !== 'accessKey' && !allowed.has(k));\n if (unknown.length) {\n return sendError(res, 400, `Unknown setting${unknown.length === 1 ? '' : 's'}: ${unknown.join(', ')}.`);\n }\n if ('accessKey' in body) {\n return sendError(res, 400, 'The access key is rotated, not patched — POST /api/settings/access-key.');\n }\n try {\n const next = await updateSettings(body, file);\n sendJSON(res, 200, {\n settings: publicSettings(next),\n restartRequired: restartRequired(config, next),\n });\n } catch (err) {\n sendError(res, 400, err.message || 'Those settings did not validate.');\n }\n}\n\n// Rotation returns the new key in the clear, once. It is never in a GET after\n// that — `hasAccessKey`/`accessKeyMasked` are all the readback there is. The\n// person copies it from this response into the devices that need it.\nexport async function handleRotateAccessKey(req, res, config) {\n const file = settingsFile(config);\n if (!file) {\n return sendError(res, 400, 'This server was started without a config file, so there is nothing to save to.');\n }\n try {\n const accessKey = generateAccessKey();\n const current = await currentSettings(config);\n const next = await writeSettings({ ...current, accessKey }, file);\n sendJSON(res, 200, {\n accessKey,\n settings: publicSettings(next),\n note: 'Shown once. This browser is signed in with the new key; update every other device.',\n }, {\n 'cache-control': 'no-store',\n 'set-cookie': sessionCookie(req, next),\n });\n } catch (err) {\n sendError(res, 400, err.message || 'The key could not be rotated.');\n }\n}\n\n// Kept exported for the tests: a patch that survives normalizeSettings is the\n// same patch the wizard and the API would both accept.\nexport function validatePatch(patch) {\n return normalizeSettings({ ...DEFAULT_SETTINGS, ...patch });\n}\n"]}
@@ -0,0 +1,3 @@
1
+ export declare function handleToolsStatus(res: any): void;
2
+ export declare function handleToolsRun(res: any, body: any): Promise<void>;
3
+ export declare function handleToolsInstall(res: any, body: any): Promise<void>;
@@ -7,46 +7,42 @@
7
7
  // else. `tools.js` spawns with argument arrays and hard timeouts, so a crafted
8
8
  // path becomes an argument and a hung tool becomes a failed pass, not a stuck
9
9
  // server.
10
-
11
10
  import { sendError, sendJSON } from './http.js';
12
11
  import { getSession } from './sessions.js';
13
12
  import { runExternalAnalysis, toolsStatus } from './tools/scan.js';
14
13
  import { installTool, isInstalling, plansFor } from './tools/install.js';
15
14
  import { TOOL_DEFS } from './tools/registry.js';
16
-
17
15
  const KNOWN_TOOLS = new Set(TOOL_DEFS.map((d) => d.id));
18
-
19
16
  export function handleToolsStatus(res) {
20
- sendJSON(res, 200, { tools: toolsStatus() });
17
+ sendJSON(res, 200, { tools: toolsStatus() });
21
18
  }
22
-
23
19
  export async function handleToolsRun(res, body) {
24
- const { scanId, kinds, tools, options } = body || {};
25
- if (!scanId) return sendError(res, 400, 'Missing scanId.');
26
- const session = getSession(scanId);
27
- if (!session) return sendError(res, 404, 'That scan is gone. Rescan the repo.');
28
-
29
- // `kinds` narrows by purpose, `tools` to named engines. An unknown value is a
30
- // typo the caller would rather hear about than have silently ignored.
31
- let kindFilter = null;
32
- if (Array.isArray(kinds) && kinds.length) {
33
- const ok = kinds.filter((k) => k === 'security' || k === 'dead-code');
34
- if (!ok.length) return sendError(res, 400, 'kinds must be "security" and/or "dead-code".');
35
- kindFilter = ok;
36
- }
37
- let toolFilter = null;
38
- if (Array.isArray(tools) && tools.length) {
39
- toolFilter = tools.map((t) => String(t));
40
- }
41
-
42
- // `options` is the per-engine GUI form. Validation happens against the
43
- // registry schema inside the run — the handler just passes it along.
44
- const report = await runExternalAnalysis(session.root, {
45
- kinds: kindFilter, tools: toolFilter, options,
46
- });
47
- return sendJSON(res, 200, report);
20
+ const { scanId, kinds, tools, options } = body || {};
21
+ if (!scanId)
22
+ return sendError(res, 400, 'Missing scanId.');
23
+ const session = getSession(scanId);
24
+ if (!session)
25
+ return sendError(res, 404, 'That scan is gone. Rescan the repo.');
26
+ // `kinds` narrows by purpose, `tools` to named engines. An unknown value is a
27
+ // typo the caller would rather hear about than have silently ignored.
28
+ let kindFilter = null;
29
+ if (Array.isArray(kinds) && kinds.length) {
30
+ const ok = kinds.filter((k) => k === 'security' || k === 'dead-code');
31
+ if (!ok.length)
32
+ return sendError(res, 400, 'kinds must be "security" and/or "dead-code".');
33
+ kindFilter = ok;
34
+ }
35
+ let toolFilter = null;
36
+ if (Array.isArray(tools) && tools.length) {
37
+ toolFilter = tools.map((t) => String(t));
38
+ }
39
+ // `options` is the per-engine GUI form. Validation happens against the
40
+ // registry schema inside the run — the handler just passes it along.
41
+ const report = await runExternalAnalysis(session.root, {
42
+ kinds: kindFilter, tools: toolFilter, options,
43
+ });
44
+ return sendJSON(res, 200, report);
48
45
  }
49
-
50
46
  // POST /api/tools/install — install a missing engine, streaming progress.
51
47
  //
52
48
  // This is the one endpoint that changes the machine, so it is worth spelling
@@ -61,30 +57,33 @@ export async function handleToolsRun(res, body) {
61
57
  // * The stream is SSE in the same `data:`-framed shape the explain proxy
62
58
  // uses, so the front end reads install progress exactly like AI output.
63
59
  export async function handleToolsInstall(res, body) {
64
- const tool = String(body?.tool || '');
65
- if (!KNOWN_TOOLS.has(tool)) {
66
- return sendError(res, 400, `Unknown tool "${tool}". Known: ${[...KNOWN_TOOLS].join(', ')}.`);
67
- }
68
- if (!plansFor(tool).length) {
69
- return sendError(res, 400, `Onboarder has no install plan for ${tool} on ${process.platform}.`);
70
- }
71
- if (isInstalling(tool)) {
72
- return sendError(res, 409, `${tool} is already being installed.`);
73
- }
74
-
75
- res.writeHead(200, {
76
- 'content-type': 'text/event-stream; charset=utf-8',
77
- 'cache-control': 'no-cache',
78
- connection: 'keep-alive',
79
- });
80
- const send = (event) => {
81
- try { res.write('data: ' + JSON.stringify(event) + '\n\n'); } catch { /* client went away */ }
82
- };
83
-
84
- try {
85
- await installTool(tool, send);
86
- } catch (err) {
87
- send({ type: 'done', ok: false, error: err.message || 'The install failed.' });
88
- }
89
- res.end();
60
+ const tool = String(body?.tool || '');
61
+ if (!KNOWN_TOOLS.has(tool)) {
62
+ return sendError(res, 400, `Unknown tool "${tool}". Known: ${[...KNOWN_TOOLS].join(', ')}.`);
63
+ }
64
+ if (!plansFor(tool).length) {
65
+ return sendError(res, 400, `Onboarder has no install plan for ${tool} on ${process.platform}.`);
66
+ }
67
+ if (isInstalling(tool)) {
68
+ return sendError(res, 409, `${tool} is already being installed.`);
69
+ }
70
+ res.writeHead(200, {
71
+ 'content-type': 'text/event-stream; charset=utf-8',
72
+ 'cache-control': 'no-cache',
73
+ connection: 'keep-alive',
74
+ });
75
+ const send = (event) => {
76
+ try {
77
+ res.write('data: ' + JSON.stringify(event) + '\n\n');
78
+ }
79
+ catch { /* client went away */ }
80
+ };
81
+ try {
82
+ await installTool(tool, send);
83
+ }
84
+ catch (err) {
85
+ send({ type: 'done', ok: false, error: err.message || 'The install failed.' });
86
+ }
87
+ res.end();
90
88
  }
89
+ //# sourceMappingURL=apiTools.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"apiTools.js","sourceRoot":"","sources":["../src/server/apiTools.ts"],"names":[],"mappings":"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","sourcesContent":["// GET /api/tools — which analyzers this machine can run, for the engines panel.\n// POST /api/tools/run — run the available analyzers against a live scan and\n// return the normalized findings.\n//\n// The scan id is a capability the same way it is for `/api/file`: it names a\n// root the person chose, and the analyzers are run inside that root and nowhere\n// else. `tools.js` spawns with argument arrays and hard timeouts, so a crafted\n// path becomes an argument and a hung tool becomes a failed pass, not a stuck\n// server.\n\nimport { sendError, sendJSON } from './http.js';\nimport { getSession } from './sessions.js';\nimport { runExternalAnalysis, toolsStatus } from './tools/scan.js';\nimport { installTool, isInstalling, plansFor } from './tools/install.js';\nimport { TOOL_DEFS } from './tools/registry.js';\n\nconst KNOWN_TOOLS = new Set(TOOL_DEFS.map((d) => d.id));\n\nexport function handleToolsStatus(res) {\n sendJSON(res, 200, { tools: toolsStatus() });\n}\n\nexport async function handleToolsRun(res, body) {\n const { scanId, kinds, tools, options } = body || {};\n if (!scanId) return sendError(res, 400, 'Missing scanId.');\n const session = getSession(scanId);\n if (!session) return sendError(res, 404, 'That scan is gone. Rescan the repo.');\n\n // `kinds` narrows by purpose, `tools` to named engines. An unknown value is a\n // typo the caller would rather hear about than have silently ignored.\n let kindFilter = null;\n if (Array.isArray(kinds) && kinds.length) {\n const ok = kinds.filter((k) => k === 'security' || k === 'dead-code');\n if (!ok.length) return sendError(res, 400, 'kinds must be \"security\" and/or \"dead-code\".');\n kindFilter = ok;\n }\n let toolFilter = null;\n if (Array.isArray(tools) && tools.length) {\n toolFilter = tools.map((t) => String(t));\n }\n\n // `options` is the per-engine GUI form. Validation happens against the\n // registry schema inside the run — the handler just passes it along.\n const report = await runExternalAnalysis(session.root, {\n kinds: kindFilter, tools: toolFilter, options,\n });\n return sendJSON(res, 200, report);\n}\n\n// POST /api/tools/install — install a missing engine, streaming progress.\n//\n// This is the one endpoint that changes the machine, so it is worth spelling\n// out why that is safe enough for a local, self-hosted app:\n//\n// * The route is a POST, which the router's same-origin gate already\n// refuses for any page that is not Onboarder's own — a foreign tab cannot\n// drive it.\n// * `tool` must name a registry entry; the install plans are fixed data in\n// `tools/install.js`, spawned as argument arrays. Nothing from the\n// request body ever becomes part of a command line.\n// * The stream is SSE in the same `data:`-framed shape the explain proxy\n// uses, so the front end reads install progress exactly like AI output.\nexport async function handleToolsInstall(res, body) {\n const tool = String(body?.tool || '');\n if (!KNOWN_TOOLS.has(tool)) {\n return sendError(res, 400, `Unknown tool \"${tool}\". Known: ${[...KNOWN_TOOLS].join(', ')}.`);\n }\n if (!plansFor(tool).length) {\n return sendError(res, 400, `Onboarder has no install plan for ${tool} on ${process.platform}.`);\n }\n if (isInstalling(tool)) {\n return sendError(res, 409, `${tool} is already being installed.`);\n }\n\n res.writeHead(200, {\n 'content-type': 'text/event-stream; charset=utf-8',\n 'cache-control': 'no-cache',\n connection: 'keep-alive',\n });\n const send = (event) => {\n try { res.write('data: ' + JSON.stringify(event) + '\\n\\n'); } catch { /* client went away */ }\n };\n\n try {\n await installTool(tool, send);\n } catch (err) {\n send({ type: 'done', ok: false, error: err.message || 'The install failed.' });\n }\n res.end();\n}\n"]}
@@ -0,0 +1,5 @@
1
+ export declare const SESSION_COOKIE = "onboarder_session";
2
+ export declare const SESSION_TTL_SECONDS: number;
3
+ export declare function createSession(settings: any, now?: number): string;
4
+ export declare function hasValidSession(req: any, settings: any, now?: number): boolean;
5
+ export declare function sessionCookie(req: any, settings: any, { clear }?: Record<string, any>): string;
package/server/auth.js CHANGED
@@ -4,69 +4,72 @@
4
4
  // opaque timestamp + HMAC using the configured key as the signing secret. The
5
5
  // browser gets an HttpOnly cookie, JavaScript cannot read it, and rotating the
6
6
  // access key invalidates every old session immediately.
7
-
8
7
  import { createHmac, timingSafeEqual } from 'node:crypto';
9
-
10
8
  export const SESSION_COOKIE = 'onboarder_session';
11
9
  export const SESSION_TTL_SECONDS = 7 * 24 * 60 * 60;
12
-
13
10
  function signature(expires, accessKey) {
14
- return createHmac('sha256', accessKey)
15
- .update(`onboarder-session-v1:${expires}`)
16
- .digest('base64url');
11
+ return createHmac('sha256', accessKey)
12
+ .update(`onboarder-session-v1:${expires}`)
13
+ .digest('base64url');
17
14
  }
18
-
19
15
  export function createSession(settings, now = Date.now()) {
20
- const key = String(settings?.accessKey || '');
21
- if (!key) return '';
22
- const expires = Math.floor(now / 1000) + SESSION_TTL_SECONDS;
23
- return `${expires}.${signature(expires, key)}`;
16
+ const key = String(settings?.accessKey || '');
17
+ if (!key)
18
+ return '';
19
+ const expires = Math.floor(now / 1000) + SESSION_TTL_SECONDS;
20
+ return `${expires}.${signature(expires, key)}`;
24
21
  }
25
-
26
22
  function cookieValue(req, name) {
27
- const header = String(req.headers?.cookie || '');
28
- for (const part of header.split(';')) {
29
- const at = part.indexOf('=');
30
- if (at === -1) continue;
31
- if (part.slice(0, at).trim() !== name) continue;
32
- try { return decodeURIComponent(part.slice(at + 1).trim()); } catch { return ''; }
33
- }
34
- return '';
23
+ const header = String(req.headers?.cookie || '');
24
+ for (const part of header.split(';')) {
25
+ const at = part.indexOf('=');
26
+ if (at === -1)
27
+ continue;
28
+ if (part.slice(0, at).trim() !== name)
29
+ continue;
30
+ try {
31
+ return decodeURIComponent(part.slice(at + 1).trim());
32
+ }
33
+ catch {
34
+ return '';
35
+ }
36
+ }
37
+ return '';
35
38
  }
36
-
37
39
  export function hasValidSession(req, settings, now = Date.now()) {
38
- const key = String(settings?.accessKey || '');
39
- const token = cookieValue(req, SESSION_COOKIE);
40
- const dot = token.indexOf('.');
41
- if (!key || dot < 1) return false;
42
-
43
- const expires = Number(token.slice(0, dot));
44
- const supplied = token.slice(dot + 1);
45
- if (!Number.isSafeInteger(expires) || expires <= Math.floor(now / 1000) || !supplied) return false;
46
-
47
- const expected = signature(expires, key);
48
- const a = Buffer.from(supplied);
49
- const b = Buffer.from(expected);
50
- return a.length === b.length && timingSafeEqual(a, b);
40
+ const key = String(settings?.accessKey || '');
41
+ const token = cookieValue(req, SESSION_COOKIE);
42
+ const dot = token.indexOf('.');
43
+ if (!key || dot < 1)
44
+ return false;
45
+ const expires = Number(token.slice(0, dot));
46
+ const supplied = token.slice(dot + 1);
47
+ if (!Number.isSafeInteger(expires) || expires <= Math.floor(now / 1000) || !supplied)
48
+ return false;
49
+ const expected = signature(expires, key);
50
+ const a = Buffer.from(supplied);
51
+ const b = Buffer.from(expected);
52
+ return a.length === b.length && timingSafeEqual(a, b);
51
53
  }
52
-
53
54
  function requestIsSecure(req) {
54
- if (req.socket?.encrypted) return true;
55
- const forwarded = String(req.headers?.['x-forwarded-proto'] || '').split(',')[0].trim().toLowerCase();
56
- return forwarded === 'https';
55
+ if (req.socket?.encrypted)
56
+ return true;
57
+ const forwarded = String(req.headers?.['x-forwarded-proto'] || '').split(',')[0].trim().toLowerCase();
58
+ return forwarded === 'https';
57
59
  }
58
-
59
60
  export function sessionCookie(req, settings, { clear = false } = {}) {
60
- const parts = [
61
- `${SESSION_COOKIE}=${clear ? '' : encodeURIComponent(createSession(settings))}`,
62
- 'Path=/',
63
- 'HttpOnly',
64
- 'SameSite=Strict',
65
- `Max-Age=${clear ? 0 : SESSION_TTL_SECONDS}`,
66
- ];
67
- // A direct bare-IP deployment may still be plain HTTP, where Secure would make
68
- // the cookie unusable. Caddy forwards the original scheme, so domain HTTPS
69
- // gets the production-safe flag automatically.
70
- if (requestIsSecure(req)) parts.push('Secure');
71
- return parts.join('; ');
61
+ const parts = [
62
+ `${SESSION_COOKIE}=${clear ? '' : encodeURIComponent(createSession(settings))}`,
63
+ 'Path=/',
64
+ 'HttpOnly',
65
+ 'SameSite=Strict',
66
+ `Max-Age=${clear ? 0 : SESSION_TTL_SECONDS}`,
67
+ ];
68
+ // A direct bare-IP deployment may still be plain HTTP, where Secure would make
69
+ // the cookie unusable. Caddy forwards the original scheme, so domain HTTPS
70
+ // gets the production-safe flag automatically.
71
+ if (requestIsSecure(req))
72
+ parts.push('Secure');
73
+ return parts.join('; ');
72
74
  }
75
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/server/auth.ts"],"names":[],"mappings":"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","sourcesContent":["// Browser sessions for the self-hosted access-key gate.\n//\n// The access key itself never becomes a cookie. A successful login mints an\n// opaque timestamp + HMAC using the configured key as the signing secret. The\n// browser gets an HttpOnly cookie, JavaScript cannot read it, and rotating the\n// access key invalidates every old session immediately.\n\nimport { createHmac, timingSafeEqual } from 'node:crypto';\n\nexport const SESSION_COOKIE = 'onboarder_session';\nexport const SESSION_TTL_SECONDS = 7 * 24 * 60 * 60;\n\nfunction signature(expires, accessKey) {\n return createHmac('sha256', accessKey)\n .update(`onboarder-session-v1:${expires}`)\n .digest('base64url');\n}\n\nexport function createSession(settings, now = Date.now()) {\n const key = String(settings?.accessKey || '');\n if (!key) return '';\n const expires = Math.floor(now / 1000) + SESSION_TTL_SECONDS;\n return `${expires}.${signature(expires, key)}`;\n}\n\nfunction cookieValue(req, name) {\n const header = String(req.headers?.cookie || '');\n for (const part of header.split(';')) {\n const at = part.indexOf('=');\n if (at === -1) continue;\n if (part.slice(0, at).trim() !== name) continue;\n try { return decodeURIComponent(part.slice(at + 1).trim()); } catch { return ''; }\n }\n return '';\n}\n\nexport function hasValidSession(req, settings, now = Date.now()) {\n const key = String(settings?.accessKey || '');\n const token = cookieValue(req, SESSION_COOKIE);\n const dot = token.indexOf('.');\n if (!key || dot < 1) return false;\n\n const expires = Number(token.slice(0, dot));\n const supplied = token.slice(dot + 1);\n if (!Number.isSafeInteger(expires) || expires <= Math.floor(now / 1000) || !supplied) return false;\n\n const expected = signature(expires, key);\n const a = Buffer.from(supplied);\n const b = Buffer.from(expected);\n return a.length === b.length && timingSafeEqual(a, b);\n}\n\nfunction requestIsSecure(req) {\n if (req.socket?.encrypted) return true;\n const forwarded = String(req.headers?.['x-forwarded-proto'] || '').split(',')[0].trim().toLowerCase();\n return forwarded === 'https';\n}\n\nexport function sessionCookie(req, settings, { clear = false }: Record<string, any> = {}) {\n const parts = [\n `${SESSION_COOKIE}=${clear ? '' : encodeURIComponent(createSession(settings))}`,\n 'Path=/',\n 'HttpOnly',\n 'SameSite=Strict',\n `Max-Age=${clear ? 0 : SESSION_TTL_SECONDS}`,\n ];\n // A direct bare-IP deployment may still be plain HTTP, where Secure would make\n // the cookie unusable. Caddy forwards the original scheme, so domain HTTPS\n // gets the production-safe flag automatically.\n if (requestIsSecure(req)) parts.push('Secure');\n return parts.join('; ');\n}\n"]}
@@ -0,0 +1,42 @@
1
+ import type { Settings, SettingsInput, ServerUrls } from '../shared/contracts.js';
2
+ export declare const CONFIG_VERSION = 2;
3
+ export declare const LOOPBACK_HOSTS: Set<string>;
4
+ export declare const DEFAULT_SETTINGS: Readonly<Settings>;
5
+ export declare function configHome(env?: NodeJS.ProcessEnv): string;
6
+ export declare function configPath(env?: NodeJS.ProcessEnv): string;
7
+ export declare function isLoopbackHost(host: any): boolean;
8
+ export declare function normalizeSettings(value?: SettingsInput): Settings;
9
+ export declare function generateAccessKey(): string;
10
+ export declare function serverUrls(value?: SettingsInput): ServerUrls;
11
+ export declare function maskAccessKey(key: any): string;
12
+ export declare function browserUrl(settings: any): string;
13
+ export declare function publicSettings(value?: SettingsInput): {
14
+ version: number;
15
+ mode: "local" | "self-hosted";
16
+ host: string;
17
+ port: number;
18
+ domain: string;
19
+ https: boolean;
20
+ autoOpen: boolean;
21
+ hasAccessKey: boolean;
22
+ accessKeyMasked: string;
23
+ account: import("../shared/contracts.js").AccountSettings;
24
+ tunnel: {
25
+ cloudflare: boolean;
26
+ tailscale: boolean;
27
+ };
28
+ urls: ServerUrls;
29
+ security: {
30
+ remoteAuth: boolean;
31
+ loopback: boolean;
32
+ };
33
+ };
34
+ export declare function allowedHosts(value?: SettingsInput): unknown[];
35
+ export declare function accessKeysMatch(expected: any, actual: any): boolean;
36
+ export declare function bearerToken(req: any): string;
37
+ export declare function requestIsLoopback(req: any): boolean;
38
+ export declare function authReason(req: any, settings?: SettingsInput): "A valid access key is required." | "Self-hosted mode has no access key configured.";
39
+ export declare function configExists(file?: string): Promise<boolean>;
40
+ export declare function readSettings(file?: string): Promise<Settings>;
41
+ export declare function writeSettings(value: any, file?: string): Promise<Settings>;
42
+ export declare function updateSettings(patch?: SettingsInput, file?: string): Promise<Settings>;