@reticlehq/server 2.4.0 → 2.5.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/SKILL.md +702 -0
- package/dist/bridge/auth-failure-reason.d.ts +19 -0
- package/dist/bridge/auth-failure-reason.js +29 -0
- package/dist/{bridge-security.d.ts → bridge/bridge-security.d.ts} +1 -1
- package/dist/{bridge-security.js → bridge/bridge-security.js} +1 -1
- package/dist/{bridge.d.ts → bridge/bridge.d.ts} +1 -1
- package/dist/{bridge.js → bridge/bridge.js} +63 -13
- package/dist/{bridge.test-harness.d.ts → bridge/bridge.test-harness.d.ts} +1 -11
- package/dist/{bridge.test-harness.js → bridge/bridge.test-harness.js} +9 -9
- package/dist/{pairing-token.js → bridge/pairing-token.js} +1 -1
- package/dist/capsule/capsule-store.d.ts +8 -3
- package/dist/capsule/capsule-store.js +7 -2
- package/dist/capsule/capsule.js +3 -3
- package/dist/capsule/causal-summary.d.ts +8 -0
- package/dist/capsule/causal-summary.js +22 -12
- package/dist/capsule/divergence.d.ts +9 -4
- package/dist/capsule/divergence.js +8 -8
- package/dist/capsule/predicate-to-links.js +11 -8
- package/dist/{cli-doctor.js → cli/cli-doctor.js} +9 -2
- package/dist/{cli-flow-commands.d.ts → cli/cli-flow-commands.d.ts} +2 -2
- package/dist/{cli-flow-commands.js → cli/cli-flow-commands.js} +15 -15
- package/dist/{cli-launch.d.ts → cli/cli-launch.d.ts} +32 -3
- package/dist/cli/cli-launch.js +171 -0
- package/dist/{cli-parse.d.ts → cli/cli-parse.d.ts} +2 -11
- package/dist/{cli-parse.js → cli/cli-parse.js} +113 -60
- package/dist/{cli-port.d.ts → cli/cli-port.d.ts} +12 -0
- package/dist/{cli-port.js → cli/cli-port.js} +9 -9
- package/dist/cli/cli-update-commands.d.ts +4 -0
- package/dist/cli/cli-update-commands.js +93 -0
- package/dist/{cli-verify.js → cli/cli-verify.js} +14 -14
- package/dist/{cloud-cli.js → cli/cloud-cli.js} +29 -24
- package/dist/cli.d.ts +2 -2
- package/dist/cli.js +174 -92
- package/dist/cloud/cloud-config.d.ts +0 -1
- package/dist/cloud/cloud-config.js +9 -9
- package/dist/cloud/cloud-sync.d.ts +1 -7
- package/dist/cloud/cloud-sync.js +12 -12
- package/dist/crawl/crawl-empty.d.ts +16 -0
- package/dist/crawl/crawl-empty.js +24 -0
- package/dist/crawl/crawl-tools.js +4 -1
- package/dist/crawl/crawl.js +26 -7
- package/dist/daemon/daemon-resilience.d.ts +49 -0
- package/dist/daemon/daemon-resilience.js +191 -0
- package/dist/daemon/daemon-usefulness.d.ts +61 -0
- package/dist/daemon/daemon-usefulness.js +62 -0
- package/dist/{daemon.d.ts → daemon/daemon.d.ts} +33 -8
- package/dist/{daemon.js → daemon/daemon.js} +80 -48
- package/dist/daemon/idle-grace.d.ts +32 -0
- package/dist/daemon/idle-grace.js +41 -0
- package/dist/{idle-shutdown.d.ts → daemon/idle-shutdown.d.ts} +13 -0
- package/dist/{idle-shutdown.js → daemon/idle-shutdown.js} +27 -4
- package/dist/domain/domain-model.js +2 -2
- package/dist/events/body-failures.js +8 -8
- package/dist/events/contradictions.d.ts +1 -1
- package/dist/events/contradictions.js +60 -10
- package/dist/events/echo-mismatch.js +10 -10
- package/dist/events/event-filters.js +6 -6
- package/dist/events/json-salvage.d.ts +2 -1
- package/dist/events/json-salvage.js +6 -6
- package/dist/events/observed-in-window.d.ts +12 -0
- package/dist/events/observed-in-window.js +21 -0
- package/dist/events/predicate-eval.d.ts +43 -20
- package/dist/events/predicate-eval.js +159 -55
- package/dist/events/predicate.d.ts +2 -2
- package/dist/events/predicate.js +112 -35
- package/dist/events/reconcile.js +5 -5
- package/dist/events/ring-buffer.js +1 -1
- package/dist/events/stale-response.js +6 -6
- package/dist/events/testid-near-miss.d.ts +22 -0
- package/dist/events/testid-near-miss.js +33 -0
- package/dist/events/unit-mismatch.js +8 -8
- package/dist/flows/affected-tools.js +19 -4
- package/dist/flows/affected.js +1 -1
- package/dist/flows/annotate-target.d.ts +1 -0
- package/dist/flows/annotate-target.js +20 -0
- package/dist/flows/annotate-tools.js +25 -3
- package/dist/flows/annotate.js +17 -4
- package/dist/flows/assertion-integrity.d.ts +2 -1
- package/dist/flows/assertion-tiers-store.d.ts +3 -66
- package/dist/flows/assertion-tiers-store.js +2 -2
- package/dist/flows/attributed-failure.d.ts +17 -0
- package/dist/flows/attributed-failure.js +20 -0
- package/dist/flows/buddy-status.d.ts +2 -1
- package/dist/flows/buddy-status.js +2 -2
- package/dist/flows/coverage.d.ts +3 -2
- package/dist/flows/coverage.js +2 -2
- package/dist/flows/decision.js +24 -9
- package/dist/flows/empty-flow.d.ts +6 -0
- package/dist/flows/empty-flow.js +24 -0
- package/dist/flows/flake-store.d.ts +0 -1
- package/dist/flows/flake-store.js +1 -1
- package/dist/flows/flake.d.ts +2 -1
- package/dist/flows/flake.js +2 -2
- package/dist/flows/flow-classify.js +12 -0
- package/dist/flows/flow-replay-run.js +4 -4
- package/dist/flows/flow-replay.d.ts +5 -7
- package/dist/flows/flow-replay.js +114 -47
- package/dist/flows/flow-report.js +1 -1
- package/dist/flows/flow-scope.d.ts +1 -6
- package/dist/flows/flow-step-runners.d.ts +26 -6
- package/dist/flows/flow-step-runners.js +157 -6
- package/dist/flows/flow-success.js +20 -15
- package/dist/flows/flow-tools.js +53 -7
- package/dist/flows/flows.js +15 -11
- package/dist/flows/gate.d.ts +4 -3
- package/dist/flows/gate.js +1 -1
- package/dist/flows/heal-run.d.ts +0 -8
- package/dist/flows/heal-run.js +5 -5
- package/dist/flows/heal.js +3 -3
- package/dist/flows/predicate-to-expect.d.ts +38 -0
- package/dist/flows/predicate-to-expect.js +95 -0
- package/dist/flows/replay.d.ts +16 -1
- package/dist/flows/replay.js +58 -22
- package/dist/flows/role-anchor-nearest.d.ts +6 -0
- package/dist/flows/role-anchor-nearest.js +45 -0
- package/dist/flows/server-verify.d.ts +1 -1
- package/dist/flows/server-verify.js +5 -5
- package/dist/flows/verify-change-tools.js +32 -10
- package/dist/flows/verify-change-verdict.d.ts +15 -0
- package/dist/flows/verify-change-verdict.js +25 -0
- package/dist/flows/watch-batcher.d.ts +3 -2
- package/dist/honesty/already-true.d.ts +2 -0
- package/dist/honesty/already-true.js +31 -0
- package/dist/honesty/blind-spots.d.ts +16 -3
- package/dist/honesty/blind-spots.js +24 -12
- package/dist/honesty/honesty.d.ts +3 -2
- package/dist/honesty/honesty.js +4 -4
- package/dist/honesty/uncaptured-bodies.js +1 -1
- package/dist/honesty/unread-outcome.js +2 -2
- package/dist/honesty/verified.d.ts +22 -3
- package/dist/honesty/verified.js +48 -8
- package/dist/http-server.js +14 -7
- package/dist/hunt/hunt-report.d.ts +2 -1
- package/dist/hunt/hunt-report.js +2 -2
- package/dist/index.d.ts +8 -6
- package/dist/index.js +32 -25
- package/dist/init/agent-rules.d.ts +2 -1
- package/dist/init/agent-rules.js +45 -7
- package/dist/init/app-choice.d.ts +20 -0
- package/dist/init/app-choice.js +25 -0
- package/dist/init/astro-host.d.ts +21 -0
- package/dist/init/astro-host.js +27 -0
- package/dist/init/astro-patch.d.ts +16 -0
- package/dist/init/astro-patch.js +240 -0
- package/dist/init/capabilities.js +4 -1
- package/dist/init/cra.d.ts +37 -0
- package/dist/init/cra.js +90 -0
- package/dist/init/cursor.js +39 -4
- package/dist/init/desktop-doctor.d.ts +3 -2
- package/dist/init/desktop-doctor.js +4 -4
- package/dist/init/detect.d.ts +3 -2
- package/dist/init/detect.js +16 -5
- package/dist/init/mcp-registered.d.ts +12 -0
- package/dist/init/mcp-registered.js +14 -0
- package/dist/init/mcp.d.ts +27 -2
- package/dist/init/mcp.js +40 -4
- package/dist/init/next-patch.d.ts +2 -4
- package/dist/init/next-patch.js +109 -19
- package/dist/init/node-io.js +23 -6
- package/dist/init/plan-framework.d.ts +16 -1
- package/dist/init/plan-framework.js +132 -8
- package/dist/init/plan.d.ts +73 -8
- package/dist/init/plan.js +133 -34
- package/dist/init/project-id.d.ts +5 -9
- package/dist/init/project-id.js +10 -22
- package/dist/init/run.d.ts +5 -0
- package/dist/init/run.js +210 -36
- package/dist/init/slash-command.d.ts +9 -0
- package/dist/init/slash-command.js +10 -1
- package/dist/init/snippets.d.ts +7 -10
- package/dist/init/snippets.js +44 -15
- package/dist/init/vite-config.js +1 -1
- package/dist/init/workspace-apps.d.ts +23 -0
- package/dist/init/workspace-apps.js +68 -0
- package/dist/input/driven-redaction.js +1 -1
- package/dist/input/network-detail.js +8 -8
- package/dist/input/network-mock-tools.js +2 -2
- package/dist/input/network-mock.js +4 -4
- package/dist/input/real-input.js +8 -3
- package/dist/input/scroll-find.js +2 -2
- package/dist/input/scroll-tools.d.ts +0 -5
- package/dist/input/scroll-tools.js +11 -4
- package/dist/input/viewport-tools.js +2 -2
- package/dist/journal/ambient-store.d.ts +0 -2
- package/dist/journal/ambient-store.js +1 -1
- package/dist/journal/ambient.js +1 -1
- package/dist/journal/attach-journal.d.ts +2 -1
- package/dist/journal/deviation-report.js +6 -6
- package/dist/journal/deviation-service.js +1 -1
- package/dist/journal/envelope-store.d.ts +0 -2
- package/dist/journal/envelope-store.js +1 -1
- package/dist/journal/envelope.d.ts +2 -3
- package/dist/journal/envelope.js +3 -3
- package/dist/journal/journal-query.js +1 -1
- package/dist/journal/journal-recorder.d.ts +2 -1
- package/dist/journal/journal-recorder.js +2 -2
- package/dist/journal/rollups.js +9 -9
- package/dist/journal/session-end.d.ts +2 -1
- package/dist/journal/session-journal.js +4 -4
- package/dist/license/license.d.ts +0 -14
- package/dist/license/license.js +8 -8
- package/dist/log.d.ts +10 -0
- package/dist/log.js +11 -1
- package/dist/mcp/mcp-is-error.d.ts +13 -0
- package/dist/mcp/mcp-is-error.js +18 -0
- package/dist/mcp/mcp-outage.d.ts +31 -0
- package/dist/mcp/mcp-outage.js +48 -0
- package/dist/mcp/mcp-proxy.d.ts +124 -0
- package/dist/mcp/mcp-proxy.js +697 -0
- package/dist/{mcp.d.ts → mcp/mcp.d.ts} +13 -10
- package/dist/{mcp.js → mcp/mcp.js} +110 -35
- package/dist/mcp/proxy-handshake.d.ts +3 -0
- package/dist/mcp/proxy-handshake.js +54 -0
- package/dist/mcp/proxy-lifecycle.d.ts +64 -0
- package/dist/mcp/proxy-lifecycle.js +70 -0
- package/dist/mcp/tool-catalog-cache.d.ts +29 -0
- package/dist/mcp/tool-catalog-cache.js +55 -0
- package/dist/oracles/propose-consequences.d.ts +2 -1
- package/dist/oracles/propose-consequences.js +16 -11
- package/dist/oracles/self-instrument.d.ts +2 -1
- package/dist/oracles/self-instrument.js +1 -1
- package/dist/phenomena/phenomena.js +3 -3
- package/dist/platform.d.ts +16 -0
- package/dist/platform.js +16 -0
- package/dist/pool/playwright-launcher.d.ts +15 -2
- package/dist/pool/playwright-launcher.js +21 -2
- package/dist/project/fs-port.js +1 -1
- package/dist/project/project-tools.js +2 -2
- package/dist/runs/build-verification-run.js +1 -1
- package/dist/runs/repair-prompt.d.ts +0 -6
- package/dist/runs/repair-prompt.js +3 -3
- package/dist/runs/risk-classify.js +1 -1
- package/dist/runs/run-diff.d.ts +0 -2
- package/dist/runs/run-diff.js +3 -3
- package/dist/runs/run-tools.js +2 -2
- package/dist/runs/verification-sync.js +2 -2
- package/dist/runs/verify-http.js +1 -1
- package/dist/runs/verify-server.js +1 -1
- package/dist/session/command-timeout.d.ts +11 -3
- package/dist/session/command-timeout.js +2 -2
- package/dist/session/control-envelope.d.ts +6 -0
- package/dist/session/control-envelope.js +7 -1
- package/dist/session/dev-server-probe.d.ts +13 -0
- package/dist/session/dev-server-probe.js +52 -0
- package/dist/session/last-act.d.ts +17 -3
- package/dist/session/last-act.js +16 -4
- package/dist/session/live-control-tools.js +7 -2
- package/dist/session/live-control.js +1 -1
- package/dist/session/looks-like-dev-server.d.ts +14 -0
- package/dist/session/looks-like-dev-server.js +25 -0
- package/dist/session/no-session-diagnosis.d.ts +38 -0
- package/dist/session/no-session-diagnosis.js +70 -0
- package/dist/session/no-session-watch.d.ts +19 -0
- package/dist/session/no-session-watch.js +61 -0
- package/dist/session/observed-state.d.ts +4 -0
- package/dist/session/observed-state.js +19 -1
- package/dist/session/output-budget.js +1 -1
- package/dist/session/session-health.d.ts +8 -1
- package/dist/session/session-health.js +8 -7
- package/dist/session/session-info.d.ts +26 -7
- package/dist/session/session-info.js +42 -1
- package/dist/session/session-lease.d.ts +0 -11
- package/dist/session/session-lease.js +1 -1
- package/dist/session/session-manager.d.ts +3 -0
- package/dist/session/session-manager.js +52 -9
- package/dist/session/session-readiness.d.ts +1 -1
- package/dist/session/session-readiness.js +6 -1
- package/dist/session/session-reaper.js +1 -1
- package/dist/session/session-reconnect.d.ts +32 -0
- package/dist/session/session-reconnect.js +42 -0
- package/dist/session/session.d.ts +14 -2
- package/dist/session/session.js +69 -26
- package/dist/session/state-select.d.ts +1 -1
- package/dist/session/state-select.js +1 -1
- package/dist/status-payload.d.ts +19 -0
- package/dist/status-payload.js +11 -0
- package/dist/telemetry/argument-shape.js +10 -1
- package/dist/telemetry/browser-mode.d.ts +27 -0
- package/dist/telemetry/browser-mode.js +33 -0
- package/dist/telemetry/bug-found.d.ts +17 -21
- package/dist/telemetry/bug-found.js +120 -8
- package/dist/telemetry/cli-telemetry.d.ts +2 -12
- package/dist/telemetry/cli-telemetry.js +27 -4
- package/dist/telemetry/daemon-telemetry.d.ts +19 -0
- package/dist/telemetry/daemon-telemetry.js +12 -2
- package/dist/telemetry/dev-repo.js +4 -4
- package/dist/telemetry/error-fingerprint.js +21 -2
- package/dist/telemetry/feedback-cli.d.ts +1 -1
- package/dist/telemetry/feedback-cli.js +4 -4
- package/dist/telemetry/feedback-context.d.ts +22 -0
- package/dist/telemetry/feedback-context.js +41 -1
- package/dist/telemetry/feedback-delivery.d.ts +20 -0
- package/dist/telemetry/feedback-delivery.js +30 -0
- package/dist/telemetry/feedback-outbox.d.ts +22 -0
- package/dist/telemetry/feedback-outbox.js +87 -0
- package/dist/telemetry/feedback.d.ts +22 -1
- package/dist/telemetry/feedback.js +67 -8
- package/dist/telemetry/git-facts.js +1 -1
- package/dist/telemetry/session-metrics.d.ts +2 -0
- package/dist/telemetry/session-metrics.js +68 -6
- package/dist/telemetry/telemetry.d.ts +27 -2
- package/dist/telemetry/telemetry.js +119 -29
- package/dist/telemetry/verification-of.d.ts +22 -0
- package/dist/telemetry/verification-of.js +84 -0
- package/dist/tools/act-capsule.js +2 -2
- package/dist/tools/act-danger.d.ts +12 -0
- package/dist/tools/act-danger.js +20 -3
- package/dist/tools/act-helpers.js +4 -4
- package/dist/tools/act-tools.d.ts +0 -8
- package/dist/tools/act-tools.js +84 -115
- package/dist/tools/act-view.js +7 -7
- package/dist/tools/alias-args.d.ts +17 -0
- package/dist/tools/alias-args.js +25 -0
- package/dist/tools/assert-grade.js +9 -9
- package/dist/tools/assert-verdict.d.ts +3 -1
- package/dist/tools/assert-verdict.js +8 -5
- package/dist/tools/browser-tools.js +30 -11
- package/dist/tools/contract-tools.js +1 -1
- package/dist/tools/coverage-identity.d.ts +34 -0
- package/dist/tools/coverage-identity.js +46 -0
- package/dist/tools/coverage-tools.js +10 -14
- package/dist/tools/dynamic-tools.d.ts +2 -1
- package/dist/tools/dynamic-tools.js +90 -18
- package/dist/tools/error-recovery.d.ts +20 -2
- package/dist/tools/error-recovery.js +179 -4
- package/dist/tools/feedback-tools.d.ts +12 -1
- package/dist/tools/feedback-tools.js +28 -5
- package/dist/tools/invoke-tool.d.ts +0 -6
- package/dist/tools/invoke-tool.js +57 -32
- package/dist/tools/lease-hint.d.ts +17 -0
- package/dist/tools/lease-hint.js +24 -0
- package/dist/tools/lease-tools.js +6 -5
- package/dist/tools/merge-tools.d.ts +10 -1
- package/dist/tools/merge-tools.js +9 -3
- package/dist/tools/merged-name-redirect.d.ts +13 -0
- package/dist/tools/merged-name-redirect.js +55 -0
- package/dist/tools/navigate-result.d.ts +17 -0
- package/dist/tools/navigate-result.js +31 -0
- package/dist/tools/observe-tools.js +54 -20
- package/dist/tools/observed-nothing.d.ts +25 -0
- package/dist/tools/observed-nothing.js +33 -0
- package/dist/tools/query-paginate.js +2 -2
- package/dist/tools/query-shape.d.ts +18 -0
- package/dist/tools/query-shape.js +39 -0
- package/dist/tools/read-tools.js +81 -20
- package/dist/tools/real-input-attempt.d.ts +28 -0
- package/dist/tools/real-input-attempt.js +88 -0
- package/dist/tools/reconcile-tools.js +7 -7
- package/dist/tools/reload-result.d.ts +14 -0
- package/dist/tools/reload-result.js +29 -0
- package/dist/tools/snapshot-delta.d.ts +4 -5
- package/dist/tools/snapshot-delta.js +0 -0
- package/dist/tools/tool-kit.d.ts +8 -0
- package/dist/tools/tool-surface.d.ts +83 -0
- package/dist/tools/tool-surface.js +204 -0
- package/dist/tools/tools-helpers.js +6 -6
- package/dist/tools/tools.d.ts +15 -0
- package/dist/tools/tools.js +53 -17
- package/dist/tools/unadvertised-help.d.ts +5 -0
- package/dist/tools/unadvertised-help.js +39 -0
- package/dist/trace.d.ts +69 -0
- package/dist/trace.js +150 -0
- package/dist/update/nudge-credit.d.ts +4 -0
- package/dist/update/nudge-credit.js +53 -0
- package/dist/update/sdk-sync.d.ts +21 -0
- package/dist/update/sdk-sync.js +42 -0
- package/dist/update/update-checker.js +3 -3
- package/dist/update/update-nudge.d.ts +63 -6
- package/dist/update/update-nudge.js +128 -15
- package/dist/update/updater.d.ts +1 -9
- package/dist/update/updater.js +13 -8
- package/dist/version/peer-announce.d.ts +4 -0
- package/dist/version/peer-announce.js +30 -0
- package/dist/{server-version.js → version/server-version.js} +5 -1
- package/dist/version/version-nudge.d.ts +28 -0
- package/dist/version/version-nudge.js +40 -0
- package/dist/version/version-skew.d.ts +66 -0
- package/dist/version/version-skew.js +71 -0
- package/dist/visual/visual-diff.js +2 -2
- package/dist/visual/visual-tools.js +4 -4
- package/docs/agent-cheatsheet.md +85 -0
- package/docs/architecture.md +113 -0
- package/docs/benchmarks.md +255 -0
- package/docs/debugging.md +102 -0
- package/docs/deploy-checks.md +69 -0
- package/docs/desktop-apps.md +256 -0
- package/docs/enterprise.md +62 -0
- package/docs/fixtures.md +28 -0
- package/docs/flows.md +166 -0
- package/docs/getting-started.md +469 -0
- package/docs/human-control.md +63 -0
- package/docs/integration-patterns.md +171 -0
- package/docs/local-registry.md +126 -0
- package/docs/mint.json +61 -0
- package/docs/multi-agent-testing.md +55 -0
- package/docs/platform-integration.md +122 -0
- package/docs/telemetry-contract.md +214 -0
- package/docs/telemetry.md +184 -0
- package/docs/testing.md +82 -0
- package/docs/token-efficiency.md +80 -0
- package/docs/usage.md +1046 -0
- package/package.json +8 -5
- package/dist/cli-launch.js +0 -112
- package/dist/daemon-resilience.d.ts +0 -22
- package/dist/daemon-resilience.js +0 -85
- package/dist/mcp-proxy.d.ts +0 -67
- package/dist/mcp-proxy.js +0 -337
- package/dist/tools/profiles.d.ts +0 -37
- package/dist/tools/profiles.js +0 -139
- /package/dist/{pairing-token.d.ts → bridge/pairing-token.d.ts} +0 -0
- /package/dist/{token-auth.d.ts → bridge/token-auth.d.ts} +0 -0
- /package/dist/{token-auth.js → bridge/token-auth.js} +0 -0
- /package/dist/{cli-doctor.d.ts → cli/cli-doctor.d.ts} +0 -0
- /package/dist/{cli-verify.d.ts → cli/cli-verify.d.ts} +0 -0
- /package/dist/{cloud-cli.d.ts → cli/cloud-cli.d.ts} +0 -0
- /package/dist/{server-version.d.ts → version/server-version.d.ts} +0 -0
|
@@ -7,15 +7,16 @@
|
|
|
7
7
|
* is `<repo>/.reticle/cloud.json`. Auth for a command = `RETICLE_CLOUD_KEY` env (agent) OR the login token.
|
|
8
8
|
*/
|
|
9
9
|
import { mkdir, writeFile, readFile } from 'node:fs/promises';
|
|
10
|
+
import { NodePlatform } from '../platform.js';
|
|
10
11
|
import { spawn } from 'node:child_process';
|
|
11
12
|
import { setTimeout as sleep } from 'node:timers/promises';
|
|
12
13
|
import { homedir } from 'node:os';
|
|
13
14
|
import { join } from 'node:path';
|
|
14
15
|
import { z } from 'zod';
|
|
15
|
-
import { createNodeFileSystem } from '
|
|
16
|
-
import { RunStore } from '
|
|
17
|
-
import { resolveProjectCloud } from '
|
|
18
|
-
import { syncRunToCloud, SyncOutcome } from '
|
|
16
|
+
import { createNodeFileSystem } from '../project/fs-port.js';
|
|
17
|
+
import { RunStore } from '../runs/run-store.js';
|
|
18
|
+
import { resolveProjectCloud } from '../cloud/cloud-config.js';
|
|
19
|
+
import { syncRunToCloud, SyncOutcome } from '../cloud/cloud-sync.js';
|
|
19
20
|
const DEFAULT_URL = 'http://localhost:8890';
|
|
20
21
|
const RETICLE_DIR = '.reticle';
|
|
21
22
|
const SESSION_FILE = 'session.json';
|
|
@@ -162,8 +163,12 @@ const DevicePollSchema = z.object({
|
|
|
162
163
|
});
|
|
163
164
|
/** Best-effort open the approval page in the default browser; the printed URL is the headless fallback. */
|
|
164
165
|
const openBrowser = (target) => {
|
|
165
|
-
const cmd =
|
|
166
|
-
|
|
166
|
+
const cmd = NodePlatform.MACOS === process.platform
|
|
167
|
+
? 'open'
|
|
168
|
+
: NodePlatform.WINDOWS === process.platform
|
|
169
|
+
? 'cmd'
|
|
170
|
+
: 'xdg-open';
|
|
171
|
+
const args = NodePlatform.WINDOWS === process.platform ? ['/c', 'start', '', target] : [target];
|
|
167
172
|
try {
|
|
168
173
|
const child = spawn(cmd, args, { stdio: 'ignore', detached: true });
|
|
169
174
|
child.on('error', () => undefined);
|
|
@@ -193,18 +198,18 @@ const cmdLoginDevice = async () => {
|
|
|
193
198
|
for (;;) {
|
|
194
199
|
await sleep(intervalMs);
|
|
195
200
|
const poll = DevicePollSchema.parse(await api('POST', `${url}/v1/auth/device/token`, null, { deviceCode: started.deviceCode }));
|
|
196
|
-
if (poll.status
|
|
201
|
+
if ('approved' === poll.status && poll.token !== undefined && poll.org !== undefined) {
|
|
197
202
|
await writeSession(url, poll.token, poll.org.name);
|
|
198
203
|
return 0;
|
|
199
204
|
}
|
|
200
|
-
if (poll.status
|
|
205
|
+
if ('pending' === poll.status) {
|
|
201
206
|
if (Date.now() > started.expiresAt) {
|
|
202
207
|
err('device login expired — run `reticle login` again');
|
|
203
208
|
return 1;
|
|
204
209
|
}
|
|
205
210
|
continue;
|
|
206
211
|
}
|
|
207
|
-
err(poll.status
|
|
212
|
+
err('denied' === poll.status
|
|
208
213
|
? 'device login was denied in the browser'
|
|
209
214
|
: 'device login expired — run `reticle login` again');
|
|
210
215
|
return 1;
|
|
@@ -264,7 +269,7 @@ const cmdWhoami = async () => {
|
|
|
264
269
|
verify: cloud.verify,
|
|
265
270
|
},
|
|
266
271
|
});
|
|
267
|
-
if (cloud.config
|
|
272
|
+
if (null === cloud.config)
|
|
268
273
|
hint('this repo is not attached — run `reticle link`');
|
|
269
274
|
return 0;
|
|
270
275
|
};
|
|
@@ -273,18 +278,18 @@ const cmdProject = async (argv) => {
|
|
|
273
278
|
const session = await readSession();
|
|
274
279
|
const token = bearer(session);
|
|
275
280
|
const url = baseUrl(session);
|
|
276
|
-
if (
|
|
281
|
+
if (null === token) {
|
|
277
282
|
err('run `reticle login` first, or set RETICLE_CLOUD_KEY');
|
|
278
283
|
return 2;
|
|
279
284
|
}
|
|
280
285
|
const sub = argv[0];
|
|
281
|
-
if (
|
|
286
|
+
if ('ls' === sub) {
|
|
282
287
|
emit(await api('GET', `${url}/v1/projects`, token));
|
|
283
288
|
return 0;
|
|
284
289
|
}
|
|
285
|
-
if (
|
|
290
|
+
if ('create' === sub) {
|
|
286
291
|
const name = argv.slice(1).join(' ').trim();
|
|
287
|
-
if (name.length
|
|
292
|
+
if (0 === name.length) {
|
|
288
293
|
err('usage: reticle project create <name>');
|
|
289
294
|
return 2;
|
|
290
295
|
}
|
|
@@ -293,17 +298,17 @@ const cmdProject = async (argv) => {
|
|
|
293
298
|
hint(`next: \`reticle link --project ${created.projectId}\` to bind this repo`);
|
|
294
299
|
return 0;
|
|
295
300
|
}
|
|
296
|
-
if (
|
|
301
|
+
if ('rename' === sub) {
|
|
297
302
|
const id = argv[1];
|
|
298
303
|
const name = argv.slice(2).join(' ').trim();
|
|
299
|
-
if (id === undefined || name.length
|
|
304
|
+
if (id === undefined || 0 === name.length) {
|
|
300
305
|
err('usage: reticle project rename <projectId> <new name>');
|
|
301
306
|
return 2;
|
|
302
307
|
}
|
|
303
308
|
emit(await api('PATCH', `${url}/v1/projects/${encodeURIComponent(id)}`, token, { name }));
|
|
304
309
|
return 0;
|
|
305
310
|
}
|
|
306
|
-
if (
|
|
311
|
+
if ('rm' === sub || 'delete' === sub) {
|
|
307
312
|
const id = argv[1];
|
|
308
313
|
if (id === undefined) {
|
|
309
314
|
err('usage: reticle project rm <projectId>');
|
|
@@ -357,7 +362,7 @@ const cmdLink = async (argv) => {
|
|
|
357
362
|
await mkdir(reticleDir, { recursive: true });
|
|
358
363
|
const linkPath = join(reticleDir, CLOUD_LINK_FILE);
|
|
359
364
|
const prev = await readJson(linkPath);
|
|
360
|
-
const prevObj = typeof prev
|
|
365
|
+
const prevObj = 'object' === typeof prev && prev !== null ? prev : {};
|
|
361
366
|
const cloudJson = {
|
|
362
367
|
projectId,
|
|
363
368
|
projectName,
|
|
@@ -369,7 +374,7 @@ const cmdLink = async (argv) => {
|
|
|
369
374
|
await mkdir(home(), { recursive: true });
|
|
370
375
|
const credPath = join(home(), CREDENTIALS_FILE);
|
|
371
376
|
const creds = (await readJson(credPath)) ?? {};
|
|
372
|
-
const credObj = typeof creds
|
|
377
|
+
const credObj = 'object' === typeof creds && creds !== null ? creds : {};
|
|
373
378
|
credObj[projectId] = key;
|
|
374
379
|
await writeFile(credPath, `${JSON.stringify(credObj, null, 2)}\n`);
|
|
375
380
|
emit({ linked: projectName, projectId, cloudJson: linkPath, credentials: credPath });
|
|
@@ -381,15 +386,15 @@ const cmdConfig = async (argv) => {
|
|
|
381
386
|
const f = flags(argv);
|
|
382
387
|
const linkPath = join(process.cwd(), RETICLE_DIR, CLOUD_LINK_FILE);
|
|
383
388
|
const raw = await readJson(linkPath);
|
|
384
|
-
if (
|
|
389
|
+
if (null === raw || typeof raw !== 'object') {
|
|
385
390
|
err('no .reticle/cloud.json here — run `reticle link` first');
|
|
386
391
|
return 2;
|
|
387
392
|
}
|
|
388
393
|
const cfg = raw;
|
|
389
|
-
const sync = typeof cfg['sync']
|
|
394
|
+
const sync = 'object' === typeof cfg['sync'] && cfg['sync'] !== null
|
|
390
395
|
? cfg['sync']
|
|
391
396
|
: { runs: true, memory: true, flows: true };
|
|
392
|
-
const onoff = (v) =>
|
|
397
|
+
const onoff = (v) => 'on' === v ? true : 'off' === v ? false : undefined;
|
|
393
398
|
for (const k of ['runs', 'memory', 'flows']) {
|
|
394
399
|
if (f[k] === undefined)
|
|
395
400
|
continue;
|
|
@@ -417,7 +422,7 @@ const cmdPush = async () => {
|
|
|
417
422
|
const fs = createNodeFileSystem();
|
|
418
423
|
const reticleRoot = join(process.cwd(), RETICLE_DIR);
|
|
419
424
|
const cloud = await resolveProjectCloud(fs, reticleRoot, homedir(), process.env);
|
|
420
|
-
if (cloud.config
|
|
425
|
+
if (null === cloud.config) {
|
|
421
426
|
err('cloud not attached here — run `reticle link` (or set RETICLE_CLOUD_URL/KEY)');
|
|
422
427
|
return 1;
|
|
423
428
|
}
|
|
@@ -448,7 +453,7 @@ const cmdPush = async () => {
|
|
|
448
453
|
const repoCloud = async () => {
|
|
449
454
|
const fs = createNodeFileSystem();
|
|
450
455
|
const cloud = await resolveProjectCloud(fs, join(process.cwd(), RETICLE_DIR), homedir(), process.env);
|
|
451
|
-
if (cloud.config
|
|
456
|
+
if (null === cloud.config)
|
|
452
457
|
throw new Error('cloud not attached here — run `reticle link` (or set RETICLE_CLOUD_URL/KEY)');
|
|
453
458
|
return cloud.config;
|
|
454
459
|
};
|
package/dist/cli.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { parseCliArgs, CLI_USAGE } from './cli-parse.js';
|
|
2
|
+
import { parseCliArgs, CLI_USAGE } from './cli/cli-parse.js';
|
|
3
3
|
export { parseCliArgs, CLI_USAGE };
|
|
4
|
-
export type { CliResult } from './cli-parse.js';
|
|
4
|
+
export type { CliResult } from './cli/cli-parse.js';
|
package/dist/cli.js
CHANGED
|
@@ -2,30 +2,30 @@
|
|
|
2
2
|
import { pathToFileURL } from 'node:url';
|
|
3
3
|
import { realpathSync } from 'node:fs';
|
|
4
4
|
import { join } from 'node:path';
|
|
5
|
-
import { handleWatch, handleCapsules, handleGate, loadNamedFlows, resolveChangedFiles, } from './cli-flow-commands.js';
|
|
5
|
+
import { handleWatch, handleCapsules, handleGate, loadNamedFlows, resolveChangedFiles, } from './cli/cli-flow-commands.js';
|
|
6
6
|
import { RETICLE_DEFAULT_PORT, ReticleDir, ReticleEnv } from '@reticlehq/core';
|
|
7
7
|
import { loadDotEnv } from './telemetry/dev-repo.js';
|
|
8
8
|
import { createNodeFileSystem } from './project/fs-port.js';
|
|
9
9
|
import { affectedSavedFlows } from './flows/flow-sources.js';
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
10
|
+
import { availableUpdate } from './update/update-nudge.js';
|
|
11
|
+
import { handleUpdate, handleRollback } from './cli/cli-update-commands.js';
|
|
12
12
|
import { start, startDaemon } from './index.js';
|
|
13
|
-
import { isCloudCommand, runCloudCommand } from './cloud-cli.js';
|
|
14
|
-
import { SERVER_VERSION } from './server-version.js';
|
|
13
|
+
import { isCloudCommand, runCloudCommand } from './cli/cloud-cli.js';
|
|
14
|
+
import { SERVER_VERSION } from './version/server-version.js';
|
|
15
15
|
import { log } from './log.js';
|
|
16
|
-
import { readPid, isAlive, isRunning, removePid, spawnDaemon, discoverDaemonPort, writeDaemonRegistry, } from './daemon.js';
|
|
17
|
-
import { waitForDaemon, startMcpProxy, probeDaemon } from './mcp-proxy.js';
|
|
18
|
-
import { installDaemonResilience } from './daemon-resilience.js';
|
|
19
|
-
import { IdleShutdown, resolveIdleShutdownMs } from './idle-shutdown.js';
|
|
20
|
-
import { fetchStatus, summarizeStatus, decideOpen, openInBrowser } from './cli-launch.js';
|
|
21
|
-
import { handleVerify } from './cli-verify.js';
|
|
16
|
+
import { readPid, isAlive, isRunning, removePid, spawnDaemon, discoverDaemonPort, writeDaemonRegistry, } from './daemon/daemon.js';
|
|
17
|
+
import { waitForDaemon, startMcpProxy, probeDaemon, proxyLog, setProxyLogPort, } from './mcp/mcp-proxy.js';
|
|
18
|
+
import { installDaemonResilience, installProxyResilience } from './daemon/daemon-resilience.js';
|
|
19
|
+
import { IdleShutdown, resolveIdleShutdownMs, resolveIdleCheckMs } from './daemon/idle-shutdown.js';
|
|
20
|
+
import { fetchStatus, summarizeStatus, warnOnDaemonSkew, decideOpen, openInBrowser, } from './cli/cli-launch.js';
|
|
21
|
+
import { handleVerify } from './cli/cli-verify.js';
|
|
22
22
|
import { summarizeHunt } from './hunt/hunt-report.js';
|
|
23
23
|
import { runInit } from './init/run.js';
|
|
24
|
-
import { handleDoctor } from './cli-doctor.js';
|
|
24
|
+
import { handleDoctor } from './cli/cli-doctor.js';
|
|
25
25
|
import { buildNodeIo } from './init/node-io.js';
|
|
26
26
|
import { describeLicense } from './license/license.js';
|
|
27
|
-
import { isLikelyDevServerPort, devServerPortWarning, readProjectPort, readProjectId } from './cli-port.js';
|
|
28
|
-
import { DAEMON_INNER_COMMAND, PORT_FLAG, DRIVE_FLAG, HEADED_FLAG, HTTP_FLAG, HTTP_PORT_FLAG, HTTP_TOKEN_FLAG, parseCliArgs, CLI_USAGE, } from './cli-parse.js';
|
|
27
|
+
import { isLikelyDevServerPort, devServerPortWarning, readProjectPort, readProjectId, } from './cli/cli-port.js';
|
|
28
|
+
import { DAEMON_INNER_COMMAND, PORT_FLAG, DRIVE_FLAG, HEADED_FLAG, HTTP_FLAG, HTTP_PORT_FLAG, HTTP_TOKEN_FLAG, parseCliArgs, CLI_USAGE, } from './cli/cli-parse.js';
|
|
29
29
|
import { handleFeedback, handleIdentify, handleTelemetry } from './telemetry/feedback-cli.js';
|
|
30
30
|
import { installDaemonTelemetry } from './telemetry/daemon-telemetry.js';
|
|
31
31
|
import { reportCliRun } from './telemetry/cli-telemetry.js';
|
|
@@ -33,7 +33,14 @@ import { reportCliRun } from './telemetry/cli-telemetry.js';
|
|
|
33
33
|
export { parseCliArgs, CLI_USAGE };
|
|
34
34
|
function handleInit(parsed) {
|
|
35
35
|
const cwd = process.cwd();
|
|
36
|
-
const result = runInit({
|
|
36
|
+
const result = runInit({
|
|
37
|
+
cwd,
|
|
38
|
+
port: parsed.port,
|
|
39
|
+
mcp: parsed.mcp,
|
|
40
|
+
dryRun: parsed.dryRun,
|
|
41
|
+
install: parsed.install,
|
|
42
|
+
...(parsed.app === undefined ? {} : { app: parsed.app }),
|
|
43
|
+
}, buildNodeIo(cwd));
|
|
37
44
|
if (!result.ok)
|
|
38
45
|
process.exit(1);
|
|
39
46
|
}
|
|
@@ -66,7 +73,7 @@ function handleServe(parsed) {
|
|
|
66
73
|
}
|
|
67
74
|
function handleStop(port, quiet) {
|
|
68
75
|
const pid = readPid(port);
|
|
69
|
-
if (
|
|
76
|
+
if (null === pid || !isAlive(pid)) {
|
|
70
77
|
removePid(port);
|
|
71
78
|
if (!quiet)
|
|
72
79
|
log('reticle_daemon_not_running', { port });
|
|
@@ -92,17 +99,22 @@ function handleStop(port, quiet) {
|
|
|
92
99
|
}
|
|
93
100
|
function handleStatus(port) {
|
|
94
101
|
const pid = readPid(port);
|
|
95
|
-
if (
|
|
102
|
+
if (null === pid || !isAlive(pid)) {
|
|
96
103
|
log('reticle_status', { port, running: false });
|
|
97
104
|
return;
|
|
98
105
|
}
|
|
99
106
|
// The daemon is up — ask it for live sessions + health so status is at-a-glance, not just a pid.
|
|
100
107
|
void fetchStatus(port).then((payload) => {
|
|
108
|
+
// `status` is the second-most-run command and the one a HUMAN types. The update nudge otherwise
|
|
109
|
+
// only rides a tool result, which the majority of daemons never produce — so the people most in
|
|
110
|
+
// need of an upgrade were the ones with no path to hearing about it.
|
|
111
|
+
const update = availableUpdate();
|
|
112
|
+
const nudge = update === undefined ? {} : { updateAvailable: update };
|
|
101
113
|
if (payload === undefined) {
|
|
102
|
-
log('reticle_status', { port, running: true, pid });
|
|
114
|
+
log('reticle_status', { port, running: true, pid, ...nudge });
|
|
103
115
|
return;
|
|
104
116
|
}
|
|
105
|
-
log('reticle_status', { port, running: true, pid, ...summarizeStatus(payload) });
|
|
117
|
+
log('reticle_status', { port, running: true, pid, ...summarizeStatus(payload), ...nudge });
|
|
106
118
|
});
|
|
107
119
|
}
|
|
108
120
|
/** Print the running package version (resolved once in server-version.ts). */
|
|
@@ -172,43 +184,15 @@ async function handleHunt(dir) {
|
|
|
172
184
|
});
|
|
173
185
|
}
|
|
174
186
|
}
|
|
175
|
-
/** `reticle update` — install the latest server version and restart (moved off the MCP surface). */
|
|
176
|
-
async function handleUpdate() {
|
|
177
|
-
try {
|
|
178
|
-
const manifest = await checkForUpdate(SERVER_VERSION, () => Date.now());
|
|
179
|
-
if (!manifest.updateAvailable || manifest.latestVersion === undefined) {
|
|
180
|
-
log('reticle_update', {
|
|
181
|
-
ok: false,
|
|
182
|
-
message: 'already on the latest version',
|
|
183
|
-
version: SERVER_VERSION,
|
|
184
|
-
});
|
|
185
|
-
return;
|
|
186
|
-
}
|
|
187
|
-
log('reticle_update', { ok: true, from: SERVER_VERSION, to: manifest.latestVersion });
|
|
188
|
-
await applyUpdate(manifest.latestVersion); // calls process.exit; Claude Code restarts
|
|
189
|
-
}
|
|
190
|
-
catch (error) {
|
|
191
|
-
log('reticle_update_failed', { error: error instanceof Error ? error.message : String(error) });
|
|
192
|
-
process.exitCode = 1;
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
/** `reticle rollback` — restore the previous server version and restart. */
|
|
196
|
-
async function handleRollback() {
|
|
197
|
-
try {
|
|
198
|
-
await rollback(); // calls process.exit
|
|
199
|
-
}
|
|
200
|
-
catch (error) {
|
|
201
|
-
log('reticle_rollback_failed', {
|
|
202
|
-
error: error instanceof Error ? error.message : String(error),
|
|
203
|
-
});
|
|
204
|
-
process.exitCode = 1;
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
187
|
/** Ensure a daemon is reachable on `port` (probe the real port; spawn + wait only if nothing's there). */
|
|
208
188
|
function ensureDaemon(port) {
|
|
209
|
-
return probeDaemon(port).then((listening) => {
|
|
189
|
+
return probeDaemon(port).then(async (listening) => {
|
|
190
|
+
// Attaching to whatever already owns the port is the whole point of a daemon — but it means an
|
|
191
|
+
// upgrade does NOT take effect until that daemon dies, and nothing used to say so. Say it here,
|
|
192
|
+
// where both versions are in hand, and keep attaching: killing another agent's daemon on a
|
|
193
|
+
// version bump is worse than a loud warning.
|
|
210
194
|
if (listening)
|
|
211
|
-
return
|
|
195
|
+
return warnOnDaemonSkew(port);
|
|
212
196
|
const scriptPath = process.argv[1];
|
|
213
197
|
if (scriptPath === undefined)
|
|
214
198
|
throw new Error('cannot locate the reticle daemon script');
|
|
@@ -222,6 +206,24 @@ function ensureDaemon(port) {
|
|
|
222
206
|
* the daemon, then reuses the already-connected tab or opens a new browser at the url. Idempotent:
|
|
223
207
|
* re-running never piles up duplicate tabs.
|
|
224
208
|
*/
|
|
209
|
+
/** How long `reticle open` waits for the launched page to register before reporting on it. */
|
|
210
|
+
const OPEN_SESSION_WAIT_MS = 8000;
|
|
211
|
+
/**
|
|
212
|
+
* Poll until a NEW session shows up, or the wait runs out. Returns whether one did.
|
|
213
|
+
*
|
|
214
|
+
* Compares against the count taken before launching rather than against zero, so opening a second
|
|
215
|
+
* app while one is already connected is not reported as an instant success.
|
|
216
|
+
*/
|
|
217
|
+
async function waitForNewSession(port, before) {
|
|
218
|
+
const deadline = Date.now() + OPEN_SESSION_WAIT_MS;
|
|
219
|
+
while (Date.now() < deadline) {
|
|
220
|
+
await new Promise((resolve) => setTimeout(resolve, 400));
|
|
221
|
+
const { sessions } = summarizeStatus(await fetchStatus(port));
|
|
222
|
+
if (sessions.length > before)
|
|
223
|
+
return true;
|
|
224
|
+
}
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
225
227
|
function handleOpen(requestedPort, url) {
|
|
226
228
|
probeDaemon(requestedPort)
|
|
227
229
|
.then((here) => (here ? requestedPort : (discoverDaemonPort() ?? requestedPort)))
|
|
@@ -229,7 +231,7 @@ function handleOpen(requestedPort, url) {
|
|
|
229
231
|
await ensureDaemon(port);
|
|
230
232
|
const { sessions } = summarizeStatus(await fetchStatus(port));
|
|
231
233
|
const decision = decideOpen(sessions, url);
|
|
232
|
-
if (
|
|
234
|
+
if ('need-url' === decision.action) {
|
|
233
235
|
log('reticle_open', {
|
|
234
236
|
port,
|
|
235
237
|
error: 'no app connected — pass a url: reticle open <url>. Desktop app (Electron/Tauri)? ' +
|
|
@@ -237,12 +239,53 @@ function handleOpen(requestedPort, url) {
|
|
|
237
239
|
});
|
|
238
240
|
return;
|
|
239
241
|
}
|
|
240
|
-
if (decision.action
|
|
242
|
+
if ('reuse' === decision.action) {
|
|
241
243
|
log('reticle_open', { port, reusing: decision.url });
|
|
242
244
|
return;
|
|
243
245
|
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
+
// A tab on the right origin, on the WRONG page. Kept (that is what stops a tab piling up per
|
|
247
|
+
// run) but never reported as done: `reusing` here read as "your url is open" for a page nobody
|
|
248
|
+
// had opened.
|
|
249
|
+
if ('left-as-is' === decision.action) {
|
|
250
|
+
log('reticle_open', {
|
|
251
|
+
port,
|
|
252
|
+
reusing: decision.url,
|
|
253
|
+
requested: decision.requested,
|
|
254
|
+
note: `a tab is connected on this origin but sitting on ${decision.url} — it was LEFT THERE, ` +
|
|
255
|
+
`not navigated to ${decision.requested}. Drive it with reticle_navigate, or open the url ` +
|
|
256
|
+
'in the browser yourself.',
|
|
257
|
+
});
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
const launchError = await openInBrowser(decision.url);
|
|
261
|
+
if (launchError !== null) {
|
|
262
|
+
log('reticle_open', {
|
|
263
|
+
port,
|
|
264
|
+
error: `could not launch a browser: ${launchError}`,
|
|
265
|
+
recovery: 'Open the url yourself, or run `reticle doctor` — it checks the pieces this command ' +
|
|
266
|
+
'depends on.',
|
|
267
|
+
});
|
|
268
|
+
process.exit(1);
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
// Say whether a SESSION appeared, not merely that a launcher was invoked. `{"opened": url}`
|
|
272
|
+
// was printed unconditionally, so a run where nothing ever opened looked identical to a run
|
|
273
|
+
// where it did — reported from the field as twenty minutes lost to a phantom port problem.
|
|
274
|
+
const connected = await waitForNewSession(port, sessions.length);
|
|
275
|
+
log('reticle_open', {
|
|
276
|
+
port,
|
|
277
|
+
opened: decision.url,
|
|
278
|
+
connected,
|
|
279
|
+
...(connected
|
|
280
|
+
? {}
|
|
281
|
+
: {
|
|
282
|
+
note: 'the browser was launched but no Reticle session appeared. The app may still be ' +
|
|
283
|
+
'loading, or it is not wired to this bridge — run `reticle doctor`, and check the ' +
|
|
284
|
+
'app connects on port ' +
|
|
285
|
+
String(port) +
|
|
286
|
+
'.',
|
|
287
|
+
}),
|
|
288
|
+
});
|
|
246
289
|
})
|
|
247
290
|
.catch((err) => {
|
|
248
291
|
log('reticle_open', { error: err instanceof Error ? err.message : String(err) });
|
|
@@ -306,9 +349,18 @@ function handleDaemonInner(parsed) {
|
|
|
306
349
|
process.on('SIGINT', shutdown);
|
|
307
350
|
// Self-shut-down when idle so a detached daemon (and any headless Chromium it launched) never
|
|
308
351
|
// lingers on the user's machine after the editor closes. Reuses the same clean shutdown path.
|
|
352
|
+
const attachedGraceEnv = process.env[ReticleEnv.IDLE_ATTACHED];
|
|
309
353
|
const idleShutdown = new IdleShutdown({
|
|
310
354
|
graceMs: resolveIdleShutdownMs(process.env[ReticleEnv.IDLE_SHUTDOWN]),
|
|
355
|
+
checkIntervalMs: resolveIdleCheckMs(process.env[ReticleEnv.IDLE_CHECK]),
|
|
311
356
|
isIdle: server.isIdle ?? (() => false),
|
|
357
|
+
...(server.agentAttached === undefined ? {} : { agentAttached: server.agentAttached }),
|
|
358
|
+
// Only when the var is actually SET. `resolveIdleShutdownMs(undefined)` returns the 5-minute
|
|
359
|
+
// DEFAULT, so passing it unconditionally would override the derived attached grace with the
|
|
360
|
+
// very number this change exists to stop using — shipping the bug while looking fixed.
|
|
361
|
+
...(attachedGraceEnv === undefined || '' === attachedGraceEnv.trim()
|
|
362
|
+
? {}
|
|
363
|
+
: { attachedGraceMs: resolveIdleShutdownMs(attachedGraceEnv) }),
|
|
312
364
|
onShutdown: () => {
|
|
313
365
|
log('reticle_daemon_idle_exit', { port: parsed.port });
|
|
314
366
|
shutdown();
|
|
@@ -333,43 +385,69 @@ function handleDaemonInner(parsed) {
|
|
|
333
385
|
*/
|
|
334
386
|
function handleMcp(opts) {
|
|
335
387
|
const { port, driveUrl, headless, http, httpPort, httpToken } = opts;
|
|
336
|
-
//
|
|
337
|
-
//
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
}
|
|
361
|
-
spawnDaemon(process.execPath, scriptPath, daemonArgs, port);
|
|
362
|
-
log('reticle_mcp_daemon_started', {
|
|
363
|
-
port,
|
|
364
|
-
...(driveUrl !== undefined ? { driveUrl } : {}),
|
|
365
|
-
});
|
|
388
|
+
// The proxy IS the MCP server the editor launched. Nothing respawns it, so an uncaught throw here
|
|
389
|
+
// is what a user experiences as "the MCP server disconnected — open /mcp and reconnect". Log it,
|
|
390
|
+
// report it, keep serving: the reconnect and dormant paths already know how to rebuild the only
|
|
391
|
+
// state this process has. See installProxyResilience for why its rule inverts the daemon's.
|
|
392
|
+
// The proxy's crash handlers must write to the proxy's LOG FILE, not just stderr. The editor
|
|
393
|
+
// swallows stderr, so wiring these to `log` meant a crash was handled and then unrecorded — the
|
|
394
|
+
// failure a user reports as "it disconnected" left nothing behind to read.
|
|
395
|
+
setProxyLogPort(port);
|
|
396
|
+
installProxyResilience(process, proxyLog);
|
|
397
|
+
/**
|
|
398
|
+
* Make sure a daemon is on the port, spawning one if not.
|
|
399
|
+
*
|
|
400
|
+
* Called on first start AND on every reconnect. The proxy used to only retry the socket, so a
|
|
401
|
+
* daemon that exited — crashed, `reticle stop`, or self-shut-down as idle — meant the retries hit
|
|
402
|
+
* a dead port until the budget ran out and the agent's MCP server exited with it. Reticle simply
|
|
403
|
+
* disappeared mid-session with nothing said. Respawning here makes the reconnect self-healing.
|
|
404
|
+
*/
|
|
405
|
+
const ensure = async () => {
|
|
406
|
+
if (await probeDaemon(port))
|
|
407
|
+
return;
|
|
408
|
+
const scriptPath = process.argv[1];
|
|
409
|
+
if (scriptPath === undefined) {
|
|
410
|
+
log('reticle_mcp_no_script', {});
|
|
411
|
+
process.exit(1);
|
|
366
412
|
}
|
|
367
|
-
|
|
368
|
-
|
|
413
|
+
const daemonArgs = [DAEMON_INNER_COMMAND, PORT_FLAG, String(port)];
|
|
414
|
+
if (driveUrl !== undefined) {
|
|
415
|
+
daemonArgs.push(DRIVE_FLAG, driveUrl);
|
|
416
|
+
if (!headless)
|
|
417
|
+
daemonArgs.push(HEADED_FLAG);
|
|
418
|
+
}
|
|
419
|
+
// Forward the HTTP-verify flags too (previously silently dropped for `reticle mcp`).
|
|
420
|
+
if (http) {
|
|
421
|
+
daemonArgs.push(HTTP_FLAG);
|
|
422
|
+
if (httpPort !== undefined)
|
|
423
|
+
daemonArgs.push(HTTP_PORT_FLAG, String(httpPort));
|
|
424
|
+
if (httpToken !== undefined)
|
|
425
|
+
daemonArgs.push(HTTP_TOKEN_FLAG, httpToken);
|
|
426
|
+
}
|
|
427
|
+
spawnDaemon(process.execPath, scriptPath, daemonArgs, port);
|
|
428
|
+
log('reticle_mcp_daemon_started', { port, ...(driveUrl !== undefined ? { driveUrl } : {}) });
|
|
429
|
+
await waitForDaemon(port);
|
|
430
|
+
};
|
|
431
|
+
// Start the proxy WHATEVER happened to the daemon.
|
|
432
|
+
//
|
|
433
|
+
// This used to exit(1) when `ensure` failed, which is the third way an MCP server disappears on a
|
|
434
|
+
// user: something else is holding the bridge port — a foreign daemon from another project, a
|
|
435
|
+
// half-dead process, a port a colleague's tool grabbed — the spawned daemon cannot bind, and the
|
|
436
|
+
// editor shows a server that failed to start. Nothing about that is unrecoverable: the proxy
|
|
437
|
+
// answers `initialize` itself, serves the cached catalog, and its wake path retries a daemon on
|
|
438
|
+
// every client request. Present-and-complaining beats absent, because absent needs a human.
|
|
439
|
+
// `void`: the chain handles its own failure and the process must not wait on it — the proxy is
|
|
440
|
+
// started from `finally` either way.
|
|
441
|
+
void ensure()
|
|
369
442
|
.catch((err) => {
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
443
|
+
log('reticle_mcp_daemon_unavailable', {
|
|
444
|
+
error: err instanceof Error ? err.message : String(err),
|
|
445
|
+
note: 'serving anyway — the next tool call will try to start a daemon again',
|
|
446
|
+
});
|
|
447
|
+
})
|
|
448
|
+
.finally(() => {
|
|
449
|
+
// Also `void`: the proxy runs for the life of the process and is never awaited by anyone.
|
|
450
|
+
void startMcpProxy(port, ensure);
|
|
373
451
|
});
|
|
374
452
|
}
|
|
375
453
|
function handleLegacyDrive(parsed) {
|
|
@@ -424,7 +502,11 @@ function main() {
|
|
|
424
502
|
const parsed = parseCliArgs(argv, defaultPort, process.env['CI'] !== undefined);
|
|
425
503
|
switch (parsed.kind) {
|
|
426
504
|
case 'error':
|
|
505
|
+
// Two audiences, two channels. The structured line is for logs and scripts; the plain text is
|
|
506
|
+
// for the person who just typed the command. Emitting only the JSON meant a single typo came
|
|
507
|
+
// back as an escaped one-line wall of the entire help text — see the ParseError builders.
|
|
427
508
|
log('reticle_usage_error', { message: parsed.message });
|
|
509
|
+
process.stderr.write(parsed.message === CLI_USAGE ? `${CLI_USAGE}\n` : `${parsed.message}\n\n${CLI_USAGE}\n`);
|
|
428
510
|
process.exit(1);
|
|
429
511
|
break;
|
|
430
512
|
case 'init':
|
|
@@ -13,7 +13,6 @@ export interface SyncPolicy {
|
|
|
13
13
|
/** Sync saved flow files (the shared regression suite). */
|
|
14
14
|
flows: boolean;
|
|
15
15
|
}
|
|
16
|
-
export declare const DEFAULT_SYNC_POLICY: SyncPolicy;
|
|
17
16
|
/** Where a verification actually executes. Reserved for the hosted-runner path; default local. */
|
|
18
17
|
export declare const VerifyMode: {
|
|
19
18
|
readonly LOCAL: "local";
|
|
@@ -16,10 +16,10 @@ import { resolveCloudConfig } from './cloud-sync.js';
|
|
|
16
16
|
export const CLOUD_LINK_FILE = 'cloud.json';
|
|
17
17
|
/** `~/.reticle/credentials.json` — the user's per-cloud-project API keys (the only secret). */
|
|
18
18
|
export const CREDENTIALS_FILE = 'credentials.json';
|
|
19
|
-
|
|
19
|
+
const DEFAULT_SYNC_POLICY = { runs: true, memory: true, flows: true };
|
|
20
20
|
/** Where a verification actually executes. Reserved for the hosted-runner path; default local. */
|
|
21
21
|
export const VerifyMode = { LOCAL: 'local', SERVER: 'server' };
|
|
22
|
-
const asBool = (v, fallback) => (typeof v
|
|
22
|
+
const asBool = (v, fallback) => ('boolean' === typeof v ? v : fallback);
|
|
23
23
|
/** Read + JSON-parse a file, or null on any problem (missing/malformed) — never throws. */
|
|
24
24
|
async function readJson(fs, path) {
|
|
25
25
|
try {
|
|
@@ -33,14 +33,14 @@ async function readJson(fs, path) {
|
|
|
33
33
|
}
|
|
34
34
|
/** Validate the shape of `.reticle/cloud.json`. Requires projectId + url; policy/verify default in. */
|
|
35
35
|
function parseLink(raw) {
|
|
36
|
-
if (typeof raw !== 'object' ||
|
|
36
|
+
if (typeof raw !== 'object' || null === raw)
|
|
37
37
|
return null;
|
|
38
38
|
const o = raw;
|
|
39
|
-
if (typeof o.projectId !== 'string' || o.projectId.length
|
|
39
|
+
if (typeof o.projectId !== 'string' || 0 === o.projectId.length)
|
|
40
40
|
return null;
|
|
41
|
-
if (typeof o.url !== 'string' || o.url.length
|
|
41
|
+
if (typeof o.url !== 'string' || 0 === o.url.length)
|
|
42
42
|
return null;
|
|
43
|
-
const sync = typeof o.sync
|
|
43
|
+
const sync = 'object' === typeof o.sync && o.sync !== null ? o.sync : {};
|
|
44
44
|
return {
|
|
45
45
|
projectId: o.projectId,
|
|
46
46
|
url: o.url,
|
|
@@ -54,10 +54,10 @@ function parseLink(raw) {
|
|
|
54
54
|
}
|
|
55
55
|
/** Look up the API key for a cloud project id in the user credentials map. */
|
|
56
56
|
function credentialFor(raw, projectId) {
|
|
57
|
-
if (typeof raw !== 'object' ||
|
|
57
|
+
if (typeof raw !== 'object' || null === raw)
|
|
58
58
|
return null;
|
|
59
59
|
const key = raw[projectId];
|
|
60
|
-
return typeof key
|
|
60
|
+
return 'string' === typeof key && key.length > 0 ? key : null;
|
|
61
61
|
}
|
|
62
62
|
/**
|
|
63
63
|
* Resolve the cloud picture for a project rooted at `reticleRoot`. Reads the project's link file + the
|
|
@@ -66,7 +66,7 @@ function credentialFor(raw, projectId) {
|
|
|
66
66
|
*/
|
|
67
67
|
export async function resolveProjectCloud(fs, reticleRoot, homeDir, env) {
|
|
68
68
|
const link = parseLink(await readJson(fs, join(reticleRoot, CLOUD_LINK_FILE)));
|
|
69
|
-
if (
|
|
69
|
+
if (null === link) {
|
|
70
70
|
// No per-project link → the env vars are the whole story (legacy single-project / CI behaviour).
|
|
71
71
|
return {
|
|
72
72
|
config: resolveCloudConfig(env),
|
|
@@ -15,12 +15,6 @@ export declare const CloudEnv: {
|
|
|
15
15
|
readonly URL: "RETICLE_CLOUD_URL";
|
|
16
16
|
readonly KEY: "RETICLE_CLOUD_KEY";
|
|
17
17
|
};
|
|
18
|
-
/** Paths the OSS server pushes to (match the cloud app's contract). */
|
|
19
|
-
export declare const CLOUD_FLOWS_PATH = "/v1/flows";
|
|
20
|
-
export declare const CLOUD_RUNS_PATH = "/v1/runs";
|
|
21
|
-
export declare const CLOUD_PROJECT_RUNS_PATH = "/v1/project/runs";
|
|
22
|
-
export declare const CLOUD_PROJECT_REGRESSION_PATH = "/v1/project/regression";
|
|
23
|
-
export declare const CLOUD_VERIFICATIONS_PATH = "/v1/verifications";
|
|
24
18
|
export interface CloudConfig {
|
|
25
19
|
url: string;
|
|
26
20
|
apiKey: string;
|
|
@@ -33,7 +27,7 @@ export declare const SyncOutcome: {
|
|
|
33
27
|
readonly FAILED: "failed";
|
|
34
28
|
};
|
|
35
29
|
export type SyncOutcome = (typeof SyncOutcome)[keyof typeof SyncOutcome];
|
|
36
|
-
|
|
30
|
+
interface SyncResult {
|
|
37
31
|
outcome: SyncOutcome;
|
|
38
32
|
status?: number;
|
|
39
33
|
error?: string;
|