spexcode 0.6.1 → 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 +99 -149
- 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} +115 -22
- 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 +27 -7
- 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/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/core/comment-altitude/spec.md +9 -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/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/core/spec.md +22 -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-cli/templates/spec/project/.plugins/prompts/atomic-landing/spec.md +15 -0
- package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/prompts/forge-link/spec.md +10 -0
- package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/prompts/memory-hygiene/spec.md +13 -0
- package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/prompts/reproduce-before-fix/spec.md +12 -0
- 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 -706
- package/spec-cli/src/attach.ts +0 -43
- package/spec-cli/src/claude-headless.ts +0 -321
- package/spec-cli/src/cli.ts +0 -1115
- package/spec-cli/src/client.ts +0 -315
- 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 -107
- package/spec-cli/src/doctor.ts +0 -516
- 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 -356
- package/spec-cli/src/gateway.ts +0 -368
- package/spec-cli/src/git.ts +0 -2180
- package/spec-cli/src/graph.ts +0 -221
- package/spec-cli/src/graphCache.ts +0 -641
- 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 -2747
- package/spec-cli/src/headless-controller.ts +0 -63
- package/spec-cli/src/help.ts +0 -480
- package/spec-cli/src/host-resources.ts +0 -707
- 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 -128
- package/spec-cli/src/index.ts +0 -730
- 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 -557
- package/spec-cli/src/lint.ts +0 -458
- package/spec-cli/src/localIssues.ts +0 -538
- package/spec-cli/src/materialize.ts +0 -412
- 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 -405
- 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-follow.ts +0 -182
- package/spec-cli/src/session-timeline.ts +0 -98
- package/spec-cli/src/sessions.ts +0 -3403
- 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-cli/templates/spec/project/.plugins/core/comment-altitude/spec.md +0 -28
- package/spec-cli/templates/spec/project/.plugins/core/spec.md +0 -23
- package/spec-cli/templates/spec/project/.plugins/prompts/atomic-landing/spec.md +0 -32
- package/spec-cli/templates/spec/project/.plugins/prompts/forge-link/spec.md +0 -9
- package/spec-cli/templates/spec/project/.plugins/prompts/memory-hygiene/spec.md +0 -15
- package/spec-cli/templates/spec/project/.plugins/prompts/reproduce-before-fix/spec.md +0 -18
- package/spec-dashboard/dist/assets/App-F9uaAVcH.js +0 -2
- package/spec-dashboard/dist/assets/Dashboard-Ba_jhxp1.js +0 -27
- package/spec-dashboard/dist/assets/Dashboard-C5ap-Sga.css +0 -1
- package/spec-dashboard/dist/assets/EvalsPage-FixoOg_n.js +0 -2
- package/spec-dashboard/dist/assets/IssuesPage-CuKLFhH3.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-CHgEHORJ.js +0 -2
- package/spec-dashboard/dist/assets/Modal-CQgYymmr.js +0 -1
- package/spec-dashboard/dist/assets/PageScroll-hT7UTLvD.js +0 -1
- package/spec-dashboard/dist/assets/ProjectsPage-CtXxakF9.js +0 -1
- package/spec-dashboard/dist/assets/SessionInterface-6GBZ9nXN.css +0 -32
- package/spec-dashboard/dist/assets/SessionInterface-Bpie-9fs.js +0 -39
- package/spec-dashboard/dist/assets/SessionWindow-CixDi4PI.js +0 -1
- package/spec-dashboard/dist/assets/Settings-C2MsucfE.js +0 -1
- package/spec-dashboard/dist/assets/Thread-C6Go8HRh.js +0 -13
- package/spec-dashboard/dist/assets/bindings-BC9vqpYU.js +0 -1
- package/spec-dashboard/dist/assets/data-B-RQmit6.js +0 -1
- package/spec-dashboard/dist/assets/index-CsI8DElI.css +0 -1
- package/spec-dashboard/dist/assets/index-DrVao0Ep.js +0 -41
- package/spec-dashboard/dist/assets/launch-BBH02b1v.js +0 -276
- package/spec-dashboard/dist/assets/launch-Cp579UoJ.css +0 -1
- package/spec-dashboard/dist/assets/terminalFont-C6oGNKrf.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 -889
- package/spec-eval/src/evaltab.ts +0 -262
- package/spec-eval/src/filing.ts +0 -39
- package/spec-eval/src/freshness.ts +0 -557
- 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 -2226
- 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/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/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
|
@@ -0,0 +1,503 @@
|
|
|
1
|
+
import http from 'node:http';
|
|
2
|
+
import https from 'node:https';
|
|
3
|
+
import net from 'node:net';
|
|
4
|
+
import { createHmac, timingSafeEqual } from 'node:crypto';
|
|
5
|
+
import { execFileSync, spawnSync } from 'node:child_process';
|
|
6
|
+
import { readFileSync, existsSync, mkdirSync, statSync } from 'node:fs';
|
|
7
|
+
import { gzipSync, createGzip } from 'node:zlib';
|
|
8
|
+
import { join, normalize, extname } from 'node:path';
|
|
9
|
+
import { homedir } from 'node:os';
|
|
10
|
+
import { loginPage } from './login-page.js';
|
|
11
|
+
import { listenOrExit } from './listen.js';
|
|
12
|
+
import { installConnectionReaper } from './reaper.js';
|
|
13
|
+
import { postedSessionWeb, SessionWebError } from './session-web.js';
|
|
14
|
+
import { ensureDashboardArtifact } from './dashboard-assets.js';
|
|
15
|
+
function argFlag(name) {
|
|
16
|
+
const i = process.argv.indexOf(`--${name}`);
|
|
17
|
+
return i >= 0 ? process.argv[i + 1] : undefined;
|
|
18
|
+
}
|
|
19
|
+
const argHas = (name) => process.argv.includes(`--${name}`);
|
|
20
|
+
export function resolvePublicConfig(repoRoot) {
|
|
21
|
+
let fileCfg = {};
|
|
22
|
+
// a MISSING spexcode.json is fine (defaults); a MALFORMED one fails LOUD — silently swallowing it would
|
|
23
|
+
// serve the dashboard with the wrong public/TLS posture, the opposite of what the file says.
|
|
24
|
+
try {
|
|
25
|
+
fileCfg = JSON.parse(readFileSync(join(repoRoot, 'spexcode.json'), 'utf8'))?.serve?.public ?? {};
|
|
26
|
+
}
|
|
27
|
+
catch (e) {
|
|
28
|
+
if (e.code !== 'ENOENT')
|
|
29
|
+
throw new Error(`spexcode.json is malformed (cannot resolve public-mode config): ${e.message}`);
|
|
30
|
+
}
|
|
31
|
+
const enabled = argHas('public') || process.env.SPEXCODE_PUBLIC === '1' || fileCfg?.enabled === true;
|
|
32
|
+
if (!enabled)
|
|
33
|
+
return null;
|
|
34
|
+
// the gate is OPT-IN: a password (flag/env only — never spexcode.json) makes the login appear; WITHOUT one
|
|
35
|
+
// the dashboard is served OPEN. That is loud-warned, not refused — open public access drives the agents, so
|
|
36
|
+
// anyone who reaches the URL has them. The caller (you) chooses; we never silently gate or silently expose.
|
|
37
|
+
const password = argFlag('password') ?? process.env.SPEXCODE_PASSWORD ?? '';
|
|
38
|
+
if (!password)
|
|
39
|
+
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.');
|
|
40
|
+
// --http: knowingly drop TLS. Loud, because the password then crosses the wire in clear and secure-context
|
|
41
|
+
// browser features (clipboard) break. Anything else resolves a cert; absent any source → self-signed.
|
|
42
|
+
if (argHas('http') || fileCfg?.http === true) {
|
|
43
|
+
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.');
|
|
44
|
+
return { password, tls: null };
|
|
45
|
+
}
|
|
46
|
+
const certPath = argFlag('tls-cert') ?? process.env.SPEXCODE_TLS_CERT ?? fileCfg?.tls?.cert;
|
|
47
|
+
const keyPath = argFlag('tls-key') ?? process.env.SPEXCODE_TLS_KEY ?? fileCfg?.tls?.key;
|
|
48
|
+
if (certPath || keyPath) {
|
|
49
|
+
if (!certPath || !keyPath) {
|
|
50
|
+
console.error('spex serve --public: --tls-cert and --tls-key must be given together.');
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
for (const [label, p] of [['cert', certPath], ['key', keyPath]]) {
|
|
54
|
+
if (!existsSync(p)) {
|
|
55
|
+
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.`);
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return { password, tls: { cert: readFileSync(certPath, 'utf8'), key: readFileSync(keyPath, 'utf8') } };
|
|
60
|
+
}
|
|
61
|
+
return { password, tls: selfSignedCert() };
|
|
62
|
+
}
|
|
63
|
+
// @@@ self-signed default - generated ONCE via openssl into ~/.spexcode/tls and reused, so a visitor accepts
|
|
64
|
+
// the cert only once (not on every restart). openssl is near-universal on Linux/macOS; if it is genuinely
|
|
65
|
+
// absent we FAIL LOUD with the three repair paths rather than silently dropping to plaintext. Web PKI will
|
|
66
|
+
// not issue a browser-trusted cert for a bare IP, so this cert is untrusted by construction — the visitor's
|
|
67
|
+
// one-time "proceed" is the price of needing no domain, not a bug.
|
|
68
|
+
function selfSignedCert() {
|
|
69
|
+
const dir = join(homedir(), '.spexcode', 'tls');
|
|
70
|
+
const certFile = join(dir, 'self-signed.cert.pem'), keyFile = join(dir, 'self-signed.key.pem');
|
|
71
|
+
if (!existsSync(certFile) || !existsSync(keyFile)) {
|
|
72
|
+
if (spawnSync('openssl', ['version']).status !== 0) {
|
|
73
|
+
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).');
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
mkdirSync(dir, { recursive: true });
|
|
77
|
+
console.log('[gateway] generating a self-signed TLS cert (one-time) → ' + dir);
|
|
78
|
+
execFileSync('openssl', ['req', '-x509', '-newkey', 'rsa:2048', '-nodes', '-keyout', keyFile, '-out', certFile,
|
|
79
|
+
'-days', '3650', '-subj', '/CN=spexcode', '-addext', 'subjectAltName=DNS:localhost,IP:127.0.0.1'], { stdio: 'ignore' });
|
|
80
|
+
}
|
|
81
|
+
return { cert: readFileSync(certFile, 'utf8'), key: readFileSync(keyFile, 'utf8') };
|
|
82
|
+
}
|
|
83
|
+
const COOKIE = 'spex_auth';
|
|
84
|
+
function authToken(password) {
|
|
85
|
+
const secret = createHmac('sha256', password).update('spexcode-public-gateway-v1').digest();
|
|
86
|
+
return createHmac('sha256', secret).update('authed').digest('base64url');
|
|
87
|
+
}
|
|
88
|
+
function constEq(a, b) {
|
|
89
|
+
const ab = Buffer.from(a), bb = Buffer.from(b);
|
|
90
|
+
return ab.length === bb.length && timingSafeEqual(ab, bb);
|
|
91
|
+
}
|
|
92
|
+
function cookieOf(header, name) {
|
|
93
|
+
for (const part of (header ?? '').split(';')) {
|
|
94
|
+
const eq = part.indexOf('=');
|
|
95
|
+
if (eq > 0 && part.slice(0, eq).trim() === name)
|
|
96
|
+
return decodeURIComponent(part.slice(eq + 1).trim());
|
|
97
|
+
}
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
function isAuthed(req, token, cookieName) {
|
|
101
|
+
const c = cookieOf(req.headers.cookie, cookieName);
|
|
102
|
+
return c != null && constEq(c, token);
|
|
103
|
+
}
|
|
104
|
+
const MIME = { '.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' };
|
|
105
|
+
// Dashboard assets are resolved from their owning package, whether npm installed it or the workspace links it.
|
|
106
|
+
export function resolveDistDir() {
|
|
107
|
+
return ensureDashboardArtifact('dist');
|
|
108
|
+
}
|
|
109
|
+
export function startGateway(opts) {
|
|
110
|
+
// gated ONLY when a password is set; otherwise the login layer doesn't exist and the dashboard is served open.
|
|
111
|
+
const gated = !!opts.password;
|
|
112
|
+
const token = gated ? authToken(opts.password) : '';
|
|
113
|
+
const secure = !!opts.tls;
|
|
114
|
+
// the auth cookie is HOST-scoped (RFC 6265 ignores the port), so two gateways on one IP would share a
|
|
115
|
+
// single 'spex_auth' jar entry and clobber each other's login. Key the name by the public port — the
|
|
116
|
+
// unique discriminator on a host, exactly what the user's two URLs differ by — so same-host instances
|
|
117
|
+
// (e.g. :8787 and :8788) stay logged in concurrently and a logout clears only its own.
|
|
118
|
+
const cookieName = `${COOKIE}_${opts.publicPort}`;
|
|
119
|
+
const setCookie = `${cookieName}=${token}; HttpOnly; Path=/; SameSite=Lax; Max-Age=2592000${secure ? '; Secure' : ''}`;
|
|
120
|
+
const handler = (req, res) => {
|
|
121
|
+
const url = (req.url || '/').split('?')[0];
|
|
122
|
+
if (gated) {
|
|
123
|
+
// login surface — the only routes reachable without a cookie. Absent entirely when ungated.
|
|
124
|
+
if (url === '/login' && req.method === 'POST')
|
|
125
|
+
return doLogin(req, res, opts.password, setCookie);
|
|
126
|
+
if (url === '/login')
|
|
127
|
+
return sendHtml(res, 200, loginPage());
|
|
128
|
+
if (url === '/logout') {
|
|
129
|
+
res.writeHead(302, { 'Set-Cookie': `${cookieName}=; Path=/; Max-Age=0`, Location: '/login' });
|
|
130
|
+
return res.end();
|
|
131
|
+
}
|
|
132
|
+
if (!isAuthed(req, token, cookieName)) {
|
|
133
|
+
if (url.startsWith('/api')) {
|
|
134
|
+
res.writeHead(401, { 'Content-Type': 'application/json' });
|
|
135
|
+
return res.end('{"error":"authentication required"}');
|
|
136
|
+
}
|
|
137
|
+
res.writeHead(302, { Location: '/login' });
|
|
138
|
+
return res.end();
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
if (proxySessionWeb(req, res, req.url || '/', opts.projectRoot))
|
|
142
|
+
return;
|
|
143
|
+
if (url.startsWith('/api'))
|
|
144
|
+
return proxyHttp(req, res, opts.upstreamPort);
|
|
145
|
+
return serveStatic(req, res, opts.distDir, url);
|
|
146
|
+
};
|
|
147
|
+
const server = secure
|
|
148
|
+
? https.createServer({ cert: opts.tls.cert, key: opts.tls.key }, handler)
|
|
149
|
+
: http.createServer(handler);
|
|
150
|
+
installConnectionReaper(server);
|
|
151
|
+
server.on('upgrade', (req, socket, head) => {
|
|
152
|
+
if (gated && !isAuthed(req, token, cookieName)) {
|
|
153
|
+
socket.destroy();
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
if (proxySessionWebUpgrade(req, socket, head, req.url || '/', opts.projectRoot))
|
|
157
|
+
return;
|
|
158
|
+
const up = net.connect(opts.upstreamPort, '127.0.0.1', () => {
|
|
159
|
+
up.write(`${req.method} ${req.url} HTTP/1.1\r\n` + rawHeaders(req));
|
|
160
|
+
if (head && head.length)
|
|
161
|
+
up.write(head);
|
|
162
|
+
socket.pipe(up);
|
|
163
|
+
up.pipe(socket);
|
|
164
|
+
});
|
|
165
|
+
const bail = () => { socket.destroy(); up.destroy(); };
|
|
166
|
+
socket.on('error', bail);
|
|
167
|
+
up.on('error', bail);
|
|
168
|
+
// pair the halves fully (the supervisor's rule): an http server's sockets are allowHalfOpen, so a bare
|
|
169
|
+
// client FIN ('end') would otherwise leave this side open forever — an upgraded stream never
|
|
170
|
+
// half-closes legitimately, so either half's FIN or close tears down both.
|
|
171
|
+
socket.on('end', bail);
|
|
172
|
+
up.on('end', bail);
|
|
173
|
+
socket.once('close', () => up.destroy());
|
|
174
|
+
up.once('close', () => socket.destroy());
|
|
175
|
+
});
|
|
176
|
+
// `spex serve ui` passes an explicit host (loopback by default, --host to widen); `--public` passes
|
|
177
|
+
// none → bind ALL interfaces (the original behaviour, IPv4+IPv6), so adding the local path never
|
|
178
|
+
// narrows the public gateway's reach. The gate note keys on LOOPBACK, not on host-being-explicit:
|
|
179
|
+
// an ungated loopback bind is normal, an ungated wide bind is announced — never silent.
|
|
180
|
+
const isLoopback = opts.host === '127.0.0.1' || opts.host === 'localhost' || opts.host === '::1';
|
|
181
|
+
const onListen = () => {
|
|
182
|
+
const scheme = secure ? 'https' : 'http';
|
|
183
|
+
const label = opts.label ?? 'public mode';
|
|
184
|
+
const gate = isLoopback ? '' : ` — ${gated ? 'password-gated' : 'OPEN (no password)'}`;
|
|
185
|
+
console.log(`[gateway] ${label} on ${scheme}://${isLoopback ? 'localhost' : (opts.host ?? '0.0.0.0')}:${opts.publicPort}${gate}, proxying /api to :${opts.upstreamPort}`);
|
|
186
|
+
if (!secure && !isLoopback && !opts.host)
|
|
187
|
+
console.log('[gateway] (TLS off — --http)');
|
|
188
|
+
};
|
|
189
|
+
// a busy public port is a hard, loud, non-zero exit — the SAME contract as the supervisor's proxy
|
|
190
|
+
// (see [[spec-cli]] / listen.ts), so `spex serve` and `spex serve ui` fail a port clash identically.
|
|
191
|
+
listenOrExit(server, opts.publicPort, { host: opts.host, label: opts.label ?? 'gateway', cleanup: opts.onBindFail, onListen });
|
|
192
|
+
}
|
|
193
|
+
// re-serialize an upgrade request's headers for replay against the upstream (exported for the host
|
|
194
|
+
// gateway's per-project WS pipe, which replays the same way).
|
|
195
|
+
export function rawHeaders(req) {
|
|
196
|
+
let s = '';
|
|
197
|
+
for (let i = 0; i < req.rawHeaders.length; i += 2)
|
|
198
|
+
s += `${req.rawHeaders[i]}: ${req.rawHeaders[i + 1]}\r\n`;
|
|
199
|
+
return s + '\r\n';
|
|
200
|
+
}
|
|
201
|
+
function doLogin(req, res, password, setCookie) {
|
|
202
|
+
let body = '';
|
|
203
|
+
req.on('data', (d) => { body += d; if (body.length > 4096)
|
|
204
|
+
req.destroy(); });
|
|
205
|
+
req.on('end', () => {
|
|
206
|
+
let pw = '';
|
|
207
|
+
try {
|
|
208
|
+
pw = req.headers['content-type']?.includes('application/json') ? JSON.parse(body).password ?? '' : new URLSearchParams(body).get('password') ?? '';
|
|
209
|
+
}
|
|
210
|
+
catch { /* malformed */ }
|
|
211
|
+
if (constEq(pw, password)) {
|
|
212
|
+
res.writeHead(302, { 'Set-Cookie': setCookie, Location: '/' });
|
|
213
|
+
res.end();
|
|
214
|
+
}
|
|
215
|
+
else
|
|
216
|
+
sendHtml(res, 401, loginPage(true));
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
// @@@ gzip at the gateway - compression is TRANSPORT, so it lives here, once, for every deployment — the
|
|
220
|
+
// loopback upstream and the product semantics never know it exists. Text-ish payloads only; three
|
|
221
|
+
// structural exclusions, each load-bearing: an SSE stream must not sit in a zlib buffer (event latency),
|
|
222
|
+
// an already-encoded response is not re-encoded, and binary media (video/image evidence) gains nothing
|
|
223
|
+
// and would fight Range requests.
|
|
224
|
+
const COMPRESSIBLE = /^(text\/|application\/(json|javascript|xml)|image\/svg)/;
|
|
225
|
+
const wantsGzip = (req) => /\bgzip\b/.test(String(req.headers['accept-encoding'] || ''));
|
|
226
|
+
// reverse-proxy an /api request to the loopback supervisor (which forwards to the live child) —
|
|
227
|
+
// stream-gzipping compressible bodies (measured: the board JSON rides down at under a third).
|
|
228
|
+
// `path` and `headers` optionally override routing inputs (the host gateway strips its /p/:projectId
|
|
229
|
+
// prefix and gateway cookies); transport ownership stays here once. Defaults pass the request through.
|
|
230
|
+
export function proxyHttp(req, res, upstreamPort, path, headers = req.headers, unavailableMessage = 'upstream unreachable', upstreamHost = '127.0.0.1') {
|
|
231
|
+
let upstreamResponse = null;
|
|
232
|
+
let transform = null;
|
|
233
|
+
let settled = false;
|
|
234
|
+
const removeDownstreamListeners = () => {
|
|
235
|
+
req.off('aborted', abortFromDownstream);
|
|
236
|
+
req.off('error', abortFromDownstream);
|
|
237
|
+
req.off('end', onRequestEnd);
|
|
238
|
+
req.off('close', onRequestClose);
|
|
239
|
+
res.off('error', abortFromDownstream);
|
|
240
|
+
res.off('close', onResponseClose);
|
|
241
|
+
res.off('finish', onResponseFinish);
|
|
242
|
+
};
|
|
243
|
+
const destroyUpstream = () => {
|
|
244
|
+
req.unpipe(up);
|
|
245
|
+
if (upstreamResponse && transform) {
|
|
246
|
+
upstreamResponse.unpipe(transform);
|
|
247
|
+
transform.unpipe(res);
|
|
248
|
+
transform.destroy();
|
|
249
|
+
}
|
|
250
|
+
else if (upstreamResponse) {
|
|
251
|
+
upstreamResponse.unpipe(res);
|
|
252
|
+
}
|
|
253
|
+
upstreamResponse?.destroy();
|
|
254
|
+
up.destroy();
|
|
255
|
+
};
|
|
256
|
+
const settle = () => {
|
|
257
|
+
if (settled)
|
|
258
|
+
return;
|
|
259
|
+
settled = true;
|
|
260
|
+
removeDownstreamListeners();
|
|
261
|
+
};
|
|
262
|
+
const onResponseFinish = () => {
|
|
263
|
+
if (settled)
|
|
264
|
+
return;
|
|
265
|
+
if (req.complete) {
|
|
266
|
+
settle();
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
// An upstream may reject a body before the client finishes sending it. The response is valid, but the
|
|
270
|
+
// upload leg is over: sever it upstream and drain the downstream body so the flushed response and
|
|
271
|
+
// keep-alive socket are not turned into a reset. Request abort ownership stays until end/close.
|
|
272
|
+
req.unpipe(up);
|
|
273
|
+
upstreamResponse?.destroy();
|
|
274
|
+
up.socket?.destroy();
|
|
275
|
+
up.destroy();
|
|
276
|
+
if (!req.destroyed)
|
|
277
|
+
req.resume();
|
|
278
|
+
};
|
|
279
|
+
const abortFromDownstream = () => {
|
|
280
|
+
if (settled)
|
|
281
|
+
return;
|
|
282
|
+
settled = true;
|
|
283
|
+
removeDownstreamListeners();
|
|
284
|
+
destroyUpstream();
|
|
285
|
+
};
|
|
286
|
+
const onRequestClose = () => {
|
|
287
|
+
if (!req.complete)
|
|
288
|
+
abortFromDownstream();
|
|
289
|
+
else if (res.writableFinished)
|
|
290
|
+
settle();
|
|
291
|
+
};
|
|
292
|
+
const onRequestEnd = () => { if (res.writableFinished)
|
|
293
|
+
settle(); };
|
|
294
|
+
const onResponseClose = () => {
|
|
295
|
+
if (!res.writableFinished)
|
|
296
|
+
abortFromDownstream();
|
|
297
|
+
};
|
|
298
|
+
const failFromUpstream = () => {
|
|
299
|
+
if (settled)
|
|
300
|
+
return;
|
|
301
|
+
settled = true;
|
|
302
|
+
removeDownstreamListeners();
|
|
303
|
+
destroyUpstream();
|
|
304
|
+
if (res.destroyed)
|
|
305
|
+
return;
|
|
306
|
+
if (!res.headersSent) {
|
|
307
|
+
res.writeHead(502);
|
|
308
|
+
res.end(unavailableMessage);
|
|
309
|
+
}
|
|
310
|
+
else
|
|
311
|
+
res.destroy();
|
|
312
|
+
};
|
|
313
|
+
const up = http.request({ host: upstreamHost, port: upstreamPort, path: path ?? req.url, method: req.method, headers }, (received) => {
|
|
314
|
+
if (settled || res.destroyed) {
|
|
315
|
+
received.destroy();
|
|
316
|
+
up.destroy();
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
upstreamResponse = received;
|
|
320
|
+
received.once('aborted', failFromUpstream);
|
|
321
|
+
received.once('error', failFromUpstream);
|
|
322
|
+
received.once('close', () => { if (!received.complete)
|
|
323
|
+
failFromUpstream(); });
|
|
324
|
+
const type = String(received.headers['content-type'] || '');
|
|
325
|
+
const skip = !wantsGzip(req) || received.headers['content-encoding'] || !COMPRESSIBLE.test(type) || type.startsWith('text/event-stream');
|
|
326
|
+
if (skip) {
|
|
327
|
+
res.writeHead(received.statusCode || 502, received.headers);
|
|
328
|
+
received.pipe(res);
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
const headers = { ...received.headers, 'content-encoding': 'gzip', vary: 'Accept-Encoding' };
|
|
332
|
+
delete headers['content-length']; // streamed; the encoded length isn't knowable up front
|
|
333
|
+
res.writeHead(received.statusCode || 502, headers);
|
|
334
|
+
transform = createGzip();
|
|
335
|
+
transform.once('error', failFromUpstream);
|
|
336
|
+
received.pipe(transform).pipe(res);
|
|
337
|
+
});
|
|
338
|
+
up.once('error', failFromUpstream);
|
|
339
|
+
req.once('aborted', abortFromDownstream);
|
|
340
|
+
req.once('error', abortFromDownstream);
|
|
341
|
+
req.once('end', onRequestEnd);
|
|
342
|
+
req.once('close', onRequestClose);
|
|
343
|
+
res.once('error', abortFromDownstream);
|
|
344
|
+
res.once('close', onResponseClose);
|
|
345
|
+
res.once('finish', onResponseFinish);
|
|
346
|
+
req.pipe(up);
|
|
347
|
+
}
|
|
348
|
+
function webRoute(raw) {
|
|
349
|
+
const q = raw.indexOf('?');
|
|
350
|
+
const path = q >= 0 ? raw.slice(0, q) : raw;
|
|
351
|
+
const match = path.match(/^\/web\/([^/]+)\/([A-Za-z0-9_-]+)(\/.*)?$/);
|
|
352
|
+
if (!match)
|
|
353
|
+
return null;
|
|
354
|
+
return { sessionId: decodeURIComponent(match[1]), key: match[2], tail: match[3] || '/', query: q >= 0 ? raw.slice(q) : '' };
|
|
355
|
+
}
|
|
356
|
+
function sessionWebTarget(route, projectRoot) {
|
|
357
|
+
const endpoint = postedSessionWeb(route.sessionId, route.key, projectRoot);
|
|
358
|
+
const base = new URL('.', endpoint).pathname;
|
|
359
|
+
const path = route.tail === '/' ? endpoint.pathname : `${base}${route.tail.slice(1)}`;
|
|
360
|
+
return { port: Number(endpoint.port), host: endpoint.host, connectHost: endpoint.hostname.replace(/^\[|\]$/g, ''), path: `${path}${route.query}` };
|
|
361
|
+
}
|
|
362
|
+
function withoutGatewayCookies(cookie) {
|
|
363
|
+
return (cookie ?? '').split(';').map((part) => part.trim())
|
|
364
|
+
.filter((part) => part && !/^spex_(admin|proj|auth)_/i.test(part.slice(0, Math.max(part.indexOf('='), 0))))
|
|
365
|
+
.join('; ');
|
|
366
|
+
}
|
|
367
|
+
function webHeaders(req, host) {
|
|
368
|
+
const headers = { ...req.headers, host };
|
|
369
|
+
const kept = withoutGatewayCookies(req.headers.cookie);
|
|
370
|
+
if (kept)
|
|
371
|
+
headers.cookie = kept;
|
|
372
|
+
else
|
|
373
|
+
delete headers.cookie;
|
|
374
|
+
return headers;
|
|
375
|
+
}
|
|
376
|
+
function webRawHeaders(req, host) {
|
|
377
|
+
let out = '';
|
|
378
|
+
let wroteHost = false;
|
|
379
|
+
for (let i = 0; i < req.rawHeaders.length; i += 2) {
|
|
380
|
+
const name = req.rawHeaders[i];
|
|
381
|
+
const value = req.rawHeaders[i + 1];
|
|
382
|
+
if (name.toLowerCase() === 'host') {
|
|
383
|
+
out += `Host: ${host}\r\n`;
|
|
384
|
+
wroteHost = true;
|
|
385
|
+
continue;
|
|
386
|
+
}
|
|
387
|
+
if (name.toLowerCase() === 'cookie') {
|
|
388
|
+
const kept = withoutGatewayCookies(value);
|
|
389
|
+
if (kept)
|
|
390
|
+
out += `Cookie: ${kept}\r\n`;
|
|
391
|
+
continue;
|
|
392
|
+
}
|
|
393
|
+
out += `${name}: ${value}\r\n`;
|
|
394
|
+
}
|
|
395
|
+
return `${out}${wroteHost ? '' : `Host: ${host}\r\n`}\r\n`;
|
|
396
|
+
}
|
|
397
|
+
function sessionWebFailure(res, error) {
|
|
398
|
+
if (error instanceof SessionWebError) {
|
|
399
|
+
res.writeHead(error.status, { 'Content-Type': 'text/plain; charset=utf-8', 'Cache-Control': 'no-store' });
|
|
400
|
+
res.end(error.message);
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
console.error(`[web] route failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
404
|
+
res.writeHead(500, { 'Content-Type': 'text/plain; charset=utf-8', 'Cache-Control': 'no-store' });
|
|
405
|
+
res.end('web preview failed');
|
|
406
|
+
}
|
|
407
|
+
// A posted URL selects a loopback service; the gateway owns only the prefix and transport. It never opens
|
|
408
|
+
// an endpoint until a browser asks for this route, so each request observes the service's current bytes.
|
|
409
|
+
export function proxySessionWeb(req, res, raw = req.url || '/', projectRoot) {
|
|
410
|
+
const route = webRoute(raw);
|
|
411
|
+
if (!route)
|
|
412
|
+
return false;
|
|
413
|
+
try {
|
|
414
|
+
const target = sessionWebTarget(route, projectRoot);
|
|
415
|
+
proxyHttp(req, res, target.port, target.path, webHeaders(req, target.host), 'posted web service is unavailable', target.connectHost);
|
|
416
|
+
}
|
|
417
|
+
catch (error) {
|
|
418
|
+
sessionWebFailure(res, error);
|
|
419
|
+
}
|
|
420
|
+
return true;
|
|
421
|
+
}
|
|
422
|
+
export function proxySessionWebUpgrade(req, socket, head, raw = req.url || '/', projectRoot) {
|
|
423
|
+
const route = webRoute(raw);
|
|
424
|
+
if (!route)
|
|
425
|
+
return false;
|
|
426
|
+
let target;
|
|
427
|
+
try {
|
|
428
|
+
target = sessionWebTarget(route, projectRoot);
|
|
429
|
+
}
|
|
430
|
+
catch {
|
|
431
|
+
socket.destroy();
|
|
432
|
+
return true;
|
|
433
|
+
}
|
|
434
|
+
const upstream = net.connect(target.port, target.connectHost, () => {
|
|
435
|
+
upstream.write(`${req.method} ${target.path} HTTP/1.1\r\n` + webRawHeaders(req, target.host));
|
|
436
|
+
if (head.length)
|
|
437
|
+
upstream.write(head);
|
|
438
|
+
socket.pipe(upstream);
|
|
439
|
+
upstream.pipe(socket);
|
|
440
|
+
});
|
|
441
|
+
const bail = () => { socket.destroy(); upstream.destroy(); };
|
|
442
|
+
socket.on('error', bail);
|
|
443
|
+
upstream.on('error', bail);
|
|
444
|
+
socket.on('end', bail);
|
|
445
|
+
upstream.on('end', bail);
|
|
446
|
+
socket.once('close', () => upstream.destroy());
|
|
447
|
+
upstream.once('close', () => socket.destroy());
|
|
448
|
+
return true;
|
|
449
|
+
}
|
|
450
|
+
// serve the built dashboard (vite dist). Unknown non-file paths fall back to index.html (SPA). Path
|
|
451
|
+
// traversal is blocked by normalising and confining to distDir. Compressible files ship gzipped, memoized
|
|
452
|
+
// per (path, mtime) — a dist file is immutable per build, so each is compressed once, not per request.
|
|
453
|
+
// Exported: the host gateway serves the same dist through the same function.
|
|
454
|
+
const gzMemo = new Map();
|
|
455
|
+
export function serveStatic(req, res, distDir, urlPath) {
|
|
456
|
+
const rel = normalize(decodeURIComponent(urlPath)).replace(/^(\.\.[/\\])+/, '');
|
|
457
|
+
let file = join(distDir, rel);
|
|
458
|
+
if (!file.startsWith(distDir))
|
|
459
|
+
file = join(distDir, 'index.html');
|
|
460
|
+
if (urlPath === '/' || !existsSync(file)) {
|
|
461
|
+
// @@@ missing-asset 404 - a missing extensioned path is a stale hashed chunk, not an SPA route: answer
|
|
462
|
+
// 404, not HTML (which trips the module-MIME check), so the shell's reload recovery sees it ([[public-mode]]).
|
|
463
|
+
if (urlPath !== '/' && extname(file)) {
|
|
464
|
+
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
465
|
+
return res.end('not found');
|
|
466
|
+
}
|
|
467
|
+
file = join(distDir, 'index.html');
|
|
468
|
+
}
|
|
469
|
+
if (!existsSync(file)) {
|
|
470
|
+
res.writeHead(503);
|
|
471
|
+
return res.end('dashboard build missing');
|
|
472
|
+
}
|
|
473
|
+
const type = MIME[extname(file)] || 'application/octet-stream';
|
|
474
|
+
const cacheControl = /[\\/]assets[\\/]/.test(file) ? 'public, max-age=31536000, immutable' : 'no-cache';
|
|
475
|
+
const raw = readFileSync(file);
|
|
476
|
+
if (wantsGzip(req) && COMPRESSIBLE.test(type)) {
|
|
477
|
+
const mtime = statSync(file).mtimeMs;
|
|
478
|
+
let hit = gzMemo.get(file);
|
|
479
|
+
if (!hit || hit.mtime !== mtime) {
|
|
480
|
+
hit = { mtime, gz: gzipSync(raw) };
|
|
481
|
+
gzMemo.set(file, hit);
|
|
482
|
+
}
|
|
483
|
+
res.writeHead(200, { 'Content-Type': type, 'Content-Encoding': 'gzip', Vary: 'Accept-Encoding', 'Cache-Control': cacheControl });
|
|
484
|
+
return res.end(hit.gz);
|
|
485
|
+
}
|
|
486
|
+
res.writeHead(200, { 'Content-Type': type, 'Cache-Control': cacheControl });
|
|
487
|
+
res.end(raw);
|
|
488
|
+
}
|
|
489
|
+
function sendHtml(res, status, html) {
|
|
490
|
+
res.writeHead(status, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
491
|
+
res.end(html);
|
|
492
|
+
}
|
|
493
|
+
// @@@ serveDashboardLocal - the engine behind `spex serve ui`: the SAME gateway as public mode, bound to
|
|
494
|
+
// loopback by default with no TLS and no password — `--host` widens the bind to a chosen interface
|
|
495
|
+
// (LAN/tailnet viewing) while staying plain HTTP; the internet face remains `spex serve --public`.
|
|
496
|
+
// It serves the bundled dist and proxies /api + the terminal socket to a separately-run `spex serve`.
|
|
497
|
+
// This is the post-install replacement for the dogfood-only `npm run web` (a vite dev server an
|
|
498
|
+
// installed user has no source tree for). See [[packaging]].
|
|
499
|
+
export function serveDashboardLocal(opts) {
|
|
500
|
+
const distDir = resolveDistDir();
|
|
501
|
+
console.log(`[dashboard] serving ${distDir}, /api → backend :${opts.apiPort}`);
|
|
502
|
+
startGateway({ host: opts.host ?? '127.0.0.1', publicPort: opts.port, upstreamPort: opts.apiPort, password: '', tls: null, distDir, label: 'dashboard', projectRoot: opts.projectRoot });
|
|
503
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { buildBoard } from './graphSnapshot.js';
|
|
2
|
+
export type Board = Awaited<ReturnType<typeof buildBoard>>;
|
|
3
|
+
export type BoardConsistency = 'fresh' | 'stale-ok';
|
|
4
|
+
export type BoardRead = {
|
|
5
|
+
board: Board;
|
|
6
|
+
freshness: 'fresh' | 'stale';
|
|
7
|
+
refreshing: boolean;
|
|
8
|
+
error?: string;
|
|
9
|
+
};
|
|
10
|
+
export type BoardJsonRead = BoardRead & {
|
|
11
|
+
json: string;
|
|
12
|
+
};
|
|
13
|
+
export type RevisionPublication = {
|
|
14
|
+
revision: () => number;
|
|
15
|
+
invalidate: () => void;
|
|
16
|
+
wait: () => Promise<void>;
|
|
17
|
+
};
|
|
18
|
+
type Scope = 'sessions' | 'full';
|
|
19
|
+
export declare function invalidateBoard(scope?: Scope): void;
|
|
20
|
+
export declare function getBoard(): Promise<Board>;
|
|
21
|
+
export declare function waitForPublishedRevision(required: number, publication: RevisionPublication): Promise<void>;
|
|
22
|
+
export declare function getBoardForForgeRevision(required: number): Promise<Board>;
|
|
23
|
+
export declare function getBoardForSessionRefresh(): Promise<Board>;
|
|
24
|
+
export declare function patrolBoard(): Promise<Board>;
|
|
25
|
+
export declare function readBoard(consistency?: BoardConsistency): Promise<BoardRead>;
|
|
26
|
+
export declare function getBoardJson(consistency?: BoardConsistency): Promise<BoardJsonRead>;
|
|
27
|
+
export {};
|