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
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { mkdirSync, readFileSync, existsSync, readdirSync, rmSync } from 'node:fs';
|
|
2
|
+
import { join, dirname } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { copyFileIfChanged, writeFileIfChanged } from './file-write.js';
|
|
5
|
+
// @@@ plugin-harness - the PLUGIN BUNDLE emitter: materialize the whole SpexCode system into ONE self-contained
|
|
6
|
+
// Claude-plugin bundle dropped into the host-agent-scanned folder [[harness-select]] resolved (e.g. `.adopter-a` /
|
|
7
|
+
// `.claude` → <folder>/plugins/spexcode/). It is the plugin-target counterpart of the native per-harness
|
|
8
|
+
// write [[harness-adapter]] does — chosen INSTEAD of the natives (plugin exclusivity), so [[harness-delivery]]'s
|
|
9
|
+
// materialize prunes every native first, then emits this. The bundle follows the de-facto Claude-plugin schema
|
|
10
|
+
// (a `.claude-plugin/plugin.json` pointing at hooks/skills/commands/agents); the host's discovery order is
|
|
11
|
+
// `.adopter-a-plugin > .claude-plugin > .codex-plugin` and adopter-a/Claude both read a `.claude-plugin` directly, so
|
|
12
|
+
// the ONE `.claude-plugin` bundle reaches AdopterA, Claude, and (future) Codex from a single emit.
|
|
13
|
+
//
|
|
14
|
+
// The contract is NOT delivered by an always-on CLAUDE.md block here (the bundle never touches the repo's own
|
|
15
|
+
// files) — it maps to a SessionStart hook that emits hookSpecificOutput.additionalContext (the harness-neutral
|
|
16
|
+
// injection Claude/adopter-a normalize, the superpowers pattern), so a plugin host gets the contract with no
|
|
17
|
+
// --append-system-prompt. The hooks reuse the SAME dispatch.sh wiring as the natives, located via the host's
|
|
18
|
+
// ${CLAUDE_PLUGIN_ROOT} variable; dispatch.sh's first arg is the harness id `plugin`, so its shell mirror
|
|
19
|
+
// (harness.sh) parses payloads as the claude family (adopter-a/Claude share Claude's tool names + file_path).
|
|
20
|
+
const PKG = fileURLToPath(new URL('..', import.meta.url)); // installed spec-cli root
|
|
21
|
+
const HOOKS_SRC = join(PKG, 'hooks'); // the canonical dispatch.sh + harness.sh source
|
|
22
|
+
const PLUGIN_NAME = 'spexcode';
|
|
23
|
+
// the host substitutes ${CLAUDE_PLUGIN_ROOT} with the bundle's own absolute path before running a hook command
|
|
24
|
+
// (the same variable adopter-a's hook-compat honours), so dispatch.sh/inject-contract.sh resolve regardless of
|
|
25
|
+
// where the host scanned the bundle from.
|
|
26
|
+
const PLUGIN_ROOT = '${CLAUDE_PLUGIN_ROOT}';
|
|
27
|
+
// the lifecycle events the bundle binds — the Claude/adopter-a superset; a host that fires fewer (Codex) simply
|
|
28
|
+
// never invokes the extras, so binding all is harmless and one emit serves every host.
|
|
29
|
+
const PLUGIN_EVENTS = ['SessionStart', 'UserPromptSubmit', 'PreToolUse', 'PostToolUse', 'Stop', 'StopFailure', 'Notification'];
|
|
30
|
+
// the bundle dir for a plugin folder: <proj>/<folder>/plugins/spexcode — `<folder>` is the host's plugins-scan
|
|
31
|
+
// root ([[harness-select]] requires it explicit), `plugins/<name>` the conventional bundle location under it.
|
|
32
|
+
export function pluginBundleDir(proj, folder) {
|
|
33
|
+
return join(proj, folder, 'plugins', PLUGIN_NAME);
|
|
34
|
+
}
|
|
35
|
+
// the de-facto Claude-plugin manifest: name (the bundle identity clean() gates on), version, description, and
|
|
36
|
+
// the component pointers Claude/adopter-a discover (hooks.json + the skills/commands/agents dirs).
|
|
37
|
+
function pluginManifest(version) {
|
|
38
|
+
return JSON.stringify({
|
|
39
|
+
name: PLUGIN_NAME,
|
|
40
|
+
version,
|
|
41
|
+
description: 'SpexCode — spec-driven dev-flow contract, hooks, skills, commands & agents as one self-contained plugin.',
|
|
42
|
+
hooks: './hooks/hooks.json',
|
|
43
|
+
commands: './commands',
|
|
44
|
+
agents: './agents',
|
|
45
|
+
skills: './skills',
|
|
46
|
+
}, null, 2);
|
|
47
|
+
}
|
|
48
|
+
// hooks.json in the Claude/adopter-a-compatible shape { "hooks": { "<Event>": [{ "hooks": [command…] }] } }. Every
|
|
49
|
+
// event → the SHARED dispatch.sh (`plugin` baked as its harness id, SPEX inherited by handlers); SessionStart
|
|
50
|
+
// ALSO runs inject-contract.sh first, so the contract additionalContext lands alongside the normal dispatch.
|
|
51
|
+
function pluginHooksJson(spex) {
|
|
52
|
+
const dispatch = (e) => `SPEX='${spex}' bash "${PLUGIN_ROOT}/hooks/dispatch.sh" plugin ${e}`;
|
|
53
|
+
const inject = `bash "${PLUGIN_ROOT}/hooks/inject-contract.sh"`;
|
|
54
|
+
const hooks = {};
|
|
55
|
+
for (const e of PLUGIN_EVENTS) {
|
|
56
|
+
const cmds = e === 'SessionStart'
|
|
57
|
+
? [{ type: 'command', command: inject }, { type: 'command', command: dispatch(e) }]
|
|
58
|
+
: [{ type: 'command', command: dispatch(e) }];
|
|
59
|
+
hooks[e] = [{ hooks: cmds }];
|
|
60
|
+
}
|
|
61
|
+
return JSON.stringify({ hooks }, null, 2);
|
|
62
|
+
}
|
|
63
|
+
// the SessionStart hook OUTPUT carrying the contract as additionalContext. JSON-encoded HERE, at materialize time,
|
|
64
|
+
// so the runtime hook is a trivial `cat` — never a fragile shell escaping of arbitrary contract prose.
|
|
65
|
+
function contractContextJson(contract) {
|
|
66
|
+
return JSON.stringify({ hookSpecificOutput: { hookEventName: 'SessionStart', additionalContext: contract } }, null, 2);
|
|
67
|
+
}
|
|
68
|
+
// the SessionStart contract injector: print the pre-encoded additionalContext payload (the harness-neutral
|
|
69
|
+
// stand-in for --append-system-prompt). A bare cat — all the encoding happened at materialize time.
|
|
70
|
+
const INJECT_SH = `#!/usr/bin/env bash
|
|
71
|
+
# Emit the SpexCode contract as SessionStart additionalContext — the harness-neutral contract injection (the
|
|
72
|
+
# superpowers pattern; Claude/adopter-a normalize hookSpecificOutput.additionalContext) that replaces a plugin
|
|
73
|
+
# host's missing --append-system-prompt. The JSON was written at materialize time, so this is a trivial cat.
|
|
74
|
+
here="$(cd "$(dirname "\${BASH_SOURCE[0]}")" && pwd)"
|
|
75
|
+
[ -f "$here/contract-context.json" ] && cat "$here/contract-context.json"
|
|
76
|
+
exit 0
|
|
77
|
+
`;
|
|
78
|
+
// emit (or idempotently re-emit) the whole bundle into <folder>/plugins/spexcode.
|
|
79
|
+
export function emitPlugin(proj, folder, r) {
|
|
80
|
+
const bundle = pluginBundleDir(proj, folder);
|
|
81
|
+
const meta = join(bundle, '.claude-plugin');
|
|
82
|
+
const hooksDir = join(bundle, 'hooks');
|
|
83
|
+
mkdirSync(meta, { recursive: true });
|
|
84
|
+
mkdirSync(hooksDir, { recursive: true });
|
|
85
|
+
writeFileIfChanged(join(meta, 'plugin.json'), pluginManifest(r.version));
|
|
86
|
+
// hooks: the SHARED dispatcher + its shell mirror (copied verbatim — the exact native wiring), the contract
|
|
87
|
+
// injector + its pre-encoded payload, and the event→dispatch binding.
|
|
88
|
+
copyFileIfChanged(join(HOOKS_SRC, 'dispatch.sh'), join(hooksDir, 'dispatch.sh'));
|
|
89
|
+
copyFileIfChanged(join(HOOKS_SRC, 'harness.sh'), join(hooksDir, 'harness.sh'));
|
|
90
|
+
writeFileIfChanged(join(hooksDir, 'inject-contract.sh'), INJECT_SH);
|
|
91
|
+
writeFileIfChanged(join(hooksDir, 'contract-context.json'), contractContextJson(r.contract));
|
|
92
|
+
writeFileIfChanged(join(hooksDir, 'hooks.json'), pluginHooksJson(r.spex));
|
|
93
|
+
// skills / agents / commands — the Claude-plugin layout, the SAME materialized contents as the native dirs.
|
|
94
|
+
reconcileBundleDirectory(join(bundle, 'skills'), new Set(r.skills.map((s) => s.name)));
|
|
95
|
+
reconcileBundleDirectory(join(bundle, 'agents'), new Set(r.agents.map((a) => `${a.name}.md`)));
|
|
96
|
+
reconcileBundleDirectory(join(bundle, 'commands'), new Set(r.commands.map((c) => `${c.name}.md`)));
|
|
97
|
+
for (const s of r.skills)
|
|
98
|
+
writeBundleFile(join(bundle, 'skills', s.name, 'SKILL.md'), s.content);
|
|
99
|
+
for (const a of r.agents)
|
|
100
|
+
writeBundleFile(join(bundle, 'agents', `${a.name}.md`), a.content);
|
|
101
|
+
for (const c of r.commands)
|
|
102
|
+
writeBundleFile(join(bundle, 'commands', `${c.name}.md`), c.content);
|
|
103
|
+
}
|
|
104
|
+
function reconcileBundleDirectory(dir, expectedNames) {
|
|
105
|
+
if (!existsSync(dir))
|
|
106
|
+
return;
|
|
107
|
+
for (const entry of readdirSync(dir)) {
|
|
108
|
+
if (!expectedNames.has(entry))
|
|
109
|
+
rmSync(join(dir, entry), { recursive: true, force: true });
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
function writeBundleFile(f, content) {
|
|
113
|
+
mkdirSync(dirname(f), { recursive: true });
|
|
114
|
+
writeFileIfChanged(f, content);
|
|
115
|
+
}
|
|
116
|
+
// the INVERSE of emitPlugin — prune the bundle when its folder is DESELECTED ([[harness-delivery]] tracks the
|
|
117
|
+
// previously-emitted folders and cleans any the current set dropped, e.g. switching plugin→native or folder A→B).
|
|
118
|
+
// Identity-gated on the bundle's own plugin.json `name`, so it removes ONLY a spexcode bundle, never a folder
|
|
119
|
+
// the user populated with another plugin.
|
|
120
|
+
export function cleanPlugin(proj, folder) {
|
|
121
|
+
const bundle = pluginBundleDir(proj, folder);
|
|
122
|
+
const manifest = join(bundle, '.claude-plugin', 'plugin.json');
|
|
123
|
+
if (!existsSync(manifest))
|
|
124
|
+
return;
|
|
125
|
+
try {
|
|
126
|
+
if (String(JSON.parse(readFileSync(manifest, 'utf8'))?.name) !== PLUGIN_NAME)
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
rmSync(bundle, { recursive: true, force: true });
|
|
133
|
+
}
|
|
134
|
+
// the bundle version stamped into plugin.json — the monorepo root `spexcode` package version (the published
|
|
135
|
+
// artifact's version), falling back to the spec-cli package, then 0.0.0.
|
|
136
|
+
export function pluginVersion() {
|
|
137
|
+
for (const p of [join(PKG, '..', 'package.json'), join(PKG, 'package.json')]) {
|
|
138
|
+
try {
|
|
139
|
+
const v = JSON.parse(readFileSync(p, 'utf8'))?.version;
|
|
140
|
+
if (v)
|
|
141
|
+
return String(v);
|
|
142
|
+
}
|
|
143
|
+
catch { /* keep trying */ }
|
|
144
|
+
}
|
|
145
|
+
return '0.0.0';
|
|
146
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type Viewer = {
|
|
2
|
+
send: (data: Buffer) => void;
|
|
3
|
+
commitSize?: (cols: number, rows: number) => void;
|
|
4
|
+
};
|
|
5
|
+
export declare function attachViewer(id: string, viewer: Viewer): void;
|
|
6
|
+
export declare function hideViewer(id: string, viewer: Viewer): void;
|
|
7
|
+
export declare function detachViewer(id: string, viewer: Viewer): void;
|
|
8
|
+
export declare function resizeBridge(id: string, viewer: Viewer, colsValue: number, rowsValue: number): void;
|
|
9
|
+
export declare function forwardInput(id: string, viewer: Viewer, data: string): boolean;
|
|
10
|
+
export declare function superviseBridges(intervalMs?: number): void;
|
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
import { execFile, spawn } from 'node:child_process';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
import { promisify } from 'node:util';
|
|
4
|
+
import { alive, withSessionInputLock } from './sessions.js';
|
|
5
|
+
const pexec = promisify(execFile);
|
|
6
|
+
const TMUX_SOCK = process.env.SPEXCODE_TMUX || 'spexcode';
|
|
7
|
+
const HELPER = fileURLToPath(new URL('./pty-helper.mjs', import.meta.url));
|
|
8
|
+
const subscribers = new Map();
|
|
9
|
+
const BSU = Buffer.from('\x1b[?2026h');
|
|
10
|
+
const ESU = Buffer.from('\x1b[?2026l');
|
|
11
|
+
const GEOMETRY_STABILIZATION_MS = 400;
|
|
12
|
+
// A hidden but still-alive browser keeps its own client briefly so a quick return is continuous. A dead
|
|
13
|
+
// socket bypasses this window and detaches immediately.
|
|
14
|
+
const LINGER_MS = Number(process.env.SPEXCODE_TERM_LINGER_MS) > 0 ? Number(process.env.SPEXCODE_TERM_LINGER_MS) : 30_000;
|
|
15
|
+
function subscriptionMap(id) {
|
|
16
|
+
let map = subscribers.get(id);
|
|
17
|
+
if (!map)
|
|
18
|
+
subscribers.set(id, map = new Map());
|
|
19
|
+
return map;
|
|
20
|
+
}
|
|
21
|
+
function currentSubscription(id, viewer) {
|
|
22
|
+
return subscribers.get(id)?.get(viewer);
|
|
23
|
+
}
|
|
24
|
+
function isCurrent(bridge) {
|
|
25
|
+
return currentSubscription(bridge.id, bridge.viewer)?.bridge === bridge;
|
|
26
|
+
}
|
|
27
|
+
function deliver(bridge, data) {
|
|
28
|
+
const subscription = currentSubscription(bridge.id, bridge.viewer);
|
|
29
|
+
if (!subscription || (!subscription.visible && !subscription.lingering))
|
|
30
|
+
return;
|
|
31
|
+
try {
|
|
32
|
+
bridge.viewer.send(data);
|
|
33
|
+
}
|
|
34
|
+
catch { /* socket close or heartbeat expiry owns removal */ }
|
|
35
|
+
}
|
|
36
|
+
function commitSize(bridge) {
|
|
37
|
+
const subscription = currentSubscription(bridge.id, bridge.viewer);
|
|
38
|
+
if (!subscription?.visible)
|
|
39
|
+
return;
|
|
40
|
+
try {
|
|
41
|
+
bridge.viewer.commitSize?.(bridge.cols, bridge.rows);
|
|
42
|
+
}
|
|
43
|
+
catch { /* socket close owns removal */ }
|
|
44
|
+
}
|
|
45
|
+
async function tmux(args) {
|
|
46
|
+
try {
|
|
47
|
+
const { stdout } = await pexec('tmux', ['-L', TMUX_SOCK, ...args]);
|
|
48
|
+
return stdout.trim();
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return '';
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function onHelperOutput(bridge, chunk) {
|
|
55
|
+
if (!isCurrent(bridge))
|
|
56
|
+
return;
|
|
57
|
+
if (bridge.delivery === 'stream') {
|
|
58
|
+
deliver(bridge, chunk);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
bridge.refreshBuf = bridge.refreshBuf.length ? Buffer.concat([bridge.refreshBuf, chunk]) : chunk;
|
|
62
|
+
releaseInitialRefresh(bridge);
|
|
63
|
+
}
|
|
64
|
+
function sendControl(bridge, message) {
|
|
65
|
+
try {
|
|
66
|
+
bridge.proc.stdin.write(`${JSON.stringify(message)}\n`);
|
|
67
|
+
}
|
|
68
|
+
catch { /* exit recovery owns retry */ }
|
|
69
|
+
}
|
|
70
|
+
function onHelperStderr(bridge, chunk) {
|
|
71
|
+
bridge.stderr += chunk.toString('utf8');
|
|
72
|
+
let newline;
|
|
73
|
+
while ((newline = bridge.stderr.indexOf('\n')) >= 0) {
|
|
74
|
+
const line = bridge.stderr.slice(0, newline);
|
|
75
|
+
bridge.stderr = bridge.stderr.slice(newline + 1);
|
|
76
|
+
const ready = line.match(/^READY (\d+)$/);
|
|
77
|
+
if (ready) {
|
|
78
|
+
bridge.ptyPid = Number(ready[1]);
|
|
79
|
+
const subscription = currentSubscription(bridge.id, bridge.viewer);
|
|
80
|
+
if (subscription)
|
|
81
|
+
subscription.startupError = undefined;
|
|
82
|
+
if (bridge.delivery === 'initial') {
|
|
83
|
+
armDeliveryBoundary(bridge);
|
|
84
|
+
queueRefresh(bridge);
|
|
85
|
+
}
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
const resized = line.match(/^RESIZED (\d+) (\d+)$/);
|
|
89
|
+
if (resized) {
|
|
90
|
+
if (bridge.delivery === 'initial'
|
|
91
|
+
&& Number(resized[1]) === bridge.cols
|
|
92
|
+
&& Number(resized[2]) === bridge.rows) {
|
|
93
|
+
queueRefresh(bridge);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
else if (line) {
|
|
97
|
+
const failed = line.match(/^ERROR (.+)$/);
|
|
98
|
+
if (failed)
|
|
99
|
+
reportStartupError(bridge, failed[1]);
|
|
100
|
+
console.error(`[terminal helper ${bridge.id}/${bridge.ptyPid ?? 'starting'}] ${line}`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function reportStartupError(bridge, detail) {
|
|
105
|
+
const subscription = currentSubscription(bridge.id, bridge.viewer);
|
|
106
|
+
if (!subscription || subscription.bridge !== bridge)
|
|
107
|
+
return;
|
|
108
|
+
const message = boundedStartupError(detail);
|
|
109
|
+
bridge.startupError = message;
|
|
110
|
+
if (!message || subscription.startupError === message)
|
|
111
|
+
return;
|
|
112
|
+
subscription.startupError = message;
|
|
113
|
+
deliver(bridge, Buffer.from(`\r\n[SpexCode terminal unavailable] ${message}\r\n`, 'utf8'));
|
|
114
|
+
}
|
|
115
|
+
function boundedStartupError(detail) {
|
|
116
|
+
return (detail instanceof Error ? detail.message : String(detail))
|
|
117
|
+
.replace(/[\x00-\x1f\x7f]+/g, ' ').trim().slice(0, 500) || 'native PTY failed to start';
|
|
118
|
+
}
|
|
119
|
+
function ensureBridge(id, viewer, subscription, cols, rows) {
|
|
120
|
+
if (subscription.bridge)
|
|
121
|
+
return { bridge: subscription.bridge, created: false };
|
|
122
|
+
let proc;
|
|
123
|
+
try {
|
|
124
|
+
proc = spawn(process.execPath, [HELPER, id, String(cols), String(rows)], {
|
|
125
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
126
|
+
env: process.env,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
try {
|
|
131
|
+
proc?.kill();
|
|
132
|
+
}
|
|
133
|
+
catch { /* spawn did not complete */ }
|
|
134
|
+
const detail = boundedStartupError(error);
|
|
135
|
+
if (subscription.startupError !== detail) {
|
|
136
|
+
subscription.startupError = detail;
|
|
137
|
+
try {
|
|
138
|
+
viewer.send(Buffer.from(`\r\n[SpexCode terminal unavailable] ${detail}\r\n`, 'utf8'));
|
|
139
|
+
}
|
|
140
|
+
catch { /* socket closed */ }
|
|
141
|
+
}
|
|
142
|
+
return { bridge: null, created: false };
|
|
143
|
+
}
|
|
144
|
+
const bridge = {
|
|
145
|
+
id, viewer, proc, cols, rows, stderr: '', delivery: 'initial',
|
|
146
|
+
refreshBuf: Buffer.alloc(0), refreshPending: false, refreshRunning: false,
|
|
147
|
+
};
|
|
148
|
+
subscription.bridge = bridge;
|
|
149
|
+
proc.stdout.on('data', (data) => onHelperOutput(bridge, data));
|
|
150
|
+
proc.stderr.on('data', (data) => onHelperStderr(bridge, data));
|
|
151
|
+
let reaped = false;
|
|
152
|
+
const gone = (detail) => {
|
|
153
|
+
if (reaped)
|
|
154
|
+
return;
|
|
155
|
+
reaped = true;
|
|
156
|
+
const current = currentSubscription(id, viewer);
|
|
157
|
+
if (current?.bridge !== bridge)
|
|
158
|
+
return;
|
|
159
|
+
if (!bridge.ptyPid && !bridge.startupError)
|
|
160
|
+
reportStartupError(bridge, detail || 'helper exited before native PTY startup');
|
|
161
|
+
current.bridge = undefined;
|
|
162
|
+
clearDelivery(bridge);
|
|
163
|
+
try {
|
|
164
|
+
bridge.proc.kill();
|
|
165
|
+
}
|
|
166
|
+
catch { /* already gone */ }
|
|
167
|
+
scheduleRestore(id, viewer, current);
|
|
168
|
+
};
|
|
169
|
+
proc.on('exit', (code, signal) => gone(`helper exited before native PTY startup (${signal ? `signal ${signal}` : `code ${code ?? 'unknown'}`})`));
|
|
170
|
+
proc.on('error', (error) => gone(`helper process failed: ${error.message}`));
|
|
171
|
+
return { bridge, created: true };
|
|
172
|
+
}
|
|
173
|
+
function cancelLinger(subscription) {
|
|
174
|
+
if (subscription.lingerTimer)
|
|
175
|
+
clearTimeout(subscription.lingerTimer);
|
|
176
|
+
subscription.lingerTimer = undefined;
|
|
177
|
+
subscription.lingering = false;
|
|
178
|
+
}
|
|
179
|
+
function cancelRestore(subscription) {
|
|
180
|
+
if (subscription.restoreTimer)
|
|
181
|
+
clearTimeout(subscription.restoreTimer);
|
|
182
|
+
subscription.restoreTimer = undefined;
|
|
183
|
+
}
|
|
184
|
+
function killBridge(subscription) {
|
|
185
|
+
cancelLinger(subscription);
|
|
186
|
+
cancelRestore(subscription);
|
|
187
|
+
const bridge = subscription.bridge;
|
|
188
|
+
if (!bridge)
|
|
189
|
+
return;
|
|
190
|
+
subscription.bridge = undefined;
|
|
191
|
+
clearDelivery(bridge);
|
|
192
|
+
try {
|
|
193
|
+
bridge.proc.stdin.end();
|
|
194
|
+
}
|
|
195
|
+
catch { /* already gone */ }
|
|
196
|
+
const kill = setTimeout(() => { try {
|
|
197
|
+
bridge.proc.kill();
|
|
198
|
+
}
|
|
199
|
+
catch { /* already gone */ } }, 250);
|
|
200
|
+
kill.unref();
|
|
201
|
+
}
|
|
202
|
+
function beginDelivery(bridge) {
|
|
203
|
+
if (bridge.delivery === 'stream')
|
|
204
|
+
bridge.refreshBuf = Buffer.alloc(0);
|
|
205
|
+
bridge.delivery = 'initial';
|
|
206
|
+
bridge.refreshOffset = undefined;
|
|
207
|
+
armDeliveryBoundary(bridge);
|
|
208
|
+
}
|
|
209
|
+
function armDeliveryBoundary(bridge) {
|
|
210
|
+
if (bridge.deliveryTimer)
|
|
211
|
+
clearTimeout(bridge.deliveryTimer);
|
|
212
|
+
bridge.deliveryTimer = setTimeout(() => finishDelivery(bridge), GEOMETRY_STABILIZATION_MS);
|
|
213
|
+
bridge.deliveryTimer.unref();
|
|
214
|
+
}
|
|
215
|
+
function clearDelivery(bridge) {
|
|
216
|
+
bridge.delivery = 'stream';
|
|
217
|
+
if (bridge.deliveryTimer)
|
|
218
|
+
clearTimeout(bridge.deliveryTimer);
|
|
219
|
+
bridge.deliveryTimer = undefined;
|
|
220
|
+
bridge.refreshBuf = Buffer.alloc(0);
|
|
221
|
+
bridge.refreshPending = false;
|
|
222
|
+
bridge.refreshOffset = undefined;
|
|
223
|
+
}
|
|
224
|
+
function completeTransactionEnd(buffer, offset) {
|
|
225
|
+
const begin = buffer.indexOf(BSU, offset);
|
|
226
|
+
const end = begin >= 0 ? buffer.indexOf(ESU, begin + BSU.length) : -1;
|
|
227
|
+
return end >= 0 ? end + ESU.length : undefined;
|
|
228
|
+
}
|
|
229
|
+
function releaseInitialRefresh(bridge) {
|
|
230
|
+
if (bridge.delivery !== 'initial' || bridge.refreshOffset === undefined)
|
|
231
|
+
return;
|
|
232
|
+
const end = completeTransactionEnd(bridge.refreshBuf, bridge.refreshOffset);
|
|
233
|
+
if (end === undefined)
|
|
234
|
+
return;
|
|
235
|
+
const batch = bridge.refreshBuf.subarray(0, end);
|
|
236
|
+
bridge.refreshBuf = bridge.refreshBuf.subarray(end);
|
|
237
|
+
bridge.delivery = 'quarantine';
|
|
238
|
+
commitSize(bridge);
|
|
239
|
+
if (batch.length)
|
|
240
|
+
deliver(bridge, batch);
|
|
241
|
+
}
|
|
242
|
+
function finishDelivery(bridge) {
|
|
243
|
+
if (bridge.delivery === 'stream' || !isCurrent(bridge))
|
|
244
|
+
return;
|
|
245
|
+
const failOpen = bridge.delivery === 'initial';
|
|
246
|
+
const batch = bridge.refreshBuf;
|
|
247
|
+
clearDelivery(bridge);
|
|
248
|
+
if (!failOpen && !batch.length)
|
|
249
|
+
return;
|
|
250
|
+
// The control commit and following binary frame are one browser render transaction, including fail-open.
|
|
251
|
+
commitSize(bridge);
|
|
252
|
+
deliver(bridge, batch);
|
|
253
|
+
}
|
|
254
|
+
async function refreshBridge(bridge) {
|
|
255
|
+
if (!isCurrent(bridge) || !bridge.ptyPid)
|
|
256
|
+
return false;
|
|
257
|
+
const tty = await clientTty(bridge);
|
|
258
|
+
if (!tty)
|
|
259
|
+
return false;
|
|
260
|
+
await tmux(['refresh-client', '-t', tty]);
|
|
261
|
+
return true;
|
|
262
|
+
}
|
|
263
|
+
function queueRefresh(bridge) {
|
|
264
|
+
if (bridge.delivery !== 'initial')
|
|
265
|
+
return;
|
|
266
|
+
bridge.refreshPending = true;
|
|
267
|
+
if (bridge.refreshRunning)
|
|
268
|
+
return;
|
|
269
|
+
bridge.refreshPending = false;
|
|
270
|
+
bridge.refreshRunning = true;
|
|
271
|
+
void refreshBridge(bridge).then((refreshed) => {
|
|
272
|
+
bridge.refreshRunning = false;
|
|
273
|
+
if (!isCurrent(bridge) || bridge.delivery !== 'initial')
|
|
274
|
+
return;
|
|
275
|
+
if (bridge.refreshPending) {
|
|
276
|
+
queueRefresh(bridge);
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
if (!refreshed)
|
|
280
|
+
return;
|
|
281
|
+
bridge.refreshOffset = bridge.refreshBuf.length;
|
|
282
|
+
releaseInitialRefresh(bridge);
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
async function clientTty(bridge) {
|
|
286
|
+
if (bridge.clientTty)
|
|
287
|
+
return bridge.clientTty;
|
|
288
|
+
if (!bridge.ptyPid)
|
|
289
|
+
return undefined;
|
|
290
|
+
const clients = await tmux(['list-clients', '-t', bridge.id, '-F', '#{client_pid} #{client_tty}']);
|
|
291
|
+
for (const line of clients.split('\n')) {
|
|
292
|
+
const space = line.indexOf(' ');
|
|
293
|
+
if (space > 0 && Number(line.slice(0, space)) === bridge.ptyPid) {
|
|
294
|
+
return (bridge.clientTty = line.slice(space + 1).trim());
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
return undefined;
|
|
298
|
+
}
|
|
299
|
+
function resize(bridge, cols, rows) {
|
|
300
|
+
if (bridge.cols === cols && bridge.rows === rows)
|
|
301
|
+
return;
|
|
302
|
+
beginDelivery(bridge);
|
|
303
|
+
bridge.cols = cols;
|
|
304
|
+
bridge.rows = rows;
|
|
305
|
+
sendControl(bridge, { t: 'resize', cols, rows });
|
|
306
|
+
}
|
|
307
|
+
export function attachViewer(id, viewer) {
|
|
308
|
+
const map = subscriptionMap(id);
|
|
309
|
+
const previous = map.get(viewer);
|
|
310
|
+
if (previous)
|
|
311
|
+
killBridge(previous);
|
|
312
|
+
map.set(viewer, { visible: false, lingering: false, cols: 0, rows: 0 });
|
|
313
|
+
}
|
|
314
|
+
export function hideViewer(id, viewer) {
|
|
315
|
+
const subscription = currentSubscription(id, viewer);
|
|
316
|
+
if (!subscription)
|
|
317
|
+
return;
|
|
318
|
+
subscription.visible = false;
|
|
319
|
+
if (!subscription.bridge)
|
|
320
|
+
return;
|
|
321
|
+
subscription.lingering = true;
|
|
322
|
+
if (subscription.lingerTimer)
|
|
323
|
+
return;
|
|
324
|
+
subscription.lingerTimer = setTimeout(() => {
|
|
325
|
+
subscription.lingerTimer = undefined;
|
|
326
|
+
if (!subscription.visible && currentSubscription(id, viewer) === subscription)
|
|
327
|
+
killBridge(subscription);
|
|
328
|
+
}, LINGER_MS);
|
|
329
|
+
subscription.lingerTimer.unref();
|
|
330
|
+
}
|
|
331
|
+
export function detachViewer(id, viewer) {
|
|
332
|
+
const map = subscribers.get(id);
|
|
333
|
+
const subscription = map?.get(viewer);
|
|
334
|
+
if (!map || !subscription)
|
|
335
|
+
return;
|
|
336
|
+
// A closed/dead socket is not a hidden live tab: remove its native client now, never after linger.
|
|
337
|
+
killBridge(subscription);
|
|
338
|
+
map.delete(viewer);
|
|
339
|
+
if (map.size === 0)
|
|
340
|
+
subscribers.delete(id);
|
|
341
|
+
}
|
|
342
|
+
export function resizeBridge(id, viewer, colsValue, rowsValue) {
|
|
343
|
+
const cols = Math.floor(colsValue), rows = Math.floor(rowsValue);
|
|
344
|
+
if (!(cols > 0 && rows > 0))
|
|
345
|
+
return;
|
|
346
|
+
const subscription = currentSubscription(id, viewer);
|
|
347
|
+
if (!subscription)
|
|
348
|
+
return;
|
|
349
|
+
const seamless = subscription.lingering && !!subscription.bridge
|
|
350
|
+
&& subscription.bridge.cols === cols && subscription.bridge.rows === rows;
|
|
351
|
+
cancelLinger(subscription);
|
|
352
|
+
subscription.visible = true;
|
|
353
|
+
subscription.cols = cols;
|
|
354
|
+
subscription.rows = rows;
|
|
355
|
+
const { bridge, created } = ensureBridge(id, viewer, subscription, cols, rows);
|
|
356
|
+
if (!bridge || created || seamless)
|
|
357
|
+
return;
|
|
358
|
+
if (bridge.cols === cols && bridge.rows === rows) {
|
|
359
|
+
beginDelivery(bridge);
|
|
360
|
+
queueRefresh(bridge);
|
|
361
|
+
}
|
|
362
|
+
else {
|
|
363
|
+
resize(bridge, cols, rows);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
const MAX_INPUT_BYTES = 64 * 1024;
|
|
367
|
+
export function forwardInput(id, viewer, data) {
|
|
368
|
+
const subscription = currentSubscription(id, viewer);
|
|
369
|
+
if (!subscription?.visible || !subscription.bridge || !data || Buffer.byteLength(data, 'utf8') > MAX_INPUT_BYTES)
|
|
370
|
+
return false;
|
|
371
|
+
return withSessionInputLock(id, () => {
|
|
372
|
+
sendControl(subscription.bridge, { t: 'input', data });
|
|
373
|
+
return true;
|
|
374
|
+
}) ?? false;
|
|
375
|
+
}
|
|
376
|
+
async function restoreBridge(id, viewer, subscription) {
|
|
377
|
+
subscription.restoreTimer = undefined;
|
|
378
|
+
if (currentSubscription(id, viewer) !== subscription || subscription.bridge || !subscription.visible || !(await alive(id)))
|
|
379
|
+
return;
|
|
380
|
+
if (!(subscription.cols > 0 && subscription.rows > 0))
|
|
381
|
+
return;
|
|
382
|
+
ensureBridge(id, viewer, subscription, subscription.cols, subscription.rows);
|
|
383
|
+
}
|
|
384
|
+
function scheduleRestore(id, viewer, subscription) {
|
|
385
|
+
if (currentSubscription(id, viewer) !== subscription || !subscription.visible || subscription.bridge || subscription.restoreTimer)
|
|
386
|
+
return;
|
|
387
|
+
subscription.restoreTimer = setTimeout(() => void restoreBridge(id, viewer, subscription), 750);
|
|
388
|
+
subscription.restoreTimer.unref();
|
|
389
|
+
}
|
|
390
|
+
let supervising = false;
|
|
391
|
+
export function superviseBridges(intervalMs = 4000) {
|
|
392
|
+
if (supervising)
|
|
393
|
+
return;
|
|
394
|
+
supervising = true;
|
|
395
|
+
const tick = () => {
|
|
396
|
+
for (const [id, viewers] of subscribers) {
|
|
397
|
+
for (const [viewer, subscription] of viewers)
|
|
398
|
+
scheduleRestore(id, viewer, subscription);
|
|
399
|
+
}
|
|
400
|
+
const timer = setTimeout(tick, intervalMs);
|
|
401
|
+
timer.unref();
|
|
402
|
+
};
|
|
403
|
+
tick();
|
|
404
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import * as pty from 'node-pty';
|
|
2
|
+
import { execFileSync } from 'node:child_process';
|
|
3
|
+
import { ensureExecutableIfPresent, nodePtySpawnHelperPath } from './pty-native-helper.mjs';
|
|
4
|
+
const [id, colsArg, rowsArg] = process.argv.slice(2);
|
|
5
|
+
const cols = Number(colsArg);
|
|
6
|
+
const rows = Number(rowsArg);
|
|
7
|
+
const socket = process.env.SPEXCODE_TMUX || 'spexcode';
|
|
8
|
+
if (!id || !(cols > 0 && rows > 0)) {
|
|
9
|
+
process.stderr.write('ERROR invalid helper arguments\n');
|
|
10
|
+
process.exit(2);
|
|
11
|
+
}
|
|
12
|
+
// Establish the outer-terminal contract before this process owns a PTY. tmux must preserve hyperlinks and
|
|
13
|
+
// wrap client updates in DEC 2026 for xterm 6; doing the setup before forkpty preserves helper isolation.
|
|
14
|
+
try {
|
|
15
|
+
// The dashboard renders the pane, not tmux's client chrome. Keeping this as a tmux session option makes
|
|
16
|
+
// the requested PTY grid the pane grid too; filtering a coloured final row in xterm would corrupt real
|
|
17
|
+
// pane content. SpexCode owns these sessions, so a later foreground attach sees the same status-free pane.
|
|
18
|
+
execFileSync('tmux', ['-L', socket, 'set-option', '-t', id, 'status', 'off']);
|
|
19
|
+
// Control follows the person actually driving: `latest` sizes the window to the most recently
|
|
20
|
+
// active client, so a small screen takes the grid the moment its user interacts and sees the WHOLE
|
|
21
|
+
// pane, while an idle larger viewer letterboxes. `largest` optimized the big display's real estate
|
|
22
|
+
// but left a small concurrent viewer a cropped corner it could never take back.
|
|
23
|
+
execFileSync('tmux', ['-L', socket, 'set-window-option', '-t', id, 'window-size', 'latest']);
|
|
24
|
+
execFileSync('tmux', ['-L', socket, 'set-option', '-g', 'mouse', 'on']);
|
|
25
|
+
execFileSync('tmux', ['-L', socket, 'set-option', '-g', 'history-limit', '50000']);
|
|
26
|
+
// Wheel routing stays tmux-default: copy-mode history for a plain pane, pass-through to a
|
|
27
|
+
// mouse-owning TUI. Claude's TUI virtual-scrolls its own transcript on those reports (it lives on
|
|
28
|
+
// the alternate screen and repaints in place, so tmux history cannot substitute — an in-place
|
|
29
|
+
// renderer never scrolls lines off the top). Restore the default bindings idempotently in case an
|
|
30
|
+
// earlier release rebound them server-wide; the unbind may already be clean, so it fails quietly.
|
|
31
|
+
execFileSync('tmux', ['-L', socket, 'bind-key', '-n', 'WheelUpPane', 'if', '-F', '-t=', '#{||:#{pane_in_mode},#{mouse_any_flag}}', 'send-keys -M', 'copy-mode -et=']);
|
|
32
|
+
try {
|
|
33
|
+
execFileSync('tmux', ['-L', socket, 'unbind-key', '-n', 'WheelDownPane']);
|
|
34
|
+
}
|
|
35
|
+
catch { /* already unbound */ }
|
|
36
|
+
const features = execFileSync('tmux', ['-L', socket, 'show-options', '-gsv', 'terminal-features'], { encoding: 'utf8' });
|
|
37
|
+
const xtermFeatures = new Set(features.split('\n').filter((line) => line.startsWith('xterm*:')).flatMap((line) => line.split(':').slice(1)));
|
|
38
|
+
const missing = ['sync', 'hyperlinks'].filter((feature) => !xtermFeatures.has(feature));
|
|
39
|
+
if (missing.length) {
|
|
40
|
+
execFileSync('tmux', ['-L', socket, 'set-option', '-as', 'terminal-features', `,xterm*:${missing.join(':')}`]);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
catch { /* attach below fails loudly if the tmux server/session is unavailable */ }
|
|
44
|
+
let terminal;
|
|
45
|
+
try {
|
|
46
|
+
ensureExecutableIfPresent(nodePtySpawnHelperPath(pty.native));
|
|
47
|
+
terminal = pty.spawn('tmux', ['-u', '-L', socket, 'attach-session', '-t', id], {
|
|
48
|
+
name: 'xterm-256color',
|
|
49
|
+
cols,
|
|
50
|
+
rows,
|
|
51
|
+
env: { ...process.env, LANG: process.env.LANG || 'en_US.UTF-8' },
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
const detail = (error instanceof Error ? error.message : String(error))
|
|
56
|
+
.replace(/[\x00-\x1f\x7f]+/g, ' ').trim().slice(0, 500);
|
|
57
|
+
process.stderr.write(`ERROR ${detail || 'native PTY failed to start'}\n`);
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
terminal.onData((data) => process.stdout.write(Buffer.from(data, 'utf8')));
|
|
61
|
+
terminal.onExit(({ exitCode }) => process.exit(exitCode === 0 ? 0 : 1));
|
|
62
|
+
process.stderr.write(`READY ${terminal.pid}\n`);
|
|
63
|
+
let input = '';
|
|
64
|
+
process.stdin.setEncoding('utf8');
|
|
65
|
+
process.stdin.on('data', (chunk) => {
|
|
66
|
+
input += chunk;
|
|
67
|
+
let newline;
|
|
68
|
+
while ((newline = input.indexOf('\n')) >= 0) {
|
|
69
|
+
const line = input.slice(0, newline);
|
|
70
|
+
input = input.slice(newline + 1);
|
|
71
|
+
if (!line)
|
|
72
|
+
continue;
|
|
73
|
+
try {
|
|
74
|
+
const message = JSON.parse(line);
|
|
75
|
+
if (message?.t === 'resize' && message.cols > 0 && message.rows > 0) {
|
|
76
|
+
const nextCols = Math.floor(message.cols);
|
|
77
|
+
const nextRows = Math.floor(message.rows);
|
|
78
|
+
terminal.resize(nextCols, nextRows);
|
|
79
|
+
process.stderr.write(`RESIZED ${nextCols} ${nextRows}\n`);
|
|
80
|
+
}
|
|
81
|
+
else if (message?.t === 'input' && typeof message.data === 'string' && Buffer.byteLength(message.data, 'utf8') <= 64 * 1024) {
|
|
82
|
+
terminal.write(message.data);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
catch { /* malformed controls cannot become terminal input */ }
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
let closing = false;
|
|
89
|
+
function close() {
|
|
90
|
+
if (closing)
|
|
91
|
+
return;
|
|
92
|
+
closing = true;
|
|
93
|
+
try {
|
|
94
|
+
terminal.kill();
|
|
95
|
+
}
|
|
96
|
+
catch { /* already gone */ }
|
|
97
|
+
setTimeout(() => process.exit(0), 100).unref();
|
|
98
|
+
}
|
|
99
|
+
process.stdin.on('end', close);
|
|
100
|
+
process.on('SIGTERM', close);
|
|
101
|
+
process.on('SIGINT', close);
|