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.
- package/cli/dist/args.js +80 -0
- package/cli/dist/desktop-client.js +125 -0
- package/cli/dist/desktop-control.js +339 -0
- package/cli/dist/format.js +45 -0
- package/cli/dist/help.js +68 -0
- package/cli/dist/output.js +47 -0
- package/cli/dist/run-command.js +340 -0
- package/cli/dist/specrails-desktop.js +41 -1107
- package/cli/dist/status.js +111 -0
- package/client/dist/assets/{ActivityFeedPage-ILXMZXcb.js → ActivityFeedPage-DdMNmVBk.js} +1 -1
- package/client/dist/assets/{AgentBrowserCapture-UcsupCy7.js → AgentBrowserCapture-CeerUf--.js} +1 -1
- package/client/dist/assets/{AgentModeAnalyticsPane-CQuxYh8B.js → AgentModeAnalyticsPane-BjGb-8rh.js} +2 -2
- package/client/dist/assets/{AgentModeCodePane-zPWbW7ei.js → AgentModeCodePane-8XRkQEo_.js} +2 -2
- package/client/dist/assets/{AgentModeJobsPane-CInByIdl.js → AgentModeJobsPane-BN3dgJOA.js} +1 -1
- package/client/dist/assets/{AgentsPage-BWvgxaBv.js → AgentsPage-DfHWgP_x.js} +1 -1
- package/client/dist/assets/{AnalyticsPage-DP3jakv-.js → AnalyticsPage-CyfhYiBn.js} +1 -1
- package/client/dist/assets/{CodePage-BKXiwE5a.js → CodePage-C2Nf-Q5W.js} +1 -1
- package/client/dist/assets/{DesktopAnalyticsPage-DryJOaMz.js → DesktopAnalyticsPage-tc0p4jJW.js} +1 -1
- package/client/dist/assets/{DocsDialog-BsCG9ksG.js → DocsDialog-Br3bczCp.js} +1 -1
- package/client/dist/assets/{DocsPage-Cc-GeKYl.js → DocsPage-BcDr2cBm.js} +1 -1
- package/client/dist/assets/{ExportDropdown-ClahJdDn.js → ExportDropdown-BVGKEgkd.js} +1 -1
- package/client/dist/assets/{InteractiveJobComposer-DfLF6N0f.js → InteractiveJobComposer-DFPpdg2D.js} +1 -1
- package/client/dist/assets/{JobDetailModal-DHeur9YK.js → JobDetailModal-BdOv-l-M.js} +1 -1
- package/client/dist/assets/{JobDetailPage-CraDKzeG.js → JobDetailPage-vBbNeRuz.js} +1 -1
- package/client/dist/assets/{JobsPage-CcbuSwj7.js → JobsPage-CJCtPhIz.js} +1 -1
- package/client/dist/assets/{LoopBuilderPage-BKtNp494.js → LoopBuilderPage-BemvUOxy.js} +1 -1
- package/client/dist/assets/{LoopPreviewModal-DbZd9isy.js → LoopPreviewModal-C6hNAeYN.js} +1 -1
- package/client/dist/assets/{LoopsPage-DEg6sIkG.js → LoopsPage-BZr0quvh.js} +1 -1
- package/client/dist/assets/{MinimizedChatsContext-BGtEjWsc.js → MinimizedChatsContext-D3QzgV0B.js} +1 -1
- package/client/dist/assets/{PluginsPage-BVqSsPzL.js → PluginsPage-DOpTV75p.js} +1 -1
- package/client/dist/assets/{ProjectSettingsDialog-CAjLgJAZ.js → ProjectSettingsDialog-Dkwrn0z1.js} +1 -1
- package/client/dist/assets/{RepositoryDeliveries-DrcrxLe0.js → RepositoryDeliveries-Du2UCf1a.js} +1 -1
- package/client/dist/assets/{RepositoryScopeSelector-CAGY3xW6.js → RepositoryScopeSelector-hPGZNRBm.js} +1 -1
- package/client/dist/assets/ReviewPacketPage-DcOq-UTA.js +4 -0
- package/client/dist/assets/{TemplatePreviewModal-Bqs-9fhn.js → TemplatePreviewModal-Cejcquah.js} +1 -1
- package/client/dist/assets/{TicketDetailModalContext-C0jQ5yOk.js → TicketDetailModalContext-DAK-NoK6.js} +1 -1
- package/client/dist/assets/{Trans-DtWlCJN9.js → Trans-DEbgkuAY.js} +1 -1
- package/client/dist/assets/{dashboard-CTfEtkqX.js → dashboard-B9uWNGO2.js} +1 -1
- package/client/dist/assets/{dashboard-DMklepmz.js → dashboard-BIwmO9wc.js} +1 -1
- package/client/dist/assets/{dashboard-DBAP2_SQ.js → dashboard-BV4vOZkR.js} +1 -1
- package/client/dist/assets/{dashboard-D40HP2ks.js → dashboard-Bf6MPVsM.js} +1 -1
- package/client/dist/assets/{dashboard-Csf1yGSN.js → dashboard-C8klWVvZ.js} +1 -1
- package/client/dist/assets/{dashboard-C29Qwa-L.js → dashboard-D1ORhPF0.js} +1 -1
- package/client/dist/assets/{dashboard-Cr4D8oMn.js → dashboard-DSubvs9x.js} +1 -1
- package/client/dist/assets/{dashboard-CSJzf5OT.js → dashboard-px_vlRx_.js} +1 -1
- package/client/dist/assets/{formatDistanceToNow-DYmvOA3R.js → formatDistanceToNow-Cc2u5FwQ.js} +1 -1
- package/client/dist/assets/{getTimezoneOffsetInMilliseconds-BAhW5E6V.js → getTimezoneOffsetInMilliseconds-BHm2U-w1.js} +1 -1
- package/client/dist/assets/index-C51x61Bz.css +2 -0
- package/client/dist/assets/{index-BfAZg5DG.js → index-Cxb288Rj.js} +29 -29
- package/client/dist/assets/{jira-api-B27Za5FP.js → jira-api-DrGxiD9p.js} +1 -1
- package/client/dist/assets/packet-8nn9RsU7.js +1 -0
- package/client/dist/assets/packet-B0H15elv.js +1 -0
- package/client/dist/assets/packet-BQvBgrwh.js +1 -0
- package/client/dist/assets/packet-BR7mO0-a.js +1 -0
- package/client/dist/assets/packet-BgcQ6q_K.js +1 -0
- package/client/dist/assets/packet-D2s92Jnn.js +1 -0
- package/client/dist/assets/packet-YbtcD1HS.js +1 -0
- package/client/dist/assets/packet-c0Qj_9eX.js +1 -0
- package/client/dist/assets/{project-repositories-DixIZHTA.js → project-repositories-9Wzs0coQ.js} +1 -1
- package/client/dist/assets/{spending-BGmZBX5B.js → spending-T5GM7wpn.js} +1 -1
- package/client/dist/assets/{useDesktop-CPB4cnwI.js → useDesktop-QogiN0zR.js} +1 -1
- package/client/dist/assets/{useRuntimeRuns-DEQ9HdAM.js → useRuntimeRuns-Dqmz9fS9.js} +1 -1
- package/client/dist/assets/{useSharedWebSocket-DcPRKR8R.js → useSharedWebSocket-Dz-HI6De.js} +2 -2
- package/client/dist/index.html +17 -17
- package/docs/codex.md +5 -5
- package/docs/customizing.md +1 -1
- package/docs/gemini-cli-provider-study.md +14 -14
- package/docs/gemini-core-support-evaluation.md +1 -1
- package/docs/gemini.md +5 -5
- package/docs/internals/README.md +6 -2
- package/docs/internals/adding-a-provider.md +22 -15
- package/docs/internals/agent-runtime-framework-evaluation.md +8 -8
- package/docs/internals/api-reference.md +2 -2
- package/docs/internals/architecture.md +9 -9
- package/docs/internals/browser-capture-performance.md +3 -3
- package/docs/internals/code-explorer-story.md +2 -2
- package/docs/internals/companion-rails-as-loops-contract.md +2 -2
- package/docs/internals/core-runtime-updates.md +5 -0
- package/docs/internals/embedded-browser-native-webview-evaluation.md +5 -5
- package/docs/internals/gemini-mcp-registration.md +2 -2
- package/docs/internals/global-artifacts-alignment-contract.md +1 -1
- package/docs/internals/global-artifacts-relocation-evaluation.md +1 -1
- package/docs/internals/interactive-jobs.md +8 -8
- package/docs/internals/legacy-implementation-notes.md +722 -0
- package/docs/internals/loop-step-log-explorer.md +8 -8
- package/docs/internals/mission-rail-cards.md +14 -14
- package/docs/internals/modular-architecture.md +107 -0
- package/docs/internals/profiles.md +1 -1
- package/docs/internals/programmatic-agent-runtime.md +66 -0
- package/docs/internals/project-builder.md +18 -18
- package/docs/internals/review-packet.md +8 -8
- package/docs/internals/safe-pr-review-flow.md +11 -11
- package/docs/internals/source-architecture.md +131 -0
- package/docs/internals/source-map.md +1356 -0
- package/docs/internals/spec-addenda.md +65 -9
- package/docs/internals/switch-to-agent-mode-design.md +11 -11
- package/docs/tracking-cost.md +1 -1
- package/package.json +5 -2
- package/server/dist/agent-mcp-config.js +1 -1
- package/server/dist/attachment-manager.js +1 -1
- package/server/dist/build-dirs.js +2 -2
- package/server/dist/config.js +0 -3
- package/server/dist/core-completion.js +1 -1
- package/server/dist/core-execution.js +1 -1
- package/server/dist/db/activity.js +39 -0
- package/server/dist/db/connection.js +41 -0
- package/server/dist/db/conversations.js +48 -0
- package/server/dist/db/jobs.js +251 -0
- package/server/dist/db/migrations.js +1521 -0
- package/server/dist/db/proposals.js +50 -0
- package/server/dist/db/settings.js +15 -0
- package/server/dist/db/stats.js +61 -0
- package/server/dist/db/telemetry.js +56 -0
- package/server/dist/db/templates.js +37 -0
- package/server/dist/db/types.js +2 -0
- package/server/dist/db.js +66 -2224
- package/server/dist/desktop-db.js +3 -3
- package/server/dist/desktop-router.js +8 -8
- package/server/dist/git-diagnostics.js +1 -1
- package/server/dist/index.js +11 -11
- package/server/dist/integration-branch.js +3 -14
- package/server/dist/jira/jira-issue-fields.js +0 -1
- package/server/dist/jira/jira-materializer.js +1 -1
- package/server/dist/jira/jira-sync-manager.js +1 -1
- package/server/dist/mcp/guide.js +16 -2
- package/server/dist/mcp/mcp-admin-router.js +1 -1
- package/server/dist/mcp/mcp-server.js +1 -1
- package/server/dist/mcp/tools/catalog.js +2 -0
- package/server/dist/mcp/tools/jobs.js +23 -5
- package/server/dist/mcp/tools/rails.js +2 -2
- package/server/dist/mcp/tools/recovery.js +35 -0
- package/server/dist/mcp/tools/specs.js +2 -2
- package/server/dist/mcp/tools/support.js +4 -1
- package/server/dist/mcp/tools/types.js +3 -3
- package/server/dist/mobile/mobile-gateway.js +1 -1
- package/server/dist/mobile/mobile-missions.js +1 -1
- package/server/dist/{desktop-analytics.js → modules/accounting/runtime/desktop-analytics.js} +2 -2
- package/server/dist/{pricing.js → modules/accounting/runtime/pricing.js} +10 -3
- package/server/dist/{result-event.js → modules/accounting/runtime/result-event.js} +3 -3
- package/server/dist/{spending.js → modules/accounting/runtime/spending.js} +0 -3
- package/server/dist/{telemetry-compactor.js → modules/accounting/runtime/telemetry-compactor.js} +1 -1
- package/server/dist/{telemetry-receiver.js → modules/accounting/runtime/telemetry-receiver.js} +2 -2
- package/server/dist/{agent-runtime-bridge.js → modules/agent-runtime/runtime/agent-runtime-bridge.js} +2 -2
- package/server/dist/{agent-runtime-controls-router.js → modules/agent-runtime/runtime/agent-runtime-controls-router.js} +16 -0
- package/server/dist/{agent-runtime-controls.js → modules/agent-runtime/runtime/agent-runtime-controls.js} +100 -8
- package/server/dist/{agent-runtime-effective-config.js → modules/agent-runtime/runtime/agent-runtime-effective-config.js} +1 -1
- package/server/dist/{agent-runtime-loader.js → modules/agent-runtime/runtime/agent-runtime-loader.js} +3 -3
- package/server/dist/{agent-runtime-paths.js → modules/agent-runtime/runtime/agent-runtime-paths.js} +1 -1
- package/server/dist/modules/agent-runtime/runtime/agent-runtime-recovery.js +53 -0
- package/server/dist/{agent-runtime-settings-router.js → modules/agent-runtime/runtime/agent-runtime-settings-router.js} +2 -2
- package/server/dist/{agent-runtime-settings.js → modules/agent-runtime/runtime/agent-runtime-settings.js} +3 -3
- package/server/dist/{agent-runtime-settlement.js → modules/agent-runtime/runtime/agent-runtime-settlement.js} +6 -6
- package/server/dist/{agent-defaults.js → modules/agents/runtime/agent-defaults.js} +3 -3
- package/server/dist/{agent-generator.js → modules/agents/runtime/agent-generator.js} +7 -7
- package/server/dist/{agent-refine-manager.js → modules/agents/runtime/agent-refine-manager.js} +5 -5
- package/server/dist/{agent-store.js → modules/agents/runtime/agent-store.js} +1 -1
- package/server/dist/{profile-manager.js → modules/agents/runtime/profile-manager.js} +2 -2
- package/server/dist/{profiles-router.js → modules/agents/runtime/profiles-router.js} +5 -5
- package/server/dist/{browser-capture-manager.js → modules/browser/runtime/browser-capture-manager.js} +2 -4
- package/server/dist/{browser-playwright.js → modules/browser/runtime/browser-playwright.js} +1 -1
- package/server/dist/{blueprint-chat-manager.js → modules/builder/runtime/blueprint-chat-manager.js} +7 -7
- package/server/dist/{blueprint-commit.js → modules/builder/runtime/blueprint-commit.js} +8 -8
- package/server/dist/{blueprint-draft-parser.js → modules/builder/runtime/blueprint-draft-parser.js} +1 -1
- package/server/dist/{blueprint-generation.js → modules/builder/runtime/blueprint-generation.js} +1 -1
- package/server/dist/{blueprint-operator-prompt.js → modules/builder/runtime/blueprint-operator-prompt.js} +1 -1
- package/server/dist/{blueprint-router.js → modules/builder/runtime/blueprint-router.js} +3 -3
- package/server/dist/{blueprint-spec-quality.js → modules/builder/runtime/blueprint-spec-quality.js} +1 -1
- package/server/dist/{milestone-chain.js → modules/builder/runtime/milestone-chain.js} +1 -1
- package/server/dist/{milestone-progress.js → modules/builder/runtime/milestone-progress.js} +6 -6
- package/server/dist/{code-activity.js → modules/code/runtime/code-activity.js} +1 -1
- package/server/dist/{code-explorer-router.js → modules/code/runtime/code-explorer-router.js} +5 -5
- package/server/dist/{file-provenance.js → modules/code/runtime/file-provenance.js} +1 -1
- package/server/dist/{file-story-manager.js → modules/code/runtime/file-story-manager.js} +2 -2
- package/server/dist/{file-story.js → modules/code/runtime/file-story.js} +2 -2
- package/server/dist/{file-summary-generator.js → modules/code/runtime/file-summary-generator.js} +3 -3
- package/server/dist/{file-summary-manager.js → modules/code/runtime/file-summary-manager.js} +3 -3
- package/server/dist/{project-code-discovery.js → modules/code/runtime/project-code-discovery.js} +2 -2
- package/server/dist/modules/conversations/domain/draft-stream.js +91 -0
- package/server/dist/modules/conversations/domain/recovery-context.js +101 -0
- package/server/dist/modules/conversations/index.js +10 -0
- package/server/dist/{chat-manager.js → modules/conversations/runtime/chat-manager.js} +32 -216
- package/server/dist/{context-budget.js → modules/conversations/runtime/context-budget.js} +1 -1
- package/server/dist/{explore-cwd-manager.js → modules/conversations/runtime/explore-cwd-manager.js} +1 -1
- package/server/dist/{explore-smash.js → modules/conversations/runtime/explore-smash.js} +1 -1
- package/server/dist/{explore-stdin-session.js → modules/conversations/runtime/explore-stdin-session.js} +1 -1
- package/server/dist/modules/delivery/adapters/decisions/chains.js +138 -0
- package/server/dist/modules/delivery/adapters/decisions/contracts.js +2 -0
- package/server/dist/modules/delivery/adapters/decisions/create.js +234 -0
- package/server/dist/modules/delivery/adapters/decisions/discard.js +251 -0
- package/server/dist/modules/delivery/adapters/decisions/dispatch.js +107 -0
- package/server/dist/modules/delivery/adapters/decisions/evidence.js +205 -0
- package/server/dist/modules/delivery/adapters/decisions/merge-local.js +338 -0
- package/server/dist/modules/delivery/adapters/decisions/metadata.js +109 -0
- package/server/dist/modules/delivery/adapters/decisions/observations.js +260 -0
- package/server/dist/modules/delivery/adapters/decisions/ownership.js +83 -0
- package/server/dist/modules/delivery/adapters/decisions/publish.js +242 -0
- package/server/dist/modules/delivery/adapters/decisions/recovery.js +425 -0
- package/server/dist/modules/delivery/adapters/decisions/retry.js +112 -0
- package/server/dist/modules/delivery/adapters/decisions/transitions.js +94 -0
- package/server/dist/modules/delivery/domain/decision-policy.js +50 -0
- package/server/dist/modules/delivery/domain/state.js +2 -0
- package/server/dist/modules/delivery/index.js +7 -0
- package/server/dist/{active-pr-continuation.js → modules/delivery/runtime/active-pr-continuation.js} +2 -2
- package/server/dist/{delivery-evidence.js → modules/delivery/runtime/delivery-evidence.js} +3 -3
- package/server/dist/{multi-repo-bases.js → modules/delivery/runtime/multi-repo-bases.js} +1 -1
- package/server/dist/{multi-repo-checkout.js → modules/delivery/runtime/multi-repo-checkout.js} +4 -4
- package/server/dist/{multi-repo-delivery.js → modules/delivery/runtime/multi-repo-delivery.js} +4 -4
- package/server/dist/{multi-repo-execution.js → modules/delivery/runtime/multi-repo-execution.js} +8 -8
- package/server/dist/{pr-body.js → modules/delivery/runtime/pr-body.js} +1 -1
- package/server/dist/{pr-follow-up-scope.js → modules/delivery/runtime/pr-follow-up-scope.js} +2 -2
- package/server/dist/{pr-follow-up.js → modules/delivery/runtime/pr-follow-up.js} +1 -1
- package/server/dist/{pr-naming.js → modules/delivery/runtime/pr-naming.js} +1 -1
- package/server/dist/{pr-publisher.js → modules/delivery/runtime/pr-publisher.js} +2 -2
- package/server/dist/{rail-isolated-launch.js → modules/delivery/runtime/rail-isolated-launch.js} +29 -27
- package/server/dist/{rail-launch-parser.js → modules/delivery/runtime/rail-launch-parser.js} +2 -2
- package/server/dist/{rail-merge-orchestrator.js → modules/delivery/runtime/rail-merge-orchestrator.js} +5 -5
- package/server/dist/modules/delivery/runtime/rail-pr-decision.js +12 -0
- package/server/dist/{rail-pr-delivery.js → modules/delivery/runtime/rail-pr-delivery.js} +1 -1
- package/server/dist/{rail-pr-store.js → modules/delivery/runtime/rail-pr-store.js} +2 -2
- package/server/dist/{rail-pr-ticket-effects.js → modules/delivery/runtime/rail-pr-ticket-effects.js} +3 -3
- package/server/dist/{rail-worktree-release.js → modules/delivery/runtime/rail-worktree-release.js} +4 -4
- package/server/dist/{rails-router.js → modules/delivery/runtime/rails-router.js} +66 -67
- package/server/dist/{review-packet.js → modules/delivery/runtime/review-packet.js} +3 -3
- package/server/dist/modules/execution/adapters/budget-storage.js +21 -0
- package/server/dist/modules/execution/adapters/usage-reader.js +38 -0
- package/server/dist/modules/execution/application/enforce-budget.js +14 -0
- package/server/dist/modules/execution/application/record-job-invocations.js +64 -0
- package/server/dist/modules/execution/application/recover-job-usage.js +200 -0
- package/server/dist/modules/execution/domain/job-accounting.js +2 -0
- package/server/dist/modules/execution/domain/scheduling.js +24 -0
- package/server/dist/modules/execution/domain/usage.js +21 -0
- package/server/dist/modules/execution/index.js +14 -0
- package/server/dist/modules/execution/ports.js +2 -0
- package/server/dist/{accept-ladder.js → modules/execution/runtime/accept-ladder.js} +1 -1
- package/server/dist/{interactive-job-session.js → modules/execution/runtime/interactive-job-session.js} +11 -11
- package/server/dist/{job-phase-breakdown.js → modules/execution/runtime/job-phase-breakdown.js} +1 -1
- package/server/dist/{queue-manager.js → modules/execution/runtime/queue-manager.js} +74 -491
- package/server/dist/{revision-seed.js → modules/execution/runtime/revision-seed.js} +1 -1
- package/server/dist/{spawn-lifecycle.js → modules/execution/runtime/spawn-lifecycle.js} +3 -3
- package/server/dist/{stuck-run-detector.js → modules/execution/runtime/stuck-run-detector.js} +1 -1
- package/server/dist/{loop-command-catalog.js → modules/loops/runtime/loop-command-catalog.js} +1 -1
- package/server/dist/{loop-executors.js → modules/loops/runtime/loop-executors.js} +17 -17
- package/server/dist/{loop-factory.js → modules/loops/runtime/loop-factory.js} +2 -21
- package/server/dist/{loop-graph.js → modules/loops/runtime/loop-graph.js} +25 -0
- package/server/dist/{loop-role-engines.js → modules/loops/runtime/loop-role-engines.js} +4 -4
- package/server/dist/{loop-run-manager.js → modules/loops/runtime/loop-run-manager.js} +101 -26
- package/server/dist/{loop-runs-store.js → modules/loops/runtime/loop-runs-store.js} +1 -1
- package/server/dist/{loop-shell-invocation.js → modules/loops/runtime/loop-shell-invocation.js} +2 -2
- package/server/dist/{loop-step-idle.js → modules/loops/runtime/loop-step-idle.js} +1 -1
- package/server/dist/{loop-templates.js → modules/loops/runtime/loop-templates.js} +24 -29
- package/server/dist/{loops-router.js → modules/loops/runtime/loops-router.js} +2 -2
- package/server/dist/{agent-chat-manager.js → modules/missions/runtime/agent-chat-manager.js} +19 -19
- package/server/dist/{agent-chat-router.js → modules/missions/runtime/agent-chat-router.js} +6 -6
- package/server/dist/{agent-context-resolver.js → modules/missions/runtime/agent-context-resolver.js} +8 -8
- package/server/dist/{agent-failure-briefing.js → modules/missions/runtime/agent-failure-briefing.js} +4 -2
- package/server/dist/{agent-fence-promotion.js → modules/missions/runtime/agent-fence-promotion.js} +1 -1
- package/server/dist/{agent-input-store.js → modules/missions/runtime/agent-input-store.js} +1 -1
- package/server/dist/{agent-operator-prompt.js → modules/missions/runtime/agent-operator-prompt.js} +75 -12
- package/server/dist/{agent-spec-framing.js → modules/missions/runtime/agent-spec-framing.js} +4 -4
- package/server/dist/{agent-steering.js → modules/missions/runtime/agent-steering.js} +1 -1
- package/server/dist/{mission-run-notify.js → modules/missions/runtime/mission-run-notify.js} +5 -2
- package/server/dist/modules/project-settings/adapters/http.js +24 -0
- package/server/dist/modules/project-settings/adapters/sqlite.js +120 -0
- package/server/dist/modules/project-settings/application.js +11 -0
- package/server/dist/modules/project-settings/domain.js +95 -0
- package/server/dist/modules/project-settings/index.js +11 -0
- package/server/dist/modules/project-settings/ports.js +2 -0
- package/server/dist/{contract-refine-runner.js → modules/specs/runtime/contract-refine-runner.js} +12 -12
- package/server/dist/{proposal-manager.js → modules/specs/runtime/proposal-manager.js} +8 -8
- package/server/dist/{smash-runner.js → modules/specs/runtime/smash-runner.js} +26 -58
- package/server/dist/{spec-addenda-core.js → modules/specs/runtime/spec-addenda-core.js} +2 -2
- package/server/dist/{spec-addenda.js → modules/specs/runtime/spec-addenda.js} +3 -3
- package/server/dist/{spec-launcher-manager.js → modules/specs/runtime/spec-launcher-manager.js} +8 -8
- package/server/dist/{spec-models.js → modules/specs/runtime/spec-models.js} +1 -1
- package/server/dist/{ticket-watcher.js → modules/specs/runtime/ticket-watcher.js} +1 -1
- package/server/dist/{background-process-control.js → modules/terminals/runtime/background-process-control.js} +1 -1
- package/server/dist/{background-process-service.js → modules/terminals/runtime/background-process-service.js} +3 -3
- package/server/dist/{background-process-store.js → modules/terminals/runtime/background-process-store.js} +1 -1
- package/server/dist/{background-windows-bootstrap.js → modules/terminals/runtime/background-windows-bootstrap.js} +3 -3
- package/server/dist/{terminal-manager.js → modules/terminals/runtime/terminal-manager.js} +3 -3
- package/server/dist/project-git.js +1 -1
- package/server/dist/project-profile-support.js +32 -0
- package/server/dist/project-registry.js +34 -40
- package/server/dist/project-router-background-processes.js +1 -1
- package/server/dist/project-router-chat.js +6 -6
- package/server/dist/project-router-git.js +1 -1
- package/server/dist/project-router-helpers.js +2 -2
- package/server/dist/project-router-jobs.js +14 -12
- package/server/dist/project-router-loop-runs.js +12 -12
- package/server/dist/project-router-settings.js +15 -75
- package/server/dist/project-router-setup.js +2 -2
- package/server/dist/project-router-spending.js +4 -4
- package/server/dist/project-router-terminals.js +4 -4
- package/server/dist/project-router-tickets.js +14 -14
- package/server/dist/project-router.js +13 -13
- package/server/dist/providers/claude-adapter.js +3 -2
- package/server/dist/providers/codex-adapter.js +3 -3
- package/server/dist/providers/gemini-adapter.js +1 -1
- package/server/dist/providers/telemetry-env.js +55 -0
- package/server/dist/runtime-role-prompts-router.js +2 -2
- package/server/dist/setup-manager.js +2 -48
- package/server/dist/shared/git-branch-name.js +16 -0
- package/server/dist/transient-children.js +3 -3
- package/server/dist/util/distribute-int.js +33 -0
- package/client/dist/assets/ReviewPacketPage-D-UjTef3.js +0 -4
- package/client/dist/assets/index-BAtqmAZR.css +0 -2
- package/client/dist/assets/packet-CjS_478C.js +0 -1
- package/client/dist/assets/packet-D69dKsHN.js +0 -1
- package/client/dist/assets/packet-D9rU6Oxo.js +0 -1
- package/client/dist/assets/packet-DMDFoamg.js +0 -1
- package/client/dist/assets/packet-Dd2RMs1h.js +0 -1
- package/client/dist/assets/packet-DjC9OibC.js +0 -1
- package/client/dist/assets/packet-DwPtJoDz.js +0 -1
- package/client/dist/assets/packet-ceahDg8_.js +0 -1
- package/server/dist/rail-pr-decision.js +0 -2377
- package/server/dist/ticket-broadcast.js +0 -47
- /package/server/dist/{ai-invocations.js → modules/accounting/runtime/ai-invocations.js} +0 -0
- /package/server/dist/{codex-otel-bridge.js → modules/accounting/runtime/codex-otel-bridge.js} +0 -0
- /package/server/dist/{metrics.js → modules/accounting/runtime/metrics.js} +0 -0
- /package/server/dist/{telemetry-export.js → modules/accounting/runtime/telemetry-export.js} +0 -0
- /package/server/dist/{agent-runtime-accounting.js → modules/agent-runtime/runtime/agent-runtime-accounting.js} +0 -0
- /package/server/dist/{agent-runtime-events.js → modules/agent-runtime/runtime/agent-runtime-events.js} +0 -0
- /package/server/dist/{agent-runtime-history.js → modules/agent-runtime/runtime/agent-runtime-history.js} +0 -0
- /package/server/dist/{agent-runtime-metrics.js → modules/agent-runtime/runtime/agent-runtime-metrics.js} +0 -0
- /package/server/dist/{agent-runtime-package.js → modules/agent-runtime/runtime/agent-runtime-package.js} +0 -0
- /package/server/dist/{agent-runtime-repositories.js → modules/agent-runtime/runtime/agent-runtime-repositories.js} +0 -0
- /package/server/dist/{agent-runtime-verification-suggestions.js → modules/agent-runtime/runtime/agent-runtime-verification-suggestions.js} +0 -0
- /package/server/dist/{agent-refine-db.js → modules/agents/runtime/agent-refine-db.js} +0 -0
- /package/server/dist/{browser-capture-types.js → modules/browser/runtime/browser-capture-types.js} +0 -0
- /package/server/dist/{browser-context-pool.js → modules/browser/runtime/browser-context-pool.js} +0 -0
- /package/server/dist/{browser-network.js → modules/browser/runtime/browser-network.js} +0 -0
- /package/server/dist/{browser-viewport.js → modules/browser/runtime/browser-viewport.js} +0 -0
- /package/server/dist/{blueprint-render.js → modules/builder/runtime/blueprint-render.js} +0 -0
- /package/server/dist/{blueprint-spec-fixtures.js → modules/builder/runtime/blueprint-spec-fixtures.js} +0 -0
- /package/server/dist/{blueprint-store.js → modules/builder/runtime/blueprint-store.js} +0 -0
- /package/server/dist/{blueprint-types.js → modules/builder/runtime/blueprint-types.js} +0 -0
- /package/server/dist/{builder-cwd-manager.js → modules/builder/runtime/builder-cwd-manager.js} +0 -0
- /package/server/dist/{milestone-chain-store.js → modules/builder/runtime/milestone-chain-store.js} +0 -0
- /package/server/dist/{changes-reader.js → modules/code/runtime/changes-reader.js} +0 -0
- /package/server/dist/{context-scope.js → modules/conversations/runtime/context-scope.js} +0 -0
- /package/server/dist/{explore-contract-refine.js → modules/conversations/runtime/explore-contract-refine.js} +0 -0
- /package/server/dist/{explore-draft-title.js → modules/conversations/runtime/explore-draft-title.js} +0 -0
- /package/server/dist/{merge-manager.js → modules/delivery/runtime/merge-manager.js} +0 -0
- /package/server/dist/{multi-repo-execution-store.js → modules/delivery/runtime/multi-repo-execution-store.js} +0 -0
- /package/server/dist/{pr-lifecycle.js → modules/delivery/runtime/pr-lifecycle.js} +0 -0
- /package/server/dist/{rail-isolation.js → modules/delivery/runtime/rail-isolation.js} +0 -0
- /package/server/dist/{rail-pr-recovery-git.js → modules/delivery/runtime/rail-pr-recovery-git.js} +0 -0
- /package/server/dist/{rail-worktrees-store.js → modules/delivery/runtime/rail-worktrees-store.js} +0 -0
- /package/server/dist/{rails-store.js → modules/delivery/runtime/rails-store.js} +0 -0
- /package/server/dist/{job-listing.js → modules/execution/runtime/job-listing.js} +0 -0
- /package/server/dist/{job-spawn-idempotency.js → modules/execution/runtime/job-spawn-idempotency.js} +0 -0
- /package/server/dist/{run-duration-stats.js → modules/execution/runtime/run-duration-stats.js} +0 -0
- /package/server/dist/{verification-sentinel.js → modules/execution/runtime/verification-sentinel.js} +0 -0
- /package/server/dist/{loop-constants.js → modules/loops/runtime/loop-constants.js} +0 -0
- /package/server/dist/{loop-decider.js → modules/loops/runtime/loop-decider.js} +0 -0
- /package/server/dist/{loop-effect.js → modules/loops/runtime/loop-effect.js} +0 -0
- /package/server/dist/{loop-preview.js → modules/loops/runtime/loop-preview.js} +0 -0
- /package/server/dist/{loop-templates-ported.js → modules/loops/runtime/loop-templates-ported.js} +0 -0
- /package/server/dist/{loops-store.js → modules/loops/runtime/loops-store.js} +0 -0
- /package/server/dist/{agent-chat-registry.js → modules/missions/runtime/agent-chat-registry.js} +0 -0
- /package/server/dist/{agent-cwd-manager.js → modules/missions/runtime/agent-cwd-manager.js} +0 -0
- /package/server/dist/{agent-tier.js → modules/missions/runtime/agent-tier.js} +0 -0
- /package/server/dist/{spec-contract-prompt.js → modules/specs/runtime/spec-contract-prompt.js} +0 -0
- /package/server/dist/{spec-draft-parser.js → modules/specs/runtime/spec-draft-parser.js} +0 -0
- /package/server/dist/{ticket-store.js → modules/specs/runtime/ticket-store.js} +0 -0
- /package/server/dist/{terminal-marks-store.js → modules/terminals/runtime/terminal-marks-store.js} +0 -0
- /package/server/dist/{terminal-osc-parser.js → modules/terminals/runtime/terminal-osc-parser.js} +0 -0
- /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
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
};
|