specrails-desktop 2.55.0 → 2.56.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (368) hide show
  1. package/cli/dist/args.js +80 -0
  2. package/cli/dist/desktop-client.js +125 -0
  3. package/cli/dist/desktop-control.js +339 -0
  4. package/cli/dist/format.js +45 -0
  5. package/cli/dist/help.js +68 -0
  6. package/cli/dist/output.js +47 -0
  7. package/cli/dist/run-command.js +340 -0
  8. package/cli/dist/specrails-desktop.js +41 -1107
  9. package/cli/dist/status.js +111 -0
  10. package/client/dist/assets/{ActivityFeedPage-ILXMZXcb.js → ActivityFeedPage-DdMNmVBk.js} +1 -1
  11. package/client/dist/assets/{AgentBrowserCapture-UcsupCy7.js → AgentBrowserCapture-CeerUf--.js} +1 -1
  12. package/client/dist/assets/{AgentModeAnalyticsPane-CQuxYh8B.js → AgentModeAnalyticsPane-BjGb-8rh.js} +2 -2
  13. package/client/dist/assets/{AgentModeCodePane-zPWbW7ei.js → AgentModeCodePane-8XRkQEo_.js} +2 -2
  14. package/client/dist/assets/{AgentModeJobsPane-CInByIdl.js → AgentModeJobsPane-BN3dgJOA.js} +1 -1
  15. package/client/dist/assets/{AgentsPage-BWvgxaBv.js → AgentsPage-DfHWgP_x.js} +1 -1
  16. package/client/dist/assets/{AnalyticsPage-DP3jakv-.js → AnalyticsPage-CyfhYiBn.js} +1 -1
  17. package/client/dist/assets/{CodePage-BKXiwE5a.js → CodePage-C2Nf-Q5W.js} +1 -1
  18. package/client/dist/assets/{DesktopAnalyticsPage-DryJOaMz.js → DesktopAnalyticsPage-tc0p4jJW.js} +1 -1
  19. package/client/dist/assets/{DocsDialog-BsCG9ksG.js → DocsDialog-Br3bczCp.js} +1 -1
  20. package/client/dist/assets/{DocsPage-Cc-GeKYl.js → DocsPage-BcDr2cBm.js} +1 -1
  21. package/client/dist/assets/{ExportDropdown-ClahJdDn.js → ExportDropdown-BVGKEgkd.js} +1 -1
  22. package/client/dist/assets/{InteractiveJobComposer-DfLF6N0f.js → InteractiveJobComposer-DFPpdg2D.js} +1 -1
  23. package/client/dist/assets/{JobDetailModal-DHeur9YK.js → JobDetailModal-BdOv-l-M.js} +1 -1
  24. package/client/dist/assets/{JobDetailPage-CraDKzeG.js → JobDetailPage-vBbNeRuz.js} +1 -1
  25. package/client/dist/assets/{JobsPage-CcbuSwj7.js → JobsPage-CJCtPhIz.js} +1 -1
  26. package/client/dist/assets/{LoopBuilderPage-BKtNp494.js → LoopBuilderPage-BemvUOxy.js} +1 -1
  27. package/client/dist/assets/{LoopPreviewModal-DbZd9isy.js → LoopPreviewModal-C6hNAeYN.js} +1 -1
  28. package/client/dist/assets/{LoopsPage-DEg6sIkG.js → LoopsPage-BZr0quvh.js} +1 -1
  29. package/client/dist/assets/{MinimizedChatsContext-BGtEjWsc.js → MinimizedChatsContext-D3QzgV0B.js} +1 -1
  30. package/client/dist/assets/{PluginsPage-BVqSsPzL.js → PluginsPage-DOpTV75p.js} +1 -1
  31. package/client/dist/assets/{ProjectSettingsDialog-CAjLgJAZ.js → ProjectSettingsDialog-Dkwrn0z1.js} +1 -1
  32. package/client/dist/assets/{RepositoryDeliveries-DrcrxLe0.js → RepositoryDeliveries-Du2UCf1a.js} +1 -1
  33. package/client/dist/assets/{RepositoryScopeSelector-CAGY3xW6.js → RepositoryScopeSelector-hPGZNRBm.js} +1 -1
  34. package/client/dist/assets/ReviewPacketPage-DcOq-UTA.js +4 -0
  35. package/client/dist/assets/{TemplatePreviewModal-Bqs-9fhn.js → TemplatePreviewModal-Cejcquah.js} +1 -1
  36. package/client/dist/assets/{TicketDetailModalContext-C0jQ5yOk.js → TicketDetailModalContext-DAK-NoK6.js} +1 -1
  37. package/client/dist/assets/{Trans-DtWlCJN9.js → Trans-DEbgkuAY.js} +1 -1
  38. package/client/dist/assets/{dashboard-CTfEtkqX.js → dashboard-B9uWNGO2.js} +1 -1
  39. package/client/dist/assets/{dashboard-DMklepmz.js → dashboard-BIwmO9wc.js} +1 -1
  40. package/client/dist/assets/{dashboard-DBAP2_SQ.js → dashboard-BV4vOZkR.js} +1 -1
  41. package/client/dist/assets/{dashboard-D40HP2ks.js → dashboard-Bf6MPVsM.js} +1 -1
  42. package/client/dist/assets/{dashboard-Csf1yGSN.js → dashboard-C8klWVvZ.js} +1 -1
  43. package/client/dist/assets/{dashboard-C29Qwa-L.js → dashboard-D1ORhPF0.js} +1 -1
  44. package/client/dist/assets/{dashboard-Cr4D8oMn.js → dashboard-DSubvs9x.js} +1 -1
  45. package/client/dist/assets/{dashboard-CSJzf5OT.js → dashboard-px_vlRx_.js} +1 -1
  46. package/client/dist/assets/{formatDistanceToNow-DYmvOA3R.js → formatDistanceToNow-Cc2u5FwQ.js} +1 -1
  47. package/client/dist/assets/{getTimezoneOffsetInMilliseconds-BAhW5E6V.js → getTimezoneOffsetInMilliseconds-BHm2U-w1.js} +1 -1
  48. package/client/dist/assets/index-C51x61Bz.css +2 -0
  49. package/client/dist/assets/{index-BfAZg5DG.js → index-Cxb288Rj.js} +29 -29
  50. package/client/dist/assets/{jira-api-B27Za5FP.js → jira-api-DrGxiD9p.js} +1 -1
  51. package/client/dist/assets/packet-8nn9RsU7.js +1 -0
  52. package/client/dist/assets/packet-B0H15elv.js +1 -0
  53. package/client/dist/assets/packet-BQvBgrwh.js +1 -0
  54. package/client/dist/assets/packet-BR7mO0-a.js +1 -0
  55. package/client/dist/assets/packet-BgcQ6q_K.js +1 -0
  56. package/client/dist/assets/packet-D2s92Jnn.js +1 -0
  57. package/client/dist/assets/packet-YbtcD1HS.js +1 -0
  58. package/client/dist/assets/packet-c0Qj_9eX.js +1 -0
  59. package/client/dist/assets/{project-repositories-DixIZHTA.js → project-repositories-9Wzs0coQ.js} +1 -1
  60. package/client/dist/assets/{spending-BGmZBX5B.js → spending-T5GM7wpn.js} +1 -1
  61. package/client/dist/assets/{useDesktop-CPB4cnwI.js → useDesktop-QogiN0zR.js} +1 -1
  62. package/client/dist/assets/{useRuntimeRuns-DEQ9HdAM.js → useRuntimeRuns-Dqmz9fS9.js} +1 -1
  63. package/client/dist/assets/{useSharedWebSocket-DcPRKR8R.js → useSharedWebSocket-Dz-HI6De.js} +2 -2
  64. package/client/dist/index.html +17 -17
  65. package/docs/codex.md +5 -5
  66. package/docs/customizing.md +1 -1
  67. package/docs/gemini-cli-provider-study.md +14 -14
  68. package/docs/gemini-core-support-evaluation.md +1 -1
  69. package/docs/gemini.md +5 -5
  70. package/docs/internals/README.md +6 -2
  71. package/docs/internals/adding-a-provider.md +22 -15
  72. package/docs/internals/agent-runtime-framework-evaluation.md +8 -8
  73. package/docs/internals/api-reference.md +2 -2
  74. package/docs/internals/architecture.md +9 -9
  75. package/docs/internals/browser-capture-performance.md +3 -3
  76. package/docs/internals/code-explorer-story.md +2 -2
  77. package/docs/internals/companion-rails-as-loops-contract.md +2 -2
  78. package/docs/internals/core-runtime-updates.md +5 -0
  79. package/docs/internals/embedded-browser-native-webview-evaluation.md +5 -5
  80. package/docs/internals/gemini-mcp-registration.md +2 -2
  81. package/docs/internals/global-artifacts-alignment-contract.md +1 -1
  82. package/docs/internals/global-artifacts-relocation-evaluation.md +1 -1
  83. package/docs/internals/interactive-jobs.md +8 -8
  84. package/docs/internals/legacy-implementation-notes.md +722 -0
  85. package/docs/internals/loop-step-log-explorer.md +8 -8
  86. package/docs/internals/mission-rail-cards.md +14 -14
  87. package/docs/internals/modular-architecture.md +107 -0
  88. package/docs/internals/profiles.md +1 -1
  89. package/docs/internals/programmatic-agent-runtime.md +66 -0
  90. package/docs/internals/project-builder.md +18 -18
  91. package/docs/internals/review-packet.md +8 -8
  92. package/docs/internals/safe-pr-review-flow.md +11 -11
  93. package/docs/internals/source-architecture.md +131 -0
  94. package/docs/internals/source-map.md +1356 -0
  95. package/docs/internals/spec-addenda.md +65 -9
  96. package/docs/internals/switch-to-agent-mode-design.md +11 -11
  97. package/docs/tracking-cost.md +1 -1
  98. package/package.json +5 -2
  99. package/server/dist/agent-mcp-config.js +1 -1
  100. package/server/dist/attachment-manager.js +1 -1
  101. package/server/dist/build-dirs.js +2 -2
  102. package/server/dist/config.js +0 -3
  103. package/server/dist/core-completion.js +1 -1
  104. package/server/dist/core-execution.js +1 -1
  105. package/server/dist/db/activity.js +39 -0
  106. package/server/dist/db/connection.js +41 -0
  107. package/server/dist/db/conversations.js +48 -0
  108. package/server/dist/db/jobs.js +251 -0
  109. package/server/dist/db/migrations.js +1521 -0
  110. package/server/dist/db/proposals.js +50 -0
  111. package/server/dist/db/settings.js +15 -0
  112. package/server/dist/db/stats.js +61 -0
  113. package/server/dist/db/telemetry.js +56 -0
  114. package/server/dist/db/templates.js +37 -0
  115. package/server/dist/db/types.js +2 -0
  116. package/server/dist/db.js +66 -2224
  117. package/server/dist/desktop-db.js +3 -3
  118. package/server/dist/desktop-router.js +8 -8
  119. package/server/dist/git-diagnostics.js +1 -1
  120. package/server/dist/index.js +11 -11
  121. package/server/dist/integration-branch.js +3 -14
  122. package/server/dist/jira/jira-issue-fields.js +0 -1
  123. package/server/dist/jira/jira-materializer.js +1 -1
  124. package/server/dist/jira/jira-sync-manager.js +1 -1
  125. package/server/dist/mcp/guide.js +16 -2
  126. package/server/dist/mcp/mcp-admin-router.js +1 -1
  127. package/server/dist/mcp/mcp-server.js +1 -1
  128. package/server/dist/mcp/tools/catalog.js +2 -0
  129. package/server/dist/mcp/tools/jobs.js +23 -5
  130. package/server/dist/mcp/tools/rails.js +2 -2
  131. package/server/dist/mcp/tools/recovery.js +35 -0
  132. package/server/dist/mcp/tools/specs.js +2 -2
  133. package/server/dist/mcp/tools/support.js +4 -1
  134. package/server/dist/mcp/tools/types.js +3 -3
  135. package/server/dist/mobile/mobile-gateway.js +1 -1
  136. package/server/dist/mobile/mobile-missions.js +1 -1
  137. package/server/dist/{desktop-analytics.js → modules/accounting/runtime/desktop-analytics.js} +2 -2
  138. package/server/dist/{pricing.js → modules/accounting/runtime/pricing.js} +10 -3
  139. package/server/dist/{result-event.js → modules/accounting/runtime/result-event.js} +3 -3
  140. package/server/dist/{spending.js → modules/accounting/runtime/spending.js} +0 -3
  141. package/server/dist/{telemetry-compactor.js → modules/accounting/runtime/telemetry-compactor.js} +1 -1
  142. package/server/dist/{telemetry-receiver.js → modules/accounting/runtime/telemetry-receiver.js} +2 -2
  143. package/server/dist/{agent-runtime-bridge.js → modules/agent-runtime/runtime/agent-runtime-bridge.js} +2 -2
  144. package/server/dist/{agent-runtime-controls-router.js → modules/agent-runtime/runtime/agent-runtime-controls-router.js} +16 -0
  145. package/server/dist/{agent-runtime-controls.js → modules/agent-runtime/runtime/agent-runtime-controls.js} +100 -8
  146. package/server/dist/{agent-runtime-effective-config.js → modules/agent-runtime/runtime/agent-runtime-effective-config.js} +1 -1
  147. package/server/dist/{agent-runtime-loader.js → modules/agent-runtime/runtime/agent-runtime-loader.js} +3 -3
  148. package/server/dist/{agent-runtime-paths.js → modules/agent-runtime/runtime/agent-runtime-paths.js} +1 -1
  149. package/server/dist/modules/agent-runtime/runtime/agent-runtime-recovery.js +53 -0
  150. package/server/dist/{agent-runtime-settings-router.js → modules/agent-runtime/runtime/agent-runtime-settings-router.js} +2 -2
  151. package/server/dist/{agent-runtime-settings.js → modules/agent-runtime/runtime/agent-runtime-settings.js} +3 -3
  152. package/server/dist/{agent-runtime-settlement.js → modules/agent-runtime/runtime/agent-runtime-settlement.js} +6 -6
  153. package/server/dist/{agent-defaults.js → modules/agents/runtime/agent-defaults.js} +3 -3
  154. package/server/dist/{agent-generator.js → modules/agents/runtime/agent-generator.js} +7 -7
  155. package/server/dist/{agent-refine-manager.js → modules/agents/runtime/agent-refine-manager.js} +5 -5
  156. package/server/dist/{agent-store.js → modules/agents/runtime/agent-store.js} +1 -1
  157. package/server/dist/{profile-manager.js → modules/agents/runtime/profile-manager.js} +2 -2
  158. package/server/dist/{profiles-router.js → modules/agents/runtime/profiles-router.js} +5 -5
  159. package/server/dist/{browser-capture-manager.js → modules/browser/runtime/browser-capture-manager.js} +2 -4
  160. package/server/dist/{browser-playwright.js → modules/browser/runtime/browser-playwright.js} +1 -1
  161. package/server/dist/{blueprint-chat-manager.js → modules/builder/runtime/blueprint-chat-manager.js} +7 -7
  162. package/server/dist/{blueprint-commit.js → modules/builder/runtime/blueprint-commit.js} +8 -8
  163. package/server/dist/{blueprint-draft-parser.js → modules/builder/runtime/blueprint-draft-parser.js} +1 -1
  164. package/server/dist/{blueprint-generation.js → modules/builder/runtime/blueprint-generation.js} +1 -1
  165. package/server/dist/{blueprint-operator-prompt.js → modules/builder/runtime/blueprint-operator-prompt.js} +1 -1
  166. package/server/dist/{blueprint-router.js → modules/builder/runtime/blueprint-router.js} +3 -3
  167. package/server/dist/{blueprint-spec-quality.js → modules/builder/runtime/blueprint-spec-quality.js} +1 -1
  168. package/server/dist/{milestone-chain.js → modules/builder/runtime/milestone-chain.js} +1 -1
  169. package/server/dist/{milestone-progress.js → modules/builder/runtime/milestone-progress.js} +6 -6
  170. package/server/dist/{code-activity.js → modules/code/runtime/code-activity.js} +1 -1
  171. package/server/dist/{code-explorer-router.js → modules/code/runtime/code-explorer-router.js} +5 -5
  172. package/server/dist/{file-provenance.js → modules/code/runtime/file-provenance.js} +1 -1
  173. package/server/dist/{file-story-manager.js → modules/code/runtime/file-story-manager.js} +2 -2
  174. package/server/dist/{file-story.js → modules/code/runtime/file-story.js} +2 -2
  175. package/server/dist/{file-summary-generator.js → modules/code/runtime/file-summary-generator.js} +3 -3
  176. package/server/dist/{file-summary-manager.js → modules/code/runtime/file-summary-manager.js} +3 -3
  177. package/server/dist/{project-code-discovery.js → modules/code/runtime/project-code-discovery.js} +2 -2
  178. package/server/dist/modules/conversations/domain/draft-stream.js +91 -0
  179. package/server/dist/modules/conversations/domain/recovery-context.js +101 -0
  180. package/server/dist/modules/conversations/index.js +10 -0
  181. package/server/dist/{chat-manager.js → modules/conversations/runtime/chat-manager.js} +32 -216
  182. package/server/dist/{context-budget.js → modules/conversations/runtime/context-budget.js} +1 -1
  183. package/server/dist/{explore-cwd-manager.js → modules/conversations/runtime/explore-cwd-manager.js} +1 -1
  184. package/server/dist/{explore-smash.js → modules/conversations/runtime/explore-smash.js} +1 -1
  185. package/server/dist/{explore-stdin-session.js → modules/conversations/runtime/explore-stdin-session.js} +1 -1
  186. package/server/dist/modules/delivery/adapters/decisions/chains.js +138 -0
  187. package/server/dist/modules/delivery/adapters/decisions/contracts.js +2 -0
  188. package/server/dist/modules/delivery/adapters/decisions/create.js +234 -0
  189. package/server/dist/modules/delivery/adapters/decisions/discard.js +251 -0
  190. package/server/dist/modules/delivery/adapters/decisions/dispatch.js +107 -0
  191. package/server/dist/modules/delivery/adapters/decisions/evidence.js +205 -0
  192. package/server/dist/modules/delivery/adapters/decisions/merge-local.js +338 -0
  193. package/server/dist/modules/delivery/adapters/decisions/metadata.js +109 -0
  194. package/server/dist/modules/delivery/adapters/decisions/observations.js +260 -0
  195. package/server/dist/modules/delivery/adapters/decisions/ownership.js +83 -0
  196. package/server/dist/modules/delivery/adapters/decisions/publish.js +242 -0
  197. package/server/dist/modules/delivery/adapters/decisions/recovery.js +425 -0
  198. package/server/dist/modules/delivery/adapters/decisions/retry.js +112 -0
  199. package/server/dist/modules/delivery/adapters/decisions/transitions.js +94 -0
  200. package/server/dist/modules/delivery/domain/decision-policy.js +50 -0
  201. package/server/dist/modules/delivery/domain/state.js +2 -0
  202. package/server/dist/modules/delivery/index.js +7 -0
  203. package/server/dist/{active-pr-continuation.js → modules/delivery/runtime/active-pr-continuation.js} +2 -2
  204. package/server/dist/{delivery-evidence.js → modules/delivery/runtime/delivery-evidence.js} +3 -3
  205. package/server/dist/{multi-repo-bases.js → modules/delivery/runtime/multi-repo-bases.js} +1 -1
  206. package/server/dist/{multi-repo-checkout.js → modules/delivery/runtime/multi-repo-checkout.js} +4 -4
  207. package/server/dist/{multi-repo-delivery.js → modules/delivery/runtime/multi-repo-delivery.js} +4 -4
  208. package/server/dist/{multi-repo-execution.js → modules/delivery/runtime/multi-repo-execution.js} +8 -8
  209. package/server/dist/{pr-body.js → modules/delivery/runtime/pr-body.js} +1 -1
  210. package/server/dist/{pr-follow-up-scope.js → modules/delivery/runtime/pr-follow-up-scope.js} +2 -2
  211. package/server/dist/{pr-follow-up.js → modules/delivery/runtime/pr-follow-up.js} +1 -1
  212. package/server/dist/{pr-naming.js → modules/delivery/runtime/pr-naming.js} +1 -1
  213. package/server/dist/{pr-publisher.js → modules/delivery/runtime/pr-publisher.js} +2 -2
  214. package/server/dist/{rail-isolated-launch.js → modules/delivery/runtime/rail-isolated-launch.js} +29 -27
  215. package/server/dist/{rail-launch-parser.js → modules/delivery/runtime/rail-launch-parser.js} +2 -2
  216. package/server/dist/{rail-merge-orchestrator.js → modules/delivery/runtime/rail-merge-orchestrator.js} +5 -5
  217. package/server/dist/modules/delivery/runtime/rail-pr-decision.js +12 -0
  218. package/server/dist/{rail-pr-delivery.js → modules/delivery/runtime/rail-pr-delivery.js} +1 -1
  219. package/server/dist/{rail-pr-store.js → modules/delivery/runtime/rail-pr-store.js} +2 -2
  220. package/server/dist/{rail-pr-ticket-effects.js → modules/delivery/runtime/rail-pr-ticket-effects.js} +3 -3
  221. package/server/dist/{rail-worktree-release.js → modules/delivery/runtime/rail-worktree-release.js} +4 -4
  222. package/server/dist/{rails-router.js → modules/delivery/runtime/rails-router.js} +66 -67
  223. package/server/dist/{review-packet.js → modules/delivery/runtime/review-packet.js} +3 -3
  224. package/server/dist/modules/execution/adapters/budget-storage.js +21 -0
  225. package/server/dist/modules/execution/adapters/usage-reader.js +38 -0
  226. package/server/dist/modules/execution/application/enforce-budget.js +14 -0
  227. package/server/dist/modules/execution/application/record-job-invocations.js +64 -0
  228. package/server/dist/modules/execution/application/recover-job-usage.js +200 -0
  229. package/server/dist/modules/execution/domain/job-accounting.js +2 -0
  230. package/server/dist/modules/execution/domain/scheduling.js +24 -0
  231. package/server/dist/modules/execution/domain/usage.js +21 -0
  232. package/server/dist/modules/execution/index.js +14 -0
  233. package/server/dist/modules/execution/ports.js +2 -0
  234. package/server/dist/{accept-ladder.js → modules/execution/runtime/accept-ladder.js} +1 -1
  235. package/server/dist/{interactive-job-session.js → modules/execution/runtime/interactive-job-session.js} +11 -11
  236. package/server/dist/{job-phase-breakdown.js → modules/execution/runtime/job-phase-breakdown.js} +1 -1
  237. package/server/dist/{queue-manager.js → modules/execution/runtime/queue-manager.js} +74 -491
  238. package/server/dist/{revision-seed.js → modules/execution/runtime/revision-seed.js} +1 -1
  239. package/server/dist/{spawn-lifecycle.js → modules/execution/runtime/spawn-lifecycle.js} +3 -3
  240. package/server/dist/{stuck-run-detector.js → modules/execution/runtime/stuck-run-detector.js} +1 -1
  241. package/server/dist/{loop-command-catalog.js → modules/loops/runtime/loop-command-catalog.js} +1 -1
  242. package/server/dist/{loop-executors.js → modules/loops/runtime/loop-executors.js} +17 -17
  243. package/server/dist/{loop-factory.js → modules/loops/runtime/loop-factory.js} +2 -21
  244. package/server/dist/{loop-graph.js → modules/loops/runtime/loop-graph.js} +25 -0
  245. package/server/dist/{loop-role-engines.js → modules/loops/runtime/loop-role-engines.js} +4 -4
  246. package/server/dist/{loop-run-manager.js → modules/loops/runtime/loop-run-manager.js} +101 -26
  247. package/server/dist/{loop-runs-store.js → modules/loops/runtime/loop-runs-store.js} +1 -1
  248. package/server/dist/{loop-shell-invocation.js → modules/loops/runtime/loop-shell-invocation.js} +2 -2
  249. package/server/dist/{loop-step-idle.js → modules/loops/runtime/loop-step-idle.js} +1 -1
  250. package/server/dist/{loop-templates.js → modules/loops/runtime/loop-templates.js} +24 -29
  251. package/server/dist/{loops-router.js → modules/loops/runtime/loops-router.js} +2 -2
  252. package/server/dist/{agent-chat-manager.js → modules/missions/runtime/agent-chat-manager.js} +19 -19
  253. package/server/dist/{agent-chat-router.js → modules/missions/runtime/agent-chat-router.js} +6 -6
  254. package/server/dist/{agent-context-resolver.js → modules/missions/runtime/agent-context-resolver.js} +8 -8
  255. package/server/dist/{agent-failure-briefing.js → modules/missions/runtime/agent-failure-briefing.js} +4 -2
  256. package/server/dist/{agent-fence-promotion.js → modules/missions/runtime/agent-fence-promotion.js} +1 -1
  257. package/server/dist/{agent-input-store.js → modules/missions/runtime/agent-input-store.js} +1 -1
  258. package/server/dist/{agent-operator-prompt.js → modules/missions/runtime/agent-operator-prompt.js} +75 -12
  259. package/server/dist/{agent-spec-framing.js → modules/missions/runtime/agent-spec-framing.js} +4 -4
  260. package/server/dist/{agent-steering.js → modules/missions/runtime/agent-steering.js} +1 -1
  261. package/server/dist/{mission-run-notify.js → modules/missions/runtime/mission-run-notify.js} +5 -2
  262. package/server/dist/modules/project-settings/adapters/http.js +24 -0
  263. package/server/dist/modules/project-settings/adapters/sqlite.js +120 -0
  264. package/server/dist/modules/project-settings/application.js +11 -0
  265. package/server/dist/modules/project-settings/domain.js +95 -0
  266. package/server/dist/modules/project-settings/index.js +11 -0
  267. package/server/dist/modules/project-settings/ports.js +2 -0
  268. package/server/dist/{contract-refine-runner.js → modules/specs/runtime/contract-refine-runner.js} +12 -12
  269. package/server/dist/{proposal-manager.js → modules/specs/runtime/proposal-manager.js} +8 -8
  270. package/server/dist/{smash-runner.js → modules/specs/runtime/smash-runner.js} +26 -58
  271. package/server/dist/{spec-addenda-core.js → modules/specs/runtime/spec-addenda-core.js} +2 -2
  272. package/server/dist/{spec-addenda.js → modules/specs/runtime/spec-addenda.js} +3 -3
  273. package/server/dist/{spec-launcher-manager.js → modules/specs/runtime/spec-launcher-manager.js} +8 -8
  274. package/server/dist/{spec-models.js → modules/specs/runtime/spec-models.js} +1 -1
  275. package/server/dist/{ticket-watcher.js → modules/specs/runtime/ticket-watcher.js} +1 -1
  276. package/server/dist/{background-process-control.js → modules/terminals/runtime/background-process-control.js} +1 -1
  277. package/server/dist/{background-process-service.js → modules/terminals/runtime/background-process-service.js} +3 -3
  278. package/server/dist/{background-process-store.js → modules/terminals/runtime/background-process-store.js} +1 -1
  279. package/server/dist/{background-windows-bootstrap.js → modules/terminals/runtime/background-windows-bootstrap.js} +3 -3
  280. package/server/dist/{terminal-manager.js → modules/terminals/runtime/terminal-manager.js} +3 -3
  281. package/server/dist/project-git.js +1 -1
  282. package/server/dist/project-profile-support.js +32 -0
  283. package/server/dist/project-registry.js +34 -40
  284. package/server/dist/project-router-background-processes.js +1 -1
  285. package/server/dist/project-router-chat.js +6 -6
  286. package/server/dist/project-router-git.js +1 -1
  287. package/server/dist/project-router-helpers.js +2 -2
  288. package/server/dist/project-router-jobs.js +14 -12
  289. package/server/dist/project-router-loop-runs.js +12 -12
  290. package/server/dist/project-router-settings.js +15 -75
  291. package/server/dist/project-router-setup.js +2 -2
  292. package/server/dist/project-router-spending.js +4 -4
  293. package/server/dist/project-router-terminals.js +4 -4
  294. package/server/dist/project-router-tickets.js +14 -14
  295. package/server/dist/project-router.js +13 -13
  296. package/server/dist/providers/claude-adapter.js +3 -2
  297. package/server/dist/providers/codex-adapter.js +3 -3
  298. package/server/dist/providers/gemini-adapter.js +1 -1
  299. package/server/dist/providers/telemetry-env.js +55 -0
  300. package/server/dist/runtime-role-prompts-router.js +2 -2
  301. package/server/dist/setup-manager.js +2 -48
  302. package/server/dist/shared/git-branch-name.js +16 -0
  303. package/server/dist/transient-children.js +3 -3
  304. package/server/dist/util/distribute-int.js +33 -0
  305. package/client/dist/assets/ReviewPacketPage-D-UjTef3.js +0 -4
  306. package/client/dist/assets/index-BAtqmAZR.css +0 -2
  307. package/client/dist/assets/packet-CjS_478C.js +0 -1
  308. package/client/dist/assets/packet-D69dKsHN.js +0 -1
  309. package/client/dist/assets/packet-D9rU6Oxo.js +0 -1
  310. package/client/dist/assets/packet-DMDFoamg.js +0 -1
  311. package/client/dist/assets/packet-Dd2RMs1h.js +0 -1
  312. package/client/dist/assets/packet-DjC9OibC.js +0 -1
  313. package/client/dist/assets/packet-DwPtJoDz.js +0 -1
  314. package/client/dist/assets/packet-ceahDg8_.js +0 -1
  315. package/server/dist/rail-pr-decision.js +0 -2377
  316. package/server/dist/ticket-broadcast.js +0 -47
  317. /package/server/dist/{ai-invocations.js → modules/accounting/runtime/ai-invocations.js} +0 -0
  318. /package/server/dist/{codex-otel-bridge.js → modules/accounting/runtime/codex-otel-bridge.js} +0 -0
  319. /package/server/dist/{metrics.js → modules/accounting/runtime/metrics.js} +0 -0
  320. /package/server/dist/{telemetry-export.js → modules/accounting/runtime/telemetry-export.js} +0 -0
  321. /package/server/dist/{agent-runtime-accounting.js → modules/agent-runtime/runtime/agent-runtime-accounting.js} +0 -0
  322. /package/server/dist/{agent-runtime-events.js → modules/agent-runtime/runtime/agent-runtime-events.js} +0 -0
  323. /package/server/dist/{agent-runtime-history.js → modules/agent-runtime/runtime/agent-runtime-history.js} +0 -0
  324. /package/server/dist/{agent-runtime-metrics.js → modules/agent-runtime/runtime/agent-runtime-metrics.js} +0 -0
  325. /package/server/dist/{agent-runtime-package.js → modules/agent-runtime/runtime/agent-runtime-package.js} +0 -0
  326. /package/server/dist/{agent-runtime-repositories.js → modules/agent-runtime/runtime/agent-runtime-repositories.js} +0 -0
  327. /package/server/dist/{agent-runtime-verification-suggestions.js → modules/agent-runtime/runtime/agent-runtime-verification-suggestions.js} +0 -0
  328. /package/server/dist/{agent-refine-db.js → modules/agents/runtime/agent-refine-db.js} +0 -0
  329. /package/server/dist/{browser-capture-types.js → modules/browser/runtime/browser-capture-types.js} +0 -0
  330. /package/server/dist/{browser-context-pool.js → modules/browser/runtime/browser-context-pool.js} +0 -0
  331. /package/server/dist/{browser-network.js → modules/browser/runtime/browser-network.js} +0 -0
  332. /package/server/dist/{browser-viewport.js → modules/browser/runtime/browser-viewport.js} +0 -0
  333. /package/server/dist/{blueprint-render.js → modules/builder/runtime/blueprint-render.js} +0 -0
  334. /package/server/dist/{blueprint-spec-fixtures.js → modules/builder/runtime/blueprint-spec-fixtures.js} +0 -0
  335. /package/server/dist/{blueprint-store.js → modules/builder/runtime/blueprint-store.js} +0 -0
  336. /package/server/dist/{blueprint-types.js → modules/builder/runtime/blueprint-types.js} +0 -0
  337. /package/server/dist/{builder-cwd-manager.js → modules/builder/runtime/builder-cwd-manager.js} +0 -0
  338. /package/server/dist/{milestone-chain-store.js → modules/builder/runtime/milestone-chain-store.js} +0 -0
  339. /package/server/dist/{changes-reader.js → modules/code/runtime/changes-reader.js} +0 -0
  340. /package/server/dist/{context-scope.js → modules/conversations/runtime/context-scope.js} +0 -0
  341. /package/server/dist/{explore-contract-refine.js → modules/conversations/runtime/explore-contract-refine.js} +0 -0
  342. /package/server/dist/{explore-draft-title.js → modules/conversations/runtime/explore-draft-title.js} +0 -0
  343. /package/server/dist/{merge-manager.js → modules/delivery/runtime/merge-manager.js} +0 -0
  344. /package/server/dist/{multi-repo-execution-store.js → modules/delivery/runtime/multi-repo-execution-store.js} +0 -0
  345. /package/server/dist/{pr-lifecycle.js → modules/delivery/runtime/pr-lifecycle.js} +0 -0
  346. /package/server/dist/{rail-isolation.js → modules/delivery/runtime/rail-isolation.js} +0 -0
  347. /package/server/dist/{rail-pr-recovery-git.js → modules/delivery/runtime/rail-pr-recovery-git.js} +0 -0
  348. /package/server/dist/{rail-worktrees-store.js → modules/delivery/runtime/rail-worktrees-store.js} +0 -0
  349. /package/server/dist/{rails-store.js → modules/delivery/runtime/rails-store.js} +0 -0
  350. /package/server/dist/{job-listing.js → modules/execution/runtime/job-listing.js} +0 -0
  351. /package/server/dist/{job-spawn-idempotency.js → modules/execution/runtime/job-spawn-idempotency.js} +0 -0
  352. /package/server/dist/{run-duration-stats.js → modules/execution/runtime/run-duration-stats.js} +0 -0
  353. /package/server/dist/{verification-sentinel.js → modules/execution/runtime/verification-sentinel.js} +0 -0
  354. /package/server/dist/{loop-constants.js → modules/loops/runtime/loop-constants.js} +0 -0
  355. /package/server/dist/{loop-decider.js → modules/loops/runtime/loop-decider.js} +0 -0
  356. /package/server/dist/{loop-effect.js → modules/loops/runtime/loop-effect.js} +0 -0
  357. /package/server/dist/{loop-preview.js → modules/loops/runtime/loop-preview.js} +0 -0
  358. /package/server/dist/{loop-templates-ported.js → modules/loops/runtime/loop-templates-ported.js} +0 -0
  359. /package/server/dist/{loops-store.js → modules/loops/runtime/loops-store.js} +0 -0
  360. /package/server/dist/{agent-chat-registry.js → modules/missions/runtime/agent-chat-registry.js} +0 -0
  361. /package/server/dist/{agent-cwd-manager.js → modules/missions/runtime/agent-cwd-manager.js} +0 -0
  362. /package/server/dist/{agent-tier.js → modules/missions/runtime/agent-tier.js} +0 -0
  363. /package/server/dist/{spec-contract-prompt.js → modules/specs/runtime/spec-contract-prompt.js} +0 -0
  364. /package/server/dist/{spec-draft-parser.js → modules/specs/runtime/spec-draft-parser.js} +0 -0
  365. /package/server/dist/{ticket-store.js → modules/specs/runtime/ticket-store.js} +0 -0
  366. /package/server/dist/{terminal-marks-store.js → modules/terminals/runtime/terminal-marks-store.js} +0 -0
  367. /package/server/dist/{terminal-osc-parser.js → modules/terminals/runtime/terminal-osc-parser.js} +0 -0
  368. /package/server/dist/{terminal-settings.js → modules/terminals/runtime/terminal-settings.js} +0 -0
@@ -1,1142 +1,76 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
- /**
4
- * specrails-desktop — specrails CLI bridge
5
- *
6
- * Routes commands to the manager when running, or falls back to invoking
7
- * claude directly when the manager is not reachable.
8
- *
9
- * Usage:
10
- * specrails-desktop implement #42 → /specrails:implement #42 (via manager or direct)
11
- * specrails-desktop "any raw prompt" → raw prompt (no /specrails: prefix)
12
- * specrails-desktop --status → print manager state
13
- * specrails-desktop --jobs → print job history table
14
- * specrails-desktop --port 5000 <command> → use port 5000 instead of 4200
15
- * specrails-desktop --help → print usage and exit 0
16
- */
17
- var __importDefault = (this && this.__importDefault) || function (mod) {
18
- return (mod && mod.__esModule) ? mod : { "default": mod };
19
- };
20
3
  Object.defineProperty(exports, "__esModule", { value: true });
21
- exports._internal = exports.KNOWN_VERBS = void 0;
22
- exports.parseArgs = parseArgs;
23
- exports.getVersion = getVersion;
24
- exports.detectWebManager = detectWebManager;
25
- exports.formatDuration = formatDuration;
26
- exports.formatTokens = formatTokens;
27
- exports.printSummary = printSummary;
28
- const http_1 = __importDefault(require("http"));
29
- const net_1 = __importDefault(require("net"));
30
- const readline_1 = require("readline");
31
- const ws_1 = __importDefault(require("ws"));
32
- const path_1 = __importDefault(require("path"));
33
- const os_1 = __importDefault(require("os"));
34
- const win_spawn_1 = require("./win-spawn");
35
- const fs_1 = __importDefault(require("fs"));
36
- // ---------------------------------------------------------------------------
37
- // Constants
38
- // ---------------------------------------------------------------------------
39
- const DEFAULT_PORT = 4200;
40
- const DETECTION_TIMEOUT_MS = 500;
41
- const HTTP_REQUEST_TIMEOUT_MS = 15_000;
42
- exports.KNOWN_VERBS = new Set([
43
- 'implement',
44
- 'batch-implement',
45
- 'why',
46
- 'get-backlog-specs',
47
- 'auto-propose-backlog-specs',
48
- 'propose-spec',
49
- 'refactor-recommender',
50
- 'health-check',
51
- 'compat-check',
52
- 'enrich',
53
- ]);
54
- const EXIT_PATTERN = /\[process exited with code (\d+)/;
55
- // ---------------------------------------------------------------------------
56
- // ANSI helpers
57
- // ---------------------------------------------------------------------------
58
- const isTTY = process.stdout.isTTY === true;
59
- function ansi(code, text) {
60
- if (!isTTY)
61
- return text;
62
- return `\x1b[${code}m${text}\x1b[0m`;
63
- }
64
- const dim = (t) => ansi('2', t);
65
- const red = (t) => ansi('31', t);
66
- const bold = (t) => ansi('1', t);
67
- const dimCyan = (t) => ansi('2;36', t);
68
- function cliPrefix() {
69
- return dim('[specrails-desktop]');
70
- }
71
- function cliLog(msg) {
72
- process.stdout.write(`${cliPrefix()} ${msg}\n`);
73
- }
74
- function cliError(msg) {
75
- process.stderr.write(`${cliPrefix()} ${red(`error: ${msg}`)}\n`);
76
- }
77
- function cliWarn(msg) {
78
- process.stderr.write(`${cliPrefix()} ${dim(`warning: ${msg}`)}\n`);
79
- }
80
- function parseArgs(argv) {
81
- // argv is process.argv.slice(2)
82
- let port = DEFAULT_PORT;
83
- let projectOverride;
84
- const args = [...argv];
85
- // Extract --port <n> and --project <name|path> from any position
86
- for (let i = 0; i < args.length; i++) {
87
- if (args[i] === '--port' && i + 1 < args.length) {
88
- const parsed = parseInt(args[i + 1], 10);
89
- if (!isNaN(parsed)) {
90
- port = parsed;
91
- }
92
- args.splice(i, 2);
93
- i--;
94
- }
95
- else if (args[i] === '--project' && i + 1 < args.length) {
96
- projectOverride = args[i + 1];
97
- args.splice(i, 2);
98
- i--;
99
- }
100
- }
101
- if (args[0] === '--version' || args[0] === '-v') {
102
- return { mode: 'version' };
103
- }
104
- if (args.length === 0 || args[0] === '--help' || args[0] === '-h') {
105
- return { mode: 'help' };
106
- }
107
- if (args[0] === '--status') {
108
- return { mode: 'status', port };
109
- }
110
- if (args[0] === '--jobs') {
111
- return { mode: 'jobs', port };
112
- }
113
- if (args[0] === 'desktop') {
114
- return { mode: 'desktop', subArgs: args.slice(1), port };
115
- }
116
- // Allow server-management subcommands directly without the 'desktop' prefix:
117
- // specrails-desktop start → specrails-desktop desktop start
118
- // specrails-desktop stop → specrails-desktop desktop stop
119
- // specrails-desktop add → specrails-desktop desktop add
120
- // etc.
121
- const DESKTOP_SUBCOMMANDS = new Set(['start', 'stop', 'status', 'add', 'remove', 'list']);
122
- if (DESKTOP_SUBCOMMANDS.has(args[0])) {
123
- return { mode: 'desktop', subArgs: args, port };
124
- }
125
- const first = args[0];
126
- // Slash-prefixed command: pass through unchanged
127
- if (first.startsWith('/')) {
128
- const resolved = args.join(' ');
129
- return { mode: 'raw', resolved, port, projectOverride };
130
- }
131
- // Known verb: inject /specrails: prefix
132
- if (exports.KNOWN_VERBS.has(first)) {
133
- const resolved = `/specrails:${args.join(' ')}`;
134
- return { mode: 'command', resolved, port, projectOverride };
135
- }
136
- // Unknown first token: treat as raw prompt
137
- const resolved = args.join(' ');
138
- return { mode: 'raw', resolved, port, projectOverride };
139
- }
140
- function getVersion() {
141
- for (const rel of ['../package.json', '../../package.json']) {
142
- try {
143
- const pkgPath = path_1.default.join(__dirname, rel);
144
- const pkg = JSON.parse(fs_1.default.readFileSync(pkgPath, 'utf8'));
145
- if (typeof pkg.version === 'string')
146
- return pkg.version;
147
- }
148
- catch {
149
- // try next
150
- }
151
- }
152
- return 'unknown';
153
- }
154
- function printVersion() {
155
- process.stdout.write(`specrails-desktop v${getVersion()}\n`);
156
- }
157
- function printHelp() {
158
- const version = getVersion();
159
- process.stdout.write(`
160
- ${bold(`specrails-desktop v${version}`)} — specrails CLI bridge
161
-
162
- ${bold('Project Required:')}
163
- Every command runs in the context of a project registered for the current
164
- directory. Register your project once before running any commands:
165
-
166
- ${dim('# Register your project (run once per project):')}
167
- specrails-desktop add .
168
-
169
- ${dim('# Then run commands from that directory:')}
170
- specrails-desktop implement #42
171
-
172
- ${bold('Usage:')}
173
- specrails-desktop implement #42 Run a known specrails verb (prepends /specrails:)
174
- specrails-desktop batch-implement #40 #41 Batch implementation across issues
175
- specrails-desktop why Explain recent changes
176
- specrails-desktop get-backlog-specs View prioritized spec backlog
177
- specrails-desktop auto-propose-backlog-specs Generate new spec ideas
178
- specrails-desktop propose-spec Explore an idea and produce a spec
179
- specrails-desktop refactor-recommender Find refactoring opportunities
180
- specrails-desktop health-check Run codebase health check
181
- specrails-desktop compat-check Check for breaking API changes
182
- specrails-desktop "any raw prompt" Pass a raw prompt directly to claude
183
- specrails-desktop --status Print manager status and exit
184
- specrails-desktop --jobs Print recent job history and exit
185
- specrails-desktop start|stop|add|remove|list Manage the server
186
- specrails-desktop --project <name|path> Override project (default: current directory)
187
- specrails-desktop --port <n> Override default port (${DEFAULT_PORT})
188
- specrails-desktop --version, -v Print version and exit
189
- specrails-desktop --help, -h Show this help text
190
-
191
- ${bold('Execution paths:')}
192
- Manager running → POST /api/spawn + stream logs via WebSocket
193
- Manager not running → spawn claude directly with stream-json output
194
- `.trimStart());
195
- }
196
- function detectWebManager(port) {
197
- const baseUrl = `http://127.0.0.1:${port}`;
198
- return new Promise((resolve) => {
199
- const timer = setTimeout(() => {
200
- req.destroy();
201
- resolve({ running: false, baseUrl });
202
- }, DETECTION_TIMEOUT_MS);
203
- const req = http_1.default.get(`${baseUrl}/api/health`, { timeout: DETECTION_TIMEOUT_MS }, (res) => {
204
- clearTimeout(timer);
205
- res.resume(); // drain the response
206
- if (res.statusCode !== undefined && res.statusCode >= 200 && res.statusCode < 300) {
207
- resolve({ running: true, baseUrl });
208
- }
209
- else {
210
- resolve({ running: false, baseUrl });
211
- }
212
- });
213
- req.on('error', () => {
214
- clearTimeout(timer);
215
- resolve({ running: false, baseUrl });
216
- });
217
- req.on('timeout', () => {
218
- req.destroy();
219
- clearTimeout(timer);
220
- resolve({ running: false, baseUrl });
221
- });
222
- });
223
- }
224
- // ---------------------------------------------------------------------------
225
- // HTTP helpers
226
- // ---------------------------------------------------------------------------
227
- function loadDesktopToken() {
228
- const candidates = [
229
- path_1.default.join(os_1.default.homedir(), '.specrails', 'desktop.token'),
230
- // Legacy fallback: pre-rename installs keep the token at hub.token until the
231
- // server migrates it on next startup. Compat only — do not extend.
232
- path_1.default.join(os_1.default.homedir(), '.specrails', 'hub.token'),
233
- ];
234
- for (const tokenPath of candidates) {
235
- try {
236
- const t = fs_1.default.readFileSync(tokenPath, 'utf-8').trim();
237
- if (t.length >= 32)
238
- return t;
239
- }
240
- catch {
241
- // try next candidate
242
- }
243
- }
244
- return null;
245
- }
246
- function requestTimeoutError(url, timeoutMs) {
247
- return new Error(`Request to ${url} timed out after ${timeoutMs}ms`);
248
- }
249
- function httpGet(url, timeoutMs = HTTP_REQUEST_TIMEOUT_MS) {
250
- return new Promise((resolve, reject) => {
251
- const token = loadDesktopToken();
252
- const parsed = new URL(url);
253
- const headers = {};
254
- if (token)
255
- headers['Authorization'] = `Bearer ${token}`;
256
- const options = {
257
- hostname: parsed.hostname,
258
- port: parsed.port,
259
- path: parsed.pathname + parsed.search,
260
- headers,
261
- };
262
- const req = http_1.default.get(options, (res) => {
263
- let body = '';
264
- res.on('data', (chunk) => { body += chunk; });
265
- res.on('end', () => resolve({ status: res.statusCode ?? 0, body }));
266
- });
267
- req.setTimeout(timeoutMs, () => {
268
- req.destroy(requestTimeoutError(url, timeoutMs));
269
- });
270
- req.on('error', reject);
271
- });
272
- }
273
- function httpPost(url, payload, timeoutMs = HTTP_REQUEST_TIMEOUT_MS) {
274
- return new Promise((resolve, reject) => {
275
- const data = JSON.stringify(payload);
276
- const urlObj = new URL(url);
277
- const token = loadDesktopToken();
278
- const headers = {
279
- 'Content-Type': 'application/json',
280
- 'Content-Length': Buffer.byteLength(data),
281
- };
282
- if (token)
283
- headers['Authorization'] = `Bearer ${token}`;
284
- const options = {
285
- hostname: urlObj.hostname,
286
- port: urlObj.port,
287
- path: urlObj.pathname,
288
- method: 'POST',
289
- headers,
290
- };
291
- const req = http_1.default.request(options, (res) => {
292
- let body = '';
293
- res.on('data', (chunk) => { body += chunk; });
294
- res.on('end', () => resolve({ status: res.statusCode ?? 0, body }));
295
- });
296
- req.setTimeout(timeoutMs, () => {
297
- req.destroy(requestTimeoutError(url, timeoutMs));
298
- });
299
- req.on('error', reject);
300
- req.write(data);
301
- req.end();
302
- });
303
- }
304
- // ---------------------------------------------------------------------------
305
- // Duration formatting
306
- // ---------------------------------------------------------------------------
307
- function formatDuration(ms) {
308
- const totalSeconds = Math.floor(ms / 1000);
309
- if (totalSeconds < 60) {
310
- return `${totalSeconds}s`;
311
- }
312
- const minutes = Math.floor(totalSeconds / 60);
313
- const seconds = totalSeconds % 60;
314
- return `${minutes}m ${seconds}s`;
315
- }
316
- // ---------------------------------------------------------------------------
317
- // Token formatting
318
- // ---------------------------------------------------------------------------
319
- function formatTokens(n) {
320
- return new Intl.NumberFormat('en-US', { useGrouping: true })
321
- .format(n)
322
- .replace(/,/g, ' ');
323
- }
324
- function printSummary(data) {
325
- const doneLabel = isTTY ? bold('[specrails-desktop] done') : '[specrails-desktop] done';
326
- const durationPart = `duration: ${formatDuration(data.durationMs)}`;
327
- const costPart = data.costUsd != null ? ` cost: $${data.costUsd.toFixed(2)}` : '';
328
- const tokenPart = data.totalTokens != null ? ` tokens: ${formatTokens(data.totalTokens)}` : '';
329
- const exitPart = ` exit: ${data.exitCode}`;
330
- process.stdout.write(`${doneLabel} ${durationPart}${costPart}${tokenPart}${exitPart}\n`);
331
- }
332
- async function resolveProjectFromCwd(baseUrl, projectOverride) {
333
- try {
334
- // --project flag: resolve by path (absolute/relative) or by name
335
- if (projectOverride) {
336
- // Path vs name: POSIX absolute (`/…`), relative (`./…`), OR a Windows
337
- // absolute (`C:\…` / `C:/…`) or any path containing a separator. Without
338
- // the drive-letter/backslash cases `--project C:\repo` was mis-read as a
339
- // project NAME on Windows and never matched.
340
- const isPathLike = path_1.default.isAbsolute(projectOverride) ||
341
- projectOverride.startsWith('.') ||
342
- /^[A-Za-z]:[\\/]/.test(projectOverride) ||
343
- projectOverride.includes('/') ||
344
- projectOverride.includes('\\');
345
- if (isPathLike) {
346
- const res = await httpGet(`${baseUrl}/api/resolve?path=${encodeURIComponent(projectOverride)}`);
347
- if (res.status === 200) {
348
- const data = JSON.parse(res.body);
349
- return data.project ?? null;
350
- }
351
- }
352
- else {
353
- // Resolve by name: fetch all projects and match. Names are NOT unique
354
- // (two repos with the same basename register the same name), so a bare
355
- // first-match `find` would silently mis-route. Auto-pick only on a
356
- // unique match; on collision, error out listing the candidates.
357
- const res = await httpGet(`${baseUrl}/api/projects`);
358
- if (res.status === 200) {
359
- const data = JSON.parse(res.body);
360
- const matches = (data.projects ?? []).filter((p) => p.name.toLowerCase() === projectOverride.toLowerCase());
361
- if (matches.length > 1) {
362
- cliError(`--project "${projectOverride}" is ambiguous (${matches.length} projects share this name):`);
363
- for (const m of matches) {
364
- cliError(` ${m.id} ${m.path}`);
365
- }
366
- cliError('disambiguate with --project <path> instead of the name');
367
- return null;
368
- }
369
- return matches[0] ?? null;
370
- }
371
- }
372
- return null;
373
- }
374
- // Default: resolve from CWD
375
- const cwd = process.cwd();
376
- const res = await httpGet(`${baseUrl}/api/resolve?path=${encodeURIComponent(cwd)}`);
377
- if (res.status === 200) {
378
- const data = JSON.parse(res.body);
379
- return data.project ?? null;
380
- }
381
- }
382
- catch {
383
- // Resolve endpoint not available — not in Super mode
384
- }
385
- return null;
386
- }
387
- async function runViaWebManager(command, baseUrl, projectOverride) {
388
- // Detect Super mode: check if /api/state is reachable
389
- let spawnUrl = `${baseUrl}/api/spawn`;
390
- let jobApiBase = `${baseUrl}/api`;
391
- try {
392
- const superCheck = await httpGet(`${baseUrl}/api/state`);
393
- if (superCheck.status === 200) {
394
- // Super mode: resolve project from CWD or --project override
395
- const project = await resolveProjectFromCwd(baseUrl, projectOverride);
396
- if (!project) {
397
- const hint = projectOverride
398
- ? `no project found matching: ${projectOverride}`
399
- : `no project registered for the current directory.\n Run: specrails-desktop add ${process.cwd()}`;
400
- cliError(`server is running but ${hint}`);
401
- return 1;
402
- }
403
- spawnUrl = `${baseUrl}/api/projects/${project.id}/spawn`;
404
- jobApiBase = `${baseUrl}/api/projects/${project.id}`;
405
- cliLog(`project: ${project.name}`);
406
- }
407
- }
408
- catch {
409
- // Single-project mode — use default paths
410
- }
411
- // Spawn the job
412
- let spawnRes;
413
- try {
414
- spawnRes = await httpPost(spawnUrl, { command });
415
- }
416
- catch (err) {
417
- cliError('failed to connect to manager');
418
- return 1;
419
- }
420
- if (spawnRes.status === 409) {
421
- cliError('manager is busy (another job is running)');
422
- return 1;
423
- }
424
- if (spawnRes.status >= 400) {
425
- let errMsg = `spawn failed with HTTP ${spawnRes.status}`;
426
- try {
427
- const parsed = JSON.parse(spawnRes.body);
428
- if (parsed.error)
429
- errMsg = parsed.error;
430
- }
431
- catch { /* use default */ }
432
- cliError(errMsg);
433
- return 1;
434
- }
435
- let processId;
436
- try {
437
- const parsed = JSON.parse(spawnRes.body);
438
- // Server returns jobId; processId is the legacy field name used in LogMessage
439
- processId = (parsed.jobId ?? parsed.processId) ?? '';
440
- if (!processId)
441
- throw new Error('missing jobId');
442
- }
443
- catch {
444
- cliError('invalid response from /api/spawn');
445
- return 1;
446
- }
447
- const startTime = Date.now();
448
- // Connect WebSocket and stream logs
449
- const wsUrl = baseUrl.replace(/^http/, 'ws');
450
- const token = loadDesktopToken();
451
- let exitCode = 1;
452
- let resolved = false;
453
- await new Promise((resolve) => {
454
- const ws = new ws_1.default(wsUrl, {
455
- headers: token ? { Authorization: `Bearer ${token}` } : undefined,
456
- });
457
- ws.on('message', (data) => {
458
- let msg;
459
- try {
460
- msg = JSON.parse(data.toString());
461
- }
462
- catch {
463
- return;
464
- }
465
- if (msg.type === 'init') {
466
- // Replay only log lines from our processId
467
- const initMsg = msg;
468
- for (const logLine of initMsg.logBuffer) {
469
- if (logLine.processId === processId) {
470
- handleLogLine(logLine);
471
- }
472
- }
473
- return;
474
- }
475
- if (msg.type === 'log') {
476
- const logMsg = msg;
477
- if (logMsg.processId !== processId)
478
- return;
479
- handleLogLine(logMsg);
480
- return;
481
- }
482
- if (msg.type === 'phase') {
483
- const phaseMsg = msg;
484
- process.stdout.write(` ${dimCyan(`→ [${phaseMsg.phase}] ${phaseMsg.state}`)}\n`);
485
- return;
486
- }
487
- });
488
- function handleLogLine(logMsg) {
489
- if (resolved)
490
- return;
491
- // Check for exit signal
492
- const match = EXIT_PATTERN.exec(logMsg.line);
493
- if (match) {
494
- exitCode = parseInt(match[1], 10);
495
- resolved = true;
496
- ws.close();
497
- resolve();
498
- return;
499
- }
500
- // Print to appropriate stream, preserving ANSI
501
- if (logMsg.source === 'stderr') {
502
- process.stderr.write(`${logMsg.line}\n`);
503
- }
504
- else {
505
- process.stdout.write(`${logMsg.line}\n`);
506
- }
507
- }
508
- ws.on('close', () => {
509
- if (!resolved) {
510
- cliWarn('lost connection to manager');
511
- resolved = true;
512
- resolve();
513
- }
514
- });
515
- ws.on('error', (err) => {
516
- if (!resolved) {
517
- cliWarn(`WebSocket error: ${err.message}`);
518
- resolved = true;
519
- resolve();
520
- }
521
- });
522
- });
523
- const durationMs = Date.now() - startTime;
524
- // Fetch job metadata for cost/tokens
525
- let costUsd;
526
- let totalTokens;
527
- try {
528
- const jobRes = await httpGet(`${jobApiBase}/jobs/${processId}`);
529
- if (jobRes.status === 200) {
530
- const parsed = JSON.parse(jobRes.body);
531
- if (parsed.job) {
532
- if (parsed.job.total_cost_usd != null)
533
- costUsd = parsed.job.total_cost_usd;
534
- const tokensIn = parsed.job.tokens_in ?? 0;
535
- const tokensOut = parsed.job.tokens_out ?? 0;
536
- if (parsed.job.tokens_in != null || parsed.job.tokens_out != null) {
537
- totalTokens = tokensIn + tokensOut;
538
- }
539
- // Prefer server-side duration when available
540
- if (parsed.job.duration_ms != null) {
541
- printSummary({ durationMs: parsed.job.duration_ms, costUsd, totalTokens, exitCode });
542
- return exitCode;
543
- }
544
- }
545
- }
546
- }
547
- catch { /* fall through to duration-only summary */ }
548
- printSummary({ durationMs, costUsd, totalTokens, exitCode });
549
- return exitCode;
550
- }
551
- async function runDirect(command) {
552
- const startTime = Date.now();
553
- const args = [
554
- '--dangerously-skip-permissions',
555
- // `-p` takes a SINGLE operand — the whole prompt. Splitting on whitespace
556
- // (the old `...command.trim().split(/\s+/)`) shattered multi-word prompts
557
- // into stray positionals and lost quoting/whitespace. Pass it intact.
558
- '-p',
559
- command.trim(),
560
- '--output-format', 'stream-json',
561
- '--verbose',
562
- ];
563
- let child;
564
- try {
565
- // Windows: `claude` is `claude.cmd`; spawnCli routes through cross-spawn so
566
- // the shim resolves and multi-line args survive (raw `spawn(shell:false)`
567
- // threw ENOENT/EINVAL on Windows even with claude installed).
568
- child = (0, win_spawn_1.spawnCli)('claude', args, {
569
- env: process.env,
570
- shell: false,
571
- });
572
- }
573
- catch (err) {
574
- const code = err.code;
575
- if (code === 'ENOENT') {
576
- cliError('claude binary not found');
577
- }
578
- else {
579
- cliError(`failed to spawn claude: ${err.message}`);
580
- }
581
- return 1;
582
- }
583
- let resultData;
584
- // Stderr: pass through unchanged
585
- child.stderr?.pipe(process.stderr);
586
- // Stdout: parse NDJSON line by line
587
- const rl = (0, readline_1.createInterface)({ input: child.stdout, crlfDelay: Infinity });
588
- rl.on('line', (line) => {
589
- if (!line.trim())
590
- return;
591
- let parsed = null;
592
- try {
593
- parsed = JSON.parse(line);
594
- }
595
- catch {
596
- // Non-JSON line: print as-is
597
- process.stdout.write(`${line}\n`);
598
- return;
599
- }
600
- if (parsed.type === 'assistant') {
601
- // Real claude emits assistant text as `type:'assistant'` with
602
- // `message.content[]` `{type:'text', text}` blocks (mirror
603
- // parseClaudeStreamLine). The legacy `type:'text'`/`content` shape is
604
- // kept as a fallback for older/synthetic streams.
605
- const msg = parsed.message;
606
- const text = (msg?.content ?? [])
607
- .filter((b) => b.type === 'text')
608
- .map((b) => b.text ?? '')
609
- .join('');
610
- if (text)
611
- process.stdout.write(`${text}\n`);
612
- }
613
- else if (parsed.type === 'text') {
614
- const content = parsed.content ?? '';
615
- if (content)
616
- process.stdout.write(`${content}\n`);
617
- }
618
- else if (parsed.type === 'result') {
619
- resultData = parsed;
620
- // The `result` event also carries the final answer text on `result`.
621
- if (resultData.result)
622
- process.stdout.write(`${resultData.result}\n`);
623
- }
624
- // All other types: silently ignore
625
- });
626
- const exitCode = await new Promise((resolve) => {
627
- child.on('close', (code) => {
628
- resolve(code ?? 1);
629
- });
630
- child.on('error', (err) => {
631
- if (err.code === 'ENOENT') {
632
- cliError('claude binary not found');
633
- }
634
- else {
635
- cliError(`claude process error: ${err.message}`);
636
- }
637
- resolve(1);
638
- });
639
- });
640
- const durationMs = Date.now() - startTime;
641
- let costUsd;
642
- let totalTokens;
643
- if (resultData) {
644
- if (resultData.total_cost_usd != null)
645
- costUsd = resultData.total_cost_usd;
646
- const usage = resultData.usage;
647
- const tokensIn = usage?.input_tokens ?? 0;
648
- const tokensOut = usage?.output_tokens ?? 0;
649
- if (usage?.input_tokens != null || usage?.output_tokens != null) {
650
- totalTokens = tokensIn + tokensOut;
651
- }
652
- }
653
- printSummary({ durationMs, costUsd, totalTokens, exitCode });
654
- return exitCode;
655
- }
656
- // ---------------------------------------------------------------------------
657
- // --status handler
658
- // ---------------------------------------------------------------------------
659
- async function handleStatus(port) {
660
- const baseUrl = `http://127.0.0.1:${port}`;
661
- const detection = await detectWebManager(port);
662
- if (!detection.running) {
663
- process.stdout.write(`manager: not running (${baseUrl})\n`);
664
- return 1;
665
- }
666
- try {
667
- const healthRes = await httpGet(`${baseUrl}/api/health`);
668
- if (healthRes.status !== 200) {
669
- process.stdout.write(`manager: not running (${baseUrl})\n`);
670
- return 1;
671
- }
672
- const health = JSON.parse(healthRes.body);
673
- const version = health.version ? ` (v${health.version})` : '';
674
- process.stdout.write(`manager: running${version}\n`);
675
- process.stdout.write(`mode: ${health.mode ?? 'unknown'}\n`);
676
- if (health.projects !== undefined) {
677
- process.stdout.write(`projects: ${health.projects}\n`);
678
- }
679
- // Legacy mode: fetch additional per-project details from /api/state
680
- if (health.mode !== 'super') {
681
- const stateRes = await httpGet(`${baseUrl}/api/state`);
682
- if (stateRes.status === 200) {
683
- const state = JSON.parse(stateRes.body);
684
- process.stdout.write(`project: ${state.projectName ?? 'unknown'}\n`);
685
- process.stdout.write(`busy: ${state.busy ? 'true' : 'false'}\n`);
686
- if (state.phases) {
687
- const phaseStr = Object.entries(state.phases)
688
- .map(([phase, st]) => `${phase}=${st}`)
689
- .join(' ');
690
- process.stdout.write(`phases: ${phaseStr}\n`);
691
- }
692
- }
693
- }
694
- return 0;
695
- }
696
- catch {
697
- process.stdout.write(`manager: not running (${baseUrl})\n`);
698
- return 1;
699
- }
700
- }
701
- function formatJobDuration(ms) {
702
- if (ms == null)
703
- return '-';
704
- return formatDuration(ms);
705
- }
706
- function formatJobStarted(isoStr) {
707
- try {
708
- const d = new Date(isoStr);
709
- const year = d.getFullYear();
710
- const month = String(d.getMonth() + 1).padStart(2, '0');
711
- const day = String(d.getDate()).padStart(2, '0');
712
- const hour = String(d.getHours()).padStart(2, '0');
713
- const min = String(d.getMinutes()).padStart(2, '0');
714
- return `${year}-${month}-${day} ${hour}:${min}`;
715
- }
716
- catch {
717
- return isoStr.slice(0, 16);
718
- }
719
- }
720
- async function handleJobs(port) {
721
- const baseUrl = `http://127.0.0.1:${port}`;
722
- const detection = await detectWebManager(port);
723
- if (!detection.running) {
724
- cliError(`manager is not running (${baseUrl})`);
725
- return 1;
726
- }
727
- let res;
728
- try {
729
- res = await httpGet(`${baseUrl}/api/jobs`);
730
- }
731
- catch {
732
- cliError('failed to fetch job list');
733
- return 1;
734
- }
735
- if (res.status === 501 || res.status === 404) {
736
- cliLog('jobs history requires manager with SQLite persistence (#57)');
737
- return 1;
738
- }
739
- if (res.status !== 200) {
740
- cliError(`unexpected response from /api/jobs: HTTP ${res.status}`);
741
- return 1;
742
- }
743
- let data;
744
- try {
745
- data = JSON.parse(res.body);
746
- }
747
- catch {
748
- cliError('invalid response from /api/jobs');
749
- return 1;
750
- }
751
- if (!data.jobs || data.jobs.length === 0) {
752
- cliLog('no jobs recorded yet');
753
- return 0;
754
- }
755
- // Column widths
756
- const idW = 8;
757
- const cmdW = 30;
758
- const startW = 18;
759
- const durW = 8;
760
- const exitW = 4;
761
- const header = [
762
- 'ID'.padEnd(idW),
763
- 'COMMAND'.padEnd(cmdW),
764
- 'STARTED'.padEnd(startW),
765
- 'DURATION'.padEnd(durW),
766
- 'EXIT'.padEnd(exitW),
767
- ].join(' ');
768
- process.stdout.write(`${bold(header)}\n`);
769
- for (const job of data.jobs) {
770
- const idCell = job.id.slice(0, idW).padEnd(idW);
771
- const cmdCell = job.command.slice(0, cmdW).padEnd(cmdW);
772
- const startCell = formatJobStarted(job.started_at).padEnd(startW);
773
- const durCell = formatJobDuration(job.duration_ms).padEnd(durW);
774
- const exitCell = (job.exit_code ?? '-').toString().padEnd(exitW);
775
- process.stdout.write(`${idCell} ${cmdCell} ${startCell} ${durCell} ${exitCell}\n`);
776
- }
777
- return 0;
778
- }
779
- // ---------------------------------------------------------------------------
780
- // Server-management subcommand group
781
- // ---------------------------------------------------------------------------
782
- const DESKTOP_PID_FILE = path_1.default.join(os_1.default.homedir(), '.specrails', 'manager.pid');
783
- const DESKTOP_LOG_FILE = path_1.default.join(os_1.default.homedir(), '.specrails', 'desktop.log');
784
- function isPortInUse(port) {
785
- return new Promise((resolve) => {
786
- const srv = net_1.default.createServer();
787
- srv.once('error', (err) => {
788
- resolve(err.code === 'EADDRINUSE');
789
- });
790
- srv.once('listening', () => {
791
- srv.close();
792
- resolve(false);
793
- });
794
- srv.listen(port, '127.0.0.1');
795
- });
796
- }
797
- function readPid() {
798
- try {
799
- const raw = fs_1.default.readFileSync(DESKTOP_PID_FILE, 'utf-8').trim();
800
- const pid = parseInt(raw, 10);
801
- return isNaN(pid) ? null : pid;
802
- }
803
- catch {
804
- return null;
805
- }
806
- }
807
- function isProcessRunning(pid) {
808
- try {
809
- process.kill(pid, 0);
810
- return true;
811
- }
812
- catch {
813
- return false;
814
- }
815
- }
816
- function desktopServerPath() {
817
- // __dirname differs by runtime:
818
- // compiled (npm install): <root>/cli/dist/ → need ../../server/dist/index.js
819
- // tsx dev: <root>/cli/ → need ../server/dist/index.js
820
- // Try both, compiled path first.
821
- const fromDist = path_1.default.resolve(__dirname, '..', '..', 'server', 'dist', 'index.js');
822
- const fromSrc = path_1.default.resolve(__dirname, '..', 'server', 'dist', 'index.js');
823
- const devTs = path_1.default.resolve(__dirname, '..', 'server', 'index.ts');
824
- if (fs_1.default.existsSync(fromDist))
825
- return fromDist;
826
- if (fs_1.default.existsSync(fromSrc))
827
- return fromSrc;
828
- if (fs_1.default.existsSync(devTs))
829
- return devTs;
830
- return fromDist;
831
- }
832
- async function desktopStart(port) {
833
- const pid = readPid();
834
- if (pid !== null && isProcessRunning(pid)) {
835
- cliLog(`server already running (pid ${pid}) on port ${port}`);
836
- return 0;
837
- }
838
- // Check if port is already in use by another process
839
- const portBusy = await isPortInUse(port);
840
- if (portBusy) {
841
- cliError(`port ${port} is already in use by another process`);
842
- cliError(`if a previous server is stale, run: specrails-desktop stop`);
843
- cliError(`or use a different port: specrails-desktop --port <port> start`);
844
- return 1;
845
- }
846
- const serverPath = desktopServerPath();
847
- const isTs = serverPath.endsWith('.ts');
848
- const args = isTs
849
- ? ['tsx', serverPath, '--port', String(port)]
850
- : ['node', serverPath, '--port', String(port)];
851
- // Ensure log dir exists and open log file for server output
852
- try {
853
- fs_1.default.mkdirSync(path_1.default.dirname(DESKTOP_LOG_FILE), { recursive: true });
854
- }
855
- catch { /* ignore */ }
856
- let logFd;
857
- try {
858
- logFd = fs_1.default.openSync(DESKTOP_LOG_FILE, 'a');
859
- }
860
- catch { /* ignore — fall back to silent */ }
861
- const stdio = [
862
- 'ignore',
863
- logFd ?? 'ignore',
864
- logFd ?? 'ignore',
865
- ];
866
- // spawnCli so a dev `tsx`(.cmd) launcher resolves on Windows and the env is
867
- // SystemRoot-backfilled; for the packaged `node` path cross-spawn spawns the
868
- // .exe directly (no cmd.exe wrapper), keeping `detached` clean.
869
- const child = (0, win_spawn_1.spawnCli)(args[0], args.slice(1), {
870
- detached: true,
871
- stdio,
872
- env: { ...process.env },
873
- });
874
- if (logFd !== undefined) {
875
- try {
876
- fs_1.default.closeSync(logFd);
877
- }
878
- catch { /* ignore */ }
879
- }
880
- child.unref();
881
- // Poll until the server is ready (up to 15 seconds, checking every 300ms)
882
- const pollTimeoutMs = 15000;
883
- const pollIntervalMs = 300;
884
- const startPoll = Date.now();
885
- while (Date.now() - startPoll < pollTimeoutMs) {
886
- await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
887
- const detection = await detectWebManager(port);
888
- if (detection.running) {
889
- cliLog(`server started on http://127.0.0.1:${port}`);
890
- return 0;
891
- }
892
- }
893
- cliError(`server failed to start — logs: ${DESKTOP_LOG_FILE}`);
894
- return 1;
895
- }
896
- async function desktopStop() {
897
- const pid = readPid();
898
- if (pid === null) {
899
- cliLog('server is not running (no pid file)');
900
- return 0;
901
- }
902
- if (!isProcessRunning(pid)) {
903
- cliLog('server is not running (stale pid file)');
904
- try {
905
- fs_1.default.unlinkSync(DESKTOP_PID_FILE);
906
- }
907
- catch { /* ignore */ }
908
- return 0;
909
- }
910
- try {
911
- process.kill(pid, 'SIGTERM');
912
- cliLog(`server stopped (pid ${pid})`);
913
- return 0;
914
- }
915
- catch (err) {
916
- cliError(`failed to stop server: ${err.message}`);
917
- return 1;
918
- }
919
- }
920
- async function desktopStatus(port) {
921
- const pid = readPid();
922
- const detection = await detectWebManager(port);
923
- if (!detection.running) {
924
- process.stdout.write(`server: not running\n`);
925
- return 1;
926
- }
927
- try {
928
- const res = await httpGet(`${detection.baseUrl}/api/state`);
929
- const state = JSON.parse(res.body);
930
- process.stdout.write(`server: running (pid ${pid ?? '?'}) on ${detection.baseUrl}\n`);
931
- process.stdout.write(`projects: ${state.projectCount ?? 0}\n`);
932
- if (state.projects) {
933
- for (const p of state.projects) {
934
- process.stdout.write(` - ${p.name}\n`);
935
- }
936
- }
937
- return 0;
938
- }
939
- catch {
940
- process.stdout.write(`server: running on ${detection.baseUrl}\n`);
941
- return 0;
942
- }
943
- }
944
- async function desktopAdd(projectPath, port) {
945
- const detection = await detectWebManager(port);
946
- if (!detection.running) {
947
- cliError('server is not running. Start it first with: specrails-desktop start');
948
- return 1;
949
- }
950
- try {
951
- const res = await httpPost(`${detection.baseUrl}/api/projects`, {
952
- path: path_1.default.resolve(projectPath),
953
- });
954
- if (res.status === 201) {
955
- const data = JSON.parse(res.body);
956
- cliLog(`added project: ${data.project?.name ?? projectPath}`);
957
- return 0;
958
- }
959
- else if (res.status === 409) {
960
- cliLog('project already registered');
961
- return 0;
962
- }
963
- else {
964
- let errMsg = `HTTP ${res.status}`;
965
- try {
966
- errMsg = JSON.parse(res.body).error ?? errMsg;
967
- }
968
- catch { /* use default */ }
969
- cliError(`failed to add project: ${errMsg}`);
970
- return 1;
971
- }
972
- }
973
- catch (err) {
974
- cliError(`failed to connect to server: ${err.message}`);
975
- return 1;
976
- }
977
- }
978
- async function desktopRemove(projectId, port) {
979
- const detection = await detectWebManager(port);
980
- if (!detection.running) {
981
- cliError('server is not running');
982
- return 1;
983
- }
984
- try {
985
- const deleteRes = await new Promise((resolve, reject) => {
986
- const urlObj = new URL(`${detection.baseUrl}/api/projects/${projectId}`);
987
- const token = loadDesktopToken();
988
- const headers = {};
989
- if (token)
990
- headers['Authorization'] = `Bearer ${token}`;
991
- const options = {
992
- hostname: urlObj.hostname,
993
- port: urlObj.port,
994
- path: urlObj.pathname,
995
- method: 'DELETE',
996
- headers,
997
- };
998
- const req = http_1.default.request(options, (res) => {
999
- let body = '';
1000
- res.on('data', (chunk) => { body += chunk; });
1001
- res.on('end', () => resolve({ status: res.statusCode ?? 0, body }));
1002
- });
1003
- req.on('error', reject);
1004
- req.end();
1005
- });
1006
- if (deleteRes.status === 200) {
1007
- cliLog(`project removed`);
1008
- return 0;
1009
- }
1010
- else {
1011
- cliError(`failed to remove project: HTTP ${deleteRes.status}`);
1012
- return 1;
1013
- }
1014
- }
1015
- catch (err) {
1016
- cliError(`failed to connect to server: ${err.message}`);
1017
- return 1;
1018
- }
1019
- }
1020
- async function desktopList(port) {
1021
- const detection = await detectWebManager(port);
1022
- if (!detection.running) {
1023
- cliError('server is not running');
1024
- return 1;
1025
- }
1026
- try {
1027
- const res = await httpGet(`${detection.baseUrl}/api/projects`);
1028
- const data = JSON.parse(res.body);
1029
- if (!data.projects || data.projects.length === 0) {
1030
- cliLog('no projects registered');
1031
- return 0;
1032
- }
1033
- const idW = 36;
1034
- const nameW = 24;
1035
- process.stdout.write(`${bold('ID'.padEnd(idW))} ${bold('NAME'.padEnd(nameW))} ${bold('PATH')}\n`);
1036
- for (const p of data.projects) {
1037
- process.stdout.write(`${p.id.padEnd(idW)} ${p.name.padEnd(nameW)} ${p.path}\n`);
1038
- }
1039
- return 0;
1040
- }
1041
- catch (err) {
1042
- cliError(`failed to fetch projects: ${err.message}`);
1043
- return 1;
1044
- }
1045
- }
1046
- async function handleDesktop(subArgs, port) {
1047
- const sub = subArgs[0];
1048
- if (!sub || sub === 'help' || sub === '--help' || sub === '-h') {
1049
- process.stdout.write(`
1050
- ${bold('specrails-desktop')} — server management
1051
-
1052
- ${bold('Usage:')}
1053
- specrails-desktop start Start the Specrails server
1054
- specrails-desktop stop Stop the Specrails server
1055
- specrails-desktop status Show server status and registered projects
1056
- specrails-desktop add <path> Register a project by path
1057
- specrails-desktop remove <id> Unregister a project by ID
1058
- specrails-desktop list List all registered projects
1059
- `.trimStart());
1060
- return 0;
1061
- }
1062
- if (sub === 'start') {
1063
- return desktopStart(port);
1064
- }
1065
- if (sub === 'stop') {
1066
- return desktopStop();
1067
- }
1068
- if (sub === 'status') {
1069
- return desktopStatus(port);
1070
- }
1071
- if (sub === 'add') {
1072
- const projectPath = subArgs[1];
1073
- if (!projectPath) {
1074
- cliError('usage: specrails-desktop add <path>');
1075
- return 1;
1076
- }
1077
- return desktopAdd(projectPath, port);
1078
- }
1079
- if (sub === 'remove') {
1080
- const projectId = subArgs[1];
1081
- if (!projectId) {
1082
- cliError('usage: specrails-desktop remove <id>');
1083
- return 1;
1084
- }
1085
- return desktopRemove(projectId, port);
1086
- }
1087
- if (sub === 'list') {
1088
- return desktopList(port);
1089
- }
1090
- cliError(`unknown subcommand: ${sub}`);
1091
- return 1;
1092
- }
4
+ exports._internal = exports.printSummary = exports.formatTokens = exports.formatDuration = exports.detectWebManager = exports.getVersion = exports.parseArgs = exports.KNOWN_VERBS = void 0;
5
+ const args_1 = require("./args");
6
+ const help_1 = require("./help");
7
+ const status_1 = require("./status");
8
+ const desktop_control_1 = require("./desktop-control");
9
+ const output_1 = require("./output");
10
+ const desktop_client_1 = require("./desktop-client");
11
+ const run_command_1 = require("./run-command");
12
+ const format_1 = require("./format");
13
+ var args_2 = require("./args");
14
+ Object.defineProperty(exports, "KNOWN_VERBS", { enumerable: true, get: function () { return args_2.KNOWN_VERBS; } });
15
+ var args_3 = require("./args");
16
+ Object.defineProperty(exports, "parseArgs", { enumerable: true, get: function () { return args_3.parseArgs; } });
17
+ var help_2 = require("./help");
18
+ Object.defineProperty(exports, "getVersion", { enumerable: true, get: function () { return help_2.getVersion; } });
19
+ var desktop_client_2 = require("./desktop-client");
20
+ Object.defineProperty(exports, "detectWebManager", { enumerable: true, get: function () { return desktop_client_2.detectWebManager; } });
21
+ var format_2 = require("./format");
22
+ Object.defineProperty(exports, "formatDuration", { enumerable: true, get: function () { return format_2.formatDuration; } });
23
+ var format_3 = require("./format");
24
+ Object.defineProperty(exports, "formatTokens", { enumerable: true, get: function () { return format_3.formatTokens; } });
25
+ var output_2 = require("./output");
26
+ Object.defineProperty(exports, "printSummary", { enumerable: true, get: function () { return output_2.printSummary; } });
1093
27
  // ---------------------------------------------------------------------------
1094
28
  // Main entry point
1095
29
  // ---------------------------------------------------------------------------
1096
30
  async function main() {
1097
31
  const argv = process.argv.slice(2);
1098
- const parsed = parseArgs(argv);
32
+ const parsed = (0, args_1.parseArgs)(argv);
1099
33
  if (parsed.mode === 'version') {
1100
- printVersion();
34
+ (0, help_1.printVersion)();
1101
35
  process.exit(0);
1102
36
  }
1103
37
  if (parsed.mode === 'help') {
1104
- printHelp();
38
+ (0, help_1.printHelp)();
1105
39
  process.exit(0);
1106
40
  }
1107
41
  if (parsed.mode === 'status') {
1108
- const code = await handleStatus(parsed.port);
42
+ const code = await (0, status_1.handleStatus)(parsed.port);
1109
43
  process.exit(code);
1110
44
  }
1111
45
  if (parsed.mode === 'jobs') {
1112
- const code = await handleJobs(parsed.port);
46
+ const code = await (0, status_1.handleJobs)(parsed.port);
1113
47
  process.exit(code);
1114
48
  }
1115
49
  if (parsed.mode === 'desktop') {
1116
- const code = await handleDesktop(parsed.subArgs, parsed.port);
50
+ const code = await (0, desktop_control_1.handleDesktop)(parsed.subArgs, parsed.port);
1117
51
  process.exit(code);
1118
52
  }
1119
53
  // Command or raw: resolve command string
1120
54
  const command = parsed.resolved;
1121
55
  const port = parsed.port;
1122
- cliLog(`running: ${command}`);
1123
- const detection = await detectWebManager(port);
56
+ (0, output_1.cliLog)(`running: ${command}`);
57
+ const detection = await (0, desktop_client_1.detectWebManager)(port);
1124
58
  let exitCode;
1125
59
  const projectOverride = (parsed.mode === 'command' || parsed.mode === 'raw') ? parsed.projectOverride : undefined;
1126
60
  if (detection.running) {
1127
- cliLog(`routing via manager at ${detection.baseUrl}`);
1128
- exitCode = await runViaWebManager(command, detection.baseUrl, projectOverride);
61
+ (0, output_1.cliLog)(`routing via manager at ${detection.baseUrl}`);
62
+ exitCode = await (0, run_command_1.runViaWebManager)(command, detection.baseUrl, projectOverride);
1129
63
  }
1130
64
  else {
1131
- cliLog('manager not running — invoking claude directly');
1132
- exitCode = await runDirect(command);
65
+ (0, output_1.cliLog)('manager not running — invoking claude directly');
66
+ exitCode = await (0, run_command_1.runDirect)(command);
1133
67
  }
1134
68
  process.exit(exitCode);
1135
69
  }
1136
70
  // Only run main() when this file is executed directly (not when imported in tests)
1137
71
  if (require.main === module) {
1138
72
  main().catch((err) => {
1139
- cliError(err.message ?? String(err));
73
+ (0, output_1.cliError)(err.message ?? String(err));
1140
74
  process.exit(1);
1141
75
  });
1142
76
  }
@@ -1144,9 +78,9 @@ if (require.main === module) {
1144
78
  // Test-only exports — not part of the public API
1145
79
  // ---------------------------------------------------------------------------
1146
80
  exports._internal = {
1147
- ansi, dim, red, bold, dimCyan, cliPrefix, cliLog, cliError, cliWarn,
1148
- httpGet, httpPost, formatJobDuration, formatJobStarted, printVersion, printHelp,
1149
- handleStatus, handleJobs, handleDesktop, desktopStart, desktopStop, desktopStatus, desktopAdd, desktopRemove, desktopList, desktopServerPath,
1150
- resolveProjectFromCwd, runViaWebManager, runDirect, isPortInUse, readPid, isProcessRunning, main,
1151
- isTTY, DESKTOP_PID_FILE, DESKTOP_LOG_FILE, EXIT_PATTERN, DEFAULT_PORT, DETECTION_TIMEOUT_MS, HTTP_REQUEST_TIMEOUT_MS,
81
+ ansi: output_1.ansi, dim: output_1.dim, red: output_1.red, bold: output_1.bold, dimCyan: output_1.dimCyan, cliPrefix: output_1.cliPrefix, cliLog: output_1.cliLog, cliError: output_1.cliError, cliWarn: output_1.cliWarn,
82
+ httpGet: desktop_client_1.httpGet, httpPost: desktop_client_1.httpPost, formatJobDuration: format_1.formatJobDuration, formatJobStarted: format_1.formatJobStarted, printVersion: help_1.printVersion, printHelp: help_1.printHelp,
83
+ handleStatus: status_1.handleStatus, handleJobs: status_1.handleJobs, handleDesktop: desktop_control_1.handleDesktop, desktopStart: desktop_control_1.desktopStart, desktopStop: desktop_control_1.desktopStop, desktopStatus: desktop_control_1.desktopStatus, desktopAdd: desktop_control_1.desktopAdd, desktopRemove: desktop_control_1.desktopRemove, desktopList: desktop_control_1.desktopList, desktopServerPath: desktop_control_1.desktopServerPath,
84
+ resolveProjectFromCwd: run_command_1.resolveProjectFromCwd, runViaWebManager: run_command_1.runViaWebManager, runDirect: run_command_1.runDirect, isPortInUse: desktop_control_1.isPortInUse, readPid: desktop_control_1.readPid, isProcessRunning: desktop_control_1.isProcessRunning, main,
85
+ isTTY: output_1.isTTY, DESKTOP_PID_FILE: desktop_control_1.DESKTOP_PID_FILE, DESKTOP_LOG_FILE: desktop_control_1.DESKTOP_LOG_FILE, EXIT_PATTERN: run_command_1.EXIT_PATTERN, DEFAULT_PORT: args_1.DEFAULT_PORT, DETECTION_TIMEOUT_MS: desktop_client_1.DETECTION_TIMEOUT_MS, HTTP_REQUEST_TIMEOUT_MS: desktop_client_1.HTTP_REQUEST_TIMEOUT_MS,
1152
86
  };