spexcode 0.6.2 → 0.6.5
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 +3 -1
- package/bin/spex.mjs +4 -0
- package/node_modules/@spexcode/spec-cli/bin/spex.mjs +76 -0
- package/node_modules/@spexcode/spec-cli/dist/attach.d.ts +2 -0
- package/node_modules/@spexcode/spec-cli/dist/attach.js +48 -0
- package/node_modules/@spexcode/spec-cli/dist/claude-headless.d.ts +33 -0
- package/node_modules/@spexcode/spec-cli/dist/claude-headless.js +307 -0
- package/node_modules/@spexcode/spec-cli/dist/cli.d.ts +1 -0
- package/node_modules/@spexcode/spec-cli/dist/cli.js +1859 -0
- package/node_modules/@spexcode/spec-cli/dist/client.d.ts +111 -0
- package/node_modules/@spexcode/spec-cli/dist/client.js +476 -0
- package/node_modules/@spexcode/spec-cli/dist/cockpit.d.ts +7 -0
- package/node_modules/@spexcode/spec-cli/dist/cockpit.js +26 -0
- package/node_modules/@spexcode/spec-cli/dist/codex-headless.d.ts +1 -0
- package/{spec-cli/src/codex-headless.ts → node_modules/@spexcode/spec-cli/dist/codex-headless.js} +3 -9
- package/node_modules/@spexcode/spec-cli/dist/codex-runtime-generations.d.ts +53 -0
- package/node_modules/@spexcode/spec-cli/dist/codex-runtime-generations.js +686 -0
- package/node_modules/@spexcode/spec-cli/dist/commit-surgery.d.ts +1 -0
- package/node_modules/@spexcode/spec-cli/dist/commit-surgery.js +90 -0
- package/node_modules/@spexcode/spec-cli/dist/contract-filter.d.ts +14 -0
- package/node_modules/@spexcode/spec-cli/dist/contract-filter.js +219 -0
- package/node_modules/@spexcode/spec-cli/dist/dashboard-assets.d.ts +6 -0
- package/node_modules/@spexcode/spec-cli/dist/dashboard-assets.js +50 -0
- package/node_modules/@spexcode/spec-cli/dist/delivery-queue.d.ts +23 -0
- package/node_modules/@spexcode/spec-cli/dist/delivery-queue.js +179 -0
- package/node_modules/@spexcode/spec-cli/dist/doctor.d.ts +9 -0
- package/node_modules/@spexcode/spec-cli/dist/doctor.js +547 -0
- package/node_modules/@spexcode/spec-cli/dist/eval-host.d.ts +1 -0
- package/node_modules/@spexcode/spec-cli/dist/eval-host.js +19 -0
- package/node_modules/@spexcode/spec-cli/dist/execution-trace.d.ts +26 -0
- package/node_modules/@spexcode/spec-cli/dist/execution-trace.js +475 -0
- package/node_modules/@spexcode/spec-cli/dist/file-write.d.ts +2 -0
- package/node_modules/@spexcode/spec-cli/dist/file-write.js +26 -0
- package/node_modules/@spexcode/spec-cli/dist/flat.d.ts +89 -0
- package/node_modules/@spexcode/spec-cli/dist/flat.js +1076 -0
- package/node_modules/@spexcode/spec-cli/dist/gateway-auth.d.ts +56 -0
- package/node_modules/@spexcode/spec-cli/dist/gateway-auth.js +168 -0
- package/node_modules/@spexcode/spec-cli/dist/gateway-hub.d.ts +28 -0
- package/node_modules/@spexcode/spec-cli/dist/gateway-hub.js +361 -0
- package/node_modules/@spexcode/spec-cli/dist/gateway.d.ts +37 -0
- package/node_modules/@spexcode/spec-cli/dist/gateway.js +503 -0
- package/node_modules/@spexcode/spec-cli/dist/graphCache.d.ts +27 -0
- package/node_modules/@spexcode/spec-cli/dist/graphCache.js +716 -0
- package/node_modules/@spexcode/spec-cli/dist/graphSnapshot.d.ts +14 -0
- package/node_modules/@spexcode/spec-cli/dist/graphSnapshot.js +35 -0
- package/node_modules/@spexcode/spec-cli/dist/graphStream.d.ts +85 -0
- package/node_modules/@spexcode/spec-cli/dist/graphStream.js +1195 -0
- package/node_modules/@spexcode/spec-cli/dist/guidance-catalog.d.ts +64 -0
- package/node_modules/@spexcode/spec-cli/dist/guidance-catalog.js +166 -0
- package/node_modules/@spexcode/spec-cli/dist/guide.d.ts +8 -0
- package/{spec-cli/src/guide.ts → node_modules/@spexcode/spec-cli/dist/guide.js} +36 -32
- package/node_modules/@spexcode/spec-cli/dist/harness-select.d.ts +18 -0
- package/node_modules/@spexcode/spec-cli/dist/harness-select.js +62 -0
- package/node_modules/@spexcode/spec-cli/dist/harness.d.ts +325 -0
- package/node_modules/@spexcode/spec-cli/dist/harness.js +3026 -0
- package/node_modules/@spexcode/spec-cli/dist/headless-controller.d.ts +9 -0
- package/node_modules/@spexcode/spec-cli/dist/headless-controller.js +47 -0
- package/node_modules/@spexcode/spec-cli/dist/help.d.ts +10 -0
- package/node_modules/@spexcode/spec-cli/dist/help.js +574 -0
- package/node_modules/@spexcode/spec-cli/dist/hook-prompts.d.ts +18 -0
- package/node_modules/@spexcode/spec-cli/dist/hook-prompts.js +122 -0
- package/node_modules/@spexcode/spec-cli/dist/hooks.d.ts +1 -0
- package/{spec-cli/src/hooks.ts → node_modules/@spexcode/spec-cli/dist/hooks.js} +15 -15
- package/node_modules/@spexcode/spec-cli/dist/host-resources.d.ts +97 -0
- package/node_modules/@spexcode/spec-cli/dist/host-resources.js +795 -0
- package/node_modules/@spexcode/spec-cli/dist/host.d.ts +88 -0
- package/node_modules/@spexcode/spec-cli/dist/host.js +663 -0
- package/node_modules/@spexcode/spec-cli/dist/index.d.ts +1 -0
- package/node_modules/@spexcode/spec-cli/dist/index.js +899 -0
- package/node_modules/@spexcode/spec-cli/dist/init.d.ts +1 -0
- package/node_modules/@spexcode/spec-cli/dist/init.js +284 -0
- package/node_modules/@spexcode/spec-cli/dist/issues-cli.d.ts +6 -0
- package/node_modules/@spexcode/spec-cli/dist/issues-cli.js +334 -0
- package/node_modules/@spexcode/spec-cli/dist/issues.d.ts +69 -0
- package/node_modules/@spexcode/spec-cli/dist/issues.js +189 -0
- package/node_modules/@spexcode/spec-cli/dist/lint.d.ts +34 -0
- package/node_modules/@spexcode/spec-cli/dist/lint.js +476 -0
- package/node_modules/@spexcode/spec-cli/dist/listen.d.ts +7 -0
- package/{spec-cli/src/listen.ts → node_modules/@spexcode/spec-cli/dist/listen.js} +13 -17
- package/node_modules/@spexcode/spec-cli/dist/localIssues.d.ts +65 -0
- package/node_modules/@spexcode/spec-cli/dist/localIssues.js +582 -0
- package/node_modules/@spexcode/spec-cli/dist/login-page.d.ts +5 -0
- package/{spec-cli/src/login-page.ts → node_modules/@spexcode/spec-cli/dist/login-page.js} +7 -7
- package/node_modules/@spexcode/spec-cli/dist/loop-in.d.ts +23 -0
- package/{spec-cli/src/loop-in.ts → node_modules/@spexcode/spec-cli/dist/loop-in.js} +35 -45
- package/node_modules/@spexcode/spec-cli/dist/machine-peer.d.ts +69 -0
- package/node_modules/@spexcode/spec-cli/dist/machine-peer.js +603 -0
- package/node_modules/@spexcode/spec-cli/dist/materialize.d.ts +18 -0
- package/node_modules/@spexcode/spec-cli/dist/materialize.js +468 -0
- package/node_modules/@spexcode/spec-cli/dist/mentions.d.ts +50 -0
- package/node_modules/@spexcode/spec-cli/dist/mentions.js +117 -0
- package/node_modules/@spexcode/spec-cli/dist/opencode-headless.d.ts +4 -0
- package/node_modules/@spexcode/spec-cli/dist/opencode-headless.js +208 -0
- package/node_modules/@spexcode/spec-cli/dist/opencode.d.ts +3 -0
- package/{spec-cli/src/opencode.ts → node_modules/@spexcode/spec-cli/dist/opencode.js} +9 -13
- package/node_modules/@spexcode/spec-cli/dist/pi-harness.d.ts +3 -0
- package/{spec-cli/src/pi-harness.ts → node_modules/@spexcode/spec-cli/dist/pi-harness.js} +45 -40
- package/node_modules/@spexcode/spec-cli/dist/pi-headless.d.ts +25 -0
- package/node_modules/@spexcode/spec-cli/dist/pi-headless.js +196 -0
- package/node_modules/@spexcode/spec-cli/dist/plugin-harness.d.ts +21 -0
- package/node_modules/@spexcode/spec-cli/dist/plugin-harness.js +146 -0
- package/node_modules/@spexcode/spec-cli/dist/pty-bridge.d.ts +10 -0
- package/node_modules/@spexcode/spec-cli/dist/pty-bridge.js +404 -0
- package/node_modules/@spexcode/spec-cli/dist/pty-helper.d.mts +1 -0
- package/node_modules/@spexcode/spec-cli/dist/pty-helper.mjs +101 -0
- package/node_modules/@spexcode/spec-cli/dist/pty-native-helper.d.mts +2 -0
- package/node_modules/@spexcode/spec-cli/dist/pty-native-helper.mjs +23 -0
- package/node_modules/@spexcode/spec-cli/dist/public-graph.d.ts +46 -0
- package/node_modules/@spexcode/spec-cli/dist/public-graph.js +56 -0
- package/node_modules/@spexcode/spec-cli/dist/ranker.d.ts +15 -0
- package/node_modules/@spexcode/spec-cli/dist/ranker.js +188 -0
- package/node_modules/@spexcode/spec-cli/dist/reaper.d.ts +6 -0
- package/node_modules/@spexcode/spec-cli/dist/reaper.js +81 -0
- package/node_modules/@spexcode/spec-cli/dist/reviews.d.ts +189 -0
- package/node_modules/@spexcode/spec-cli/dist/reviews.js +350 -0
- package/node_modules/@spexcode/spec-cli/dist/runtime-guard.d.ts +6 -0
- package/node_modules/@spexcode/spec-cli/dist/runtime-guard.js +37 -0
- package/node_modules/@spexcode/spec-cli/dist/runtime-ownership.d.ts +21 -0
- package/node_modules/@spexcode/spec-cli/dist/runtime-ownership.js +84 -0
- package/node_modules/@spexcode/spec-cli/dist/search.bench.d.mts +1 -0
- package/node_modules/@spexcode/spec-cli/dist/search.bench.mjs +75 -0
- package/node_modules/@spexcode/spec-cli/dist/search.d.ts +20 -0
- package/node_modules/@spexcode/spec-cli/dist/search.js +61 -0
- package/node_modules/@spexcode/spec-cli/dist/session-cursors.d.ts +14 -0
- package/node_modules/@spexcode/spec-cli/dist/session-cursors.js +82 -0
- package/node_modules/@spexcode/spec-cli/dist/session-declarations.d.ts +13 -0
- package/node_modules/@spexcode/spec-cli/dist/session-declarations.js +126 -0
- package/node_modules/@spexcode/spec-cli/dist/session-execution.d.ts +10 -0
- package/node_modules/@spexcode/spec-cli/dist/session-execution.js +70 -0
- package/node_modules/@spexcode/spec-cli/dist/session-files.d.ts +28 -0
- package/node_modules/@spexcode/spec-cli/dist/session-files.js +130 -0
- package/node_modules/@spexcode/spec-cli/dist/session-follow.d.ts +31 -0
- package/node_modules/@spexcode/spec-cli/dist/session-follow.js +179 -0
- package/node_modules/@spexcode/spec-cli/dist/session-reparent.d.ts +10 -0
- package/node_modules/@spexcode/spec-cli/dist/session-reparent.js +33 -0
- package/node_modules/@spexcode/spec-cli/dist/session-timeline.d.ts +51 -0
- package/node_modules/@spexcode/spec-cli/dist/session-timeline.js +233 -0
- package/node_modules/@spexcode/spec-cli/dist/session-web.d.ts +24 -0
- package/node_modules/@spexcode/spec-cli/dist/session-web.js +141 -0
- package/node_modules/@spexcode/spec-cli/dist/sessions.d.ts +412 -0
- package/node_modules/@spexcode/spec-cli/dist/sessions.js +4239 -0
- package/node_modules/@spexcode/spec-cli/dist/sh.d.ts +1 -0
- package/node_modules/@spexcode/spec-cli/dist/sh.js +3 -0
- package/node_modules/@spexcode/spec-cli/dist/shim-runtime.d.ts +1 -0
- package/{spec-cli/src/shim-runtime.ts → node_modules/@spexcode/spec-cli/dist/shim-runtime.js} +3 -4
- package/node_modules/@spexcode/spec-cli/dist/slash-commands.d.ts +9 -0
- package/node_modules/@spexcode/spec-cli/dist/slash-commands.js +307 -0
- package/node_modules/@spexcode/spec-cli/dist/source-files.d.ts +9 -0
- package/node_modules/@spexcode/spec-cli/dist/source-files.js +113 -0
- package/node_modules/@spexcode/spec-cli/dist/supervise.d.ts +1 -0
- package/node_modules/@spexcode/spec-cli/dist/supervise.js +308 -0
- package/node_modules/@spexcode/spec-cli/dist/tree.d.ts +25 -0
- package/node_modules/@spexcode/spec-cli/dist/tree.js +98 -0
- package/node_modules/@spexcode/spec-cli/dist/tsx-bin.d.ts +3 -0
- package/node_modules/@spexcode/spec-cli/dist/tsx-bin.js +41 -0
- package/node_modules/@spexcode/spec-cli/dist/uninstall.d.ts +3 -0
- package/node_modules/@spexcode/spec-cli/dist/uninstall.js +177 -0
- package/node_modules/@spexcode/spec-cli/dist/uploads.d.ts +36 -0
- package/node_modules/@spexcode/spec-cli/dist/uploads.js +243 -0
- package/node_modules/@spexcode/spec-cli/dist/worktree-sources.d.ts +1 -0
- package/node_modules/@spexcode/spec-cli/dist/worktree-sources.js +47 -0
- package/{spec-cli → node_modules/@spexcode/spec-cli}/hooks/dispatch.sh +5 -5
- package/{spec-cli → node_modules/@spexcode/spec-cli}/hooks/harness.sh +1 -1
- package/node_modules/@spexcode/spec-cli/package.json +47 -0
- package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/commands/supervisor/spec.md +8 -0
- package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/spec-first/spec-first.sh +1 -1
- package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/spec-of-file/spec-of-file.sh +1 -0
- package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/stop-gate/spec.md +2 -2
- package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/stop-gate/stop-gate.sh +31 -29
- package/node_modules/@spexcode/spec-core/dist/anchors.d.ts +94 -0
- package/node_modules/@spexcode/spec-core/dist/anchors.js +730 -0
- package/node_modules/@spexcode/spec-core/dist/git.d.ts +166 -0
- package/node_modules/@spexcode/spec-core/dist/git.js +2736 -0
- package/node_modules/@spexcode/spec-core/dist/graph-delta.d.ts +44 -0
- package/node_modules/@spexcode/spec-core/dist/graph-delta.js +72 -0
- package/node_modules/@spexcode/spec-core/dist/graph.d.ts +34 -0
- package/node_modules/@spexcode/spec-core/dist/graph.js +237 -0
- package/node_modules/@spexcode/spec-core/dist/graphDelta.d.ts +3 -0
- package/node_modules/@spexcode/spec-core/dist/graphDelta.js +13 -0
- package/node_modules/@spexcode/spec-core/dist/harness-identity.d.ts +31 -0
- package/node_modules/@spexcode/spec-core/dist/harness-identity.js +20 -0
- package/node_modules/@spexcode/spec-core/dist/identity-presets.d.ts +152 -0
- package/node_modules/@spexcode/spec-core/dist/identity-presets.js +132 -0
- package/node_modules/@spexcode/spec-core/dist/index.d.ts +45 -0
- package/node_modules/@spexcode/spec-core/dist/index.js +19 -0
- package/node_modules/@spexcode/spec-core/dist/layout.d.ts +183 -0
- package/node_modules/@spexcode/spec-core/dist/layout.js +548 -0
- package/node_modules/@spexcode/spec-core/dist/process-identity.d.ts +37 -0
- package/node_modules/@spexcode/spec-core/dist/process-identity.js +214 -0
- package/node_modules/@spexcode/spec-core/dist/project-identity.d.ts +12 -0
- package/node_modules/@spexcode/spec-core/dist/project-identity.js +71 -0
- package/node_modules/@spexcode/spec-core/dist/project-store.d.ts +3 -0
- package/node_modules/@spexcode/spec-core/dist/project-store.js +14 -0
- package/node_modules/@spexcode/spec-core/dist/resilience.d.ts +2 -0
- package/node_modules/@spexcode/spec-core/dist/resilience.js +40 -0
- package/node_modules/@spexcode/spec-core/dist/review/index.d.ts +3 -0
- package/node_modules/@spexcode/spec-core/dist/review/index.js +5 -0
- package/node_modules/@spexcode/spec-core/dist/review/reviewFilters.d.ts +77 -0
- package/node_modules/@spexcode/spec-core/dist/review/reviewFilters.js +308 -0
- package/node_modules/@spexcode/spec-core/dist/review/reviewQuery.d.ts +66 -0
- package/node_modules/@spexcode/spec-core/dist/review/reviewQuery.js +180 -0
- package/node_modules/@spexcode/spec-core/dist/review/session.d.ts +4 -0
- package/node_modules/@spexcode/spec-core/dist/review/session.js +8 -0
- package/node_modules/@spexcode/spec-core/dist/reviewSnapshot.d.ts +15 -0
- package/node_modules/@spexcode/spec-core/dist/reviewSnapshot.js +12 -0
- package/node_modules/@spexcode/spec-core/dist/root-lru.d.ts +4 -0
- package/{spec-cli/src/root-lru.ts → node_modules/@spexcode/spec-core/dist/root-lru.js} +26 -30
- package/node_modules/@spexcode/spec-core/dist/specs.d.ts +117 -0
- package/node_modules/@spexcode/spec-core/dist/specs.js +489 -0
- package/node_modules/@spexcode/spec-core/package.json +28 -0
- package/node_modules/@spexcode/spec-eval/dist/cache.d.ts +10 -0
- package/node_modules/@spexcode/spec-eval/dist/cache.js +50 -0
- package/node_modules/@spexcode/spec-eval/dist/cli.d.ts +22 -0
- package/node_modules/@spexcode/spec-eval/dist/cli.js +981 -0
- package/node_modules/@spexcode/spec-eval/dist/evaltab.d.ts +98 -0
- package/node_modules/@spexcode/spec-eval/dist/evaltab.js +176 -0
- package/node_modules/@spexcode/spec-eval/dist/filing.d.ts +16 -0
- package/node_modules/@spexcode/spec-eval/dist/filing.js +36 -0
- package/node_modules/@spexcode/spec-eval/dist/freshness.d.ts +48 -0
- package/node_modules/@spexcode/spec-eval/dist/freshness.js +799 -0
- package/node_modules/@spexcode/spec-eval/dist/host.d.ts +46 -0
- package/node_modules/@spexcode/spec-eval/dist/host.js +53 -0
- package/node_modules/@spexcode/spec-eval/dist/humanok.d.ts +11 -0
- package/node_modules/@spexcode/spec-eval/dist/humanok.js +27 -0
- package/node_modules/@spexcode/spec-eval/dist/index.d.ts +4 -0
- package/node_modules/@spexcode/spec-eval/dist/index.js +4 -0
- package/node_modules/@spexcode/spec-eval/dist/remarks.d.ts +31 -0
- package/node_modules/@spexcode/spec-eval/dist/remarks.js +1 -0
- package/node_modules/@spexcode/spec-eval/dist/scenariofresh.d.ts +13 -0
- package/node_modules/@spexcode/spec-eval/dist/scenariofresh.js +303 -0
- package/node_modules/@spexcode/spec-eval/dist/scenarios.d.ts +99 -0
- package/node_modules/@spexcode/spec-eval/dist/scenarios.js +650 -0
- package/node_modules/@spexcode/spec-eval/dist/sessioneval.d.ts +307 -0
- package/node_modules/@spexcode/spec-eval/dist/sessioneval.js +1883 -0
- package/node_modules/@spexcode/spec-eval/dist/sidecar.d.ts +55 -0
- package/node_modules/@spexcode/spec-eval/dist/sidecar.js +82 -0
- package/node_modules/@spexcode/spec-eval/dist/timeline.d.ts +25 -0
- package/node_modules/@spexcode/spec-eval/dist/timeline.js +65 -0
- package/node_modules/@spexcode/spec-eval/dist/ui-path.d.ts +1 -0
- package/node_modules/@spexcode/spec-eval/dist/ui-path.js +2 -0
- package/node_modules/@spexcode/spec-eval/package.json +36 -0
- package/node_modules/@spexcode/spec-forge/dist/cache.d.ts +29 -0
- package/node_modules/@spexcode/spec-forge/dist/cache.js +59 -0
- package/node_modules/@spexcode/spec-forge/dist/cli.d.ts +1 -0
- package/node_modules/@spexcode/spec-forge/dist/cli.js +100 -0
- package/node_modules/@spexcode/spec-forge/dist/drivers/github.d.ts +2 -0
- package/node_modules/@spexcode/spec-forge/dist/drivers/github.js +116 -0
- package/node_modules/@spexcode/spec-forge/dist/drivers/gitlab.d.ts +7 -0
- package/node_modules/@spexcode/spec-forge/dist/drivers/gitlab.js +144 -0
- package/node_modules/@spexcode/spec-forge/dist/drivers.d.ts +10 -0
- package/node_modules/@spexcode/spec-forge/dist/drivers.js +83 -0
- package/node_modules/@spexcode/spec-forge/dist/index.d.ts +4 -0
- package/node_modules/@spexcode/spec-forge/dist/index.js +4 -0
- package/node_modules/@spexcode/spec-forge/dist/links.d.ts +15 -0
- package/node_modules/@spexcode/spec-forge/dist/links.js +65 -0
- package/node_modules/@spexcode/spec-forge/dist/needs-eval.d.ts +10 -0
- package/node_modules/@spexcode/spec-forge/dist/needs-eval.js +20 -0
- package/node_modules/@spexcode/spec-forge/dist/port.d.ts +53 -0
- package/node_modules/@spexcode/spec-forge/dist/port.js +1 -0
- package/node_modules/@spexcode/spec-forge/dist/resident.d.ts +7 -0
- package/node_modules/@spexcode/spec-forge/dist/resident.js +43 -0
- package/node_modules/@spexcode/spec-forge/package.json +32 -0
- package/package.json +33 -19
- package/spec-cli/bin/spex.mjs +0 -69
- package/spec-cli/src/anchors.ts +0 -728
- package/spec-cli/src/attach.ts +0 -43
- package/spec-cli/src/claude-headless.ts +0 -321
- package/spec-cli/src/cli.ts +0 -1258
- package/spec-cli/src/client.ts +0 -414
- package/spec-cli/src/cockpit.ts +0 -43
- package/spec-cli/src/codex-runtime-generations.ts +0 -646
- package/spec-cli/src/commit-surgery.ts +0 -68
- package/spec-cli/src/contract-filter.ts +0 -202
- package/spec-cli/src/delivery-queue.ts +0 -165
- package/spec-cli/src/doctor.ts +0 -516
- package/spec-cli/src/execution-trace.ts +0 -444
- package/spec-cli/src/file-write.ts +0 -22
- package/spec-cli/src/gateway-auth.ts +0 -181
- package/spec-cli/src/gateway-hub.ts +0 -358
- package/spec-cli/src/gateway.ts +0 -461
- package/spec-cli/src/git.ts +0 -2556
- package/spec-cli/src/graph.ts +0 -271
- package/spec-cli/src/graphCache.ts +0 -710
- package/spec-cli/src/graphDelta.ts +0 -77
- package/spec-cli/src/graphStream.ts +0 -1019
- package/spec-cli/src/harness-select.ts +0 -77
- package/spec-cli/src/harness.ts +0 -3025
- package/spec-cli/src/headless-controller.ts +0 -63
- package/spec-cli/src/help.ts +0 -492
- package/spec-cli/src/host-resources.ts +0 -762
- package/spec-cli/src/host.ts +0 -611
- package/spec-cli/src/identity-presets.d.ts +0 -13
- package/spec-cli/src/identity-presets.js +0 -138
- package/spec-cli/src/index.ts +0 -818
- package/spec-cli/src/init.ts +0 -258
- package/spec-cli/src/issues-cli.ts +0 -276
- package/spec-cli/src/issues.ts +0 -237
- package/spec-cli/src/layout.ts +0 -640
- package/spec-cli/src/lint.ts +0 -488
- package/spec-cli/src/localIssues.ts +0 -538
- package/spec-cli/src/materialize.ts +0 -417
- package/spec-cli/src/mentions.ts +0 -81
- package/spec-cli/src/opencode-headless.ts +0 -205
- package/spec-cli/src/pi-headless.ts +0 -153
- package/spec-cli/src/plugin-harness.ts +0 -157
- package/spec-cli/src/process-identity.ts +0 -207
- package/spec-cli/src/project-identity.ts +0 -73
- package/spec-cli/src/project-store.ts +0 -17
- package/spec-cli/src/pty-bridge.ts +0 -396
- package/spec-cli/src/pty-helper.mjs +0 -96
- package/spec-cli/src/pty-native-helper.mjs +0 -22
- package/spec-cli/src/ranker.ts +0 -191
- package/spec-cli/src/reaper.ts +0 -125
- package/spec-cli/src/resilience.ts +0 -41
- package/spec-cli/src/reviewSnapshot.ts +0 -28
- package/spec-cli/src/reviews.ts +0 -434
- package/spec-cli/src/runtime-guard.ts +0 -35
- package/spec-cli/src/runtime-ownership.ts +0 -88
- package/spec-cli/src/search.bench.mjs +0 -80
- package/spec-cli/src/search.ts +0 -63
- package/spec-cli/src/session-cursors.ts +0 -91
- package/spec-cli/src/session-execution.ts +0 -68
- package/spec-cli/src/session-files.ts +0 -121
- package/spec-cli/src/session-follow.ts +0 -182
- package/spec-cli/src/session-reparent.ts +0 -33
- package/spec-cli/src/session-timeline.ts +0 -247
- package/spec-cli/src/session-web.ts +0 -135
- package/spec-cli/src/sessions.ts +0 -3883
- package/spec-cli/src/sh.ts +0 -3
- package/spec-cli/src/slash-commands.ts +0 -318
- package/spec-cli/src/source-files.ts +0 -104
- package/spec-cli/src/specs.ts +0 -498
- package/spec-cli/src/supervise.ts +0 -245
- package/spec-cli/src/tree.ts +0 -118
- package/spec-cli/src/tsx-bin.ts +0 -18
- package/spec-cli/src/uninstall.ts +0 -171
- package/spec-cli/src/uploads.ts +0 -253
- package/spec-cli/src/worktree-sources.ts +0 -42
- package/spec-cli/templates/spec/project/.plugins/commands/supervisor/spec.md +0 -8
- package/spec-dashboard/dist/assets/App-B6IbFbGu.js +0 -2
- package/spec-dashboard/dist/assets/Dashboard-C5ap-Sga.css +0 -1
- package/spec-dashboard/dist/assets/Dashboard-_sDk7Rcg.js +0 -27
- package/spec-dashboard/dist/assets/EvalsPage-BT357Lyt.js +0 -2
- package/spec-dashboard/dist/assets/IssuesPage-BG9rNOgV.js +0 -1
- package/spec-dashboard/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/spec-dashboard/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/spec-dashboard/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/spec-dashboard/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/spec-dashboard/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/spec-dashboard/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/spec-dashboard/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/spec-dashboard/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/spec-dashboard/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/spec-dashboard/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/spec-dashboard/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/spec-dashboard/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/spec-dashboard/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/spec-dashboard/dist/assets/MobileApp-D9yPMR3A.js +0 -2
- package/spec-dashboard/dist/assets/Modal-B3EcgeA3.js +0 -1
- package/spec-dashboard/dist/assets/PageScroll-CNZOugWV.js +0 -1
- package/spec-dashboard/dist/assets/ProjectsPage-C2CRHmvs.js +0 -1
- package/spec-dashboard/dist/assets/SessionInterface-6GBZ9nXN.css +0 -32
- package/spec-dashboard/dist/assets/SessionInterface-DOEjuT3l.js +0 -40
- package/spec-dashboard/dist/assets/SessionWindow-1nYgZ4CL.js +0 -1
- package/spec-dashboard/dist/assets/Settings-7WeTJn0V.js +0 -1
- package/spec-dashboard/dist/assets/Thread-CEbzAFQn.js +0 -13
- package/spec-dashboard/dist/assets/bindings-BC9vqpYU.js +0 -1
- package/spec-dashboard/dist/assets/data-SlvxNA2r.js +0 -1
- package/spec-dashboard/dist/assets/index-DLVeOHL-.js +0 -41
- package/spec-dashboard/dist/assets/index-DYLGfPjx.css +0 -1
- package/spec-dashboard/dist/assets/launch-Cp579UoJ.css +0 -1
- package/spec-dashboard/dist/assets/launch-yxCYr64x.js +0 -276
- package/spec-dashboard/dist/assets/sessionSurface--7l69h4F.js +0 -1
- package/spec-dashboard/dist/index.html +0 -29
- package/spec-dashboard/src/reviewFilters.js +0 -322
- package/spec-dashboard/src/reviewQuery.js +0 -174
- package/spec-dashboard/src/session.js +0 -199
- package/spec-eval/src/cache.ts +0 -57
- package/spec-eval/src/cli.ts +0 -894
- package/spec-eval/src/evaltab.ts +0 -264
- package/spec-eval/src/filing.ts +0 -39
- package/spec-eval/src/freshness.ts +0 -836
- package/spec-eval/src/humanok.ts +0 -28
- package/spec-eval/src/scenariofresh.ts +0 -244
- package/spec-eval/src/scenarios.ts +0 -672
- package/spec-eval/src/sessioneval.ts +0 -2220
- package/spec-eval/src/sidecar.ts +0 -93
- package/spec-eval/src/timeline.ts +0 -54
- package/spec-forge/src/__fixtures__/github-forge.json +0 -63
- package/spec-forge/src/cache.ts +0 -66
- package/spec-forge/src/cli.ts +0 -99
- package/spec-forge/src/drivers/github.ts +0 -132
- package/spec-forge/src/drivers/gitlab.ts +0 -153
- package/spec-forge/src/drivers.ts +0 -73
- package/spec-forge/src/links.ts +0 -72
- package/spec-forge/src/needs-eval.ts +0 -29
- package/spec-forge/src/port.ts +0 -36
- package/spec-forge/src/resident.ts +0 -46
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/README.md +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/hooks/compat/mark-active-0.5.2-eef1.fixture +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/hooks/compat/mark-active-sed-v0.fixture +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/hooks/commit-msg +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/hooks/post-checkout +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/hooks/post-merge +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/hooks/pre-commit +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/hooks/prepare-commit-msg +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/hooks/reference-transaction +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/commands/extract/spec.md +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/commands/regroup/spec.md +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/commands/rename/spec.md +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/commands/spec.md +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/commands/tidy/spec.md +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/comment-altitude/spec.md +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/idle/idle.sh +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/idle/spec.md +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/mark-active/mark-active.sh +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/mark-active/spec.md +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/session-fail/fail.sh +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/session-fail/spec.md +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/spec-first/spec.md +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/spec-of-file/spec.md +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/spec.md +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/prompts/atomic-landing/spec.md +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/prompts/forge-link/spec.md +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/prompts/memory-hygiene/spec.md +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/prompts/reproduce-before-fix/spec.md +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/prompts/spec.md +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/skills/distill/digest.mjs +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/skills/distill/spec.md +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/skills/spec.md +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/spec.md +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/spec.md +0 -0
- /package/{spec-cli → node_modules/@spexcode/spec-core}/templates/spexcode.json +0 -0
package/spec-cli/src/gateway.ts
DELETED
|
@@ -1,461 +0,0 @@
|
|
|
1
|
-
import http from 'node:http'
|
|
2
|
-
import https from 'node:https'
|
|
3
|
-
import net from 'node:net'
|
|
4
|
-
import type { Duplex } from 'node:stream'
|
|
5
|
-
import { createHmac, timingSafeEqual } from 'node:crypto'
|
|
6
|
-
import { execFileSync, spawnSync } from 'node:child_process'
|
|
7
|
-
import { readFileSync, existsSync, mkdirSync, writeFileSync, statSync } from 'node:fs'
|
|
8
|
-
import { gzipSync, createGzip } from 'node:zlib'
|
|
9
|
-
import { join, normalize, extname } from 'node:path'
|
|
10
|
-
import { fileURLToPath } from 'node:url'
|
|
11
|
-
import { homedir } from 'node:os'
|
|
12
|
-
import { loginPage } from './login-page.js'
|
|
13
|
-
import { listenOrExit } from './listen.js'
|
|
14
|
-
import { installConnectionReaper } from './reaper.js'
|
|
15
|
-
import { postedSessionWeb, SessionWebError } from './session-web.js'
|
|
16
|
-
|
|
17
|
-
export type PublicConfig = { password: string; tls: { cert: string; key: string } | null }
|
|
18
|
-
function argFlag(name: string): string | undefined {
|
|
19
|
-
const i = process.argv.indexOf(`--${name}`)
|
|
20
|
-
return i >= 0 ? process.argv[i + 1] : undefined
|
|
21
|
-
}
|
|
22
|
-
const argHas = (name: string) => process.argv.includes(`--${name}`)
|
|
23
|
-
|
|
24
|
-
export function resolvePublicConfig(repoRoot: string): PublicConfig | null {
|
|
25
|
-
let fileCfg: any = {}
|
|
26
|
-
// a MISSING spexcode.json is fine (defaults); a MALFORMED one fails LOUD — silently swallowing it would
|
|
27
|
-
// serve the dashboard with the wrong public/TLS posture, the opposite of what the file says.
|
|
28
|
-
try { fileCfg = JSON.parse(readFileSync(join(repoRoot, 'spexcode.json'), 'utf8'))?.serve?.public ?? {} }
|
|
29
|
-
catch (e) { if ((e as NodeJS.ErrnoException).code !== 'ENOENT') throw new Error(`spexcode.json is malformed (cannot resolve public-mode config): ${(e as Error).message}`) }
|
|
30
|
-
const enabled = argHas('public') || process.env.SPEXCODE_PUBLIC === '1' || fileCfg?.enabled === true
|
|
31
|
-
if (!enabled) return null
|
|
32
|
-
|
|
33
|
-
// the gate is OPT-IN: a password (flag/env only — never spexcode.json) makes the login appear; WITHOUT one
|
|
34
|
-
// the dashboard is served OPEN. That is loud-warned, not refused — open public access drives the agents, so
|
|
35
|
-
// anyone who reaches the URL has them. The caller (you) chooses; we never silently gate or silently expose.
|
|
36
|
-
const password = argFlag('password') ?? process.env.SPEXCODE_PASSWORD ?? ''
|
|
37
|
-
if (!password) console.error('⚠ spex serve --public with NO password: the dashboard is OPEN — anyone who reaches it controls the agents. Add --password <pw> / SPEXCODE_PASSWORD to require a login.')
|
|
38
|
-
|
|
39
|
-
// --http: knowingly drop TLS. Loud, because the password then crosses the wire in clear and secure-context
|
|
40
|
-
// browser features (clipboard) break. Anything else resolves a cert; absent any source → self-signed.
|
|
41
|
-
if (argHas('http') || fileCfg?.http === true) {
|
|
42
|
-
console.error('⚠ spex serve --public --http: TLS is OFF. The password travels in CLEARTEXT and clipboard/secure-context features will not work. Use this only on a trusted path.')
|
|
43
|
-
return { password, tls: null }
|
|
44
|
-
}
|
|
45
|
-
const certPath = argFlag('tls-cert') ?? process.env.SPEXCODE_TLS_CERT ?? fileCfg?.tls?.cert
|
|
46
|
-
const keyPath = argFlag('tls-key') ?? process.env.SPEXCODE_TLS_KEY ?? fileCfg?.tls?.key
|
|
47
|
-
if (certPath || keyPath) {
|
|
48
|
-
if (!certPath || !keyPath) { console.error('spex serve --public: --tls-cert and --tls-key must be given together.'); process.exit(1) }
|
|
49
|
-
for (const [label, p] of [['cert', certPath], ['key', keyPath]] as const) {
|
|
50
|
-
if (!existsSync(p)) { console.error(`spex serve --public: TLS ${label} file not found: ${p} — fix the path, or omit both for a self-signed cert, or use --http.`); process.exit(1) }
|
|
51
|
-
}
|
|
52
|
-
return { password, tls: { cert: readFileSync(certPath, 'utf8'), key: readFileSync(keyPath, 'utf8') } }
|
|
53
|
-
}
|
|
54
|
-
return { password, tls: selfSignedCert() }
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// @@@ self-signed default - generated ONCE via openssl into ~/.spexcode/tls and reused, so a visitor accepts
|
|
58
|
-
// the cert only once (not on every restart). openssl is near-universal on Linux/macOS; if it is genuinely
|
|
59
|
-
// absent we FAIL LOUD with the three repair paths rather than silently dropping to plaintext. Web PKI will
|
|
60
|
-
// not issue a browser-trusted cert for a bare IP, so this cert is untrusted by construction — the visitor's
|
|
61
|
-
// one-time "proceed" is the price of needing no domain, not a bug.
|
|
62
|
-
function selfSignedCert(): { cert: string; key: string } {
|
|
63
|
-
const dir = join(homedir(), '.spexcode', 'tls')
|
|
64
|
-
const certFile = join(dir, 'self-signed.cert.pem'), keyFile = join(dir, 'self-signed.key.pem')
|
|
65
|
-
if (!existsSync(certFile) || !existsSync(keyFile)) {
|
|
66
|
-
if (spawnSync('openssl', ['version']).status !== 0) {
|
|
67
|
-
console.error('spex serve --public: openssl not found, so a self-signed cert cannot be generated. Install openssl, OR pass --tls-cert/--tls-key with your own cert, OR use --http (no TLS).')
|
|
68
|
-
process.exit(1)
|
|
69
|
-
}
|
|
70
|
-
mkdirSync(dir, { recursive: true })
|
|
71
|
-
console.log('[gateway] generating a self-signed TLS cert (one-time) → ' + dir)
|
|
72
|
-
execFileSync('openssl', ['req', '-x509', '-newkey', 'rsa:2048', '-nodes', '-keyout', keyFile, '-out', certFile,
|
|
73
|
-
'-days', '3650', '-subj', '/CN=spexcode', '-addext', 'subjectAltName=DNS:localhost,IP:127.0.0.1'], { stdio: 'ignore' })
|
|
74
|
-
}
|
|
75
|
-
return { cert: readFileSync(certFile, 'utf8'), key: readFileSync(keyFile, 'utf8') }
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const COOKIE = 'spex_auth'
|
|
79
|
-
function authToken(password: string): string {
|
|
80
|
-
const secret = createHmac('sha256', password).update('spexcode-public-gateway-v1').digest()
|
|
81
|
-
return createHmac('sha256', secret).update('authed').digest('base64url')
|
|
82
|
-
}
|
|
83
|
-
function constEq(a: string, b: string): boolean {
|
|
84
|
-
const ab = Buffer.from(a), bb = Buffer.from(b)
|
|
85
|
-
return ab.length === bb.length && timingSafeEqual(ab, bb)
|
|
86
|
-
}
|
|
87
|
-
function cookieOf(header: string | undefined, name: string): string | null {
|
|
88
|
-
for (const part of (header ?? '').split(';')) {
|
|
89
|
-
const eq = part.indexOf('=')
|
|
90
|
-
if (eq > 0 && part.slice(0, eq).trim() === name) return decodeURIComponent(part.slice(eq + 1).trim())
|
|
91
|
-
}
|
|
92
|
-
return null
|
|
93
|
-
}
|
|
94
|
-
function isAuthed(req: http.IncomingMessage, token: string, cookieName: string): boolean {
|
|
95
|
-
const c = cookieOf(req.headers.cookie, cookieName)
|
|
96
|
-
return c != null && constEq(c, token)
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
const MIME: Record<string, string> = { '.html': 'text/html; charset=utf-8', '.js': 'text/javascript', '.css': 'text/css', '.json': 'application/json', '.svg': 'image/svg+xml', '.png': 'image/png', '.ico': 'image/x-icon', '.woff2': 'font/woff2', '.map': 'application/json' }
|
|
100
|
-
|
|
101
|
-
// @@@ resolveDistDir - where the built dashboard lives, bundled-or-monorepo. In an INSTALLED `spexcode`
|
|
102
|
-
// package the dist rides inside it (prepublish copies spec-dashboard's build to <pkg>/dashboard-dist);
|
|
103
|
-
// in the dogfood monorepo there is no bundled copy, so it falls back to the sibling spec-dashboard/dist.
|
|
104
|
-
// This is the one seam that lets the same gateway code serve from either layout (see [[packaging]]).
|
|
105
|
-
export function resolveDistDir(): string {
|
|
106
|
-
const pkgRoot = fileURLToPath(new URL('..', import.meta.url)) // gateway.ts is in src/ → .. = package root
|
|
107
|
-
const bundled = join(pkgRoot, 'dashboard-dist')
|
|
108
|
-
if (existsSync(join(bundled, 'index.html'))) return bundled
|
|
109
|
-
return join(pkgRoot, '..', 'spec-dashboard', 'dist')
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
export type GatewayOpts = { publicPort: number; upstreamPort: number; password: string; tls: { cert: string; key: string } | null; distDir: string; host?: string; label?: string; onBindFail?: () => void; projectRoot?: string }
|
|
113
|
-
|
|
114
|
-
export function startGateway(opts: GatewayOpts): void {
|
|
115
|
-
// gated ONLY when a password is set; otherwise the login layer doesn't exist and the dashboard is served open.
|
|
116
|
-
const gated = !!opts.password
|
|
117
|
-
const token = gated ? authToken(opts.password) : ''
|
|
118
|
-
const secure = !!opts.tls
|
|
119
|
-
// the auth cookie is HOST-scoped (RFC 6265 ignores the port), so two gateways on one IP would share a
|
|
120
|
-
// single 'spex_auth' jar entry and clobber each other's login. Key the name by the public port — the
|
|
121
|
-
// unique discriminator on a host, exactly what the user's two URLs differ by — so same-host instances
|
|
122
|
-
// (e.g. :8787 and :8788) stay logged in concurrently and a logout clears only its own.
|
|
123
|
-
const cookieName = `${COOKIE}_${opts.publicPort}`
|
|
124
|
-
const setCookie = `${cookieName}=${token}; HttpOnly; Path=/; SameSite=Lax; Max-Age=2592000${secure ? '; Secure' : ''}`
|
|
125
|
-
|
|
126
|
-
const handler = (req: http.IncomingMessage, res: http.ServerResponse) => {
|
|
127
|
-
const url = (req.url || '/').split('?')[0]
|
|
128
|
-
if (gated) {
|
|
129
|
-
// login surface — the only routes reachable without a cookie. Absent entirely when ungated.
|
|
130
|
-
if (url === '/login' && req.method === 'POST') return doLogin(req, res, opts.password, setCookie)
|
|
131
|
-
if (url === '/login') return sendHtml(res, 200, loginPage())
|
|
132
|
-
if (url === '/logout') { res.writeHead(302, { 'Set-Cookie': `${cookieName}=; Path=/; Max-Age=0`, Location: '/login' }); return res.end() }
|
|
133
|
-
if (!isAuthed(req, token, cookieName)) {
|
|
134
|
-
if (url.startsWith('/api')) { res.writeHead(401, { 'Content-Type': 'application/json' }); return res.end('{"error":"authentication required"}') }
|
|
135
|
-
res.writeHead(302, { Location: '/login' }); return res.end()
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
if (proxySessionWeb(req, res, req.url || '/', opts.projectRoot)) return
|
|
139
|
-
if (url.startsWith('/api')) return proxyHttp(req, res, opts.upstreamPort)
|
|
140
|
-
return serveStatic(req, res, opts.distDir, url)
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
const server = secure
|
|
144
|
-
? https.createServer({ cert: opts.tls!.cert, key: opts.tls!.key }, handler)
|
|
145
|
-
: http.createServer(handler)
|
|
146
|
-
installConnectionReaper(server)
|
|
147
|
-
|
|
148
|
-
server.on('upgrade', (req, socket, head) => {
|
|
149
|
-
if (gated && !isAuthed(req, token, cookieName)) { socket.destroy(); return }
|
|
150
|
-
if (proxySessionWebUpgrade(req, socket, head, req.url || '/', opts.projectRoot)) return
|
|
151
|
-
const up = net.connect(opts.upstreamPort, '127.0.0.1', () => {
|
|
152
|
-
up.write(`${req.method} ${req.url} HTTP/1.1\r\n` + rawHeaders(req))
|
|
153
|
-
if (head && head.length) up.write(head)
|
|
154
|
-
socket.pipe(up); up.pipe(socket)
|
|
155
|
-
})
|
|
156
|
-
const bail = () => { socket.destroy(); up.destroy() }
|
|
157
|
-
socket.on('error', bail); up.on('error', bail)
|
|
158
|
-
// pair the halves fully (the supervisor's rule): an http server's sockets are allowHalfOpen, so a bare
|
|
159
|
-
// client FIN ('end') would otherwise leave this side open forever — an upgraded stream never
|
|
160
|
-
// half-closes legitimately, so either half's FIN or close tears down both.
|
|
161
|
-
socket.on('end', bail); up.on('end', bail)
|
|
162
|
-
socket.once('close', () => up.destroy())
|
|
163
|
-
up.once('close', () => socket.destroy())
|
|
164
|
-
})
|
|
165
|
-
|
|
166
|
-
// `spex serve ui` passes an explicit host (loopback by default, --host to widen); `--public` passes
|
|
167
|
-
// none → bind ALL interfaces (the original behaviour, IPv4+IPv6), so adding the local path never
|
|
168
|
-
// narrows the public gateway's reach. The gate note keys on LOOPBACK, not on host-being-explicit:
|
|
169
|
-
// an ungated loopback bind is normal, an ungated wide bind is announced — never silent.
|
|
170
|
-
const isLoopback = opts.host === '127.0.0.1' || opts.host === 'localhost' || opts.host === '::1'
|
|
171
|
-
const onListen = () => {
|
|
172
|
-
const scheme = secure ? 'https' : 'http'
|
|
173
|
-
const label = opts.label ?? 'public mode'
|
|
174
|
-
const gate = isLoopback ? '' : ` — ${gated ? 'password-gated' : 'OPEN (no password)'}`
|
|
175
|
-
console.log(`[gateway] ${label} on ${scheme}://${isLoopback ? 'localhost' : (opts.host ?? '0.0.0.0')}:${opts.publicPort}${gate}, proxying /api to :${opts.upstreamPort}`)
|
|
176
|
-
if (!secure && !isLoopback && !opts.host) console.log('[gateway] (TLS off — --http)')
|
|
177
|
-
}
|
|
178
|
-
// a busy public port is a hard, loud, non-zero exit — the SAME contract as the supervisor's proxy
|
|
179
|
-
// (see [[spec-cli]] / listen.ts), so `spex serve` and `spex serve ui` fail a port clash identically.
|
|
180
|
-
listenOrExit(server, opts.publicPort, { host: opts.host, label: opts.label ?? 'gateway', cleanup: opts.onBindFail, onListen })
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// re-serialize an upgrade request's headers for replay against the upstream (exported for the host
|
|
184
|
-
// gateway's per-project WS pipe, which replays the same way).
|
|
185
|
-
export function rawHeaders(req: http.IncomingMessage): string {
|
|
186
|
-
let s = ''
|
|
187
|
-
for (let i = 0; i < req.rawHeaders.length; i += 2) s += `${req.rawHeaders[i]}: ${req.rawHeaders[i + 1]}\r\n`
|
|
188
|
-
return s + '\r\n'
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
function doLogin(req: http.IncomingMessage, res: http.ServerResponse, password: string, setCookie: string) {
|
|
192
|
-
let body = ''
|
|
193
|
-
req.on('data', (d) => { body += d; if (body.length > 4096) req.destroy() })
|
|
194
|
-
req.on('end', () => {
|
|
195
|
-
let pw = ''
|
|
196
|
-
try { pw = req.headers['content-type']?.includes('application/json') ? JSON.parse(body).password ?? '' : new URLSearchParams(body).get('password') ?? '' } catch { /* malformed */ }
|
|
197
|
-
if (constEq(pw, password)) { res.writeHead(302, { 'Set-Cookie': setCookie, Location: '/' }); res.end() }
|
|
198
|
-
else sendHtml(res, 401, loginPage(true))
|
|
199
|
-
})
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// @@@ gzip at the gateway - compression is TRANSPORT, so it lives here, once, for every deployment — the
|
|
203
|
-
// loopback upstream and the product semantics never know it exists. Text-ish payloads only; three
|
|
204
|
-
// structural exclusions, each load-bearing: an SSE stream must not sit in a zlib buffer (event latency),
|
|
205
|
-
// an already-encoded response is not re-encoded, and binary media (video/image evidence) gains nothing
|
|
206
|
-
// and would fight Range requests.
|
|
207
|
-
const COMPRESSIBLE = /^(text\/|application\/(json|javascript|xml)|image\/svg)/
|
|
208
|
-
const wantsGzip = (req: http.IncomingMessage) => /\bgzip\b/.test(String(req.headers['accept-encoding'] || ''))
|
|
209
|
-
|
|
210
|
-
// reverse-proxy an /api request to the loopback supervisor (which forwards to the live child) —
|
|
211
|
-
// stream-gzipping compressible bodies (measured: the board JSON rides down at under a third).
|
|
212
|
-
// `path` and `headers` optionally override routing inputs (the host gateway strips its /p/:projectId
|
|
213
|
-
// prefix and gateway cookies); transport ownership stays here once. Defaults pass the request through.
|
|
214
|
-
export function proxyHttp(req: http.IncomingMessage, res: http.ServerResponse, upstreamPort: number, path?: string, headers: http.OutgoingHttpHeaders = req.headers, unavailableMessage = 'upstream unreachable', upstreamHost = '127.0.0.1') {
|
|
215
|
-
let upstreamResponse: http.IncomingMessage | null = null
|
|
216
|
-
let transform: ReturnType<typeof createGzip> | null = null
|
|
217
|
-
let settled = false
|
|
218
|
-
|
|
219
|
-
const removeDownstreamListeners = () => {
|
|
220
|
-
req.off('aborted', abortFromDownstream)
|
|
221
|
-
req.off('error', abortFromDownstream)
|
|
222
|
-
req.off('end', onRequestEnd)
|
|
223
|
-
req.off('close', onRequestClose)
|
|
224
|
-
res.off('error', abortFromDownstream)
|
|
225
|
-
res.off('close', onResponseClose)
|
|
226
|
-
res.off('finish', onResponseFinish)
|
|
227
|
-
}
|
|
228
|
-
const destroyUpstream = () => {
|
|
229
|
-
req.unpipe(up)
|
|
230
|
-
if (upstreamResponse && transform) {
|
|
231
|
-
upstreamResponse.unpipe(transform)
|
|
232
|
-
transform.unpipe(res)
|
|
233
|
-
transform.destroy()
|
|
234
|
-
} else if (upstreamResponse) {
|
|
235
|
-
upstreamResponse.unpipe(res)
|
|
236
|
-
}
|
|
237
|
-
upstreamResponse?.destroy()
|
|
238
|
-
up.destroy()
|
|
239
|
-
}
|
|
240
|
-
const settle = () => {
|
|
241
|
-
if (settled) return
|
|
242
|
-
settled = true
|
|
243
|
-
removeDownstreamListeners()
|
|
244
|
-
}
|
|
245
|
-
const onResponseFinish = () => {
|
|
246
|
-
if (settled) return
|
|
247
|
-
if (req.complete) { settle(); return }
|
|
248
|
-
// An upstream may reject a body before the client finishes sending it. The response is valid, but the
|
|
249
|
-
// upload leg is over: sever it upstream and drain the downstream body so the flushed response and
|
|
250
|
-
// keep-alive socket are not turned into a reset. Request abort ownership stays until end/close.
|
|
251
|
-
req.unpipe(up)
|
|
252
|
-
upstreamResponse?.destroy()
|
|
253
|
-
up.socket?.destroy()
|
|
254
|
-
up.destroy()
|
|
255
|
-
if (!req.destroyed) req.resume()
|
|
256
|
-
}
|
|
257
|
-
const abortFromDownstream = () => {
|
|
258
|
-
if (settled) return
|
|
259
|
-
settled = true
|
|
260
|
-
removeDownstreamListeners()
|
|
261
|
-
destroyUpstream()
|
|
262
|
-
}
|
|
263
|
-
const onRequestClose = () => {
|
|
264
|
-
if (!req.complete) abortFromDownstream()
|
|
265
|
-
else if (res.writableFinished) settle()
|
|
266
|
-
}
|
|
267
|
-
const onRequestEnd = () => { if (res.writableFinished) settle() }
|
|
268
|
-
const onResponseClose = () => {
|
|
269
|
-
if (!res.writableFinished) abortFromDownstream()
|
|
270
|
-
}
|
|
271
|
-
const failFromUpstream = () => {
|
|
272
|
-
if (settled) return
|
|
273
|
-
settled = true
|
|
274
|
-
removeDownstreamListeners()
|
|
275
|
-
destroyUpstream()
|
|
276
|
-
if (res.destroyed) return
|
|
277
|
-
if (!res.headersSent) { res.writeHead(502); res.end(unavailableMessage) }
|
|
278
|
-
else res.destroy()
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
const up = http.request({ host: upstreamHost, port: upstreamPort, path: path ?? req.url, method: req.method, headers }, (received) => {
|
|
282
|
-
if (settled || res.destroyed) { received.destroy(); up.destroy(); return }
|
|
283
|
-
upstreamResponse = received
|
|
284
|
-
received.once('aborted', failFromUpstream)
|
|
285
|
-
received.once('error', failFromUpstream)
|
|
286
|
-
received.once('close', () => { if (!received.complete) failFromUpstream() })
|
|
287
|
-
|
|
288
|
-
const type = String(received.headers['content-type'] || '')
|
|
289
|
-
const skip = !wantsGzip(req) || received.headers['content-encoding'] || !COMPRESSIBLE.test(type) || type.startsWith('text/event-stream')
|
|
290
|
-
if (skip) {
|
|
291
|
-
res.writeHead(received.statusCode || 502, received.headers)
|
|
292
|
-
received.pipe(res)
|
|
293
|
-
return
|
|
294
|
-
}
|
|
295
|
-
const headers = { ...received.headers, 'content-encoding': 'gzip', vary: 'Accept-Encoding' }
|
|
296
|
-
delete headers['content-length'] // streamed; the encoded length isn't knowable up front
|
|
297
|
-
res.writeHead(received.statusCode || 502, headers)
|
|
298
|
-
transform = createGzip()
|
|
299
|
-
transform.once('error', failFromUpstream)
|
|
300
|
-
received.pipe(transform).pipe(res)
|
|
301
|
-
})
|
|
302
|
-
up.once('error', failFromUpstream)
|
|
303
|
-
req.once('aborted', abortFromDownstream)
|
|
304
|
-
req.once('error', abortFromDownstream)
|
|
305
|
-
req.once('end', onRequestEnd)
|
|
306
|
-
req.once('close', onRequestClose)
|
|
307
|
-
res.once('error', abortFromDownstream)
|
|
308
|
-
res.once('close', onResponseClose)
|
|
309
|
-
res.once('finish', onResponseFinish)
|
|
310
|
-
req.pipe(up)
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
type WebRoute = { sessionId: string; key: string; tail: string; query: string }
|
|
314
|
-
|
|
315
|
-
function webRoute(raw: string): WebRoute | null {
|
|
316
|
-
const q = raw.indexOf('?')
|
|
317
|
-
const path = q >= 0 ? raw.slice(0, q) : raw
|
|
318
|
-
const match = path.match(/^\/web\/([^/]+)\/([A-Za-z0-9_-]+)(\/.*)?$/)
|
|
319
|
-
if (!match) return null
|
|
320
|
-
return { sessionId: decodeURIComponent(match[1]), key: match[2], tail: match[3] || '/', query: q >= 0 ? raw.slice(q) : '' }
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
function sessionWebTarget(route: WebRoute, projectRoot?: string): { port: number; host: string; connectHost: string; path: string } {
|
|
324
|
-
const endpoint = postedSessionWeb(route.sessionId, route.key, projectRoot)
|
|
325
|
-
const base = endpoint.pathname.endsWith('/') ? endpoint.pathname : `${endpoint.pathname}/`
|
|
326
|
-
const path = route.tail === '/' ? endpoint.pathname : `${base}${route.tail.slice(1)}`
|
|
327
|
-
return { port: Number(endpoint.port), host: endpoint.host, connectHost: endpoint.hostname.replace(/^\[|\]$/g, ''), path: `${path}${route.query}` }
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
function withoutGatewayCookies(cookie: string | undefined): string {
|
|
331
|
-
return (cookie ?? '').split(';').map((part) => part.trim())
|
|
332
|
-
.filter((part) => part && !/^spex_(admin|proj|auth)_/i.test(part.slice(0, Math.max(part.indexOf('='), 0))))
|
|
333
|
-
.join('; ')
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
function webHeaders(req: http.IncomingMessage, host: string): http.OutgoingHttpHeaders {
|
|
337
|
-
const headers: http.OutgoingHttpHeaders = { ...req.headers, host }
|
|
338
|
-
const kept = withoutGatewayCookies(req.headers.cookie)
|
|
339
|
-
if (kept) headers.cookie = kept; else delete headers.cookie
|
|
340
|
-
return headers
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
function webRawHeaders(req: http.IncomingMessage, host: string): string {
|
|
344
|
-
let out = ''
|
|
345
|
-
let wroteHost = false
|
|
346
|
-
for (let i = 0; i < req.rawHeaders.length; i += 2) {
|
|
347
|
-
const name = req.rawHeaders[i]
|
|
348
|
-
const value = req.rawHeaders[i + 1]
|
|
349
|
-
if (name.toLowerCase() === 'host') { out += `Host: ${host}\r\n`; wroteHost = true; continue }
|
|
350
|
-
if (name.toLowerCase() === 'cookie') {
|
|
351
|
-
const kept = withoutGatewayCookies(value)
|
|
352
|
-
if (kept) out += `Cookie: ${kept}\r\n`
|
|
353
|
-
continue
|
|
354
|
-
}
|
|
355
|
-
out += `${name}: ${value}\r\n`
|
|
356
|
-
}
|
|
357
|
-
return `${out}${wroteHost ? '' : `Host: ${host}\r\n`}\r\n`
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
function sessionWebFailure(res: http.ServerResponse, error: unknown): void {
|
|
361
|
-
if (error instanceof SessionWebError) {
|
|
362
|
-
res.writeHead(error.status, { 'Content-Type': 'text/plain; charset=utf-8', 'Cache-Control': 'no-store' })
|
|
363
|
-
res.end(error.message)
|
|
364
|
-
return
|
|
365
|
-
}
|
|
366
|
-
console.error(`[web] route failed: ${error instanceof Error ? error.message : String(error)}`)
|
|
367
|
-
res.writeHead(500, { 'Content-Type': 'text/plain; charset=utf-8', 'Cache-Control': 'no-store' })
|
|
368
|
-
res.end('web preview failed')
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
// A posted URL selects a loopback service; the gateway owns only the prefix and transport. It never opens
|
|
372
|
-
// an endpoint until a browser asks for this route, so each request observes the service's current bytes.
|
|
373
|
-
export function proxySessionWeb(req: http.IncomingMessage, res: http.ServerResponse, raw = req.url || '/', projectRoot?: string): boolean {
|
|
374
|
-
const route = webRoute(raw)
|
|
375
|
-
if (!route) return false
|
|
376
|
-
try {
|
|
377
|
-
const target = sessionWebTarget(route, projectRoot)
|
|
378
|
-
proxyHttp(req, res, target.port, target.path, webHeaders(req, target.host), 'posted web service is unavailable', target.connectHost)
|
|
379
|
-
} catch (error) { sessionWebFailure(res, error) }
|
|
380
|
-
return true
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
export function proxySessionWebUpgrade(req: http.IncomingMessage, socket: Duplex, head: Buffer, raw = req.url || '/', projectRoot?: string): boolean {
|
|
384
|
-
const route = webRoute(raw)
|
|
385
|
-
if (!route) return false
|
|
386
|
-
let target: { port: number; host: string; connectHost: string; path: string }
|
|
387
|
-
try { target = sessionWebTarget(route, projectRoot) }
|
|
388
|
-
catch { socket.destroy(); return true }
|
|
389
|
-
const upstream = net.connect(target.port, target.connectHost, () => {
|
|
390
|
-
upstream.write(`${req.method} ${target.path} HTTP/1.1\r\n` + webRawHeaders(req, target.host))
|
|
391
|
-
if (head.length) upstream.write(head)
|
|
392
|
-
socket.pipe(upstream); upstream.pipe(socket)
|
|
393
|
-
})
|
|
394
|
-
const bail = () => { socket.destroy(); upstream.destroy() }
|
|
395
|
-
socket.on('error', bail); upstream.on('error', bail)
|
|
396
|
-
socket.on('end', bail); upstream.on('end', bail)
|
|
397
|
-
socket.once('close', () => upstream.destroy())
|
|
398
|
-
upstream.once('close', () => socket.destroy())
|
|
399
|
-
return true
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
// serve the built dashboard (vite dist). Unknown non-file paths fall back to index.html (SPA). Path
|
|
403
|
-
// traversal is blocked by normalising and confining to distDir. Compressible files ship gzipped, memoized
|
|
404
|
-
// per (path, mtime) — a dist file is immutable per build, so each is compressed once, not per request.
|
|
405
|
-
// Exported: the host gateway serves the same dist through the same function.
|
|
406
|
-
const gzMemo = new Map<string, { mtime: number; gz: Buffer }>()
|
|
407
|
-
export function serveStatic(req: http.IncomingMessage, res: http.ServerResponse, distDir: string, urlPath: string) {
|
|
408
|
-
const rel = normalize(decodeURIComponent(urlPath)).replace(/^(\.\.[/\\])+/, '')
|
|
409
|
-
let file = join(distDir, rel)
|
|
410
|
-
if (!file.startsWith(distDir)) file = join(distDir, 'index.html')
|
|
411
|
-
if (urlPath === '/' || !existsSync(file)) {
|
|
412
|
-
// @@@ missing-asset 404 - a missing extensioned path is a stale hashed chunk, not an SPA route: answer
|
|
413
|
-
// 404, not HTML (which trips the module-MIME check), so the shell's reload recovery sees it ([[public-mode]]).
|
|
414
|
-
if (urlPath !== '/' && extname(file)) { res.writeHead(404, { 'Content-Type': 'text/plain' }); return res.end('not found') }
|
|
415
|
-
file = join(distDir, 'index.html')
|
|
416
|
-
}
|
|
417
|
-
if (!existsSync(file)) { res.writeHead(503); return res.end('dashboard build missing') }
|
|
418
|
-
const type = MIME[extname(file)] || 'application/octet-stream'
|
|
419
|
-
const cacheControl = /[\\/]assets[\\/]/.test(file) ? 'public, max-age=31536000, immutable' : 'no-cache'
|
|
420
|
-
const raw = readFileSync(file)
|
|
421
|
-
if (wantsGzip(req) && COMPRESSIBLE.test(type)) {
|
|
422
|
-
const mtime = statSync(file).mtimeMs
|
|
423
|
-
let hit = gzMemo.get(file)
|
|
424
|
-
if (!hit || hit.mtime !== mtime) { hit = { mtime, gz: gzipSync(raw) }; gzMemo.set(file, hit) }
|
|
425
|
-
res.writeHead(200, { 'Content-Type': type, 'Content-Encoding': 'gzip', Vary: 'Accept-Encoding', 'Cache-Control': cacheControl })
|
|
426
|
-
return res.end(hit.gz)
|
|
427
|
-
}
|
|
428
|
-
res.writeHead(200, { 'Content-Type': type, 'Cache-Control': cacheControl })
|
|
429
|
-
res.end(raw)
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
function sendHtml(res: http.ServerResponse, status: number, html: string) {
|
|
433
|
-
res.writeHead(status, { 'Content-Type': 'text/html; charset=utf-8' })
|
|
434
|
-
res.end(html)
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
// @@@ ensureDashboardBuilt - public mode serves a STATIC build, so the dist must exist. If it's missing we
|
|
438
|
-
// build it once (vite build) so "one command" holds; a build failure is loud, not a blank serve.
|
|
439
|
-
export function ensureDashboardBuilt(repoRoot: string, distDir: string): void {
|
|
440
|
-
if (existsSync(join(distDir, 'index.html'))) return
|
|
441
|
-
console.log('[gateway] dashboard build not found — building it once (vite build)…')
|
|
442
|
-
const r = spawnSync('npm', ['run', 'build'], { cwd: join(repoRoot, 'spec-dashboard'), stdio: 'inherit' })
|
|
443
|
-
if (r.status !== 0 || !existsSync(join(distDir, 'index.html'))) {
|
|
444
|
-
console.error('[gateway] dashboard build failed. Build it manually: (cd spec-dashboard && npm run build), then retry.')
|
|
445
|
-
process.exit(1)
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
// @@@ serveDashboardLocal - the engine behind `spex serve ui`: the SAME gateway as public mode, bound to
|
|
450
|
-
// loopback by default with no TLS and no password — `--host` widens the bind to a chosen interface
|
|
451
|
-
// (LAN/tailnet viewing) while staying plain HTTP; the internet face remains `spex serve --public`.
|
|
452
|
-
// It serves the bundled dist and proxies /api + the terminal socket to a separately-run `spex serve`.
|
|
453
|
-
// This is the post-install replacement for the dogfood-only `npm run web` (a vite dev server an
|
|
454
|
-
// installed user has no source tree for). See [[packaging]].
|
|
455
|
-
export function serveDashboardLocal(opts: { port: number; apiPort: number; host?: string; projectRoot?: string }): void {
|
|
456
|
-
const pkgRoot = fileURLToPath(new URL('..', import.meta.url))
|
|
457
|
-
const distDir = resolveDistDir()
|
|
458
|
-
ensureDashboardBuilt(join(pkgRoot, '..'), distDir) // bundled dist already has index.html → returns at once
|
|
459
|
-
console.log(`[dashboard] serving ${distDir.endsWith('dashboard-dist') ? 'bundled' : 'monorepo'} build, /api → backend :${opts.apiPort}`)
|
|
460
|
-
startGateway({ host: opts.host ?? '127.0.0.1', publicPort: opts.port, upstreamPort: opts.apiPort, password: '', tls: null, distDir, label: 'dashboard', projectRoot: opts.projectRoot })
|
|
461
|
-
}
|