spexcode 0.6.2 → 0.6.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (495) hide show
  1. package/README.md +3 -1
  2. package/bin/spex.mjs +4 -0
  3. package/node_modules/@spexcode/session-core/dist/delivery-queue.d.ts +23 -0
  4. package/node_modules/@spexcode/session-core/dist/delivery-queue.js +195 -0
  5. package/node_modules/@spexcode/session-core/dist/index.d.ts +5 -0
  6. package/node_modules/@spexcode/session-core/dist/index.js +5 -0
  7. package/node_modules/@spexcode/session-core/dist/internal.d.ts +3 -0
  8. package/node_modules/@spexcode/session-core/dist/internal.js +3 -0
  9. package/node_modules/@spexcode/session-core/dist/message.d.ts +22 -0
  10. package/node_modules/@spexcode/session-core/dist/message.js +52 -0
  11. package/node_modules/@spexcode/session-core/dist/record-lock.d.ts +7 -0
  12. package/node_modules/@spexcode/session-core/dist/record-lock.js +152 -0
  13. package/node_modules/@spexcode/session-core/dist/runtime-session.d.ts +50 -0
  14. package/node_modules/@spexcode/session-core/dist/runtime-session.js +286 -0
  15. package/node_modules/@spexcode/session-core/dist/session-cursors.d.ts +14 -0
  16. package/node_modules/@spexcode/session-core/dist/session-cursors.js +82 -0
  17. package/node_modules/@spexcode/session-core/dist/session-timeline.d.ts +46 -0
  18. package/node_modules/@spexcode/session-core/dist/session-timeline.js +216 -0
  19. package/node_modules/@spexcode/session-core/package.json +33 -0
  20. package/node_modules/@spexcode/spec-cli/bin/spex.mjs +77 -0
  21. package/node_modules/@spexcode/spec-cli/dist/attach.d.ts +2 -0
  22. package/node_modules/@spexcode/spec-cli/dist/attach.js +48 -0
  23. package/node_modules/@spexcode/spec-cli/dist/claude-headless.d.ts +36 -0
  24. package/node_modules/@spexcode/spec-cli/dist/claude-headless.js +316 -0
  25. package/node_modules/@spexcode/spec-cli/dist/cli.d.ts +1 -0
  26. package/node_modules/@spexcode/spec-cli/dist/cli.js +1908 -0
  27. package/node_modules/@spexcode/spec-cli/dist/client.d.ts +112 -0
  28. package/node_modules/@spexcode/spec-cli/dist/client.js +481 -0
  29. package/node_modules/@spexcode/spec-cli/dist/cockpit.d.ts +7 -0
  30. package/node_modules/@spexcode/spec-cli/dist/cockpit.js +26 -0
  31. package/node_modules/@spexcode/spec-cli/dist/codex-headless.d.ts +1 -0
  32. package/{spec-cli/src/codex-headless.ts → node_modules/@spexcode/spec-cli/dist/codex-headless.js} +3 -9
  33. package/node_modules/@spexcode/spec-cli/dist/codex-runtime-generations.d.ts +58 -0
  34. package/node_modules/@spexcode/spec-cli/dist/codex-runtime-generations.js +798 -0
  35. package/node_modules/@spexcode/spec-cli/dist/commit-surgery.d.ts +1 -0
  36. package/node_modules/@spexcode/spec-cli/dist/commit-surgery.js +90 -0
  37. package/node_modules/@spexcode/spec-cli/dist/contract-filter.d.ts +14 -0
  38. package/node_modules/@spexcode/spec-cli/dist/contract-filter.js +219 -0
  39. package/node_modules/@spexcode/spec-cli/dist/dashboard-assets.d.ts +6 -0
  40. package/node_modules/@spexcode/spec-cli/dist/dashboard-assets.js +50 -0
  41. package/node_modules/@spexcode/spec-cli/dist/doctor.d.ts +9 -0
  42. package/node_modules/@spexcode/spec-cli/dist/doctor.js +553 -0
  43. package/node_modules/@spexcode/spec-cli/dist/eval-host.d.ts +1 -0
  44. package/node_modules/@spexcode/spec-cli/dist/eval-host.js +19 -0
  45. package/node_modules/@spexcode/spec-cli/dist/execution-trace.d.ts +26 -0
  46. package/node_modules/@spexcode/spec-cli/dist/execution-trace.js +475 -0
  47. package/node_modules/@spexcode/spec-cli/dist/file-write.d.ts +2 -0
  48. package/node_modules/@spexcode/spec-cli/dist/file-write.js +26 -0
  49. package/node_modules/@spexcode/spec-cli/dist/flat.d.ts +89 -0
  50. package/node_modules/@spexcode/spec-cli/dist/flat.js +1076 -0
  51. package/node_modules/@spexcode/spec-cli/dist/gateway-auth.d.ts +56 -0
  52. package/node_modules/@spexcode/spec-cli/dist/gateway-auth.js +168 -0
  53. package/node_modules/@spexcode/spec-cli/dist/gateway-hub.d.ts +28 -0
  54. package/node_modules/@spexcode/spec-cli/dist/gateway-hub.js +363 -0
  55. package/node_modules/@spexcode/spec-cli/dist/gateway.d.ts +38 -0
  56. package/node_modules/@spexcode/spec-cli/dist/gateway.js +527 -0
  57. package/node_modules/@spexcode/spec-cli/dist/graphCache.d.ts +27 -0
  58. package/node_modules/@spexcode/spec-cli/dist/graphCache.js +717 -0
  59. package/node_modules/@spexcode/spec-cli/dist/graphSnapshot.d.ts +14 -0
  60. package/node_modules/@spexcode/spec-cli/dist/graphSnapshot.js +36 -0
  61. package/node_modules/@spexcode/spec-cli/dist/graphStream.d.ts +85 -0
  62. package/node_modules/@spexcode/spec-cli/dist/graphStream.js +1195 -0
  63. package/node_modules/@spexcode/spec-cli/dist/guidance-catalog.d.ts +64 -0
  64. package/node_modules/@spexcode/spec-cli/dist/guidance-catalog.js +166 -0
  65. package/node_modules/@spexcode/spec-cli/dist/guide.d.ts +8 -0
  66. package/{spec-cli/src/guide.ts → node_modules/@spexcode/spec-cli/dist/guide.js} +36 -32
  67. package/node_modules/@spexcode/spec-cli/dist/harness-select.d.ts +18 -0
  68. package/node_modules/@spexcode/spec-cli/dist/harness-select.js +62 -0
  69. package/node_modules/@spexcode/spec-cli/dist/harness.d.ts +338 -0
  70. package/node_modules/@spexcode/spec-cli/dist/harness.js +3146 -0
  71. package/node_modules/@spexcode/spec-cli/dist/headless-controller.d.ts +9 -0
  72. package/node_modules/@spexcode/spec-cli/dist/headless-controller.js +47 -0
  73. package/node_modules/@spexcode/spec-cli/dist/help.d.ts +15 -0
  74. package/node_modules/@spexcode/spec-cli/dist/help.js +594 -0
  75. package/node_modules/@spexcode/spec-cli/dist/hook-prompts.d.ts +18 -0
  76. package/node_modules/@spexcode/spec-cli/dist/hook-prompts.js +122 -0
  77. package/node_modules/@spexcode/spec-cli/dist/hooks.d.ts +1 -0
  78. package/{spec-cli/src/hooks.ts → node_modules/@spexcode/spec-cli/dist/hooks.js} +15 -15
  79. package/node_modules/@spexcode/spec-cli/dist/host-resources.d.ts +97 -0
  80. package/node_modules/@spexcode/spec-cli/dist/host-resources.js +795 -0
  81. package/node_modules/@spexcode/spec-cli/dist/host.d.ts +88 -0
  82. package/node_modules/@spexcode/spec-cli/dist/host.js +663 -0
  83. package/node_modules/@spexcode/spec-cli/dist/index.d.ts +1 -0
  84. package/node_modules/@spexcode/spec-cli/dist/index.js +899 -0
  85. package/node_modules/@spexcode/spec-cli/dist/init.d.ts +1 -0
  86. package/node_modules/@spexcode/spec-cli/dist/init.js +284 -0
  87. package/node_modules/@spexcode/spec-cli/dist/issues-cli.d.ts +6 -0
  88. package/node_modules/@spexcode/spec-cli/dist/issues-cli.js +334 -0
  89. package/node_modules/@spexcode/spec-cli/dist/issues.d.ts +69 -0
  90. package/node_modules/@spexcode/spec-cli/dist/issues.js +189 -0
  91. package/node_modules/@spexcode/spec-cli/dist/lint.d.ts +34 -0
  92. package/node_modules/@spexcode/spec-cli/dist/lint.js +476 -0
  93. package/node_modules/@spexcode/spec-cli/dist/listen.d.ts +8 -0
  94. package/node_modules/@spexcode/spec-cli/dist/listen.js +24 -0
  95. package/node_modules/@spexcode/spec-cli/dist/localIssues.d.ts +65 -0
  96. package/node_modules/@spexcode/spec-cli/dist/localIssues.js +582 -0
  97. package/node_modules/@spexcode/spec-cli/dist/login-page.d.ts +5 -0
  98. package/{spec-cli/src/login-page.ts → node_modules/@spexcode/spec-cli/dist/login-page.js} +7 -7
  99. package/node_modules/@spexcode/spec-cli/dist/loop-in.d.ts +23 -0
  100. package/{spec-cli/src/loop-in.ts → node_modules/@spexcode/spec-cli/dist/loop-in.js} +35 -45
  101. package/node_modules/@spexcode/spec-cli/dist/machine-peer.d.ts +69 -0
  102. package/node_modules/@spexcode/spec-cli/dist/machine-peer.js +603 -0
  103. package/node_modules/@spexcode/spec-cli/dist/materialize.d.ts +18 -0
  104. package/node_modules/@spexcode/spec-cli/dist/materialize.js +468 -0
  105. package/node_modules/@spexcode/spec-cli/dist/mentions.d.ts +50 -0
  106. package/node_modules/@spexcode/spec-cli/dist/mentions.js +117 -0
  107. package/node_modules/@spexcode/spec-cli/dist/opencode-headless.d.ts +5 -0
  108. package/node_modules/@spexcode/spec-cli/dist/opencode-headless.js +215 -0
  109. package/node_modules/@spexcode/spec-cli/dist/opencode.d.ts +3 -0
  110. package/{spec-cli/src/opencode.ts → node_modules/@spexcode/spec-cli/dist/opencode.js} +9 -13
  111. package/node_modules/@spexcode/spec-cli/dist/pi-harness.d.ts +3 -0
  112. package/{spec-cli/src/pi-harness.ts → node_modules/@spexcode/spec-cli/dist/pi-harness.js} +45 -40
  113. package/node_modules/@spexcode/spec-cli/dist/pi-headless.d.ts +25 -0
  114. package/node_modules/@spexcode/spec-cli/dist/pi-headless.js +196 -0
  115. package/node_modules/@spexcode/spec-cli/dist/plugin-harness.d.ts +21 -0
  116. package/node_modules/@spexcode/spec-cli/dist/plugin-harness.js +146 -0
  117. package/node_modules/@spexcode/spec-cli/dist/pty-bridge.d.ts +10 -0
  118. package/node_modules/@spexcode/spec-cli/dist/pty-bridge.js +404 -0
  119. package/node_modules/@spexcode/spec-cli/dist/pty-helper.d.mts +1 -0
  120. package/node_modules/@spexcode/spec-cli/dist/pty-helper.mjs +101 -0
  121. package/node_modules/@spexcode/spec-cli/dist/pty-native-helper.d.mts +2 -0
  122. package/node_modules/@spexcode/spec-cli/dist/pty-native-helper.mjs +23 -0
  123. package/node_modules/@spexcode/spec-cli/dist/public-graph.d.ts +46 -0
  124. package/node_modules/@spexcode/spec-cli/dist/public-graph.js +56 -0
  125. package/node_modules/@spexcode/spec-cli/dist/ranker.d.ts +15 -0
  126. package/node_modules/@spexcode/spec-cli/dist/ranker.js +188 -0
  127. package/node_modules/@spexcode/spec-cli/dist/reaper.d.ts +6 -0
  128. package/node_modules/@spexcode/spec-cli/dist/reaper.js +81 -0
  129. package/node_modules/@spexcode/spec-cli/dist/reviews.d.ts +189 -0
  130. package/node_modules/@spexcode/spec-cli/dist/reviews.js +350 -0
  131. package/node_modules/@spexcode/spec-cli/dist/runtime-guard.d.ts +6 -0
  132. package/node_modules/@spexcode/spec-cli/dist/runtime-guard.js +37 -0
  133. package/node_modules/@spexcode/spec-cli/dist/runtime-ownership.d.ts +21 -0
  134. package/node_modules/@spexcode/spec-cli/dist/runtime-ownership.js +84 -0
  135. package/node_modules/@spexcode/spec-cli/dist/runtime-rotate.d.ts +1 -0
  136. package/node_modules/@spexcode/spec-cli/dist/runtime-rotate.js +58 -0
  137. package/node_modules/@spexcode/spec-cli/dist/search.bench.d.mts +1 -0
  138. package/node_modules/@spexcode/spec-cli/dist/search.bench.mjs +75 -0
  139. package/node_modules/@spexcode/spec-cli/dist/search.d.ts +20 -0
  140. package/node_modules/@spexcode/spec-cli/dist/search.js +61 -0
  141. package/node_modules/@spexcode/spec-cli/dist/session-declarations.d.ts +13 -0
  142. package/node_modules/@spexcode/spec-cli/dist/session-declarations.js +126 -0
  143. package/node_modules/@spexcode/spec-cli/dist/session-execution.d.ts +10 -0
  144. package/node_modules/@spexcode/spec-cli/dist/session-execution.js +70 -0
  145. package/node_modules/@spexcode/spec-cli/dist/session-files.d.ts +28 -0
  146. package/node_modules/@spexcode/spec-cli/dist/session-files.js +130 -0
  147. package/node_modules/@spexcode/spec-cli/dist/session-follow.d.ts +31 -0
  148. package/node_modules/@spexcode/spec-cli/dist/session-follow.js +179 -0
  149. package/node_modules/@spexcode/spec-cli/dist/session-reparent.d.ts +10 -0
  150. package/node_modules/@spexcode/spec-cli/dist/session-reparent.js +33 -0
  151. package/node_modules/@spexcode/spec-cli/dist/session-timeline.d.ts +11 -0
  152. package/node_modules/@spexcode/spec-cli/dist/session-timeline.js +20 -0
  153. package/node_modules/@spexcode/spec-cli/dist/session-web.d.ts +24 -0
  154. package/node_modules/@spexcode/spec-cli/dist/session-web.js +141 -0
  155. package/node_modules/@spexcode/spec-cli/dist/sessions.d.ts +433 -0
  156. package/node_modules/@spexcode/spec-cli/dist/sessions.js +4667 -0
  157. package/node_modules/@spexcode/spec-cli/dist/sh.d.ts +1 -0
  158. package/node_modules/@spexcode/spec-cli/dist/sh.js +3 -0
  159. package/node_modules/@spexcode/spec-cli/dist/shim-runtime.d.ts +1 -0
  160. package/{spec-cli/src/shim-runtime.ts → node_modules/@spexcode/spec-cli/dist/shim-runtime.js} +3 -4
  161. package/node_modules/@spexcode/spec-cli/dist/slash-commands.d.ts +9 -0
  162. package/node_modules/@spexcode/spec-cli/dist/slash-commands.js +307 -0
  163. package/node_modules/@spexcode/spec-cli/dist/source-files.d.ts +9 -0
  164. package/node_modules/@spexcode/spec-cli/dist/source-files.js +113 -0
  165. package/node_modules/@spexcode/spec-cli/dist/supervise.d.ts +1 -0
  166. package/node_modules/@spexcode/spec-cli/dist/supervise.js +308 -0
  167. package/node_modules/@spexcode/spec-cli/dist/tree.d.ts +25 -0
  168. package/node_modules/@spexcode/spec-cli/dist/tree.js +98 -0
  169. package/node_modules/@spexcode/spec-cli/dist/tsx-bin.d.ts +3 -0
  170. package/node_modules/@spexcode/spec-cli/dist/tsx-bin.js +41 -0
  171. package/node_modules/@spexcode/spec-cli/dist/uninstall.d.ts +3 -0
  172. package/node_modules/@spexcode/spec-cli/dist/uninstall.js +177 -0
  173. package/node_modules/@spexcode/spec-cli/dist/uploads.d.ts +36 -0
  174. package/node_modules/@spexcode/spec-cli/dist/uploads.js +243 -0
  175. package/node_modules/@spexcode/spec-cli/dist/worktree-sources.d.ts +1 -0
  176. package/node_modules/@spexcode/spec-cli/dist/worktree-sources.js +47 -0
  177. package/{spec-cli → node_modules/@spexcode/spec-cli}/hooks/dispatch.sh +5 -5
  178. package/{spec-cli → node_modules/@spexcode/spec-cli}/hooks/harness.sh +1 -1
  179. package/node_modules/@spexcode/spec-cli/package.json +49 -0
  180. package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/commands/supervisor/spec.md +8 -0
  181. package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/spec-first/spec-first.sh +1 -1
  182. package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/spec-of-file/spec-of-file.sh +1 -0
  183. package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/stop-gate/spec.md +2 -2
  184. package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/stop-gate/stop-gate.sh +31 -29
  185. package/node_modules/@spexcode/spec-core/dist/anchors.d.ts +94 -0
  186. package/node_modules/@spexcode/spec-core/dist/anchors.js +730 -0
  187. package/node_modules/@spexcode/spec-core/dist/git.d.ts +170 -0
  188. package/node_modules/@spexcode/spec-core/dist/git.js +2768 -0
  189. package/node_modules/@spexcode/spec-core/dist/graph-delta.d.ts +44 -0
  190. package/node_modules/@spexcode/spec-core/dist/graph-delta.js +72 -0
  191. package/node_modules/@spexcode/spec-core/dist/graph.d.ts +34 -0
  192. package/node_modules/@spexcode/spec-core/dist/graph.js +237 -0
  193. package/node_modules/@spexcode/spec-core/dist/graphDelta.d.ts +3 -0
  194. package/node_modules/@spexcode/spec-core/dist/graphDelta.js +13 -0
  195. package/node_modules/@spexcode/spec-core/dist/harness-identity.d.ts +31 -0
  196. package/node_modules/@spexcode/spec-core/dist/harness-identity.js +20 -0
  197. package/node_modules/@spexcode/spec-core/dist/identity-presets.d.ts +152 -0
  198. package/node_modules/@spexcode/spec-core/dist/identity-presets.js +132 -0
  199. package/node_modules/@spexcode/spec-core/dist/index.d.ts +45 -0
  200. package/node_modules/@spexcode/spec-core/dist/index.js +19 -0
  201. package/node_modules/@spexcode/spec-core/dist/layout.d.ts +187 -0
  202. package/node_modules/@spexcode/spec-core/dist/layout.js +548 -0
  203. package/node_modules/@spexcode/spec-core/dist/process-identity.d.ts +37 -0
  204. package/node_modules/@spexcode/spec-core/dist/process-identity.js +214 -0
  205. package/node_modules/@spexcode/spec-core/dist/project-identity.d.ts +12 -0
  206. package/node_modules/@spexcode/spec-core/dist/project-identity.js +71 -0
  207. package/node_modules/@spexcode/spec-core/dist/project-store.d.ts +3 -0
  208. package/node_modules/@spexcode/spec-core/dist/project-store.js +14 -0
  209. package/node_modules/@spexcode/spec-core/dist/resilience.d.ts +2 -0
  210. package/node_modules/@spexcode/spec-core/dist/resilience.js +40 -0
  211. package/node_modules/@spexcode/spec-core/dist/review/index.d.ts +3 -0
  212. package/node_modules/@spexcode/spec-core/dist/review/index.js +5 -0
  213. package/node_modules/@spexcode/spec-core/dist/review/reviewFilters.d.ts +77 -0
  214. package/node_modules/@spexcode/spec-core/dist/review/reviewFilters.js +308 -0
  215. package/node_modules/@spexcode/spec-core/dist/review/reviewQuery.d.ts +66 -0
  216. package/node_modules/@spexcode/spec-core/dist/review/reviewQuery.js +180 -0
  217. package/node_modules/@spexcode/spec-core/dist/review/session.d.ts +4 -0
  218. package/node_modules/@spexcode/spec-core/dist/review/session.js +8 -0
  219. package/node_modules/@spexcode/spec-core/dist/reviewSnapshot.d.ts +15 -0
  220. package/node_modules/@spexcode/spec-core/dist/reviewSnapshot.js +12 -0
  221. package/node_modules/@spexcode/spec-core/dist/root-lru.d.ts +4 -0
  222. package/{spec-cli/src/root-lru.ts → node_modules/@spexcode/spec-core/dist/root-lru.js} +26 -30
  223. package/node_modules/@spexcode/spec-core/dist/specs.d.ts +117 -0
  224. package/node_modules/@spexcode/spec-core/dist/specs.js +489 -0
  225. package/node_modules/@spexcode/spec-core/package.json +28 -0
  226. package/node_modules/@spexcode/spec-eval/dist/cache.d.ts +10 -0
  227. package/node_modules/@spexcode/spec-eval/dist/cache.js +50 -0
  228. package/node_modules/@spexcode/spec-eval/dist/cli.d.ts +22 -0
  229. package/node_modules/@spexcode/spec-eval/dist/cli.js +981 -0
  230. package/node_modules/@spexcode/spec-eval/dist/evaltab.d.ts +98 -0
  231. package/node_modules/@spexcode/spec-eval/dist/evaltab.js +176 -0
  232. package/node_modules/@spexcode/spec-eval/dist/filing.d.ts +16 -0
  233. package/node_modules/@spexcode/spec-eval/dist/filing.js +36 -0
  234. package/node_modules/@spexcode/spec-eval/dist/freshness.d.ts +48 -0
  235. package/node_modules/@spexcode/spec-eval/dist/freshness.js +799 -0
  236. package/node_modules/@spexcode/spec-eval/dist/host.d.ts +46 -0
  237. package/node_modules/@spexcode/spec-eval/dist/host.js +53 -0
  238. package/node_modules/@spexcode/spec-eval/dist/humanok.d.ts +11 -0
  239. package/node_modules/@spexcode/spec-eval/dist/humanok.js +27 -0
  240. package/node_modules/@spexcode/spec-eval/dist/index.d.ts +4 -0
  241. package/node_modules/@spexcode/spec-eval/dist/index.js +4 -0
  242. package/node_modules/@spexcode/spec-eval/dist/remarks.d.ts +31 -0
  243. package/node_modules/@spexcode/spec-eval/dist/remarks.js +1 -0
  244. package/node_modules/@spexcode/spec-eval/dist/scenariofresh.d.ts +13 -0
  245. package/node_modules/@spexcode/spec-eval/dist/scenariofresh.js +303 -0
  246. package/node_modules/@spexcode/spec-eval/dist/scenarios.d.ts +99 -0
  247. package/node_modules/@spexcode/spec-eval/dist/scenarios.js +650 -0
  248. package/node_modules/@spexcode/spec-eval/dist/sessioneval.d.ts +307 -0
  249. package/node_modules/@spexcode/spec-eval/dist/sessioneval.js +1883 -0
  250. package/node_modules/@spexcode/spec-eval/dist/sidecar.d.ts +55 -0
  251. package/node_modules/@spexcode/spec-eval/dist/sidecar.js +82 -0
  252. package/node_modules/@spexcode/spec-eval/dist/timeline.d.ts +25 -0
  253. package/node_modules/@spexcode/spec-eval/dist/timeline.js +65 -0
  254. package/node_modules/@spexcode/spec-eval/dist/ui-path.d.ts +1 -0
  255. package/node_modules/@spexcode/spec-eval/dist/ui-path.js +2 -0
  256. package/node_modules/@spexcode/spec-eval/package.json +36 -0
  257. package/node_modules/@spexcode/spec-forge/dist/cache.d.ts +29 -0
  258. package/node_modules/@spexcode/spec-forge/dist/cache.js +59 -0
  259. package/node_modules/@spexcode/spec-forge/dist/cli.d.ts +1 -0
  260. package/node_modules/@spexcode/spec-forge/dist/cli.js +100 -0
  261. package/node_modules/@spexcode/spec-forge/dist/drivers/github.d.ts +2 -0
  262. package/node_modules/@spexcode/spec-forge/dist/drivers/github.js +116 -0
  263. package/node_modules/@spexcode/spec-forge/dist/drivers/gitlab.d.ts +7 -0
  264. package/node_modules/@spexcode/spec-forge/dist/drivers/gitlab.js +144 -0
  265. package/node_modules/@spexcode/spec-forge/dist/drivers.d.ts +10 -0
  266. package/node_modules/@spexcode/spec-forge/dist/drivers.js +83 -0
  267. package/node_modules/@spexcode/spec-forge/dist/index.d.ts +4 -0
  268. package/node_modules/@spexcode/spec-forge/dist/index.js +4 -0
  269. package/node_modules/@spexcode/spec-forge/dist/links.d.ts +15 -0
  270. package/node_modules/@spexcode/spec-forge/dist/links.js +65 -0
  271. package/node_modules/@spexcode/spec-forge/dist/needs-eval.d.ts +10 -0
  272. package/node_modules/@spexcode/spec-forge/dist/needs-eval.js +20 -0
  273. package/node_modules/@spexcode/spec-forge/dist/port.d.ts +53 -0
  274. package/node_modules/@spexcode/spec-forge/dist/port.js +1 -0
  275. package/node_modules/@spexcode/spec-forge/dist/resident.d.ts +7 -0
  276. package/node_modules/@spexcode/spec-forge/dist/resident.js +43 -0
  277. package/node_modules/@spexcode/spec-forge/package.json +32 -0
  278. package/package.json +33 -19
  279. package/spec-cli/bin/spex.mjs +0 -69
  280. package/spec-cli/src/anchors.ts +0 -728
  281. package/spec-cli/src/attach.ts +0 -43
  282. package/spec-cli/src/claude-headless.ts +0 -321
  283. package/spec-cli/src/cli.ts +0 -1258
  284. package/spec-cli/src/client.ts +0 -414
  285. package/spec-cli/src/cockpit.ts +0 -43
  286. package/spec-cli/src/codex-runtime-generations.ts +0 -646
  287. package/spec-cli/src/commit-surgery.ts +0 -68
  288. package/spec-cli/src/contract-filter.ts +0 -202
  289. package/spec-cli/src/delivery-queue.ts +0 -165
  290. package/spec-cli/src/doctor.ts +0 -516
  291. package/spec-cli/src/execution-trace.ts +0 -444
  292. package/spec-cli/src/file-write.ts +0 -22
  293. package/spec-cli/src/gateway-auth.ts +0 -181
  294. package/spec-cli/src/gateway-hub.ts +0 -358
  295. package/spec-cli/src/gateway.ts +0 -461
  296. package/spec-cli/src/git.ts +0 -2556
  297. package/spec-cli/src/graph.ts +0 -271
  298. package/spec-cli/src/graphCache.ts +0 -710
  299. package/spec-cli/src/graphDelta.ts +0 -77
  300. package/spec-cli/src/graphStream.ts +0 -1019
  301. package/spec-cli/src/harness-select.ts +0 -77
  302. package/spec-cli/src/harness.ts +0 -3025
  303. package/spec-cli/src/headless-controller.ts +0 -63
  304. package/spec-cli/src/help.ts +0 -492
  305. package/spec-cli/src/host-resources.ts +0 -762
  306. package/spec-cli/src/host.ts +0 -611
  307. package/spec-cli/src/identity-presets.d.ts +0 -13
  308. package/spec-cli/src/identity-presets.js +0 -138
  309. package/spec-cli/src/index.ts +0 -818
  310. package/spec-cli/src/init.ts +0 -258
  311. package/spec-cli/src/issues-cli.ts +0 -276
  312. package/spec-cli/src/issues.ts +0 -237
  313. package/spec-cli/src/layout.ts +0 -640
  314. package/spec-cli/src/lint.ts +0 -488
  315. package/spec-cli/src/listen.ts +0 -28
  316. package/spec-cli/src/localIssues.ts +0 -538
  317. package/spec-cli/src/materialize.ts +0 -417
  318. package/spec-cli/src/mentions.ts +0 -81
  319. package/spec-cli/src/opencode-headless.ts +0 -205
  320. package/spec-cli/src/pi-headless.ts +0 -153
  321. package/spec-cli/src/plugin-harness.ts +0 -157
  322. package/spec-cli/src/process-identity.ts +0 -207
  323. package/spec-cli/src/project-identity.ts +0 -73
  324. package/spec-cli/src/project-store.ts +0 -17
  325. package/spec-cli/src/pty-bridge.ts +0 -396
  326. package/spec-cli/src/pty-helper.mjs +0 -96
  327. package/spec-cli/src/pty-native-helper.mjs +0 -22
  328. package/spec-cli/src/ranker.ts +0 -191
  329. package/spec-cli/src/reaper.ts +0 -125
  330. package/spec-cli/src/resilience.ts +0 -41
  331. package/spec-cli/src/reviewSnapshot.ts +0 -28
  332. package/spec-cli/src/reviews.ts +0 -434
  333. package/spec-cli/src/runtime-guard.ts +0 -35
  334. package/spec-cli/src/runtime-ownership.ts +0 -88
  335. package/spec-cli/src/search.bench.mjs +0 -80
  336. package/spec-cli/src/search.ts +0 -63
  337. package/spec-cli/src/session-cursors.ts +0 -91
  338. package/spec-cli/src/session-execution.ts +0 -68
  339. package/spec-cli/src/session-files.ts +0 -121
  340. package/spec-cli/src/session-follow.ts +0 -182
  341. package/spec-cli/src/session-reparent.ts +0 -33
  342. package/spec-cli/src/session-timeline.ts +0 -247
  343. package/spec-cli/src/session-web.ts +0 -135
  344. package/spec-cli/src/sessions.ts +0 -3883
  345. package/spec-cli/src/sh.ts +0 -3
  346. package/spec-cli/src/slash-commands.ts +0 -318
  347. package/spec-cli/src/source-files.ts +0 -104
  348. package/spec-cli/src/specs.ts +0 -498
  349. package/spec-cli/src/supervise.ts +0 -245
  350. package/spec-cli/src/tree.ts +0 -118
  351. package/spec-cli/src/tsx-bin.ts +0 -18
  352. package/spec-cli/src/uninstall.ts +0 -171
  353. package/spec-cli/src/uploads.ts +0 -253
  354. package/spec-cli/src/worktree-sources.ts +0 -42
  355. package/spec-cli/templates/spec/project/.plugins/commands/supervisor/spec.md +0 -8
  356. package/spec-dashboard/dist/assets/App-B6IbFbGu.js +0 -2
  357. package/spec-dashboard/dist/assets/Dashboard-C5ap-Sga.css +0 -1
  358. package/spec-dashboard/dist/assets/Dashboard-_sDk7Rcg.js +0 -27
  359. package/spec-dashboard/dist/assets/EvalsPage-BT357Lyt.js +0 -2
  360. package/spec-dashboard/dist/assets/IssuesPage-BG9rNOgV.js +0 -1
  361. package/spec-dashboard/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  362. package/spec-dashboard/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  363. package/spec-dashboard/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  364. package/spec-dashboard/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  365. package/spec-dashboard/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  366. package/spec-dashboard/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  367. package/spec-dashboard/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  368. package/spec-dashboard/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  369. package/spec-dashboard/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  370. package/spec-dashboard/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  371. package/spec-dashboard/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  372. package/spec-dashboard/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  373. package/spec-dashboard/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  374. package/spec-dashboard/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  375. package/spec-dashboard/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  376. package/spec-dashboard/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  377. package/spec-dashboard/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  378. package/spec-dashboard/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  379. package/spec-dashboard/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  380. package/spec-dashboard/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  381. package/spec-dashboard/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  382. package/spec-dashboard/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  383. package/spec-dashboard/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  384. package/spec-dashboard/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  385. package/spec-dashboard/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  386. package/spec-dashboard/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  387. package/spec-dashboard/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  388. package/spec-dashboard/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  389. package/spec-dashboard/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  390. package/spec-dashboard/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  391. package/spec-dashboard/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  392. package/spec-dashboard/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  393. package/spec-dashboard/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  394. package/spec-dashboard/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  395. package/spec-dashboard/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  396. package/spec-dashboard/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  397. package/spec-dashboard/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  398. package/spec-dashboard/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  399. package/spec-dashboard/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  400. package/spec-dashboard/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  401. package/spec-dashboard/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  402. package/spec-dashboard/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  403. package/spec-dashboard/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  404. package/spec-dashboard/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  405. package/spec-dashboard/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  406. package/spec-dashboard/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  407. package/spec-dashboard/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  408. package/spec-dashboard/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  409. package/spec-dashboard/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  410. package/spec-dashboard/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  411. package/spec-dashboard/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  412. package/spec-dashboard/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  413. package/spec-dashboard/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  414. package/spec-dashboard/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  415. package/spec-dashboard/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  416. package/spec-dashboard/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  417. package/spec-dashboard/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  418. package/spec-dashboard/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  419. package/spec-dashboard/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  420. package/spec-dashboard/dist/assets/MobileApp-D9yPMR3A.js +0 -2
  421. package/spec-dashboard/dist/assets/Modal-B3EcgeA3.js +0 -1
  422. package/spec-dashboard/dist/assets/PageScroll-CNZOugWV.js +0 -1
  423. package/spec-dashboard/dist/assets/ProjectsPage-C2CRHmvs.js +0 -1
  424. package/spec-dashboard/dist/assets/SessionInterface-6GBZ9nXN.css +0 -32
  425. package/spec-dashboard/dist/assets/SessionInterface-DOEjuT3l.js +0 -40
  426. package/spec-dashboard/dist/assets/SessionWindow-1nYgZ4CL.js +0 -1
  427. package/spec-dashboard/dist/assets/Settings-7WeTJn0V.js +0 -1
  428. package/spec-dashboard/dist/assets/Thread-CEbzAFQn.js +0 -13
  429. package/spec-dashboard/dist/assets/bindings-BC9vqpYU.js +0 -1
  430. package/spec-dashboard/dist/assets/data-SlvxNA2r.js +0 -1
  431. package/spec-dashboard/dist/assets/index-DLVeOHL-.js +0 -41
  432. package/spec-dashboard/dist/assets/index-DYLGfPjx.css +0 -1
  433. package/spec-dashboard/dist/assets/launch-Cp579UoJ.css +0 -1
  434. package/spec-dashboard/dist/assets/launch-yxCYr64x.js +0 -276
  435. package/spec-dashboard/dist/assets/sessionSurface--7l69h4F.js +0 -1
  436. package/spec-dashboard/dist/index.html +0 -29
  437. package/spec-dashboard/src/reviewFilters.js +0 -322
  438. package/spec-dashboard/src/reviewQuery.js +0 -174
  439. package/spec-dashboard/src/session.js +0 -199
  440. package/spec-eval/src/cache.ts +0 -57
  441. package/spec-eval/src/cli.ts +0 -894
  442. package/spec-eval/src/evaltab.ts +0 -264
  443. package/spec-eval/src/filing.ts +0 -39
  444. package/spec-eval/src/freshness.ts +0 -836
  445. package/spec-eval/src/humanok.ts +0 -28
  446. package/spec-eval/src/scenariofresh.ts +0 -244
  447. package/spec-eval/src/scenarios.ts +0 -672
  448. package/spec-eval/src/sessioneval.ts +0 -2220
  449. package/spec-eval/src/sidecar.ts +0 -93
  450. package/spec-eval/src/timeline.ts +0 -54
  451. package/spec-forge/src/__fixtures__/github-forge.json +0 -63
  452. package/spec-forge/src/cache.ts +0 -66
  453. package/spec-forge/src/cli.ts +0 -99
  454. package/spec-forge/src/drivers/github.ts +0 -132
  455. package/spec-forge/src/drivers/gitlab.ts +0 -153
  456. package/spec-forge/src/drivers.ts +0 -73
  457. package/spec-forge/src/links.ts +0 -72
  458. package/spec-forge/src/needs-eval.ts +0 -29
  459. package/spec-forge/src/port.ts +0 -36
  460. package/spec-forge/src/resident.ts +0 -46
  461. /package/{spec-cli → node_modules/@spexcode/spec-cli}/README.md +0 -0
  462. /package/{spec-cli → node_modules/@spexcode/spec-cli}/hooks/compat/mark-active-0.5.2-eef1.fixture +0 -0
  463. /package/{spec-cli → node_modules/@spexcode/spec-cli}/hooks/compat/mark-active-sed-v0.fixture +0 -0
  464. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/hooks/commit-msg +0 -0
  465. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/hooks/post-checkout +0 -0
  466. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/hooks/post-merge +0 -0
  467. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/hooks/pre-commit +0 -0
  468. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/hooks/prepare-commit-msg +0 -0
  469. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/hooks/reference-transaction +0 -0
  470. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/commands/extract/spec.md +0 -0
  471. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/commands/regroup/spec.md +0 -0
  472. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/commands/rename/spec.md +0 -0
  473. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/commands/spec.md +0 -0
  474. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/commands/tidy/spec.md +0 -0
  475. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/comment-altitude/spec.md +0 -0
  476. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/idle/idle.sh +0 -0
  477. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/idle/spec.md +0 -0
  478. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/mark-active/mark-active.sh +0 -0
  479. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/mark-active/spec.md +0 -0
  480. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/session-fail/fail.sh +0 -0
  481. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/session-fail/spec.md +0 -0
  482. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/spec-first/spec.md +0 -0
  483. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/spec-of-file/spec.md +0 -0
  484. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/core/spec.md +0 -0
  485. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/prompts/atomic-landing/spec.md +0 -0
  486. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/prompts/forge-link/spec.md +0 -0
  487. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/prompts/memory-hygiene/spec.md +0 -0
  488. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/prompts/reproduce-before-fix/spec.md +0 -0
  489. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/prompts/spec.md +0 -0
  490. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/skills/distill/digest.mjs +0 -0
  491. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/skills/distill/spec.md +0 -0
  492. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/skills/spec.md +0 -0
  493. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/.plugins/spec.md +0 -0
  494. /package/{spec-cli → node_modules/@spexcode/spec-cli}/templates/spec/project/spec.md +0 -0
  495. /package/{spec-cli → node_modules/@spexcode/spec-core}/templates/spexcode.json +0 -0
@@ -1,611 +0,0 @@
1
- // @@@ host gateway ([[host-gateway]]) - the host-level face of every SpexCode project this user runs.
2
- // Each `spex serve` stays scoped to ONE repo, loopback-only and auth-unaware; what it contributes to the
3
- // host is a single instance-validated endpoint record in the per-user global store. THIS module is the
4
- // other half: the durable known-project catalog, the reconciler that turns records into a validated live
5
- // project list, portable-config editor, and host operations (register/init/doctor/start) — mounted as [[gateway-hub]]'s
6
- // extension by `spex dashboard` (startHostDashboard below), so routing, /p/:projectId proxying, and
7
- // authorization ([[gateway-auth]]) stay the hub's single seam and are never duplicated here. Backends
8
- // never depend on the gateway: they publish records and serve loopback whether or not a gateway is
9
- // running, and direct CLI discovery (sessions.ts's ladder) keeps reading the same records.
10
- import http from 'node:http'
11
- import net from 'node:net'
12
- import { spawn } from 'node:child_process'
13
- import { createHash } from 'node:crypto'
14
- import { mkdirSync, writeFileSync, readFileSync, renameSync, rmSync, readdirSync, openSync, closeSync, existsSync, realpathSync, statSync } from 'node:fs'
15
- import { homedir } from 'node:os'
16
- import { dirname, join, basename, resolve } from 'node:path'
17
- import { fileURLToPath } from 'node:url'
18
- import { spexcodeHome, encodeProject } from './layout.js'
19
- import { git } from './git.js'
20
- import { serveStatic, resolveDistDir, ensureDashboardBuilt } from './gateway.js'
21
- import { startHubGateway, type HubExtensions } from './gateway-hub.js'
22
- import { tsxBin } from './tsx-bin.js'
23
- import { DEFAULT_PROJECT_ICON, requireIdentityChoice } from './identity-presets.js'
24
- import {
25
- resolveProjectIdentity, writeGatewayIcon, type ResolvedIdentity,
26
- } from './project-identity.js'
27
-
28
- const here = dirname(fileURLToPath(import.meta.url))
29
-
30
- // ── the endpoint record ──────────────────────────────────────────────────────────────────────────────
31
- // One record per project, written by that project's `spex serve` after its public bind succeeds and
32
- // removed (only by its own writer) on a clean stop. The shape carries the serve's IDENTITY — url, pid,
33
- // instanceId, root — so a reader can validate "the backend at this url is the serve that wrote this
34
- // record, serving this root" instead of trusting a URL that a recycled port may have re-occupied.
35
- export type EndpointRecord = {
36
- version: 2; url: string; pid: number; instanceId: string; root: string
37
- identity: ResolvedIdentity; startedAt: string
38
- }
39
-
40
- export const endpointRecordPath = (root: string): string =>
41
- join(spexcodeHome(), 'projects', encodeProject(root), 'backend.json')
42
-
43
- // atomic publish: tmp + rename, so a reader never sees a torn record (the old write-in-place could be
44
- // caught mid-write by the reconciler or a bare `spex`'s discovery probe).
45
- export function publishEndpoint(rec: EndpointRecord): void {
46
- const file = endpointRecordPath(rec.root)
47
- mkdirSync(dirname(file), { recursive: true })
48
- const tmp = join(dirname(file), `.backend.json.${process.pid}.tmp`)
49
- writeFileSync(tmp, JSON.stringify(rec, null, 2) + '\n')
50
- renameSync(tmp, file)
51
- }
52
-
53
- // remove the record ONLY if it is ours (matched by instanceId): a newer serve that already overwrote it,
54
- // or another project's record, is never deleted by a retiring process.
55
- export function dropOwnEndpoint(instanceId: string, root: string): void {
56
- const file = endpointRecordPath(root)
57
- try { if (JSON.parse(readFileSync(file, 'utf8'))?.instanceId === instanceId) rmSync(file) } catch { /* not ours / already gone */ }
58
- }
59
-
60
- // a record is HOSTABLE only in the full identity shape; legacy {url,pid} records (pre-instance-identity)
61
- // are ignored by the host — the direct CLI ladder still reads their url, and they are rewritten in the new
62
- // shape the next time that serve restarts.
63
- export function readEndpointRecord(file: string): EndpointRecord | null {
64
- try {
65
- const r = JSON.parse(readFileSync(file, 'utf8'))
66
- if (r?.version === 2 && typeof r.url === 'string' && typeof r.instanceId === 'string' && typeof r.root === 'string' &&
67
- typeof r.identity?.title === 'string' && typeof r.identity?.icon === 'string') return r as EndpointRecord
68
- return null
69
- } catch { return null }
70
- }
71
-
72
- // ── the durable known-project catalog ────────────────────────────────────────────────────────────────
73
- // ~/.spexcode/projects.json — the host's memory of which projects exist, so /projects can list a project
74
- // whose backend is OFFLINE (records vanish with their serve; the catalog does not). Populated two ways:
75
- // explicitly (the add op) and by auto-adoption of any validated live record, so every project ever served
76
- // under this user shows up without a registration ceremony.
77
- export type CatalogEntry = { root: string; addedAt: string }
78
- export const catalogPath = (): string => join(spexcodeHome(), 'projects.json')
79
-
80
- let catalogWarned = false
81
- export function readCatalog(): CatalogEntry[] {
82
- let raw: string
83
- try { raw = readFileSync(catalogPath(), 'utf8') } catch { return [] }
84
- try {
85
- const parsed = JSON.parse(raw)
86
- const list = Array.isArray(parsed?.projects) ? parsed.projects : []
87
- return list.filter((e: any): e is CatalogEntry => e && typeof e.root === 'string')
88
- } catch (e) {
89
- // reads degrade loud-but-alive (the reconciler must keep serving live records); WRITES refuse below,
90
- // so a malformed catalog is never silently clobbered.
91
- if (!catalogWarned) { catalogWarned = true; console.error(`[host] malformed ${catalogPath()} (${(e as Error).message}) — known-project catalog ignored until fixed`) }
92
- return []
93
- }
94
- }
95
-
96
- function writeCatalog(entries: CatalogEntry[]): void {
97
- if (existsSync(catalogPath())) {
98
- try { JSON.parse(readFileSync(catalogPath(), 'utf8')) }
99
- catch { throw new Error(`refusing to overwrite malformed ${catalogPath()} — fix or remove it first`) }
100
- }
101
- mkdirSync(dirname(catalogPath()), { recursive: true })
102
- const tmp = join(dirname(catalogPath()), `.projects.json.${process.pid}.tmp`)
103
- writeFileSync(tmp, JSON.stringify({ projects: entries }, null, 2) + '\n')
104
- renameSync(tmp, catalogPath())
105
- }
106
-
107
- function existingDirectory(dir: string): string {
108
- let path: string
109
- try { path = realpathSync(resolve(dir)) }
110
- catch { throw new Error(`${dir} is not an existing directory`) }
111
- try { if (!statSync(path).isDirectory()) throw new Error('not a directory') }
112
- catch { throw new Error(`${dir} is not an existing directory`) }
113
- return path
114
- }
115
-
116
- function gitProjectRoot(dir: string): string | null {
117
- try { return dirname(git(['-C', dir, 'rev-parse', '--path-format=absolute', '--git-common-dir']).trim()) }
118
- catch { return null }
119
- }
120
-
121
- // The admin folder picker reads directory NAMES only. Its selected-directory projection lets the UI ask
122
- // before either side effect instead of discovering the Git precondition after submit.
123
- export type ProjectDirectoryListing = {
124
- path: string; parent: string | null; home: string; gitRoot: string | null
125
- initialized: boolean; cataloged: boolean
126
- entries: Array<{ name: string; path: string; git: boolean; initialized: boolean }>
127
- }
128
- export function browseProjectDirectories(dir?: string): ProjectDirectoryListing {
129
- const path = existingDirectory((dir ?? '').trim() || homedir())
130
- const gitRoot = gitProjectRoot(path)
131
- const entries = readdirSync(path, { withFileTypes: true })
132
- .filter((entry) => {
133
- if (entry.isDirectory()) return true
134
- if (!entry.isSymbolicLink()) return false
135
- try { return statSync(join(path, entry.name)).isDirectory() } catch { return false }
136
- })
137
- .map((entry) => {
138
- const child = join(path, entry.name)
139
- const isGit = existsSync(join(child, '.git'))
140
- return {
141
- name: entry.name,
142
- path: child,
143
- git: isGit,
144
- initialized: isGit && existsSync(join(child, '.spec')),
145
- }
146
- })
147
- .sort((a, b) => a.name.localeCompare(b.name))
148
- return {
149
- path,
150
- parent: dirname(path) === path ? null : dirname(path),
151
- home: homedir(),
152
- gitRoot,
153
- initialized: !!gitRoot && existsSync(join(gitRoot, '.spec')),
154
- cataloged: !!gitRoot && readCatalog().some((entry) => entry.root === gitRoot),
155
- entries,
156
- }
157
- }
158
-
159
- // Register a repo by normalizing any path inside it to its MAIN checkout (the identity every record and
160
- // store key uses), then dedupe + persist it.
161
- export function addKnownProject(dir: string): string {
162
- const path = existingDirectory(dir)
163
- const root = gitProjectRoot(path)
164
- if (!root) throw new Error(`${dir} is not a git repository — SpexCode projects are git-backed (run \`git init\` there first)`)
165
- catalogAdd(root)
166
- return root
167
- }
168
- function catalogAdd(root: string): void {
169
- const entries = readCatalog()
170
- if (entries.some((e) => e.root === root)) return
171
- writeCatalog([...entries, { root, addedAt: new Date().toISOString() }])
172
- }
173
-
174
- // ── the reconciler ───────────────────────────────────────────────────────────────────────────────────
175
- // Turn the global store's endpoint records + the catalog into ONE validated project list. A record counts
176
- // as ONLINE only when (a) it sits in the store slot its own root encodes to, and (b) the live backend at
177
- // its url answers /api/instance with the SAME instanceId and root — anything else (dead process, recycled
178
- // port, copied record, another project's serve) is just an offline project, never a proxy target.
179
- export type ProjectEntry = {
180
- projectId: string; root: string; identity: ResolvedIdentity; configRevision: string
181
- online: boolean; url: string | null; pid?: number; startedAt?: string
182
- }
183
-
184
- type LiveInstance = { instanceId?: string; root?: string; identity?: ResolvedIdentity }
185
- async function fetchInstance(url: string): Promise<LiveInstance | null> {
186
- const ctrl = new AbortController()
187
- const t = setTimeout(() => ctrl.abort(), 900)
188
- try {
189
- const r = await fetch(`${url}/api/instance`, { signal: ctrl.signal })
190
- if (!r.ok) return null
191
- return await r.json() as LiveInstance
192
- } catch { return null }
193
- finally { clearTimeout(t) }
194
- }
195
-
196
- export async function reconcileProjects(): Promise<ProjectEntry[]> {
197
- const projectsDir = join(spexcodeHome(), 'projects')
198
- let dirs: string[] = []
199
- try { dirs = readdirSync(projectsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name) }
200
- catch (e) { if ((e as NodeJS.ErrnoException).code !== 'ENOENT') throw e }
201
-
202
- const live = new Map<string, { rec: EndpointRecord; identity: ResolvedIdentity }>()
203
- const claimed = new Set<string>() // roots with a slot-matched record, validated or not
204
- await Promise.all(dirs.map(async (d) => {
205
- const rec = readEndpointRecord(join(projectsDir, d, 'backend.json'))
206
- if (!rec) return
207
- if (encodeProject(rec.root) !== d) return
208
- claimed.add(rec.root) // a dead/mismatched record still NAMES a project — listed offline
209
- const inst = await fetchInstance(rec.url)
210
- if (inst && inst.instanceId === rec.instanceId && inst.root === rec.root &&
211
- typeof inst.identity?.title === 'string' && typeof inst.identity?.icon === 'string') {
212
- live.set(rec.root, { rec, identity: inst.identity })
213
- }
214
- }))
215
-
216
- // auto-adopt: a VALIDATED live root becomes durable catalog knowledge, so it stays listed after its
217
- // serve stops. Unvalidated claims are listed this pass but never written durable — a stale or wrong
218
- // record must not pollute the catalog. Best-effort — a refused catalog write (malformed file) must not
219
- // hide live backends.
220
- for (const root of live.keys()) {
221
- try { catalogAdd(root) } catch (e) { if (!catalogWarned) { catalogWarned = true; console.error(`[host] ${(e as Error).message}`) } }
222
- }
223
-
224
- const byId = new Map<string, ProjectEntry>()
225
- const push = (root: string) => {
226
- if (!existsSync(root)) return
227
- const projectId = encodeProject(root)
228
- if (byId.has(projectId)) return // encodeProject is lossy; first root wins a (pathological) collision
229
- const active = live.get(root) ?? null
230
- const source = readProjectConfig(root)
231
- let identity: ResolvedIdentity
232
- try { identity = active?.identity ?? resolveProjectIdentity(root, root) }
233
- catch (e) {
234
- console.error(`[host] cannot resolve identity for ${root}: ${(e as Error).message}`)
235
- identity = { title: basename(root), icon: DEFAULT_PROJECT_ICON }
236
- }
237
- byId.set(projectId, {
238
- projectId, root, identity, configRevision: source.revision,
239
- online: !!active, url: active?.rec.url ?? null,
240
- ...(active ? { pid: active.rec.pid, startedAt: active.rec.startedAt } : {}),
241
- })
242
- }
243
- for (const e of readCatalog()) push(e.root)
244
- for (const root of claimed) push(root)
245
- return [...byId.values()].sort((a, b) => a.identity.title.localeCompare(b.identity.title) || a.root.localeCompare(b.root))
246
- }
247
-
248
- // single-flight + last-snapshot: every reader (GET, the stream loop, the per-request proxy resolution)
249
- // shares one in-flight reconcile instead of stampeding probes.
250
- let snapshot: ProjectEntry[] = []
251
- let inflight: Promise<ProjectEntry[]> | null = null
252
- export function reconcileNow(): Promise<ProjectEntry[]> {
253
- return (inflight ??= reconcileProjects()
254
- .then((list) => { snapshot = list; return list })
255
- .finally(() => { inflight = null }))
256
- }
257
-
258
- // ── host operations (spawned `spex`, never forked logic) ─────────────────────────────────────────────
259
- // init / doctor / serve run the EXISTING CLI implementations as child processes with cwd = the project
260
- // root: the same git/harness/additive guarantees `spex init` gives at a terminal, the same doctor report,
261
- // the same supervisor `spex serve` boots — no second implementation of any domain semantics. The spawned
262
- // env is scrubbed of routing state (SPEXCODE_API_URL/PORT/session/instance ids) so a child never inherits
263
- // the gateway's — or another project's — backend routing.
264
- const cliEntry = join(here, 'cli.ts')
265
- function scrubbedEnv(): NodeJS.ProcessEnv {
266
- const env = { ...process.env }
267
- delete env.SPEXCODE_API_URL; delete env.PORT
268
- delete env.SPEXCODE_SESSION_ID; delete env.SPEXCODE_INSTANCE_ID
269
- return env
270
- }
271
-
272
- export function runSpex(root: string, args: string[], timeoutMs = 120_000): Promise<{ code: number | null; output: string }> {
273
- return new Promise((resolve) => {
274
- const child = spawn(process.execPath, [tsxBin(join(here, '..')), cliEntry, ...args], { cwd: root, env: scrubbedEnv() })
275
- let output = ''
276
- child.stdout.on('data', (d) => { output += d })
277
- child.stderr.on('data', (d) => { output += d })
278
- const t = setTimeout(() => { try { child.kill('SIGKILL') } catch { /* gone */ } }, timeoutMs)
279
- child.on('close', (code) => { clearTimeout(t); resolve({ code, output }) })
280
- child.on('error', (e) => { clearTimeout(t); resolve({ code: null, output: `${output}\nspawn failed: ${e.message}` }) })
281
- })
282
- }
283
-
284
- export type AddProjectSetup = {
285
- initGit?: boolean
286
- init?: { harness: string; preset?: string }
287
- }
288
- export type AddProjectSetupResult = {
289
- ok: boolean; root: string; gitInitialized: boolean
290
- init?: { code: number | null; output: string }
291
- }
292
-
293
- // One ordered add transaction: select an existing directory, explicitly create Git when requested, run
294
- // the real CLI initializer when requested, and only then claim catalog success.
295
- export async function addKnownProjectWithSetup(dir: string, setup: AddProjectSetup = {}): Promise<AddProjectSetupResult> {
296
- const path = existingDirectory(dir)
297
- let root = gitProjectRoot(path)
298
- let gitInitialized = false
299
- if (!root) {
300
- if (!setup.initGit) throw new Error(`${dir} is not a git repository — enable Git initialization to add it`)
301
- git(['init', '--quiet', '--', path])
302
- gitInitialized = true
303
- root = gitProjectRoot(path)
304
- if (!root) throw new Error(`git init completed but ${path} is still not a Git repository`)
305
- }
306
-
307
- let init: AddProjectSetupResult['init']
308
- if (setup.init) {
309
- const harness = typeof setup.init.harness === 'string' ? setup.init.harness.trim() : ''
310
- if (!harness) throw new Error('SpexCode initialization requires at least one explicit harness target')
311
- const preset = typeof setup.init.preset === 'string' ? setup.init.preset.trim() : ''
312
- const result = await runSpex(root, ['init', '--harness', harness, ...(preset ? ['--preset', preset] : [])])
313
- init = result
314
- if (result.code !== 0) return { ok: false, root, gitInitialized, init }
315
- }
316
-
317
- catalogAdd(root)
318
- return { ok: true, root, gitInitialized, ...(init ? { init } : {}) }
319
- }
320
-
321
- // The dashboard edits the committed, portable source file verbatim. The host fixes the filename (there
322
- // is no browser-supplied path), works for offline projects, and uses a content revision so a save cannot
323
- // clobber a concurrent agent/user edit. spexcode.local.json stays outside this surface by contract.
324
- type ProjectConfigSource = { content: string; revision: string }
325
- let configWriteSeq = 0
326
- const configRevision = (raw: string | null): string => createHash('sha256').update(raw === null ? 'missing' : `present\0${raw}`).digest('hex')
327
-
328
- function readProjectConfig(root: string): ProjectConfigSource {
329
- try {
330
- const content = readFileSync(join(root, 'spexcode.json'), 'utf8')
331
- return { content, revision: configRevision(content) }
332
- } catch (e) {
333
- if ((e as NodeJS.ErrnoException).code === 'ENOENT') return { content: '{}\n', revision: configRevision(null) }
334
- throw e
335
- }
336
- }
337
-
338
- function writeProjectConfig(root: string, content: string, revision: string): ProjectConfigSource {
339
- const current = readProjectConfig(root)
340
- if (revision !== current.revision) {
341
- const e = new Error('spexcode.json changed on disk — reload before saving') as Error & { status?: number }
342
- e.status = 409
343
- throw e
344
- }
345
- let parsed: unknown
346
- try { parsed = JSON.parse(content) }
347
- catch (e) {
348
- const err = new Error(`spexcode.json is not valid JSON: ${(e as Error).message}`) as Error & { status?: number }
349
- err.status = 400
350
- throw err
351
- }
352
- if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
353
- const e = new Error('spexcode.json must contain one top-level JSON object') as Error & { status?: number }
354
- e.status = 400
355
- throw e
356
- }
357
-
358
- const normalized = content.endsWith('\n') ? content : `${content}\n`
359
- const file = join(root, 'spexcode.json')
360
- const tmp = join(root, `.spexcode.json.${process.pid}.${++configWriteSeq}.tmp`)
361
- try {
362
- writeFileSync(tmp, normalized)
363
- renameSync(tmp, file)
364
- } finally {
365
- try { rmSync(tmp) } catch { /* rename consumed it / write never created it */ }
366
- }
367
- return { content: normalized, revision: configRevision(normalized) }
368
- }
369
-
370
- function writeProjectIcon(root: string, icon: unknown, revision: string): ProjectConfigSource & { identity: ResolvedIdentity } {
371
- const canonical = requireIdentityChoice(icon)
372
- const current = readProjectConfig(root)
373
- let config: Record<string, any>
374
- try { config = JSON.parse(current.content) }
375
- catch (e) {
376
- const error = new Error(`spexcode.json is not valid JSON: ${(e as Error).message}`) as Error & { status?: number }
377
- error.status = 400
378
- throw error
379
- }
380
- const dashboard = config.dashboard === undefined
381
- ? {}
382
- : config.dashboard && typeof config.dashboard === 'object' && !Array.isArray(config.dashboard)
383
- ? config.dashboard
384
- : null
385
- if (!dashboard) {
386
- const error = new Error('spexcode.json dashboard must be an object before its icon can be changed') as Error & { status?: number }
387
- error.status = 400
388
- throw error
389
- }
390
- const saved = writeProjectConfig(root, `${JSON.stringify({ ...config, dashboard: { ...dashboard, icon: canonical } }, null, 2)}\n`, revision)
391
- return { ...saved, identity: resolveProjectIdentity(root, root) }
392
- }
393
-
394
- function freeTcpPort(): Promise<number> {
395
- return new Promise((res, rej) => {
396
- const s = net.createServer()
397
- s.once('error', rej)
398
- s.listen(0, '127.0.0.1', () => { const p = (s.address() as net.AddressInfo).port; s.close(() => res(p)) })
399
- })
400
- }
401
-
402
- // start an OFFLINE project's backend: spawn a detached `spex serve --port <free>` whose lifetime is
403
- // independent of the gateway (it owns its record exactly like a hand-run serve), log to the project's
404
- // runtime tier, and wait for its instance-validated record to reconcile online.
405
- export async function startBackend(root: string, waitMs = 45_000): Promise<ProjectEntry> {
406
- const port = await freeTcpPort()
407
- const logDir = join(spexcodeHome(), 'projects', encodeProject(root))
408
- mkdirSync(logDir, { recursive: true })
409
- const logFile = join(logDir, 'serve.log')
410
- const log = openSync(logFile, 'a')
411
- const child = spawn(process.execPath, [tsxBin(join(here, '..')), cliEntry, 'serve', '--port', String(port)],
412
- { cwd: root, env: scrubbedEnv(), detached: true, stdio: ['ignore', log, log] })
413
- child.unref()
414
- closeSync(log) // the child holds its own copy; keep no fd open in the gateway
415
- const deadline = Date.now() + waitMs
416
- while (Date.now() < deadline) {
417
- await new Promise((r) => setTimeout(r, 500))
418
- const entry = (await reconcileNow()).find((p) => p.root === root && p.online)
419
- if (entry) return entry
420
- }
421
- throw new Error(`backend for ${root} did not come online within ${Math.round(waitMs / 1000)}s — see ${logFile}`)
422
- }
423
-
424
-
425
- // ── the operator verb: `spex dashboard` = the hub + the host extensions ─────────────────────────────
426
- // The hub ([[gateway-hub]]) is the ONE routing + authorization server; this layer never runs a second
427
- // gateway or a second auth check beside it. What the host adds rides the hub's extension seam:
428
- // listProjects — GET /projects rows come from the instance-validated reconciler + the durable catalog
429
- // (online/offline/root), each carrying the hub's gating state.
430
- // adminRoute — /projects/stream (SSE), GET /projects/browse + POST /projects (select/setup/register), raw spexcode.json
431
- // GET|PUT /projects/:id/config, and POST /projects/:id/(init|doctor|serve) — all
432
- // behind the hub's admin scope
433
- // ([[gateway-auth]]: implicit from loopback until an admin password exists).
434
- // fallback — the dashboard SPA shell + assets for paths the hub doesn't own.
435
- // /p/:projectId/* routing (HTTP, SSE, WS; prefix strip; cookie strip) belongs entirely to the hub.
436
-
437
- // tls is the HUB's transport option passed through unchanged ([[gateway-hub]] terminates HTTPS itself),
438
- // so an operator deployment runs the ONE host gateway directly over TLS — no second proxy in front.
439
- // Absent tls = the default plain-HTTP loopback `spex dashboard` serves.
440
- export type HostDashboardOpts = { port: number; host?: string; distDir?: string; tls?: { cert: string; key: string } | null }
441
- export type HostDashboard = { server: http.Server; close: () => Promise<void> }
442
-
443
- const json = (res: http.ServerResponse, status: number, body: unknown) => {
444
- res.writeHead(status, { 'Content-Type': 'application/json' })
445
- res.end(JSON.stringify(body))
446
- }
447
- function readBody(req: http.IncomingMessage): Promise<string> {
448
- return new Promise((resolve) => {
449
- let body = ''
450
- req.on('data', (d) => { body += d; if (body.length > 65536) req.destroy() })
451
- req.on('end', () => resolve(body))
452
- })
453
- }
454
-
455
- export function startHostDashboard(opts: HostDashboardOpts): HostDashboard {
456
- const distDir = opts.distDir ?? resolveDistDir()
457
- if (!opts.distDir) ensureDashboardBuilt(join(here, '..', '..'), distDir)
458
-
459
- const sseClients = new Set<http.ServerResponse>()
460
- let lastBroadcast = ''
461
- // reconcile → push the fresh list to every /projects/stream subscriber when it changed.
462
- async function tick(): Promise<ProjectEntry[]> {
463
- const list = await reconcileNow()
464
- const j = JSON.stringify(list)
465
- if (j !== lastBroadcast) { lastBroadcast = j; for (const c of sseClients) c.write(`data: ${j}\n\n`) }
466
- return list
467
- }
468
-
469
- const extensions: HubExtensions = {
470
- // the hub's GET /projects rows, host-enriched: every cataloged/claimed project (not only live
471
- // records), instance-validated online state, and the hub's own gating flag per row. `id` mirrors
472
- // projectId — the hub's documented row key, kept so both shapes read the same list.
473
- listProjects: async (store) => (await tick()).map((p) => ({ id: p.projectId, ...p, gated: !!store.projects[p.projectId] })),
474
-
475
- adminRoute: async (req, res, path) => {
476
- if (path === '/projects/stream') {
477
- res.writeHead(200, { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', Connection: 'keep-alive' })
478
- res.write(`data: ${JSON.stringify(await tick())}\n\n`)
479
- sseClients.add(res)
480
- req.on('close', () => sseClients.delete(res))
481
- return true
482
- }
483
- if (path === '/projects/browse' && req.method === 'GET') {
484
- const requested = new URL(req.url ?? path, 'http://localhost').searchParams.get('path') ?? ''
485
- try { json(res, 200, browseProjectDirectories(requested)) }
486
- catch (e) { json(res, 400, { error: (e as Error).message }) }
487
- return true
488
- }
489
- if (path === '/projects' && req.method === 'POST') {
490
- let body: any = {}
491
- try { body = JSON.parse(await readBody(req) || '{}') } catch { /* malformed body */ }
492
- const root = String(body?.root ?? '').trim()
493
- if (!root) { json(res, 400, { error: 'body must be {"root": "/abs/path/to/repo"}' }); return true }
494
- if (body.init !== undefined && (!body.init || typeof body.init !== 'object' || Array.isArray(body.init))) {
495
- json(res, 400, { error: 'init must be {"harness":"<ids>","preset":"<optional>"}' })
496
- return true
497
- }
498
- try {
499
- const setup = await addKnownProjectWithSetup(root, {
500
- initGit: body.initGit === true,
501
- ...(body.init ? { init: { harness: body.init.harness, ...(body.init.preset !== undefined ? { preset: body.init.preset } : {}) } } : {}),
502
- })
503
- if (!setup.ok) {
504
- json(res, 422, { error: 'spex init failed', ...setup })
505
- return true
506
- }
507
- const entry = (await tick()).find((p) => p.root === setup.root)
508
- json(res, 200, { ...(entry ?? {
509
- projectId: encodeProject(setup.root), root: setup.root,
510
- identity: resolveProjectIdentity(setup.root, setup.root), configRevision: readProjectConfig(setup.root).revision,
511
- online: false, url: null,
512
- }), setup })
513
- } catch (e) { json(res, 400, { error: (e as Error).message }) }
514
- return true
515
- }
516
- if (path === '/projects/icon' && req.method === 'PUT') {
517
- let body: any
518
- try { body = JSON.parse(await readBody(req) || '{}') }
519
- catch { json(res, 400, { error: 'body must be {"icon":"<choice>","revision":"..."}' }); return true }
520
- if (typeof body?.icon !== 'string' || typeof body?.revision !== 'string') {
521
- json(res, 400, { error: 'body must be {"icon":"<choice>","revision":"..."}' })
522
- return true
523
- }
524
- try { json(res, 200, { ok: true, gateway: writeGatewayIcon(body.icon, body.revision) }) }
525
- catch (e) { json(res, (e as any).status ?? 400, { error: (e as Error).message }) }
526
- return true
527
- }
528
- const icon = path.match(/^\/projects\/([^/]+)\/icon$/)
529
- if (icon && req.method === 'PUT') {
530
- const projectId = decodeURIComponent(icon[1])
531
- const entry = (await reconcileNow()).find((p) => p.projectId === projectId)
532
- if (!entry) { json(res, 404, { error: `unknown project '${projectId}' — add it first (POST /projects)` }); return true }
533
- let body: any
534
- try { body = JSON.parse(await readBody(req) || '{}') }
535
- catch { json(res, 400, { error: 'body must be {"icon":"<choice>","revision":"..."}' }); return true }
536
- if (typeof body?.icon !== 'string' || typeof body?.revision !== 'string') {
537
- json(res, 400, { error: 'body must be {"icon":"<choice>","revision":"..."}' })
538
- return true
539
- }
540
- try { json(res, 200, { ok: true, ...writeProjectIcon(entry.root, body.icon, body.revision) }) }
541
- catch (e) { json(res, (e as any).status ?? 400, { error: (e as Error).message }) }
542
- return true
543
- }
544
- const config = path.match(/^\/projects\/([^/]+)\/config$/)
545
- if (config && (req.method === 'GET' || req.method === 'PUT')) {
546
- const projectId = decodeURIComponent(config[1])
547
- const entry = (await reconcileNow()).find((p) => p.projectId === projectId)
548
- if (!entry) { json(res, 404, { error: `unknown project '${projectId}' — add it first (POST /projects)` }); return true }
549
- if (req.method === 'GET') {
550
- try { json(res, 200, readProjectConfig(entry.root)) }
551
- catch (e) { json(res, 500, { error: `cannot read spexcode.json: ${(e as Error).message}` }) }
552
- return true
553
- }
554
- let body: any
555
- try { body = JSON.parse(await readBody(req) || '{}') }
556
- catch { json(res, 400, { error: 'body must be {"content":"...","revision":"..."}' }); return true }
557
- if (typeof body?.content !== 'string' || typeof body?.revision !== 'string') {
558
- json(res, 400, { error: 'body must be {"content":"...","revision":"..."}' })
559
- return true
560
- }
561
- try { json(res, 200, { ok: true, ...writeProjectConfig(entry.root, body.content, body.revision) }) }
562
- catch (e) { json(res, (e as any).status ?? 500, { error: (e as Error).message }) }
563
- return true
564
- }
565
- const op = path.match(/^\/projects\/([^/]+)\/(init|doctor|serve)$/)
566
- if (op && req.method === 'POST') {
567
- const projectId = decodeURIComponent(op[1])
568
- const entry = (await reconcileNow()).find((p) => p.projectId === projectId)
569
- if (!entry) { json(res, 404, { error: `unknown project '${projectId}' — add it first (POST /projects)` }); return true }
570
- if (op[2] === 'serve') {
571
- if (entry.online) { json(res, 409, { error: `backend already online at ${entry.url}`, project: entry }); return true }
572
- try { json(res, 200, { ok: true, project: await startBackend(entry.root) }) }
573
- catch (e) { json(res, 502, { error: (e as Error).message }) }
574
- return true
575
- }
576
- let body: any = {}
577
- try { body = JSON.parse(await readBody(req) || '{}') } catch { /* malformed body → defaults */ }
578
- const args = op[2] === 'init'
579
- ? ['init', ...(body?.harness ? ['--harness', String(body.harness)] : []), ...(body?.preset ? ['--preset', String(body.preset)] : [])]
580
- : ['doctor']
581
- const r = await runSpex(entry.root, args)
582
- json(res, 200, { ok: r.code === 0, code: r.code, output: r.output })
583
- return true
584
- }
585
- return false
586
- },
587
-
588
- fallback: (req, res, path) => serveStatic(req, res, distDir, path),
589
- }
590
-
591
- const server = startHubGateway({ port: opts.port, host: opts.host ?? '127.0.0.1', tls: opts.tls ?? null, extensions })
592
-
593
- // continuous reconciliation: the stream stays live without a client-side poll and the list the hub
594
- // serves stays fresh. Heartbeat comments keep intermediaries from timing the stream out. Both timers
595
- // unref'd — the SERVER holds the process open, not the loops.
596
- const loop = setInterval(() => void tick().catch((e) => console.error(`[host] reconcile failed: ${(e as Error).message}`)), 2500)
597
- loop.unref()
598
- const ping = setInterval(() => { for (const c of sseClients) c.write(': ping\n\n') }, 10_000)
599
- ping.unref()
600
-
601
- return {
602
- server,
603
- close: () => new Promise<void>((resolve) => {
604
- clearInterval(loop); clearInterval(ping)
605
- for (const c of sseClients) c.destroy()
606
- sseClients.clear()
607
- server.close(() => resolve())
608
- server.closeAllConnections?.()
609
- }),
610
- }
611
- }
@@ -1,13 +0,0 @@
1
- export type IdentityShape = { tag: string; [key: string]: string | number }
2
- export type IdentityPreset = { id: string; label: string; bg: string; fg: string; shapes: IdentityShape[] }
3
-
4
- export const DEFAULT_PROJECT_ICON: string
5
- export const DEFAULT_GATEWAY_ICON: string
6
- export const IDENTITY_PRESETS: readonly IdentityPreset[]
7
- export const IDENTITY_PRESET_IDS: readonly string[]
8
- export function resolvedIdentityIcon(value: unknown, fallback?: string): string
9
- export function identityPreset(value: unknown): IdentityPreset | null
10
- export function isIconifyIcon(value: unknown): boolean
11
- export function requireIdentityChoice(value: unknown): string
12
- export function identitySvg(value: unknown, fallback?: string): string
13
- export function identityFaviconHref(value: unknown, fallback?: string): string