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.
- package/README.md +174 -46
- package/bin/build-if-source.js +23 -0
- package/cli/commands.d.ts +26 -0
- package/cli/commands.js +836 -811
- package/cli/commands.js.map +1 -0
- package/cli/explorer/advanced.d.ts +14 -0
- package/cli/explorer/advanced.js +321 -329
- package/cli/explorer/advanced.js.map +1 -0
- package/cli/explorer/app.d.ts +1 -0
- package/cli/explorer/app.js +384 -313
- package/cli/explorer/app.js.map +1 -0
- package/cli/explorer/assistant.d.ts +9 -0
- package/cli/explorer/assistant.js +58 -0
- package/cli/explorer/assistant.js.map +1 -0
- package/cli/explorer/commands.d.ts +17 -0
- package/cli/explorer/commands.js +368 -302
- package/cli/explorer/commands.js.map +1 -0
- package/cli/explorer/featureViews.d.ts +124 -0
- package/cli/explorer/featureViews.js +179 -0
- package/cli/explorer/featureViews.js.map +1 -0
- package/cli/explorer/github.d.ts +21 -0
- package/cli/explorer/github.js +42 -44
- package/cli/explorer/github.js.map +1 -0
- package/cli/explorer/graphs.d.ts +7 -0
- package/cli/explorer/graphs.js +90 -95
- package/cli/explorer/graphs.js.map +1 -0
- package/cli/explorer/picker.d.ts +22 -0
- package/cli/explorer/picker.js +150 -0
- package/cli/explorer/picker.js.map +1 -0
- package/cli/explorer/report.d.ts +85 -0
- package/cli/explorer/report.js +60 -0
- package/cli/explorer/report.js.map +1 -0
- package/cli/explorer/session.d.ts +449 -0
- package/cli/explorer/session.js +143 -144
- package/cli/explorer/session.js.map +1 -0
- package/cli/explorer/views.d.ts +19 -0
- package/cli/explorer/views.js +465 -482
- package/cli/explorer/views.js.map +1 -0
- package/cli/explorer/wrap.d.ts +1 -0
- package/cli/explorer/wrap.js +28 -44
- package/cli/explorer/wrap.js.map +1 -0
- package/cli/main.d.ts +1 -0
- package/cli/main.js +214 -161
- package/cli/main.js.map +1 -0
- package/cli/prompt.d.ts +5 -0
- package/cli/prompt.js +84 -80
- package/cli/prompt.js.map +1 -0
- package/cli/scanOptions.d.ts +2 -0
- package/cli/scanOptions.js +11 -0
- package/cli/scanOptions.js.map +1 -0
- package/cli/ui.d.ts +33 -0
- package/cli/ui.js +35 -44
- package/cli/ui.js.map +1 -0
- package/cli/wizard.d.ts +187 -0
- package/cli/wizard.js +304 -284
- package/cli/wizard.js.map +1 -0
- package/package.json +15 -4
- package/public/app.d.ts +1 -0
- package/public/app.js +1057 -1009
- package/public/app.js.map +1 -0
- package/public/browser.d.ts +68 -0
- package/public/browser.js +2 -0
- package/public/browser.js.map +1 -0
- package/public/index.html +30 -13
- package/public/js/about.d.ts +6 -0
- package/public/js/about.js +152 -172
- package/public/js/about.js.map +1 -0
- package/public/js/aiDraft.d.ts +3 -0
- package/public/js/aiDraft.js +116 -124
- package/public/js/aiDraft.js.map +1 -0
- package/public/js/analysisPanel.d.ts +7 -0
- package/public/js/analysisPanel.js +166 -166
- package/public/js/analysisPanel.js.map +1 -0
- package/public/js/analysisReport.d.ts +38 -0
- package/public/js/analysisReport.js +105 -110
- package/public/js/analysisReport.js.map +1 -0
- package/public/js/api.d.ts +26 -0
- package/public/js/api.js +176 -167
- package/public/js/api.js.map +1 -0
- package/public/js/atlas.d.ts +6 -0
- package/public/js/atlas.js +50 -58
- package/public/js/atlas.js.map +1 -0
- package/public/js/blameView.d.ts +2 -0
- package/public/js/blameView.js +36 -37
- package/public/js/blameView.js.map +1 -0
- package/public/js/codeTab.d.ts +3 -0
- package/public/js/codeTab.js +175 -180
- package/public/js/codeTab.js.map +1 -0
- package/public/js/codeViewer.d.ts +5 -0
- package/public/js/codeViewer.js +162 -150
- package/public/js/codeViewer.js.map +1 -0
- package/public/js/components/FileChip.d.ts +9 -0
- package/public/js/components/FileChip.js +45 -49
- package/public/js/components/FileChip.js.map +1 -0
- package/public/js/components/MetricSparkline.d.ts +7 -0
- package/public/js/components/MetricSparkline.js +35 -44
- package/public/js/components/MetricSparkline.js.map +1 -0
- package/public/js/components/RiskBadge.d.ts +9 -0
- package/public/js/components/RiskBadge.js +55 -63
- package/public/js/components/RiskBadge.js.map +1 -0
- package/public/js/deepAnalysisView.d.ts +99 -0
- package/public/js/deepAnalysisView.js +282 -302
- package/public/js/deepAnalysisView.js.map +1 -0
- package/public/js/diagramPane.d.ts +8 -0
- package/public/js/diagramPane.js +133 -135
- package/public/js/diagramPane.js.map +1 -0
- package/public/js/diffView.d.ts +6 -0
- package/public/js/diffView.js +222 -209
- package/public/js/diffView.js.map +1 -0
- package/public/js/docsView.d.ts +2 -0
- package/public/js/docsView.js +441 -442
- package/public/js/docsView.js.map +1 -0
- package/public/js/dom.d.ts +189 -0
- package/public/js/dom.js +5 -0
- package/public/js/dom.js.map +1 -0
- package/public/js/fileSourceBrowser.d.ts +5 -0
- package/public/js/fileSourceBrowser.js +33 -41
- package/public/js/fileSourceBrowser.js.map +1 -0
- package/public/js/flameGraph.d.ts +5 -0
- package/public/js/flameGraph.js +62 -72
- package/public/js/flameGraph.js.map +1 -0
- package/public/js/forceGraph.d.ts +10 -0
- package/public/js/forceGraph.js +581 -556
- package/public/js/forceGraph.js.map +1 -0
- package/public/js/heatmap.d.ts +4 -0
- package/public/js/heatmap.js +142 -164
- package/public/js/heatmap.js.map +1 -0
- package/public/js/highlight.d.ts +2 -0
- package/public/js/highlight.js +116 -107
- package/public/js/highlight.js.map +1 -0
- package/public/js/html.d.ts +2 -0
- package/public/js/html.js +14 -44
- package/public/js/html.js.map +1 -0
- package/public/js/insightsView.d.ts +5 -0
- package/public/js/insightsView.js +93 -111
- package/public/js/insightsView.js.map +1 -0
- package/public/js/inspector.d.ts +37 -0
- package/public/js/inspector.js +416 -445
- package/public/js/inspector.js.map +1 -0
- package/public/js/llm.d.ts +113 -0
- package/public/js/llm.js +233 -272
- package/public/js/llm.js.map +1 -0
- package/public/js/mapView.d.ts +3 -0
- package/public/js/mapView.js +121 -120
- package/public/js/mapView.js.map +1 -0
- package/public/js/markdown.d.ts +3 -0
- package/public/js/markdown.js +84 -91
- package/public/js/markdown.js.map +1 -0
- package/public/js/mindmap.d.ts +32 -0
- package/public/js/mindmap.js +244 -259
- package/public/js/mindmap.js.map +1 -0
- package/public/js/mobilePanels.d.ts +5 -0
- package/public/js/mobilePanels.js +50 -0
- package/public/js/mobilePanels.js.map +1 -0
- package/public/js/repoFiles.d.ts +12 -0
- package/public/js/repoFiles.js +28 -27
- package/public/js/repoFiles.js.map +1 -0
- package/public/js/reviewView.d.ts +28 -0
- package/public/js/reviewView.js +256 -0
- package/public/js/reviewView.js.map +1 -0
- package/public/js/sbomView.d.ts +3 -0
- package/public/js/sbomView.js +59 -67
- package/public/js/sbomView.js.map +1 -0
- package/public/js/scanCache.d.ts +16 -0
- package/public/js/scanCache.js +67 -65
- package/public/js/scanCache.js.map +1 -0
- package/public/js/search.d.ts +7 -0
- package/public/js/search.js +868 -800
- package/public/js/search.js.map +1 -0
- package/public/js/searchLocal.d.ts +42 -0
- package/public/js/searchLocal.js +87 -0
- package/public/js/searchLocal.js.map +1 -0
- package/public/js/serverSettings.d.ts +5 -0
- package/public/js/serverSettings.js +153 -164
- package/public/js/serverSettings.js.map +1 -0
- package/public/js/state.d.ts +798 -0
- package/public/js/state.js +114 -122
- package/public/js/state.js.map +1 -0
- package/public/js/tour.d.ts +6 -0
- package/public/js/tour.js +5 -7
- package/public/js/tour.js.map +1 -0
- package/public/js/transitions.d.ts +2 -0
- package/public/js/transitions.js +9 -10
- package/public/js/transitions.js.map +1 -0
- package/public/js/tree.d.ts +12 -0
- package/public/js/tree.js +157 -154
- package/public/js/tree.js.map +1 -0
- package/public/js/workflowsView.d.ts +3 -0
- package/public/js/workflowsView.js +15 -22
- package/public/js/workflowsView.js.map +1 -0
- package/public/search.css +495 -0
- package/public/styles.css +163 -346
- package/public/workbench.css +166 -0
- package/server/apiAuth.d.ts +3 -0
- package/server/apiAuth.js +18 -20
- package/server/apiAuth.js.map +1 -0
- package/server/apiDiff.d.ts +2 -0
- package/server/apiDiff.js +20 -18
- package/server/apiDiff.js.map +1 -0
- package/server/apiDocs.d.ts +2 -0
- package/server/apiDocs.js +38 -40
- package/server/apiDocs.js.map +1 -0
- package/server/apiFile.d.ts +1 -0
- package/server/apiFile.js +14 -17
- package/server/apiFile.js.map +1 -0
- package/server/apiGitBlame.d.ts +2 -0
- package/server/apiGitBlame.js +50 -51
- package/server/apiGitBlame.js.map +1 -0
- package/server/apiMcp.d.ts +4 -0
- package/server/apiMcp.js +64 -66
- package/server/apiMcp.js.map +1 -0
- package/server/apiReview.d.ts +2 -0
- package/server/apiReview.js +45 -0
- package/server/apiReview.js.map +1 -0
- package/server/apiScan.d.ts +5 -0
- package/server/apiScan.js +90 -71
- package/server/apiScan.js.map +1 -0
- package/server/apiSearch.d.ts +33 -0
- package/server/apiSearch.js +184 -200
- package/server/apiSearch.js.map +1 -0
- package/server/apiSettings.d.ts +4 -0
- package/server/apiSettings.js +84 -85
- package/server/apiSettings.js.map +1 -0
- package/server/apiTools.d.ts +3 -0
- package/server/apiTools.js +55 -56
- package/server/apiTools.js.map +1 -0
- package/server/auth.d.ts +5 -0
- package/server/auth.js +54 -51
- package/server/auth.js.map +1 -0
- package/server/config.d.ts +42 -0
- package/server/config.js +189 -194
- package/server/config.js.map +1 -0
- package/server/daemon.d.ts +12 -0
- package/server/daemon.js +124 -114
- package/server/daemon.js.map +1 -0
- package/server/fileSourceNode.d.ts +2 -0
- package/server/fileSourceNode.js +34 -36
- package/server/fileSourceNode.js.map +1 -0
- package/server/gitClone.d.ts +9 -0
- package/server/gitClone.js +59 -61
- package/server/gitClone.js.map +1 -0
- package/server/gitDiff.d.ts +36 -0
- package/server/gitDiff.js +271 -169
- package/server/gitDiff.js.map +1 -0
- package/server/gitHistory.d.ts +12 -0
- package/server/gitHistory.js +80 -80
- package/server/gitHistory.js.map +1 -0
- package/server/gitRemote.d.ts +12 -0
- package/server/gitRemote.js +37 -38
- package/server/gitRemote.js.map +1 -0
- package/server/htmlText.d.ts +4 -0
- package/server/htmlText.js +16 -23
- package/server/htmlText.js.map +1 -0
- package/server/http.d.ts +5 -0
- package/server/http.js +27 -30
- package/server/http.js.map +1 -0
- package/server/httpGuards.d.ts +2 -0
- package/server/httpGuards.js +47 -43
- package/server/httpGuards.js.map +1 -0
- package/server/https.d.ts +34 -0
- package/server/https.js +121 -111
- package/server/https.js.map +1 -0
- package/server/index.d.ts +40 -0
- package/server/index.js +139 -147
- package/server/index.js.map +1 -0
- package/server/layout.d.ts +26 -0
- package/server/layout.js +59 -64
- package/server/layout.js.map +1 -0
- package/server/llmProxy.d.ts +3 -0
- package/server/llmProxy.js +137 -135
- package/server/llmProxy.js.map +1 -0
- package/server/logger.d.ts +38 -0
- package/server/logger.js +198 -211
- package/server/logger.js.map +1 -0
- package/server/mcp/analysis.d.ts +29 -0
- package/server/mcp/analysis.js +99 -120
- package/server/mcp/analysis.js.map +1 -0
- package/server/mcp/http.d.ts +9 -0
- package/server/mcp/http.js +128 -137
- package/server/mcp/http.js.map +1 -0
- package/server/mcp/runner.d.ts +68 -0
- package/server/mcp/runner.js +208 -218
- package/server/mcp/runner.js.map +1 -0
- package/server/mcp/server.d.ts +29 -0
- package/server/mcp/server.js +166 -179
- package/server/mcp/server.js.map +1 -0
- package/server/mcp/standalone.d.ts +1 -0
- package/server/mcp/standalone.js +8 -10
- package/server/mcp/standalone.js.map +1 -0
- package/server/mcp/tools.d.ts +2020 -0
- package/server/mcp/tools.js +523 -574
- package/server/mcp/tools.js.map +1 -0
- package/server/paths.d.ts +3 -0
- package/server/paths.js +17 -15
- package/server/paths.js.map +1 -0
- package/server/pidfile.d.ts +9 -0
- package/server/pidfile.js +43 -45
- package/server/pidfile.js.map +1 -0
- package/server/review.d.ts +5 -0
- package/server/review.js +35 -0
- package/server/review.js.map +1 -0
- package/server/router.d.ts +33 -0
- package/server/router.js +191 -197
- package/server/router.js.map +1 -0
- package/server/scanRequest.d.ts +3 -0
- package/server/scanRequest.js +36 -0
- package/server/scanRequest.js.map +1 -0
- package/server/searchIndex.d.ts +17 -0
- package/server/searchIndex.js +83 -80
- package/server/searchIndex.js.map +1 -0
- package/server/sessions.d.ts +14 -0
- package/server/sessions.js +62 -70
- package/server/sessions.js.map +1 -0
- package/server/startup.d.ts +118 -0
- package/server/startup.js +143 -148
- package/server/startup.js.map +1 -0
- package/server/static.d.ts +6 -0
- package/server/static.js +28 -31
- package/server/static.js.map +1 -0
- package/server/tools/install.d.ts +4 -0
- package/server/tools/install.js +176 -185
- package/server/tools/install.js.map +1 -0
- package/server/tools/parse.d.ts +8 -0
- package/server/tools/parse.js +112 -114
- package/server/tools/parse.js.map +1 -0
- package/server/tools/platform.d.ts +9 -0
- package/server/tools/platform.js +49 -47
- package/server/tools/platform.js.map +1 -0
- package/server/tools/registry.d.ts +137 -0
- package/server/tools/registry.js +173 -172
- package/server/tools/registry.js.map +1 -0
- package/server/tools/scan.d.ts +34 -0
- package/server/tools/scan.js +89 -98
- package/server/tools/scan.js.map +1 -0
- package/server/tools.d.ts +34 -0
- package/server/tools.js +94 -107
- package/server/tools.js.map +1 -0
- package/server/tunnel.d.ts +29 -0
- package/server/tunnel.js +61 -66
- package/server/tunnel.js.map +1 -0
- package/shared/analyzer/docs.d.ts +16 -0
- package/shared/analyzer/docs.js +99 -93
- package/shared/analyzer/docs.js.map +1 -0
- package/shared/analyzer/explainLocal.d.ts +4 -0
- package/shared/analyzer/explainLocal.js +122 -142
- package/shared/analyzer/explainLocal.js.map +1 -0
- package/shared/analyzer/github.d.ts +37 -0
- package/shared/analyzer/github.js +41 -43
- package/shared/analyzer/github.js.map +1 -0
- package/shared/analyzer/graph.d.ts +41 -0
- package/shared/analyzer/graph.js +393 -400
- package/shared/analyzer/graph.js.map +1 -0
- package/shared/analyzer/health.d.ts +33 -0
- package/shared/analyzer/health.js +197 -193
- package/shared/analyzer/health.js.map +1 -0
- package/shared/analyzer/history.d.ts +29 -0
- package/shared/analyzer/history.js +110 -111
- package/shared/analyzer/history.js.map +1 -0
- package/shared/analyzer/jsonConfig.d.ts +2 -0
- package/shared/analyzer/jsonConfig.js +61 -0
- package/shared/analyzer/jsonConfig.js.map +1 -0
- package/shared/analyzer/languages/csharp.d.ts +26 -0
- package/shared/analyzer/languages/csharp.js +83 -88
- package/shared/analyzer/languages/csharp.js.map +1 -0
- package/shared/analyzer/languages/generic.d.ts +23 -0
- package/shared/analyzer/languages/generic.js +94 -105
- package/shared/analyzer/languages/generic.js.map +1 -0
- package/shared/analyzer/languages/go.d.ts +24 -0
- package/shared/analyzer/languages/go.js +45 -54
- package/shared/analyzer/languages/go.js.map +1 -0
- package/shared/analyzer/languages/index.d.ts +6 -0
- package/shared/analyzer/languages/index.js +19 -20
- package/shared/analyzer/languages/index.js.map +1 -0
- package/shared/analyzer/languages/java.d.ts +20 -0
- package/shared/analyzer/languages/java.js +73 -78
- package/shared/analyzer/languages/java.js.map +1 -0
- package/shared/analyzer/languages/javascript.d.ts +12 -0
- package/shared/analyzer/languages/javascript.js +258 -209
- package/shared/analyzer/languages/javascript.js.map +1 -0
- package/shared/analyzer/languages/python.d.ts +23 -0
- package/shared/analyzer/languages/python.js +92 -100
- package/shared/analyzer/languages/python.js.map +1 -0
- package/shared/analyzer/languages/rust.d.ts +17 -0
- package/shared/analyzer/languages/rust.js +96 -98
- package/shared/analyzer/languages/rust.js.map +1 -0
- package/shared/analyzer/languages/typescript.d.ts +11 -0
- package/shared/analyzer/languages/typescript.js +117 -116
- package/shared/analyzer/languages/typescript.js.map +1 -0
- package/shared/analyzer/licenses.d.ts +18 -0
- package/shared/analyzer/licenses.js +130 -133
- package/shared/analyzer/licenses.js.map +1 -0
- package/shared/analyzer/metrics.d.ts +20 -0
- package/shared/analyzer/metrics.js +112 -104
- package/shared/analyzer/metrics.js.map +1 -0
- package/shared/analyzer/pathUtil.d.ts +10 -0
- package/shared/analyzer/pathUtil.js +29 -35
- package/shared/analyzer/pathUtil.js.map +1 -0
- package/shared/analyzer/patterns.d.ts +11 -0
- package/shared/analyzer/patterns.js +231 -253
- package/shared/analyzer/patterns.js.map +1 -0
- package/shared/analyzer/scan.d.ts +62 -0
- package/shared/analyzer/scan.js +451 -442
- package/shared/analyzer/scan.js.map +1 -0
- package/shared/analyzer/scanControl.d.ts +11 -0
- package/shared/analyzer/scanControl.js +62 -0
- package/shared/analyzer/scanControl.js.map +1 -0
- package/shared/analyzer/security.d.ts +18 -0
- package/shared/analyzer/security.js +159 -159
- package/shared/analyzer/security.js.map +1 -0
- package/shared/analyzer/services.d.ts +5 -0
- package/shared/analyzer/services.js +141 -138
- package/shared/analyzer/services.js.map +1 -0
- package/shared/analyzer/stack.d.ts +6 -0
- package/shared/analyzer/stack.js +134 -141
- package/shared/analyzer/stack.js.map +1 -0
- package/shared/analyzer/tour.d.ts +2 -0
- package/shared/analyzer/tour.js +45 -53
- package/shared/analyzer/tour.js.map +1 -0
- package/shared/analyzer/util.d.ts +8 -0
- package/shared/analyzer/util.js +130 -76
- package/shared/analyzer/util.js.map +1 -0
- package/shared/analyzer/workflows.d.ts +8 -0
- package/shared/analyzer/workflows.js +139 -134
- package/shared/analyzer/workflows.js.map +1 -0
- package/shared/contracts.d.ts +250 -0
- package/shared/contracts.js +2 -0
- package/shared/contracts.js.map +1 -0
- package/shared/diagram/aiFacts.d.ts +35 -0
- package/shared/diagram/aiFacts.js +102 -105
- package/shared/diagram/aiFacts.js.map +1 -0
- package/shared/diagram/aiMermaid.d.ts +5 -0
- package/shared/diagram/aiMermaid.js +46 -45
- package/shared/diagram/aiMermaid.js.map +1 -0
- package/shared/diagram/atlas.d.ts +16 -0
- package/shared/diagram/atlas.js +51 -60
- package/shared/diagram/atlas.js.map +1 -0
- package/shared/diagram/mermaid.d.ts +43 -0
- package/shared/diagram/mermaid.js +348 -370
- package/shared/diagram/mermaid.js.map +1 -0
- package/shared/review/contracts.d.ts +95 -0
- package/shared/review/contracts.js +2 -0
- package/shared/review/contracts.js.map +1 -0
- package/shared/review/review.d.ts +10 -0
- package/shared/review/review.js +184 -0
- package/shared/review/review.js.map +1 -0
- package/shared/search/query.d.ts +32 -0
- package/shared/search/query.js +395 -388
- package/shared/search/query.js.map +1 -0
package/server/index.js
CHANGED
|
@@ -11,12 +11,10 @@
|
|
|
11
11
|
// is 127.0.0.1 and nothing else, self-hosted mode binds what it was given and
|
|
12
12
|
// turns on the access-key gate in the router. A bare `createServer()` — what
|
|
13
13
|
// the tests drive — never touches that file.
|
|
14
|
-
|
|
15
14
|
import http from 'node:http';
|
|
16
15
|
import path from 'node:path';
|
|
17
16
|
import { spawn } from 'node:child_process';
|
|
18
17
|
import { fileURLToPath } from 'node:url';
|
|
19
|
-
|
|
20
18
|
import { createRouter } from './router.js';
|
|
21
19
|
import { installExitCleanup } from './sessions.js';
|
|
22
20
|
import { createLogger } from './logger.js';
|
|
@@ -26,43 +24,37 @@ import { tunnelStatus } from './tunnel.js';
|
|
|
26
24
|
import { pidIsAlive, readPidFile, removePidFile, writePidFile, writeRunInfo, runInfoPath } from './pidfile.js';
|
|
27
25
|
import { logPath } from './daemon.js';
|
|
28
26
|
import { panel, row } from './layout.js';
|
|
29
|
-
|
|
30
27
|
const logger = createLogger();
|
|
31
|
-
|
|
32
28
|
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
33
29
|
const PROJECT_ROOT = path.resolve(HERE, '..');
|
|
34
|
-
|
|
35
30
|
// The MCP runner is created here, not inside the router, and is put on the config
|
|
36
31
|
// object the router already passes to every handler. Two reasons: exactly one
|
|
37
32
|
// server can be supervised per process (a second button press must be a toggle,
|
|
38
33
|
// not a second child), and a test can supply its own runner in place of this one
|
|
39
34
|
// without the router knowing the difference.
|
|
40
35
|
const mcp = createMcpRunner({
|
|
41
|
-
|
|
36
|
+
onLog: (entry) => logger.info('mcp', entry.line),
|
|
42
37
|
});
|
|
43
|
-
|
|
44
38
|
export const CONFIG = {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
39
|
+
projectRoot: PROJECT_ROOT,
|
|
40
|
+
publicDir: path.join(PROJECT_ROOT, 'public'),
|
|
41
|
+
// Served at `/shared/`, which is what lets the browser and the server import
|
|
42
|
+
// the same analyzer. See `static.js` for why that mapping constrains the code.
|
|
43
|
+
sharedDir: path.join(PROJECT_ROOT, 'shared'),
|
|
44
|
+
mcp,
|
|
51
45
|
};
|
|
52
|
-
|
|
53
46
|
// The tests want a server they can put on an ephemeral port; `npm start` wants
|
|
54
47
|
// one from the settings file. Same router either way.
|
|
55
48
|
export function createServer(config = CONFIG) {
|
|
56
49
|
const router = createRouter(config);
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
50
|
+
return http.createServer((req, res) => {
|
|
51
|
+
const start = Date.now();
|
|
52
|
+
res.on('finish', () => {
|
|
53
|
+
logger.http({ method: req.method, path: req.url, status: res.statusCode, ms: Date.now() - start });
|
|
54
|
+
});
|
|
55
|
+
router(req, res);
|
|
61
56
|
});
|
|
62
|
-
router(req, res);
|
|
63
|
-
});
|
|
64
57
|
}
|
|
65
|
-
|
|
66
58
|
// What the terminal shows once the socket is listening. A pure string builder
|
|
67
59
|
// so the CLI prints exactly this too, and so a test can read it.
|
|
68
60
|
//
|
|
@@ -72,147 +64,147 @@ export function createServer(config = CONFIG) {
|
|
|
72
64
|
// access key" — wrapped unpredictably at any terminal width and buried the URL
|
|
73
65
|
// people actually need. Now the detail lives on its own row.
|
|
74
66
|
export function startupBanner(settings, { configFile, columns } = {}) {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
67
|
+
const urls = serverUrls(settings);
|
|
68
|
+
const rows = [];
|
|
69
|
+
const add = (label, value) => rows.push([label, value]);
|
|
70
|
+
add('URL', urls.local);
|
|
71
|
+
if (urls.network)
|
|
72
|
+
add('Network', urls.network);
|
|
73
|
+
if (urls.domain)
|
|
74
|
+
add('Public', urls.domain);
|
|
75
|
+
add('Bind', `${settings.host}:${settings.port}`);
|
|
76
|
+
add('Mode', settings.mode === 'self-hosted'
|
|
77
|
+
? (isLoopbackHost(settings.host) ? 'self-hosted (loopback — use a tunnel)' : 'self-hosted (network reachable)')
|
|
78
|
+
: 'local (this machine only)');
|
|
79
|
+
if (settings.mode === 'self-hosted') {
|
|
80
|
+
add('Auth', settings.accessKey
|
|
81
|
+
? 'access key required for remote browsers'
|
|
82
|
+
: 'NO ACCESS KEY — every API call is refused');
|
|
83
|
+
}
|
|
84
|
+
if (settings.domain) {
|
|
85
|
+
add('HTTPS', settings.https ? 'Caddy terminates TLS for this domain' : 'off — `onboarder https setup` enables it');
|
|
86
|
+
}
|
|
87
|
+
const tunnels = tunnelStatus(settings);
|
|
88
|
+
for (const name of ['cloudflare', 'tailscale']) {
|
|
89
|
+
const t = tunnels[name];
|
|
90
|
+
if (!t.enabled)
|
|
91
|
+
continue;
|
|
92
|
+
add('Tunnel', t.installed ? `${name}: ${t.command}` : `${name} enabled, CLI missing — ${t.install}`);
|
|
93
|
+
}
|
|
94
|
+
if (configFile)
|
|
95
|
+
add('Config', configFile);
|
|
96
|
+
// Rendered through the same panel helper the CLI uses, so `node server/index.js`
|
|
97
|
+
// and `onboarder start` cannot drift apart — and so both adapt to the terminal
|
|
98
|
+
// width instead of assuming 80 columns.
|
|
99
|
+
return '\n' + panel('Onboarder is up', rows.map(([label, value]) => row(label, value)), { columns }) + '\n';
|
|
107
100
|
}
|
|
108
|
-
|
|
109
101
|
// Ask the OS to open the app. Best-effort and detached: a missing opener on a
|
|
110
102
|
// headless box is not a reason the server failed to start.
|
|
111
103
|
export function openInBrowser(url) {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
104
|
+
const cmd = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'cmd' : 'xdg-open';
|
|
105
|
+
const args = process.platform === 'win32' ? ['/c', 'start', '', url] : [url];
|
|
106
|
+
try {
|
|
107
|
+
const child = spawn(cmd, args, { detached: true, stdio: 'ignore' });
|
|
108
|
+
child.on('error', (..._args) => { });
|
|
109
|
+
child.unref();
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
/* no opener — the URL is on the screen already */
|
|
113
|
+
}
|
|
121
114
|
}
|
|
122
|
-
|
|
123
115
|
export function listenError(error, { host, port, pid = null } = {}) {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
116
|
+
if (error?.code === 'EADDRINUSE') {
|
|
117
|
+
const owner = pid ? ` Another Onboarder process is running as PID ${pid}.` : '';
|
|
118
|
+
return new Error(`${host}:${port} is already in use.${owner} Try \`onboarder status\`, \`onboarder stop\`, or choose another port with \`onboarder config set port <number>\`.`, { cause: error });
|
|
119
|
+
}
|
|
120
|
+
if (error?.code === 'EACCES') {
|
|
121
|
+
return new Error(`Cannot bind ${host}:${port}: permission denied. Ports below 1024 normally require elevated privileges; choose a port above 1024.`, { cause: error });
|
|
122
|
+
}
|
|
123
|
+
return error;
|
|
132
124
|
}
|
|
133
|
-
|
|
134
125
|
// The real boot: read the settings, bind what they say, print the banner.
|
|
135
126
|
// Both `node server/index.js` and `onboarder start` land here.
|
|
136
127
|
export async function startServer({ configFile = configPath(), openBrowser, log = console.log } = {}) {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
boot: { host, port, domain: settings.domain, https: settings.https },
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
installExitCleanup();
|
|
152
|
-
// The MCP child has to go with the web server. `installExitCleanup` already
|
|
153
|
-
// handles cloned scan directories on SIGINT/SIGTERM; the child is registered
|
|
154
|
-
// on the same process events so a Ctrl-C in the terminal does not leave an
|
|
155
|
-
// orphan holding the port-less stdio pipe open.
|
|
156
|
-
for (const signal of ['SIGINT', 'SIGTERM']) {
|
|
157
|
-
process.on(signal, () => {
|
|
158
|
-
if (mcp.running) mcp.stop().catch(() => {});
|
|
128
|
+
const settings = await readSettings(configFile);
|
|
129
|
+
// Environment wins over the file: PORT=8080 npm start has worked since
|
|
130
|
+
// before settings existed, and a flag you can see beats a file you cannot.
|
|
131
|
+
const host = process.env.HOST || settings.host;
|
|
132
|
+
const port = Number(process.env.PORT) || settings.port;
|
|
133
|
+
const live = { ...settings, host, port };
|
|
134
|
+
const server = createServer({
|
|
135
|
+
...CONFIG,
|
|
136
|
+
configPath: configFile,
|
|
137
|
+
getSettings: () => readSettings(configFile),
|
|
138
|
+
boot: { host, port, domain: settings.domain, https: settings.https },
|
|
159
139
|
});
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
//
|
|
184
|
-
//
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
140
|
+
installExitCleanup();
|
|
141
|
+
// The MCP child has to go with the web server. `installExitCleanup` already
|
|
142
|
+
// handles cloned scan directories on SIGINT/SIGTERM; the child is registered
|
|
143
|
+
// on the same process events so a Ctrl-C in the terminal does not leave an
|
|
144
|
+
// orphan holding the port-less stdio pipe open.
|
|
145
|
+
for (const signal of ['SIGINT', 'SIGTERM']) {
|
|
146
|
+
process.on(signal, () => {
|
|
147
|
+
if (mcp.running)
|
|
148
|
+
mcp.stop().catch((..._args) => { });
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
try {
|
|
152
|
+
await new Promise((resolve, reject) => {
|
|
153
|
+
server.once('error', reject);
|
|
154
|
+
server.listen(port, host, () => resolve());
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
158
|
+
const recorded = readPidFile(configFile);
|
|
159
|
+
throw listenError(error, { host, port, pid: recorded && pidIsAlive(recorded) ? recorded : null });
|
|
160
|
+
}
|
|
161
|
+
// The process record is optional by design, so each write is guarded on its
|
|
162
|
+
// own: a read-only config directory must not stop a server that has already
|
|
163
|
+
// bound its port, but a *programming* error here would otherwise be swallowed
|
|
164
|
+
// and look like "it started, but nothing recorded it".
|
|
165
|
+
try {
|
|
166
|
+
writePidFile(configFile);
|
|
167
|
+
}
|
|
168
|
+
catch { /* read-only config dir */ }
|
|
169
|
+
// How this process was launched. `background` is a detached child with a log
|
|
170
|
+
// file; `startup` is one the OS supervisor started at login (also with a log
|
|
171
|
+
// file); anything else is a person typing `onboarder start` in a terminal.
|
|
172
|
+
const mode = process.env.ONBOARDER_LAUNCH || (process.env.ONBOARDER_BACKGROUND ? 'background' : 'foreground');
|
|
173
|
+
const logFile = mode === 'foreground' ? null : logPath(configFile);
|
|
174
|
+
try {
|
|
175
|
+
// Beside the pid: how this instance was launched and where its log goes, so
|
|
176
|
+
// `onboarder status` answers "how long has it been up, and how do I see its
|
|
177
|
+
// output" from a record instead of guessing.
|
|
178
|
+
writeRunInfo(configFile, {
|
|
179
|
+
mode,
|
|
180
|
+
host,
|
|
181
|
+
port,
|
|
182
|
+
url: serverUrls(live).local,
|
|
183
|
+
log: logFile,
|
|
184
|
+
node: process.version,
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
catch (error) {
|
|
188
|
+
logger.warn('could not write the run record', { file: runInfoPath(configFile), error: error.message });
|
|
189
|
+
}
|
|
190
|
+
server.once('close', () => removePidFile(configFile));
|
|
191
|
+
process.once('exit', () => removePidFile(configFile));
|
|
192
|
+
log(startupBanner(live, { configFile }));
|
|
193
|
+
const shouldOpen = openBrowser ?? (live.autoOpen && process.stdout.isTTY && !process.env.NO_OPEN);
|
|
194
|
+
// Local requests never need a credential. Remote browsers are sent to the
|
|
195
|
+
// themed login page and exchange the key for an HttpOnly session cookie.
|
|
196
|
+
if (shouldOpen)
|
|
197
|
+
openInBrowser(browserUrl(live));
|
|
198
|
+
return { server, settings: live, host, port };
|
|
207
199
|
}
|
|
208
|
-
|
|
209
200
|
// `node server/index.js` listens. Importing this module — which the tests do, to
|
|
210
201
|
// drive the real router — does not, and does not install signal handlers either:
|
|
211
202
|
// a test runner should keep its own Ctrl-C.
|
|
212
203
|
const THIS_FILE = fileURLToPath(import.meta.url);
|
|
213
204
|
if (process.argv[1] && path.resolve(process.argv[1]) === THIS_FILE) {
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
205
|
+
startServer().catch((err) => {
|
|
206
|
+
console.error(' Onboarder did not start: ' + (err.message || err));
|
|
207
|
+
process.exitCode = 1;
|
|
208
|
+
});
|
|
218
209
|
}
|
|
210
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/server/index.ts"],"names":[],"mappings":"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","sourcesContent":["// Onboarder's server: where it lives on disk, and how it starts.\n//\n// The work is elsewhere. `router.js` holds the route table and the request\n// gates; each endpoint has its own module beside it (`apiScan`, `apiFile`,\n// `apiDocs`, `llmProxy`, `apiSettings`); `static.js` serves the page and the\n// shared engine; `sessions.js` remembers which directory a scan came from.\n// What is left here is the part that has to know about the filesystem it was\n// installed into, and the difference between being imported and being run.\n//\n// Where it binds comes from the settings file (`server/config.js`): local mode\n// is 127.0.0.1 and nothing else, self-hosted mode binds what it was given and\n// turns on the access-key gate in the router. A bare `createServer()` — what\n// the tests drive — never touches that file.\n\nimport http from 'node:http';\nimport path from 'node:path';\nimport { spawn } from 'node:child_process';\nimport { fileURLToPath } from 'node:url';\n\nimport { createRouter } from './router.js';\nimport { installExitCleanup } from './sessions.js';\nimport { createLogger } from './logger.js';\nimport { createMcpRunner } from './mcp/runner.js';\nimport { browserUrl, configPath, isLoopbackHost, readSettings, serverUrls } from './config.js';\nimport { tunnelStatus } from './tunnel.js';\nimport { pidIsAlive, readPidFile, removePidFile, writePidFile, writeRunInfo, runInfoPath } from './pidfile.js';\nimport { logPath } from './daemon.js';\nimport { panel, row } from './layout.js';\n\nconst logger = createLogger();\n\nconst HERE = path.dirname(fileURLToPath(import.meta.url));\nconst PROJECT_ROOT = path.resolve(HERE, '..');\n\n// The MCP runner is created here, not inside the router, and is put on the config\n// object the router already passes to every handler. Two reasons: exactly one\n// server can be supervised per process (a second button press must be a toggle,\n// not a second child), and a test can supply its own runner in place of this one\n// without the router knowing the difference.\nconst mcp = createMcpRunner({\n onLog: (entry) => logger.info('mcp', entry.line),\n});\n\nexport const CONFIG = {\n projectRoot: PROJECT_ROOT,\n publicDir: path.join(PROJECT_ROOT, 'public'),\n // Served at `/shared/`, which is what lets the browser and the server import\n // the same analyzer. See `static.js` for why that mapping constrains the code.\n sharedDir: path.join(PROJECT_ROOT, 'shared'),\n mcp,\n};\n\n// The tests want a server they can put on an ephemeral port; `npm start` wants\n// one from the settings file. Same router either way.\nexport function createServer(config: typeof CONFIG & { configPath?: string; getSettings?: () => Promise<import('../shared/contracts.js').Settings>; boot?: { host: string; port: number; domain: string; https: boolean } } = CONFIG) {\n const router = createRouter(config);\n return http.createServer((req, res) => {\n const start = Date.now();\n res.on('finish', () => {\n logger.http({ method: req.method, path: req.url, status: res.statusCode, ms: Date.now() - start });\n });\n router(req, res);\n });\n}\n\n// What the terminal shows once the socket is listening. A pure string builder\n// so the CLI prints exactly this too, and so a test can read it.\n//\n// The shape is label/value rows, not prose, because every fact here has to be\n// readable at a glance and copy-pasteable. The one long sentence it used to\n// print — \"self-hosted — the network can reach this; every API call needs the\n// access key\" — wrapped unpredictably at any terminal width and buried the URL\n// people actually need. Now the detail lives on its own row.\nexport function startupBanner(settings, { configFile, columns }: Record<string, any> = {}) {\n const urls = serverUrls(settings);\n const rows = [];\n const add = (label, value) => rows.push([label, value]);\n\n add('URL', urls.local);\n if (urls.network) add('Network', urls.network);\n if (urls.domain) add('Public', urls.domain);\n add('Bind', `${settings.host}:${settings.port}`);\n add('Mode', settings.mode === 'self-hosted'\n ? (isLoopbackHost(settings.host) ? 'self-hosted (loopback — use a tunnel)' : 'self-hosted (network reachable)')\n : 'local (this machine only)');\n\n if (settings.mode === 'self-hosted') {\n add('Auth', settings.accessKey\n ? 'access key required for remote browsers'\n : 'NO ACCESS KEY — every API call is refused');\n }\n if (settings.domain) {\n add('HTTPS', settings.https ? 'Caddy terminates TLS for this domain' : 'off — `onboarder https setup` enables it');\n }\n const tunnels = tunnelStatus(settings);\n for (const name of ['cloudflare', 'tailscale']) {\n const t = tunnels[name];\n if (!t.enabled) continue;\n add('Tunnel', t.installed ? `${name}: ${t.command}` : `${name} enabled, CLI missing — ${t.install}`);\n }\n if (configFile) add('Config', configFile);\n\n // Rendered through the same panel helper the CLI uses, so `node server/index.js`\n // and `onboarder start` cannot drift apart — and so both adapt to the terminal\n // width instead of assuming 80 columns.\n return '\\n' + panel('Onboarder is up', rows.map(([label, value]) => row(label, value)), { columns }) + '\\n';\n}\n\n// Ask the OS to open the app. Best-effort and detached: a missing opener on a\n// headless box is not a reason the server failed to start.\nexport function openInBrowser(url) {\n const cmd = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'cmd' : 'xdg-open';\n const args = process.platform === 'win32' ? ['/c', 'start', '', url] : [url];\n try {\n const child = spawn(cmd, args, { detached: true, stdio: 'ignore' });\n child.on('error', (..._args: unknown[]) => {});\n child.unref();\n } catch {\n /* no opener — the URL is on the screen already */\n }\n}\n\nexport function listenError(error, { host, port, pid = null }: Record<string, any> = {}) {\n if (error?.code === 'EADDRINUSE') {\n const owner = pid ? ` Another Onboarder process is running as PID ${pid}.` : '';\n return new Error(`${host}:${port} is already in use.${owner} Try \\`onboarder status\\`, \\`onboarder stop\\`, or choose another port with \\`onboarder config set port <number>\\`.`, { cause: error });\n }\n if (error?.code === 'EACCES') {\n return new Error(`Cannot bind ${host}:${port}: permission denied. Ports below 1024 normally require elevated privileges; choose a port above 1024.`, { cause: error });\n }\n return error;\n}\n\n// The real boot: read the settings, bind what they say, print the banner.\n// Both `node server/index.js` and `onboarder start` land here.\nexport async function startServer({ configFile = configPath(), openBrowser, log = console.log }: Record<string, any> = {}) {\n const settings = await readSettings(configFile);\n // Environment wins over the file: PORT=8080 npm start has worked since\n // before settings existed, and a flag you can see beats a file you cannot.\n const host = process.env.HOST || settings.host;\n const port = Number(process.env.PORT) || settings.port;\n const live = { ...settings, host, port };\n\n const server = createServer({\n ...CONFIG,\n configPath: configFile,\n getSettings: () => readSettings(configFile),\n boot: { host, port, domain: settings.domain, https: settings.https },\n });\n\n installExitCleanup();\n // The MCP child has to go with the web server. `installExitCleanup` already\n // handles cloned scan directories on SIGINT/SIGTERM; the child is registered\n // on the same process events so a Ctrl-C in the terminal does not leave an\n // orphan holding the port-less stdio pipe open.\n for (const signal of ['SIGINT', 'SIGTERM']) {\n process.on(signal, () => {\n if (mcp.running) mcp.stop().catch((..._args: unknown[]) => {});\n });\n }\n\n try {\n await new Promise<void>((resolve, reject) => {\n server.once('error', reject);\n server.listen(port, host, () => resolve());\n });\n } catch (error) {\n const recorded = readPidFile(configFile);\n throw listenError(error, { host, port, pid: recorded && pidIsAlive(recorded) ? recorded : null });\n }\n\n // The process record is optional by design, so each write is guarded on its\n // own: a read-only config directory must not stop a server that has already\n // bound its port, but a *programming* error here would otherwise be swallowed\n // and look like \"it started, but nothing recorded it\".\n try { writePidFile(configFile); } catch { /* read-only config dir */ }\n // How this process was launched. `background` is a detached child with a log\n // file; `startup` is one the OS supervisor started at login (also with a log\n // file); anything else is a person typing `onboarder start` in a terminal.\n const mode = process.env.ONBOARDER_LAUNCH || (process.env.ONBOARDER_BACKGROUND ? 'background' : 'foreground');\n const logFile = mode === 'foreground' ? null : logPath(configFile);\n try {\n // Beside the pid: how this instance was launched and where its log goes, so\n // `onboarder status` answers \"how long has it been up, and how do I see its\n // output\" from a record instead of guessing.\n writeRunInfo(configFile, {\n mode,\n host,\n port,\n url: serverUrls(live).local,\n log: logFile,\n node: process.version,\n });\n } catch (error) {\n logger.warn('could not write the run record', { file: runInfoPath(configFile), error: error.message });\n }\n server.once('close', () => removePidFile(configFile));\n process.once('exit', () => removePidFile(configFile));\n\n log(startupBanner(live, { configFile }));\n\n const shouldOpen = openBrowser ?? (live.autoOpen && process.stdout.isTTY && !process.env.NO_OPEN);\n // Local requests never need a credential. Remote browsers are sent to the\n // themed login page and exchange the key for an HttpOnly session cookie.\n if (shouldOpen) openInBrowser(browserUrl(live));\n return { server, settings: live, host, port };\n}\n\n// `node server/index.js` listens. Importing this module — which the tests do, to\n// drive the real router — does not, and does not install signal handlers either:\n// a test runner should keep its own Ctrl-C.\nconst THIS_FILE = fileURLToPath(import.meta.url);\nif (process.argv[1] && path.resolve(process.argv[1]) === THIS_FILE) {\n startServer().catch((err) => {\n console.error(' Onboarder did not start: ' + (err.message || err));\n process.exitCode = 1;\n });\n}\n"]}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export type PanelStyle = 'label' | 'frame' | 'hint' | 'title';
|
|
2
|
+
export interface PanelRow {
|
|
3
|
+
label?: string;
|
|
4
|
+
value?: unknown;
|
|
5
|
+
hint?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface PanelOptions {
|
|
8
|
+
indent?: string;
|
|
9
|
+
columns?: number;
|
|
10
|
+
paint?: (text: string, style: PanelStyle) => string;
|
|
11
|
+
}
|
|
12
|
+
export declare function width(text: unknown): number;
|
|
13
|
+
export declare function termWidth(stream?: NodeJS.WriteStream & {
|
|
14
|
+
fd: 1;
|
|
15
|
+
}): number;
|
|
16
|
+
export declare function fit(text: unknown, max: number, { tail }?: {
|
|
17
|
+
tail?: boolean;
|
|
18
|
+
}): string;
|
|
19
|
+
export declare function panel(title: string, rows: PanelRow[], { indent, columns, paint }?: PanelOptions): string;
|
|
20
|
+
export declare const hint: (text: string) => {
|
|
21
|
+
hint: string;
|
|
22
|
+
};
|
|
23
|
+
export declare const row: (label: string, value: unknown) => {
|
|
24
|
+
label: string;
|
|
25
|
+
value: string;
|
|
26
|
+
};
|
package/server/layout.js
CHANGED
|
@@ -9,32 +9,32 @@
|
|
|
9
9
|
// Everything is pure string math on purpose: a layout decision is a thing you
|
|
10
10
|
// want to unit-test without spawning a terminal, and the whole reason panels
|
|
11
11
|
// used to look broken is that this math was scattered through the callers.
|
|
12
|
-
|
|
13
12
|
export function width(text) {
|
|
14
|
-
|
|
13
|
+
return String(text).replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
15
14
|
}
|
|
16
|
-
|
|
17
15
|
// The terminal we are drawing into, asked at call time rather than cached at
|
|
18
16
|
// import: a panel printed after the user resized their window should use the
|
|
19
17
|
// new size. Falls back to COLUMNS, then 80, so a pipe or a log file still gets
|
|
20
18
|
// sane output instead of `undefined`.
|
|
21
19
|
export function termWidth(stream = process.stdout) {
|
|
22
|
-
|
|
20
|
+
return stream.columns || Number(process.env.COLUMNS) || 80;
|
|
23
21
|
}
|
|
24
|
-
|
|
25
22
|
// Shorten to fit, with a real ellipsis. The middle is elided rather than the
|
|
26
23
|
// tail, because in a path or URL the end is the part that identifies it.
|
|
27
24
|
export function fit(text, max, { tail = true } = {}) {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
25
|
+
const value = String(text);
|
|
26
|
+
if (max <= 0)
|
|
27
|
+
return '';
|
|
28
|
+
if (value.length <= max)
|
|
29
|
+
return value;
|
|
30
|
+
if (max === 1)
|
|
31
|
+
return '…';
|
|
32
|
+
if (!tail)
|
|
33
|
+
return value.slice(0, max - 1) + '…';
|
|
34
|
+
const head = Math.ceil((max - 1) * 0.4);
|
|
35
|
+
const rest = max - 1 - head;
|
|
36
|
+
return (head ? value.slice(0, head) + '…' : '…') + value.slice(value.length - rest);
|
|
36
37
|
}
|
|
37
|
-
|
|
38
38
|
// A titled block of rows that fits the terminal it is printed into. The frame is
|
|
39
39
|
// sized from its contents, then clamped to the available width, and every value
|
|
40
40
|
// is elided to fit rather than allowed to spill past the border — a panel whose
|
|
@@ -44,57 +44,52 @@ export function fit(text, max, { tail = true } = {}) {
|
|
|
44
44
|
// 'frame'. The default is identity, which is what the server banner and the
|
|
45
45
|
// `--no-color` path want.
|
|
46
46
|
export function panel(title, rows, { indent = ' ', columns, paint = (t) => String(t) } = {}) {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
const top = paint('┌─ ', 'frame') + paint(shownTitle, 'title')
|
|
91
|
-
+ ' ' + paint('─'.repeat(Math.max(0, inner - shownTitle.length - 3)) + '┐', 'frame');
|
|
92
|
-
const bottom = paint('└' + '─'.repeat(inner) + '┘', 'frame');
|
|
93
|
-
return [indent + top, ...body.map((line) => indent + ' ' + line), indent + bottom].join('\n');
|
|
47
|
+
// The hard ceiling is the terminal, and it is the *only* ceiling. This used to
|
|
48
|
+
// clamp to a 24-column floor "to guard against a nonsensical zero", but a
|
|
49
|
+
// floor above the available width is precisely the overflow this function
|
|
50
|
+
// exists to prevent: in a 10-column terminal it produced 26-column lines and
|
|
51
|
+
// wrapped the border. A terminal can be narrower than a box; below the point
|
|
52
|
+
// where a bordered box is readable at all, the frame degrades to a plain
|
|
53
|
+
// aligned list, which is still honest output instead of a lie about width.
|
|
54
|
+
const total = Math.max(1, Math.floor(columns || termWidth()) - indent.length);
|
|
55
|
+
// A border costs 2 cells on each side; the label column costs `labelWidth`,
|
|
56
|
+
// then a 2-cell gap, then a content indent. Below what is left there is no
|
|
57
|
+
// room for a readable value, so the border is dropped entirely rather than
|
|
58
|
+
// drawn wider than the terminal. The content indent goes too: at a 3-column
|
|
59
|
+
// terminal, indent(2) + content(2) + one character is already 5, and the
|
|
60
|
+
// indent is decoration, not information.
|
|
61
|
+
const borderWorthIt = total >= 16;
|
|
62
|
+
const bodyIndent = total >= 8 ? 2 : 0;
|
|
63
|
+
const labelWidth = borderWorthIt ? Math.min(12, Math.max(...rows.map((r) => width(r.label ?? '')), 0)) : 0;
|
|
64
|
+
const gap = labelWidth ? 2 : 0;
|
|
65
|
+
const valueRoom = Math.max(1, total - (borderWorthIt ? 2 : 0) - labelWidth - gap - bodyIndent);
|
|
66
|
+
const body = rows.map((r) => (r.hint
|
|
67
|
+
? ' '.repeat(labelWidth + gap) + paint(fit(r.hint, valueRoom), 'hint')
|
|
68
|
+
: (labelWidth ? paint(fit(r.label ?? '', labelWidth).padEnd(labelWidth), 'label') + ' '.repeat(gap) : '')
|
|
69
|
+
+ fit(r.value ?? '', valueRoom)));
|
|
70
|
+
if (!borderWorthIt) {
|
|
71
|
+
// No room for a frame. Still fitted, still labeled, just not boxed. The
|
|
72
|
+
// indent goes first — it is decoration, and at a 1–2 column terminal the
|
|
73
|
+
// 2-space indent plus a single content character is already 3, which wraps.
|
|
74
|
+
const lead = ' '.repeat(Math.max(0, Math.min(indent.length, total - 1)));
|
|
75
|
+
return [
|
|
76
|
+
paint(fit(title, total), 'title'),
|
|
77
|
+
...body.map((line) => lead + ' '.repeat(bodyIndent) + fit(line, Math.max(1, total - lead.length - bodyIndent))),
|
|
78
|
+
].join('\n');
|
|
79
|
+
}
|
|
80
|
+
// Frame width = whatever the body needs, capped to what the terminal has.
|
|
81
|
+
// The 2-cell content indent belongs to the body, so it counts toward the
|
|
82
|
+
// width the border has to enclose.
|
|
83
|
+
const wanted = Math.max(...body.map((line) => width(' ' + line)), 1);
|
|
84
|
+
const inner = Math.min(total - 2, Math.max(1, wanted));
|
|
85
|
+
const shownTitle = fit(title, Math.max(0, inner - 4));
|
|
86
|
+
const top = paint('┌─ ', 'frame') + paint(shownTitle, 'title')
|
|
87
|
+
+ ' ' + paint('─'.repeat(Math.max(0, inner - shownTitle.length - 3)) + '┐', 'frame');
|
|
88
|
+
const bottom = paint('└' + '─'.repeat(inner) + '┘', 'frame');
|
|
89
|
+
return [indent + top, ...body.map((line) => indent + ' ' + line), indent + bottom].join('\n');
|
|
94
90
|
}
|
|
95
|
-
|
|
96
91
|
// A one-line note rendered in the panel's muted voice.
|
|
97
92
|
export const hint = (text) => ({ hint: text });
|
|
98
|
-
|
|
99
93
|
// A label/value row. Values are stringified here so a caller can pass a number.
|
|
100
|
-
export const row = (label, value) => ({ label, value: String(value) });
|
|
94
|
+
export const row = (label, value) => ({ label, value: String(value) });
|
|
95
|
+
//# sourceMappingURL=layout.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"layout.js","sourceRoot":"","sources":["../src/server/layout.ts"],"names":[],"mappings":"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","sourcesContent":["export type PanelStyle = 'label' | 'frame' | 'hint' | 'title';\nexport interface PanelRow { label?: string; value?: unknown; hint?: string }\nexport interface PanelOptions { indent?: string; columns?: number; paint?: (text: string, style: PanelStyle) => string }\n// Terminal geometry, with no color and no dependencies.\n//\n// This lives here, under `server/`, because two very different callers need\n// identical layout: the CLI (which paints) and the server's own startup banner\n// (which must not import `cli/ui.js` — that would invert the dependency, since\n// the CLI sits on top of the server). So the *shape* is here and the *color* is\n// injected by the caller as a `paint(text, style)` function.\n//\n// Everything is pure string math on purpose: a layout decision is a thing you\n// want to unit-test without spawning a terminal, and the whole reason panels\n// used to look broken is that this math was scattered through the callers.\n\nexport function width(text: unknown) {\n return String(text).replace(/\\x1b\\[[0-9;]*m/g, '').length;\n}\n\n// The terminal we are drawing into, asked at call time rather than cached at\n// import: a panel printed after the user resized their window should use the\n// new size. Falls back to COLUMNS, then 80, so a pipe or a log file still gets\n// sane output instead of `undefined`.\nexport function termWidth(stream = process.stdout) {\n return stream.columns || Number(process.env.COLUMNS) || 80;\n}\n\n// Shorten to fit, with a real ellipsis. The middle is elided rather than the\n// tail, because in a path or URL the end is the part that identifies it.\nexport function fit(text: unknown, max: number, { tail = true }: { tail?: boolean } = {}) {\n const value = String(text);\n if (max <= 0) return '';\n if (value.length <= max) return value;\n if (max === 1) return '…';\n if (!tail) return value.slice(0, max - 1) + '…';\n const head = Math.ceil((max - 1) * 0.4);\n const rest = max - 1 - head;\n return (head ? value.slice(0, head) + '…' : '…') + value.slice(value.length - rest);\n}\n\n// A titled block of rows that fits the terminal it is printed into. The frame is\n// sized from its contents, then clamped to the available width, and every value\n// is elided to fit rather than allowed to spill past the border — a panel whose\n// right edge is off-screen is what makes output \"mess the terminal\".\n//\n// `paint` is injected: `(text, style) => string`, where style is 'label' or\n// 'frame'. The default is identity, which is what the server banner and the\n// `--no-color` path want.\nexport function panel(title: string, rows: PanelRow[], { indent = ' ', columns, paint = (t) => String(t) }: PanelOptions = {}) {\n // The hard ceiling is the terminal, and it is the *only* ceiling. This used to\n // clamp to a 24-column floor \"to guard against a nonsensical zero\", but a\n // floor above the available width is precisely the overflow this function\n // exists to prevent: in a 10-column terminal it produced 26-column lines and\n // wrapped the border. A terminal can be narrower than a box; below the point\n // where a bordered box is readable at all, the frame degrades to a plain\n // aligned list, which is still honest output instead of a lie about width.\n const total = Math.max(1, Math.floor(columns || termWidth()) - indent.length);\n\n // A border costs 2 cells on each side; the label column costs `labelWidth`,\n // then a 2-cell gap, then a content indent. Below what is left there is no\n // room for a readable value, so the border is dropped entirely rather than\n // drawn wider than the terminal. The content indent goes too: at a 3-column\n // terminal, indent(2) + content(2) + one character is already 5, and the\n // indent is decoration, not information.\n const borderWorthIt = total >= 16;\n const bodyIndent = total >= 8 ? 2 : 0;\n const labelWidth = borderWorthIt ? Math.min(12, Math.max(...rows.map((r) => width(r.label ?? '')), 0)) : 0;\n const gap = labelWidth ? 2 : 0;\n const valueRoom = Math.max(1, total - (borderWorthIt ? 2 : 0) - labelWidth - gap - bodyIndent);\n\n const body = rows.map((r) => (r.hint\n ? ' '.repeat(labelWidth + gap) + paint(fit(r.hint, valueRoom), 'hint')\n : (labelWidth ? paint(fit(r.label ?? '', labelWidth).padEnd(labelWidth), 'label') + ' '.repeat(gap) : '')\n + fit(r.value ?? '', valueRoom)));\n\n if (!borderWorthIt) {\n // No room for a frame. Still fitted, still labeled, just not boxed. The\n // indent goes first — it is decoration, and at a 1–2 column terminal the\n // 2-space indent plus a single content character is already 3, which wraps.\n const lead = ' '.repeat(Math.max(0, Math.min(indent.length, total - 1)));\n return [\n paint(fit(title, total), 'title'),\n ...body.map((line) => lead + ' '.repeat(bodyIndent) + fit(line, Math.max(1, total - lead.length - bodyIndent))),\n ].join('\\n');\n }\n\n // Frame width = whatever the body needs, capped to what the terminal has.\n // The 2-cell content indent belongs to the body, so it counts toward the\n // width the border has to enclose.\n const wanted = Math.max(...body.map((line) => width(' ' + line)), 1);\n const inner = Math.min(total - 2, Math.max(1, wanted));\n const shownTitle = fit(title, Math.max(0, inner - 4));\n const top = paint('┌─ ', 'frame') + paint(shownTitle, 'title')\n + ' ' + paint('─'.repeat(Math.max(0, inner - shownTitle.length - 3)) + '┐', 'frame');\n const bottom = paint('└' + '─'.repeat(inner) + '┘', 'frame');\n return [indent + top, ...body.map((line) => indent + ' ' + line), indent + bottom].join('\\n');\n}\n\n// A one-line note rendered in the panel's muted voice.\nexport const hint = (text: string) => ({ hint: text });\n\n// A label/value row. Values are stringified here so a caller can pass a number.\nexport const row = (label: string, value: unknown) => ({ label, value: String(value) });"]}
|