@reticlehq/server 2.11.0 → 2.13.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 +93 -111
- package/dist/bridge/auth-failure-reason.d.ts +6 -2
- package/dist/bridge/auth-failure-reason.js +15 -8
- package/dist/bridge/bridge.d.ts +18 -14
- package/dist/bridge/bridge.js +248 -165
- package/dist/bridge/bridge.test-harness.d.ts +2 -0
- package/dist/bridge/bridge.test-harness.js +8 -1
- package/dist/bridge/pairing-token.d.ts +5 -0
- package/dist/bridge/pairing-token.js +28 -0
- package/dist/bridge/protocol-skew.d.ts +37 -0
- package/dist/bridge/protocol-skew.js +45 -0
- package/dist/capsule/capsule-store.d.ts +8 -7
- package/dist/capsule/capsule-store.js +1 -1
- package/dist/capsule/causal-summary.d.ts +2 -2
- package/dist/capsule/causal-summary.js +7 -2
- package/dist/chromium-launch-options.d.ts +28 -0
- package/dist/chromium-launch-options.js +29 -0
- package/dist/cli/cli-doctor.js +39 -1
- package/dist/cli/cli-flow-commands.d.ts +3 -1
- package/dist/cli/cli-flow-commands.js +34 -4
- package/dist/cli/cli-kill.d.ts +2 -1
- package/dist/cli/cli-launch.d.ts +11 -1
- package/dist/cli/cli-launch.js +59 -10
- package/dist/cli/cli-parse.d.ts +17 -1
- package/dist/cli/cli-parse.js +238 -12
- package/dist/cli/cli-update-commands.js +20 -1
- package/dist/cli/cli-verify.d.ts +2 -1
- package/dist/cli/cli-verify.js +5 -4
- package/dist/cli/cloud-cli.js +428 -253
- package/dist/cli/cloud-keystore.d.ts +37 -0
- package/dist/cli/cloud-keystore.js +97 -0
- package/dist/cli/cloud-kit.d.ts +37 -0
- package/dist/cli/cloud-kit.js +121 -0
- package/dist/cli/cloud-login.d.ts +17 -0
- package/dist/cli/cloud-login.js +183 -0
- package/dist/cli/cloud-session.d.ts +48 -0
- package/dist/cli/cloud-session.js +53 -0
- package/dist/cli/config-discovery.d.ts +2 -1
- package/dist/cli/config-discovery.js +1 -0
- package/dist/cli/daemon-start-options.d.ts +32 -0
- package/dist/cli/daemon-start-options.js +38 -0
- package/dist/cli/device-flow.d.ts +76 -0
- package/dist/cli/device-flow.js +56 -0
- package/dist/cli/doctor-rows.d.ts +12 -1
- package/dist/cli/doctor-rows.js +12 -1
- package/dist/cli/drive-attach.d.ts +13 -0
- package/dist/cli/drive-attach.js +18 -0
- package/dist/cli/drive-command.js +7 -3
- package/dist/cli/gate-exit.d.ts +21 -0
- package/dist/cli/gate-exit.js +21 -0
- package/dist/cli/gate-hook-message.d.ts +33 -0
- package/dist/cli/gate-hook-message.js +46 -0
- package/dist/cli/mcp-command.d.ts +25 -0
- package/dist/cli/mcp-command.js +174 -0
- package/dist/cli/project-name.d.ts +30 -0
- package/dist/cli/project-name.js +44 -0
- package/dist/cli/sibling-ports.d.ts +37 -0
- package/dist/cli/sibling-ports.js +56 -0
- package/dist/cli/status-next-action.d.ts +14 -0
- package/dist/cli/status-next-action.js +4 -4
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +167 -186
- package/dist/cloud/cloud-config.d.ts +24 -2
- package/dist/cloud/cloud-config.js +64 -5
- package/dist/cloud/cloud-sync.d.ts +16 -5
- package/dist/cloud/cloud-sync.js +64 -1
- package/dist/cloud/sync-cycle.d.ts +110 -0
- package/dist/cloud/sync-cycle.js +270 -0
- package/dist/cloud/sync-daemon.d.ts +69 -0
- package/dist/cloud/sync-daemon.js +260 -0
- package/dist/cloud/sync-disk.d.ts +9 -0
- package/dist/cloud/sync-disk.js +191 -0
- package/dist/cloud/sync-hash.d.ts +2 -0
- package/dist/cloud/sync-hash.js +35 -0
- package/dist/crawl/crawl-tools.js +5 -3
- package/dist/crawl/crawl.d.ts +12 -13
- package/dist/crawl/crawl.js +25 -3
- package/dist/daemon/daemon-resolve.d.ts +122 -0
- package/dist/daemon/daemon-resolve.js +187 -0
- package/dist/daemon/dev-servers.d.ts +15 -0
- package/dist/daemon/dev-servers.js +68 -0
- package/dist/daemon/free-port.d.ts +29 -0
- package/dist/daemon/free-port.js +55 -0
- package/dist/daemon/heartbeat.d.ts +0 -4
- package/dist/daemon/heartbeat.js +2 -2
- package/dist/daemon/wake-decision.d.ts +32 -0
- package/dist/daemon/wake-decision.js +38 -0
- package/dist/dev/stale-issue-guard.d.ts +0 -2
- package/dist/dev/stale-issue-guard.js +1 -1
- package/dist/domain/domain-tools.js +2 -2
- package/dist/events/contradictions.d.ts +59 -0
- package/dist/events/contradictions.js +253 -27
- package/dist/events/declared.d.ts +17 -0
- package/dist/events/declared.js +43 -1
- package/dist/events/echo-mismatch.js +65 -1
- package/dist/events/event-filters.d.ts +8 -0
- package/dist/events/event-filters.js +19 -2
- package/dist/events/observed-in-window.d.ts +12 -0
- package/dist/events/observed-in-window.js +17 -0
- package/dist/events/predicate-asks.d.ts +13 -0
- package/dist/events/predicate-asks.js +22 -0
- package/dist/events/predicate-eval.js +117 -27
- package/dist/events/predicate-parse.d.ts +0 -6
- package/dist/events/predicate-parse.js +43 -3
- package/dist/events/predicate-precheck.d.ts +8 -0
- package/dist/events/predicate-precheck.js +41 -0
- package/dist/events/predicate-route.d.ts +14 -0
- package/dist/events/predicate-route.js +36 -8
- package/dist/events/predicate-schema.d.ts +44 -0
- package/dist/events/predicate-schema.js +126 -23
- package/dist/events/predicate.d.ts +20 -0
- package/dist/events/predicate.js +77 -6
- package/dist/events/split-text-miss.d.ts +30 -0
- package/dist/events/split-text-miss.js +44 -0
- package/dist/flows/coverage.d.ts +0 -8
- package/dist/flows/coverage.js +1 -1
- package/dist/flows/decision.d.ts +5 -1
- package/dist/flows/decision.js +33 -7
- package/dist/flows/flake.d.ts +6 -8
- package/dist/flows/flake.js +1 -1
- package/dist/flows/flow-intent.d.ts +52 -0
- package/dist/flows/flow-intent.js +123 -0
- package/dist/flows/flow-memory-consult.d.ts +55 -0
- package/dist/flows/flow-memory-consult.js +38 -0
- package/dist/flows/flow-replay-run.d.ts +18 -7
- package/dist/flows/flow-replay-run.js +263 -23
- package/dist/flows/flow-replay.js +11 -2
- package/dist/flows/flow-step-runners.d.ts +8 -0
- package/dist/flows/flow-step-runners.js +8 -2
- package/dist/flows/flow-store-for-session.d.ts +31 -0
- package/dist/flows/flow-store-for-session.js +33 -0
- package/dist/flows/flow-success.js +15 -3
- package/dist/flows/flow-tools.js +87 -33
- package/dist/flows/flows.d.ts +14 -1
- package/dist/flows/flows.js +502 -24
- package/dist/flows/heal-run.js +5 -2
- package/dist/flows/predicate-to-expect.d.ts +17 -12
- package/dist/flows/predicate-to-expect.js +36 -15
- package/dist/flows/recordings.d.ts +13 -1
- package/dist/flows/recordings.js +17 -2
- package/dist/flows/replay.d.ts +22 -2
- package/dist/flows/replay.js +28 -2
- package/dist/flows/server-verify.js +16 -2
- package/dist/flows/verify-change-tools.js +9 -3
- package/dist/honesty/blind-spots.d.ts +51 -15
- package/dist/honesty/blind-spots.js +64 -4
- package/dist/honesty/feature-capture.d.ts +150 -0
- package/dist/honesty/feature-capture.js +233 -0
- package/dist/honesty/honesty.d.ts +24 -0
- package/dist/honesty/honesty.js +25 -1
- package/dist/honesty/instrumentation-gaps.d.ts +39 -6
- package/dist/honesty/instrumentation-gaps.js +88 -5
- package/dist/honesty/tool-hit-rate.d.ts +86 -0
- package/dist/honesty/tool-hit-rate.js +90 -0
- package/dist/honesty/undeclared-change.d.ts +51 -0
- package/dist/honesty/undeclared-change.js +56 -0
- package/dist/honesty/verified.d.ts +16 -0
- package/dist/honesty/verified.js +84 -5
- package/dist/http-server.d.ts +0 -1
- package/dist/http-server.js +13 -1
- package/dist/hunt/hunt-report.d.ts +0 -2
- package/dist/hunt/hunt-report.js +1 -1
- package/dist/impact/impact-recorder.d.ts +31 -8
- package/dist/impact/impact-recorder.js +104 -21
- package/dist/impact/impact-store.d.ts +13 -4
- package/dist/impact/impact-store.js +44 -3
- package/dist/index.d.ts +4 -1
- package/dist/index.js +113 -7
- package/dist/init/agent-rules.d.ts +30 -6
- package/dist/init/agent-rules.js +54 -13
- package/dist/init/astro-patch.d.ts +16 -0
- package/dist/init/astro-patch.js +28 -0
- package/dist/init/capabilities.d.ts +4 -3
- package/dist/init/capabilities.js +23 -3
- package/dist/init/closing-hint.d.ts +36 -0
- package/dist/init/closing-hint.js +120 -0
- package/dist/init/confirm.d.ts +27 -28
- package/dist/init/confirm.js +51 -11
- package/dist/init/cra.d.ts +34 -5
- package/dist/init/cra.js +96 -25
- package/dist/init/csp-check.js +17 -2
- package/dist/init/csp-doctor.d.ts +8 -1
- package/dist/init/csp-doctor.js +8 -3
- package/dist/init/dev-script.d.ts +50 -0
- package/dist/init/dev-script.js +66 -0
- package/dist/init/format-generated.d.ts +18 -0
- package/dist/init/format-generated.js +39 -0
- package/dist/init/install-hint.d.ts +9 -0
- package/dist/init/install-hint.js +29 -0
- package/dist/init/mcp-clients.d.ts +7 -0
- package/dist/init/mcp-clients.js +7 -0
- package/dist/init/mcp.d.ts +17 -6
- package/dist/init/mcp.js +27 -11
- package/dist/init/node-io.js +11 -1
- package/dist/init/plan-framework.d.ts +0 -5
- package/dist/init/plan-framework.js +147 -37
- package/dist/init/plan.d.ts +30 -37
- package/dist/init/plan.js +50 -60
- package/dist/init/preflight.d.ts +28 -0
- package/dist/init/preflight.js +38 -0
- package/dist/init/refresh-rules.d.ts +39 -0
- package/dist/init/refresh-rules.js +51 -0
- package/dist/init/run.d.ts +32 -35
- package/dist/init/run.js +151 -202
- package/dist/init/slash-command.d.ts +1 -1
- package/dist/init/snippets.js +90 -31
- package/dist/init/vite-config.d.ts +1 -1
- package/dist/init/vite-config.js +39 -25
- package/dist/init/workspace-apps.d.ts +17 -10
- package/dist/init/workspace-apps.js +110 -0
- package/dist/input/network-mock-tools.js +3 -6
- package/dist/input/real-input.d.ts +2 -0
- package/dist/input/real-input.js +28 -7
- package/dist/input/scroll-find.d.ts +7 -4
- package/dist/input/scroll-find.js +63 -25
- package/dist/input/scroll-tools.js +4 -6
- package/dist/input/viewport-tools.js +5 -4
- package/dist/intent/inline-intent.d.ts +37 -0
- package/dist/intent/inline-intent.js +193 -0
- package/dist/intent/intent-shard-store.d.ts +72 -0
- package/dist/intent/intent-shard-store.js +203 -0
- package/dist/intent/intent-shard.d.ts +411 -0
- package/dist/intent/intent-shard.js +176 -0
- package/dist/intent/intent-store.d.ts +49 -0
- package/dist/intent/intent-store.js +133 -0
- package/dist/intent/intent-subject.d.ts +45 -0
- package/dist/intent/intent-subject.js +177 -0
- package/dist/intent/intent-tools.d.ts +2 -0
- package/dist/intent/intent-tools.js +143 -0
- package/dist/intent/open-intents.d.ts +42 -0
- package/dist/intent/open-intents.js +40 -0
- package/dist/journal/envelope.d.ts +26 -26
- package/dist/journal/journal-recorder.d.ts +23 -0
- package/dist/journal/journal-recorder.js +35 -0
- package/dist/license/license-env.d.ts +29 -0
- package/dist/license/license-env.js +148 -0
- package/dist/loopback-agent.d.ts +31 -0
- package/dist/loopback-agent.js +57 -0
- package/dist/mcp/attach-memory.d.ts +83 -0
- package/dist/mcp/attach-memory.js +185 -0
- package/dist/mcp/client-identity.d.ts +31 -0
- package/dist/mcp/client-identity.js +51 -0
- package/dist/mcp/mcp-post-transport.d.ts +26 -0
- package/dist/mcp/mcp-post-transport.js +109 -0
- package/dist/mcp/mcp-proxy.d.ts +2 -8
- package/dist/mcp/mcp-proxy.js +64 -62
- package/dist/mcp/mcp.d.ts +2 -0
- package/dist/mcp/mcp.js +52 -24
- package/dist/mcp/proxy-backoff.d.ts +11 -0
- package/dist/mcp/proxy-backoff.js +13 -0
- package/dist/mcp/proxy-daemon-probe.d.ts +1 -0
- package/dist/mcp/proxy-daemon-probe.js +7 -1
- package/dist/mcp/proxy-handshake.d.ts +23 -0
- package/dist/mcp/proxy-handshake.js +31 -0
- package/dist/mcp/server-instructions.js +45 -3
- package/dist/mcp/tool-catalog-cache.d.ts +16 -0
- package/dist/mcp/tool-catalog-cache.js +22 -0
- package/dist/memory/memory-tools.d.ts +2 -0
- package/dist/memory/memory-tools.js +82 -0
- package/dist/memory/project-memory.d.ts +34 -0
- package/dist/memory/project-memory.js +96 -0
- package/dist/oracles/flow-instrument-gaps.d.ts +4 -4
- package/dist/oracles/flow-instrument-gaps.js +4 -5
- package/dist/oracles/propose-consequences.js +12 -3
- package/dist/oracles/self-instrument.d.ts +9 -9
- package/dist/oracles/self-instrument.js +19 -13
- package/dist/pool/browser-pool.d.ts +75 -5
- package/dist/pool/browser-pool.js +144 -5
- package/dist/pool/playwright-launcher.js +12 -1
- package/dist/project/fs-port.d.ts +3 -0
- package/dist/project/fs-port.js +3 -2
- package/dist/project/memory-fs.js +6 -1
- package/dist/project/project-tools.js +6 -35
- package/dist/project/reticle-dir.d.ts +17 -2
- package/dist/project/reticle-dir.js +22 -4
- package/dist/runs/context-tools.d.ts +2 -0
- package/dist/runs/context-tools.js +89 -0
- package/dist/runs/run-context.d.ts +40 -0
- package/dist/runs/run-context.js +158 -0
- package/dist/runs/run-diff.d.ts +2 -1
- package/dist/runs/run-store.d.ts +10 -0
- package/dist/runs/run-store.js +10 -0
- package/dist/runs/verification-sync.d.ts +0 -11
- package/dist/runs/verification-sync.js +24 -3
- package/dist/runs/verify-server.d.ts +8 -1
- package/dist/runs/verify-server.js +14 -2
- package/dist/session/command-timeout.d.ts +5 -2
- package/dist/session/command-timeout.js +5 -4
- package/dist/session/dev-server-probe.d.ts +0 -8
- package/dist/session/dev-server-probe.js +1 -1
- package/dist/session/last-act.d.ts +7 -1
- package/dist/session/last-act.js +2 -2
- package/dist/session/lease-visibility.d.ts +67 -0
- package/dist/session/lease-visibility.js +76 -0
- package/dist/session/live-control-tools.js +1 -42
- package/dist/session/no-session-diagnosis.d.ts +33 -0
- package/dist/session/no-session-diagnosis.js +82 -24
- package/dist/session/no-session-next-action.d.ts +12 -0
- package/dist/session/no-session-next-action.js +77 -6
- package/dist/session/no-session-watch.d.ts +18 -6
- package/dist/session/no-session-watch.js +125 -75
- package/dist/session/output-budget.js +4 -1
- package/dist/session/pending-commands.d.ts +11 -1
- package/dist/session/pending-commands.js +12 -2
- package/dist/session/resolve-within.d.ts +34 -0
- package/dist/session/resolve-within.js +53 -0
- package/dist/session/session-health.d.ts +9 -0
- package/dist/session/session-health.js +23 -0
- package/dist/session/session-info.d.ts +18 -0
- package/dist/session/session-info.js +11 -0
- package/dist/session/session-manager.d.ts +14 -0
- package/dist/session/session-manager.js +111 -4
- package/dist/session/session-mirror.d.ts +16 -0
- package/dist/session/session-mirror.js +22 -0
- package/dist/session/session-recommendation.d.ts +19 -5
- package/dist/session/session-recommendation.js +25 -9
- package/dist/session/session-successor.d.ts +37 -0
- package/dist/session/session-successor.js +113 -0
- package/dist/session/session-tools.js +2 -2
- package/dist/session/session.d.ts +53 -3
- package/dist/session/session.js +162 -9
- package/dist/session/wall-clock.d.ts +12 -0
- package/dist/session/wall-clock.js +14 -0
- package/dist/setup/agent-configs.d.ts +96 -0
- package/dist/setup/agent-configs.js +333 -0
- package/dist/setup/agent-io.d.ts +9 -0
- package/dist/setup/agent-io.js +16 -0
- package/dist/setup/agent-writer.d.ts +51 -0
- package/dist/setup/agent-writer.js +115 -0
- package/dist/setup/approval-migration.d.ts +47 -0
- package/dist/setup/approval-migration.js +68 -0
- package/dist/setup/auto-approve.d.ts +85 -0
- package/dist/setup/auto-approve.js +150 -0
- package/dist/setup/bridge-port.d.ts +15 -0
- package/dist/setup/bridge-port.js +24 -0
- package/dist/setup/desktop-shape.d.ts +57 -0
- package/dist/setup/desktop-shape.js +63 -0
- package/dist/setup/dev-server-wait.d.ts +60 -0
- package/dist/setup/dev-server-wait.js +65 -0
- package/dist/setup/drive-agent.d.ts +71 -0
- package/dist/setup/drive-agent.js +171 -0
- package/dist/setup/drive-plan.d.ts +68 -0
- package/dist/setup/drive-plan.js +130 -0
- package/dist/setup/ensure-daemon.d.ts +29 -0
- package/dist/setup/ensure-daemon.js +58 -0
- package/dist/setup/init-runtime.d.ts +47 -0
- package/dist/setup/init-runtime.js +160 -0
- package/dist/setup/interrupt.d.ts +25 -0
- package/dist/setup/interrupt.js +40 -0
- package/dist/setup/license-key.d.ts +39 -0
- package/dist/setup/license-key.js +72 -0
- package/dist/setup/listeners.d.ts +37 -0
- package/dist/setup/listeners.js +85 -0
- package/dist/setup/node-effects.d.ts +60 -0
- package/dist/setup/node-effects.js +210 -0
- package/dist/setup/page-probe.d.ts +39 -0
- package/dist/setup/page-probe.js +49 -0
- package/dist/setup/relaunch.d.ts +36 -0
- package/dist/setup/relaunch.js +57 -0
- package/dist/setup/remaining-steps.d.ts +19 -0
- package/dist/setup/remaining-steps.js +31 -0
- package/dist/setup/run-setup.d.ts +99 -0
- package/dist/setup/run-setup.js +187 -0
- package/dist/setup/session-pick.d.ts +47 -0
- package/dist/setup/session-pick.js +51 -0
- package/dist/setup/setup-command.d.ts +45 -0
- package/dist/setup/setup-command.js +199 -0
- package/dist/setup/setup-options.d.ts +74 -0
- package/dist/setup/setup-options.js +67 -0
- package/dist/setup/transcripts.d.ts +16 -0
- package/dist/setup/transcripts.js +43 -0
- package/dist/status-payload.d.ts +14 -1
- package/dist/status-payload.js +23 -1
- package/dist/telemetry/automation-hint.d.ts +2 -1
- package/dist/telemetry/daemon-telemetry.d.ts +8 -1
- package/dist/telemetry/daemon-telemetry.js +52 -5
- package/dist/telemetry/error-class.js +1 -1
- package/dist/telemetry/feedback-context.d.ts +9 -1
- package/dist/telemetry/feedback-context.js +60 -29
- package/dist/telemetry/feedback-local.d.ts +15 -0
- package/dist/telemetry/feedback-local.js +56 -0
- package/dist/telemetry/feedback-outbox.d.ts +2 -1
- package/dist/telemetry/feedback.d.ts +11 -0
- package/dist/telemetry/feedback.js +19 -6
- package/dist/telemetry/init-telemetry.d.ts +2 -0
- package/dist/telemetry/init-telemetry.js +2 -0
- package/dist/telemetry/install-source.d.ts +14 -0
- package/dist/telemetry/install-source.js +30 -0
- package/dist/telemetry/license-activation.d.ts +7 -1
- package/dist/telemetry/license-activation.js +9 -5
- package/dist/telemetry/mcp-connection.js +7 -0
- package/dist/telemetry/project-profile.d.ts +12 -9
- package/dist/telemetry/project-profile.js +12 -7
- package/dist/telemetry/proxy-telemetry.d.ts +3 -0
- package/dist/telemetry/proxy-telemetry.js +21 -0
- package/dist/telemetry/run-telemetry.d.ts +0 -2
- package/dist/telemetry/run-telemetry.js +1 -1
- package/dist/telemetry/session-metrics.d.ts +12 -0
- package/dist/telemetry/session-metrics.js +35 -1
- package/dist/telemetry/telemetry.js +17 -2
- package/dist/telemetry/tool-refused.d.ts +3 -1
- package/dist/telemetry/tool-refused.js +34 -2
- package/dist/telemetry/verification-of.js +2 -2
- package/dist/tools/act-capsule.d.ts +10 -0
- package/dist/tools/act-capsule.js +8 -2
- package/dist/tools/act-danger.d.ts +2 -1
- package/dist/tools/act-danger.js +11 -3
- package/dist/tools/act-observation.d.ts +41 -0
- package/dist/tools/act-observation.js +36 -0
- package/dist/tools/act-preflight.d.ts +20 -0
- package/dist/tools/act-preflight.js +98 -0
- package/dist/tools/act-sequence-retry.d.ts +45 -0
- package/dist/tools/act-sequence-retry.js +58 -0
- package/dist/tools/act-sequence-tool.d.ts +13 -0
- package/dist/tools/act-sequence-tool.js +135 -0
- package/dist/tools/act-target.d.ts +14 -0
- package/dist/tools/act-target.js +47 -0
- package/dist/tools/act-tools.d.ts +21 -1
- package/dist/tools/act-tools.js +196 -204
- package/dist/tools/assert-source.d.ts +22 -0
- package/dist/tools/assert-source.js +53 -0
- package/dist/tools/assert-verdict.d.ts +20 -3
- package/dist/tools/assert-verdict.js +58 -20
- package/dist/tools/browser-tools.js +6 -22
- package/dist/tools/complete-snapshot.d.ts +48 -0
- package/dist/tools/complete-snapshot.js +75 -0
- package/dist/tools/coverage-tools.d.ts +10 -1
- package/dist/tools/coverage-tools.js +123 -69
- package/dist/tools/dynamic-tools.js +53 -13
- package/dist/tools/error-recovery.d.ts +25 -14
- package/dist/tools/error-recovery.js +78 -9
- package/dist/tools/feedback-tools.d.ts +1 -1
- package/dist/tools/feedback-tools.js +2 -2
- package/dist/tools/invoke-tool.js +121 -11
- package/dist/tools/lease-hint.d.ts +10 -0
- package/dist/tools/lease-hint.js +53 -4
- package/dist/tools/lease-tools.d.ts +35 -5
- package/dist/tools/lease-tools.js +191 -13
- package/dist/tools/merged-name-redirect.d.ts +13 -0
- package/dist/tools/merged-name-redirect.js +22 -0
- package/dist/tools/navigate-arrival.d.ts +0 -2
- package/dist/tools/navigate-arrival.js +1 -1
- package/dist/tools/numeric-bounds.d.ts +100 -0
- package/dist/tools/numeric-bounds.js +125 -0
- package/dist/tools/observe-tools.js +109 -65
- package/dist/tools/read-tools.js +31 -8
- package/dist/tools/real-input-attempt.d.ts +32 -0
- package/dist/tools/real-input-attempt.js +212 -2
- package/dist/tools/reconcile-tools.js +22 -8
- package/dist/tools/resolve-target.d.ts +0 -7
- package/dist/tools/sdk-marker-probe.d.ts +29 -0
- package/dist/tools/sdk-marker-probe.js +57 -0
- package/dist/tools/snapshot-delta.d.ts +1 -1
- package/dist/tools/tool-kit.d.ts +24 -0
- package/dist/tools/tool-kit.js +21 -0
- package/dist/tools/tool-names.d.ts +5 -0
- package/dist/tools/tool-names.js +5 -0
- package/dist/tools/tool-surface.d.ts +85 -6
- package/dist/tools/tool-surface.js +122 -2
- package/dist/tools/tools-helpers.d.ts +19 -0
- package/dist/tools/tools-helpers.js +46 -0
- package/dist/tools/tools.d.ts +2 -0
- package/dist/tools/tools.js +78 -12
- package/dist/tools/unadvertised-help.js +4 -4
- package/dist/tools/verify-next-baton.d.ts +53 -0
- package/dist/tools/verify-next-baton.js +52 -0
- package/dist/update/reticle-deps.d.ts +9 -0
- package/dist/update/reticle-deps.js +27 -0
- package/dist/update/sdk-sync.d.ts +1 -2
- package/dist/update/sdk-sync.js +1 -21
- package/dist/update/update-nudge.d.ts +25 -10
- package/dist/update/update-nudge.js +32 -3
- package/dist/update/updater.d.ts +1 -1
- package/dist/version/peer-announce.js +4 -2
- package/dist/version/sdk-fix.d.ts +47 -0
- package/dist/version/sdk-fix.js +127 -0
- package/dist/version/version-nudge.d.ts +14 -0
- package/dist/version/version-nudge.js +21 -0
- package/dist/version/version-skew.d.ts +38 -3
- package/dist/version/version-skew.js +83 -7
- package/dist/visual/visual-store.d.ts +6 -6
- package/dist/visual/visual-store.js +18 -16
- package/dist/visual/visual-tools.js +57 -12
- package/docs/README.md +2 -1
- package/docs/cli/cloud.mdx +1 -1
- package/docs/cli/doctor.mdx +2 -0
- package/docs/cli/serve.mdx +4 -2
- package/docs/cli/verify.mdx +4 -5
- package/docs/cli.mdx +7 -3
- package/docs/desktop.mdx +1 -1
- package/docs/docs.json +2 -2
- package/docs/gates.md +1 -1
- package/docs/getting-started.md +19 -3
- package/docs/index.mdx +9 -6
- package/docs/install-agentic.mdx +38 -12
- package/docs/install-manual.mdx +1 -1
- package/docs/packages/browser.mdx +2 -2
- package/docs/packages/server.mdx +1 -1
- package/docs/packages.mdx +8 -6
- package/docs/platform-integration.md +17 -0
- package/docs/predicates.mdx +8 -0
- package/docs/reticle-manual.md +106 -0
- package/docs/skill-file.mdx +3 -3
- package/docs/telemetry-contract.md +108 -4
- package/docs/tools-act-sequence.mdx +16 -0
- package/docs/tools-tools-and-run.mdx +2 -4
- package/docs/troubleshooting.mdx +9 -0
- package/docs/usage.md +4 -3
- package/package.json +4 -3
- package/dist/init/cursor.d.ts +0 -24
- package/dist/init/cursor.js +0 -80
- package/dist/session/session-readiness.d.ts +0 -34
- package/dist/session/session-readiness.js +0 -36
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Find this repo's key, newest slot generation first.
|
|
3
|
+
*
|
|
4
|
+
* A hit in an older slot is not proof the key is ours — only that nothing better was filed. The
|
|
5
|
+
* caller is expected to establish the tenant before trusting what comes back.
|
|
6
|
+
*/
|
|
7
|
+
export declare const findCredential: (raw: unknown, projectId: string, url: string, orgId: string | undefined) => string | undefined;
|
|
8
|
+
export interface CredentialWrite {
|
|
9
|
+
projectId: string;
|
|
10
|
+
url: string;
|
|
11
|
+
key: string;
|
|
12
|
+
/** The tenant this key belongs to; undefined against a cloud that cannot name one. */
|
|
13
|
+
orgId: string | undefined;
|
|
14
|
+
/** The key this slot held before, if any — used to tell our own stale key from somebody else's. */
|
|
15
|
+
priorKey: string | undefined;
|
|
16
|
+
/** Whether that prior key was PROVED to belong to a different organisation. */
|
|
17
|
+
priorIsForeign: boolean;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Apply a link's key to the store, in place, and return it.
|
|
21
|
+
*
|
|
22
|
+
* The org slot is always ours to write. The two ambiguous slots are a compatibility shim for older
|
|
23
|
+
* daemons, and a shim must not corrupt what it is shimming:
|
|
24
|
+
*
|
|
25
|
+
* - Overwriting a slot that holds a DIFFERENT live key would hand this key to whichever repo that
|
|
26
|
+
* entry belongs to — the same cross-tenant disclosure as reusing theirs, pointing the other way.
|
|
27
|
+
* - An EMPTY ambiguous slot is a trap too, because the resolver prefers cloud+project over the
|
|
28
|
+
* bare legacy entry: filling a free `<url>::default` while another tenant's key sits in
|
|
29
|
+
* `default` silently redirects THEIR repo to our key.
|
|
30
|
+
*
|
|
31
|
+
* So once another tenant is known to be on this machine, only the org slot is written. Our own
|
|
32
|
+
* stale or revoked key is not another tenant and is replaced normally — that is the ordinary
|
|
33
|
+
* re-link, and refusing it would strand the repo.
|
|
34
|
+
*/
|
|
35
|
+
export declare const applyCredential: (store: Record<string, unknown>, write: CredentialWrite) => Record<string, unknown>;
|
|
36
|
+
/** Where the store lives, given the reticle home directory. */
|
|
37
|
+
export declare const credentialsPath: (homeDir: string, file: string) => string;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The machine's credential store: which key belongs to which repo, and who is allowed to write it.
|
|
3
|
+
*
|
|
4
|
+
* Split out of `cloud-cli` because both halves — the lookup and the write — encode the same rule
|
|
5
|
+
* from opposite ends, and reading one without the other is how the rule got broken twice. The store
|
|
6
|
+
* is `~/.reticle/credentials.json`, a flat map from slot to key.
|
|
7
|
+
*
|
|
8
|
+
* There are three generations of slot, and the order matters:
|
|
9
|
+
* `<url>::org::<orgId>::<projectId>` the only unambiguous one
|
|
10
|
+
* `<url>::<projectId>` tells two clouds apart, but not two tenants
|
|
11
|
+
* `<projectId>` tells nothing apart; the original shape
|
|
12
|
+
*
|
|
13
|
+
* Each generation exists because the one before it was found to collide in the field, so the older
|
|
14
|
+
* two are kept readable (a repo linked by an older CLI must not lose its key) and written only when
|
|
15
|
+
* doing so cannot mislead somebody else.
|
|
16
|
+
*/
|
|
17
|
+
import { join } from 'node:path';
|
|
18
|
+
import { credentialSlot } from '../cloud/cloud-config.js';
|
|
19
|
+
/** Trailing slashes are not identity: `https://x/` and `https://x` are one cloud. */
|
|
20
|
+
const normalizeUrl = (url) => url.replace(/\/+$/, '');
|
|
21
|
+
/** The key held in a slot, whether it is a bare string or the stamped `{ key, url }` shape. */
|
|
22
|
+
const heldKeyOf = (held) => {
|
|
23
|
+
if ('string' === typeof held)
|
|
24
|
+
return held.length > 0 ? held : undefined;
|
|
25
|
+
if ('object' !== typeof held || null === held)
|
|
26
|
+
return undefined;
|
|
27
|
+
const key = held['key'];
|
|
28
|
+
return 'string' === typeof key && key.length > 0 ? key : undefined;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Find this repo's key, newest slot generation first.
|
|
32
|
+
*
|
|
33
|
+
* A hit in an older slot is not proof the key is ours — only that nothing better was filed. The
|
|
34
|
+
* caller is expected to establish the tenant before trusting what comes back.
|
|
35
|
+
*/
|
|
36
|
+
export const findCredential = (raw, projectId, url, orgId) => {
|
|
37
|
+
if ('object' !== typeof raw || null === raw)
|
|
38
|
+
return undefined;
|
|
39
|
+
const store = raw;
|
|
40
|
+
const orgScoped = orgId === undefined ? undefined : store[credentialSlot(url, projectId, orgId)];
|
|
41
|
+
if ('string' === typeof orgScoped && orgScoped.length > 0)
|
|
42
|
+
return orgScoped;
|
|
43
|
+
const composite = store[credentialSlot(url, projectId)];
|
|
44
|
+
if ('string' === typeof composite && composite.length > 0)
|
|
45
|
+
return composite;
|
|
46
|
+
const found = store[projectId];
|
|
47
|
+
if ('string' === typeof found)
|
|
48
|
+
return found.length > 0 ? found : undefined;
|
|
49
|
+
if ('object' !== typeof found || null === found)
|
|
50
|
+
return undefined;
|
|
51
|
+
const record = found;
|
|
52
|
+
const key = heldKeyOf(record);
|
|
53
|
+
const forUrl = record['url'];
|
|
54
|
+
if (key === undefined)
|
|
55
|
+
return undefined;
|
|
56
|
+
// A credential stamped with a DIFFERENT cloud is not this project's, however much the ids match.
|
|
57
|
+
if ('string' === typeof forUrl && normalizeUrl(forUrl) !== normalizeUrl(url))
|
|
58
|
+
return undefined;
|
|
59
|
+
return key;
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* Apply a link's key to the store, in place, and return it.
|
|
63
|
+
*
|
|
64
|
+
* The org slot is always ours to write. The two ambiguous slots are a compatibility shim for older
|
|
65
|
+
* daemons, and a shim must not corrupt what it is shimming:
|
|
66
|
+
*
|
|
67
|
+
* - Overwriting a slot that holds a DIFFERENT live key would hand this key to whichever repo that
|
|
68
|
+
* entry belongs to — the same cross-tenant disclosure as reusing theirs, pointing the other way.
|
|
69
|
+
* - An EMPTY ambiguous slot is a trap too, because the resolver prefers cloud+project over the
|
|
70
|
+
* bare legacy entry: filling a free `<url>::default` while another tenant's key sits in
|
|
71
|
+
* `default` silently redirects THEIR repo to our key.
|
|
72
|
+
*
|
|
73
|
+
* So once another tenant is known to be on this machine, only the org slot is written. Our own
|
|
74
|
+
* stale or revoked key is not another tenant and is replaced normally — that is the ordinary
|
|
75
|
+
* re-link, and refusing it would strand the repo.
|
|
76
|
+
*/
|
|
77
|
+
export const applyCredential = (store, write) => {
|
|
78
|
+
const { projectId, url, key, orgId, priorKey, priorIsForeign } = write;
|
|
79
|
+
if (orgId !== undefined)
|
|
80
|
+
store[credentialSlot(url, projectId, orgId)] = key;
|
|
81
|
+
const mayOverwrite = (slot) => {
|
|
82
|
+
const held = heldKeyOf(store[slot]);
|
|
83
|
+
if (held === undefined)
|
|
84
|
+
return true; // free
|
|
85
|
+
if (held === key)
|
|
86
|
+
return true; // already ours
|
|
87
|
+
return held === priorKey && !priorIsForeign; // our own stale key, not somebody else's
|
|
88
|
+
};
|
|
89
|
+
const cloudSlot = credentialSlot(url, projectId);
|
|
90
|
+
if (!priorIsForeign && mayOverwrite(cloudSlot))
|
|
91
|
+
store[cloudSlot] = key;
|
|
92
|
+
if (!priorIsForeign && mayOverwrite(projectId))
|
|
93
|
+
store[projectId] = { key, url };
|
|
94
|
+
return store;
|
|
95
|
+
};
|
|
96
|
+
/** Where the store lives, given the reticle home directory. */
|
|
97
|
+
export const credentialsPath = (homeDir, file) => join(homeDir, file);
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type Session } from './cloud-session.js';
|
|
2
|
+
/** Where `reticle login` dials when nothing says otherwise: the hosted service. */
|
|
3
|
+
export declare const DEFAULT_URL = "https://app.reticle.sh";
|
|
4
|
+
export declare const RETICLE_DIR = ".reticle";
|
|
5
|
+
export declare const SESSION_FILE = "session.json";
|
|
6
|
+
export declare const CREDENTIALS_FILE = "credentials.json";
|
|
7
|
+
/** The user-level reticle directory, `~/.reticle`. Sessions and credentials live here, not in a repo. */
|
|
8
|
+
export declare const home: () => string;
|
|
9
|
+
export declare const err: (msg: string) => void;
|
|
10
|
+
/** A next-step nudge on stderr (humans read it; agents parse stdout JSON and ignore this). */
|
|
11
|
+
export declare const hint: (msg: string) => void;
|
|
12
|
+
export declare const emit: (obj: unknown) => void;
|
|
13
|
+
/** Read + parse a JSON file, or null when missing/malformed (never throws). */
|
|
14
|
+
export declare const readJson: (path: string) => Promise<unknown>;
|
|
15
|
+
/** Parse `--flag value` pairs out of an argv tail. */
|
|
16
|
+
export declare const flags: (argv: readonly string[]) => Record<string, string>;
|
|
17
|
+
/** The ACTIVE session — the last host logged in to. What a bare command resolves its URL through. */
|
|
18
|
+
export declare const readSession: () => Promise<Session | null>;
|
|
19
|
+
/**
|
|
20
|
+
* The session for ONE host, or null.
|
|
21
|
+
*
|
|
22
|
+
* The whole safety property, holding by construction rather than by a guard somebody has to
|
|
23
|
+
* remember: a token is looked up BY the host it will be sent to, so no arrangement of environment
|
|
24
|
+
* variables can fetch one host's credential for a request to another. Falls back to `session.json`
|
|
25
|
+
* when it names this host, so a machine that logged in before per-host sessions existed is not
|
|
26
|
+
* silently signed out by an upgrade.
|
|
27
|
+
*/
|
|
28
|
+
export declare const readSessionFor: (url: string) => Promise<Session | null>;
|
|
29
|
+
export declare const baseUrl: (session: {
|
|
30
|
+
url: string;
|
|
31
|
+
} | null, explicit?: string) => string;
|
|
32
|
+
/** Bearer for a command: an explicit api key (agent) wins, else the human login token. */
|
|
33
|
+
export declare const bearer: (session: {
|
|
34
|
+
token: string;
|
|
35
|
+
} | null) => string | null;
|
|
36
|
+
/** One `/v1` call. Throws a friendly Error on a non-2xx so the command surfaces it and exits 1. */
|
|
37
|
+
export declare const api: (method: string, url: string, token: string | null, body?: unknown) => Promise<unknown>;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The layer every cloud verb sits on: where files live, how output is written, and how a `/v1` call
|
|
3
|
+
* is made and authorised.
|
|
4
|
+
*
|
|
5
|
+
* Extracted because it was the reason `cloud-cli` could not be split. Each verb is small and mostly
|
|
6
|
+
* independent, but all of them reach for the same dozen helpers, so any attempt to lift one command
|
|
7
|
+
* out dragged the plumbing with it or duplicated it. Naming the layer makes the verbs separable —
|
|
8
|
+
* `cloud-login` is the first to move — and it is a real seam rather than a file-size dodge: nothing
|
|
9
|
+
* here knows what a project, a credential or a link is.
|
|
10
|
+
*/
|
|
11
|
+
import { readFile } from 'node:fs/promises';
|
|
12
|
+
import { homedir } from 'node:os';
|
|
13
|
+
import { join } from 'node:path';
|
|
14
|
+
import { z } from 'zod';
|
|
15
|
+
import { cloudFetch } from '../cloud/cloud-sync.js';
|
|
16
|
+
import { normalizeUrl, readSessionFrom, resolveSessionFor, sessionPath, } from './cloud-session.js';
|
|
17
|
+
/** Where `reticle login` dials when nothing says otherwise: the hosted service. */
|
|
18
|
+
export const DEFAULT_URL = 'https://app.reticle.sh';
|
|
19
|
+
export const RETICLE_DIR = '.reticle';
|
|
20
|
+
export const SESSION_FILE = 'session.json';
|
|
21
|
+
export const CREDENTIALS_FILE = 'credentials.json';
|
|
22
|
+
/** The user-level reticle directory, `~/.reticle`. Sessions and credentials live here, not in a repo. */
|
|
23
|
+
export const home = () => join(homedir(), RETICLE_DIR);
|
|
24
|
+
export const err = (msg) => {
|
|
25
|
+
process.stderr.write(`reticle: ${msg}\n`);
|
|
26
|
+
};
|
|
27
|
+
/** A next-step nudge on stderr (humans read it; agents parse stdout JSON and ignore this). */
|
|
28
|
+
export const hint = (msg) => {
|
|
29
|
+
process.stderr.write(`→ ${msg}\n`);
|
|
30
|
+
};
|
|
31
|
+
export const emit = (obj) => {
|
|
32
|
+
process.stdout.write(`${JSON.stringify(obj, null, 2)}\n`);
|
|
33
|
+
};
|
|
34
|
+
/** Read + parse a JSON file, or null when missing/malformed (never throws). */
|
|
35
|
+
export const readJson = async (path) => {
|
|
36
|
+
try {
|
|
37
|
+
return JSON.parse(await readFile(path, 'utf8'));
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
/** Parse `--flag value` pairs out of an argv tail. */
|
|
44
|
+
export const flags = (argv) => {
|
|
45
|
+
const f = {};
|
|
46
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
47
|
+
const a = argv[i];
|
|
48
|
+
if (a !== undefined && a.startsWith('--') && i + 1 < argv.length) {
|
|
49
|
+
const v = argv[i + 1];
|
|
50
|
+
if (v !== undefined)
|
|
51
|
+
f[a.slice(2)] = v;
|
|
52
|
+
i += 1;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return f;
|
|
56
|
+
};
|
|
57
|
+
/** The ACTIVE session — the last host logged in to. What a bare command resolves its URL through. */
|
|
58
|
+
export const readSession = async () => readSessionFrom(readJson(join(home(), SESSION_FILE)));
|
|
59
|
+
/**
|
|
60
|
+
* The session for ONE host, or null.
|
|
61
|
+
*
|
|
62
|
+
* The whole safety property, holding by construction rather than by a guard somebody has to
|
|
63
|
+
* remember: a token is looked up BY the host it will be sent to, so no arrangement of environment
|
|
64
|
+
* variables can fetch one host's credential for a request to another. Falls back to `session.json`
|
|
65
|
+
* when it names this host, so a machine that logged in before per-host sessions existed is not
|
|
66
|
+
* silently signed out by an upgrade.
|
|
67
|
+
*/
|
|
68
|
+
export const readSessionFor = async (url) => resolveSessionFor(url, await readSessionFrom(readJson(sessionPath(home(), url))), await readSession());
|
|
69
|
+
export const baseUrl = (session, explicit) => {
|
|
70
|
+
// `--url` wins over the environment: it is typed for THIS command, it is visible in shell history,
|
|
71
|
+
// and it cannot leak into a sibling process the way an exported variable does.
|
|
72
|
+
if (explicit !== undefined && explicit.length > 0)
|
|
73
|
+
return normalizeUrl(explicit);
|
|
74
|
+
const env = process.env['RETICLE_CLOUD_URL'];
|
|
75
|
+
if (env !== undefined && env.length > 0)
|
|
76
|
+
return env.replace(/\/+$/, '');
|
|
77
|
+
if (null !== session && session.url.length > 0)
|
|
78
|
+
return session.url;
|
|
79
|
+
// No hint here any more. This used to warn that it was falling back to localhost, which was worth
|
|
80
|
+
// saying because that default was wrong for everyone except us. The default is now the hosted
|
|
81
|
+
// service, so the fallback IS the intended path — and a warning printed on the correct path is
|
|
82
|
+
// how people learn to ignore stderr, which is where the real problems are written.
|
|
83
|
+
return DEFAULT_URL;
|
|
84
|
+
};
|
|
85
|
+
/** Bearer for a command: an explicit api key (agent) wins, else the human login token. */
|
|
86
|
+
export const bearer = (session) => {
|
|
87
|
+
const key = process.env['RETICLE_CLOUD_KEY'];
|
|
88
|
+
if (key !== undefined && key.length > 0)
|
|
89
|
+
return key;
|
|
90
|
+
return session?.token ?? null;
|
|
91
|
+
};
|
|
92
|
+
/** One `/v1` call. Throws a friendly Error on a non-2xx so the command surfaces it and exits 1. */
|
|
93
|
+
export const api = async (method, url, token, body) => {
|
|
94
|
+
const headers = {};
|
|
95
|
+
if (token !== null)
|
|
96
|
+
headers['authorization'] = `Bearer ${token}`;
|
|
97
|
+
if (body !== undefined)
|
|
98
|
+
headers['content-type'] = 'application/json';
|
|
99
|
+
const init = {
|
|
100
|
+
method,
|
|
101
|
+
headers,
|
|
102
|
+
};
|
|
103
|
+
if (body !== undefined)
|
|
104
|
+
init.body = JSON.stringify(body);
|
|
105
|
+
const res = await cloudFetch(url, init);
|
|
106
|
+
const text = await res.text();
|
|
107
|
+
let json = null;
|
|
108
|
+
if (text.length > 0) {
|
|
109
|
+
try {
|
|
110
|
+
json = JSON.parse(text);
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
throw new Error(`expected JSON from ${method} ${url} but got: ${text.slice(0, 120)}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (!res.ok) {
|
|
117
|
+
const parsed = z.object({ error: z.object({ message: z.string() }) }).safeParse(json);
|
|
118
|
+
throw new Error(parsed.success ? parsed.data.error.message : `${res.status} ${res.statusText}`);
|
|
119
|
+
}
|
|
120
|
+
return json;
|
|
121
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** Run `reticle link` with these arguments. Injected; see the note at the top of this file. */
|
|
2
|
+
export type Linker = (argv: readonly string[]) => Promise<number>;
|
|
3
|
+
/**
|
|
4
|
+
* `reticle login` — browser device flow by default; `--email <e>` (or a positional email) keeps the
|
|
5
|
+
* headless two-step code path for CI/servers where opening a browser makes no sense.
|
|
6
|
+
*/
|
|
7
|
+
export declare const cmdLogin: (argv: readonly string[], link: Linker) => Promise<number>;
|
|
8
|
+
/** `reticle logout` — forget the cached session token (per-project keys under credentials.json stay). */
|
|
9
|
+
/**
|
|
10
|
+
* `reticle logout` — sign out of ONE host, not of everywhere.
|
|
11
|
+
*
|
|
12
|
+
* Which host is the same question every other verb asks: `--url`, else the environment, else the
|
|
13
|
+
* active session. Signing out of staging must leave production alone — a logout that quietly
|
|
14
|
+
* cleared every environment would be discovered at the worst possible moment, mid-incident, on the
|
|
15
|
+
* one you did not mean.
|
|
16
|
+
*/
|
|
17
|
+
export declare const cmdLogout: (argv?: readonly string[]) => Promise<number>;
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `reticle login` and `reticle logout` — getting a session onto this machine, and off it.
|
|
3
|
+
*
|
|
4
|
+
* Split from `cloud-cli` because this is a coherent story on its own: prove who you are, write the
|
|
5
|
+
* token where the other verbs will find it, and finish the job by binding the repo you are standing
|
|
6
|
+
* in. The rest of the CLI reads sessions; only this file creates and destroys them.
|
|
7
|
+
*
|
|
8
|
+
* `cmdLink` arrives as a PARAMETER rather than an import. Login ends by linking and link needs a
|
|
9
|
+
* session, so the two genuinely depend on each other; passing the linker in breaks the cycle at the
|
|
10
|
+
* one call site instead of merging two commands into one file to satisfy the module graph.
|
|
11
|
+
*/
|
|
12
|
+
import { mkdir, rm, writeFile } from 'node:fs/promises';
|
|
13
|
+
import { join } from 'node:path';
|
|
14
|
+
import { z } from 'zod';
|
|
15
|
+
import { setTimeout as sleep } from 'node:timers/promises';
|
|
16
|
+
import { createNodeFileSystem } from '../project/fs-port.js';
|
|
17
|
+
import { CLOUD_LINK_FILE } from '../cloud/cloud-config.js';
|
|
18
|
+
import { RETICLE_CONFIG_BASENAME } from './cli-port.js';
|
|
19
|
+
import { DevicePollSchema, DeviceStartSchema, openBrowser } from './device-flow.js';
|
|
20
|
+
import { normalizeUrl, sessionPath, SESSIONS_DIR } from './cloud-session.js';
|
|
21
|
+
import { api, baseUrl, emit, err, flags, hint, home, readSession, RETICLE_DIR, SESSION_FILE, } from './cloud-kit.js';
|
|
22
|
+
const LoginSchema = z.object({
|
|
23
|
+
token: z.string(),
|
|
24
|
+
org: z.object({ id: z.string().optional(), name: z.string() }),
|
|
25
|
+
});
|
|
26
|
+
/**
|
|
27
|
+
* `reticle login --email <e> [--org <name>] [--code <123456>]` — sign in, cache the token under
|
|
28
|
+
* ~/.reticle.
|
|
29
|
+
*
|
|
30
|
+
* TWO STEPS, because the cloud proves you own the inbox before it hands out a session: ask for a code,
|
|
31
|
+
* then exchange it. (It used to take an email alone — which meant anyone who knew your address owned your
|
|
32
|
+
* org.) `--org` is only consulted when the account is brand new; a returning user never needs it.
|
|
33
|
+
*
|
|
34
|
+
* Without `--code` we request one and stop, telling the user to re-run with it. The one exception is a
|
|
35
|
+
* LOCAL cloud, whose dev mailer cannot actually deliver mail and so echoes the code back in its response
|
|
36
|
+
* (`devCode`) — there we complete the login in a single command rather than asking a developer to read a
|
|
37
|
+
* code out of a server log they may not even be tailing.
|
|
38
|
+
*/
|
|
39
|
+
const RequestCodeSchema = z.object({ devCode: z.string().optional() });
|
|
40
|
+
/** Persist a session token under ~/.reticle and print the next step. Shared by both login paths. */
|
|
41
|
+
const writeSession = async (url, token, orgName, orgId, project, link) => {
|
|
42
|
+
await mkdir(join(home(), SESSIONS_DIR), { recursive: true });
|
|
43
|
+
const body = `${JSON.stringify({ url: normalizeUrl(url), token, orgName, ...(orgId === undefined ? {} : { orgId }) }, null, 2)}\n`;
|
|
44
|
+
// Both, on purpose. The per-host file is what lets another environment stay logged in; the active
|
|
45
|
+
// file is what a bare command with no override resolves through, and keeping it means nothing
|
|
46
|
+
// about the single-environment workflow changes.
|
|
47
|
+
await writeFile(sessionPath(home(), url), body);
|
|
48
|
+
await writeFile(join(home(), SESSION_FILE), body);
|
|
49
|
+
emit({ loggedIn: orgName, session: join(home(), SESSION_FILE) });
|
|
50
|
+
await linkAfterLogin(url, project, link);
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Finish the job when the shell is already standing in an unlinked Reticle project.
|
|
54
|
+
*
|
|
55
|
+
* `login` used to end by printing "next: `reticle link`", which made getting from local-only to
|
|
56
|
+
* reporting a TWO command trip whose second half nothing enforces — and the HUD's own invitation
|
|
57
|
+
* names one command, so the gap was ours to close rather than the reader's to notice. Somebody who
|
|
58
|
+
* ran `reticle login` inside their instrumented repo wanted their runs on the dashboard; there is no
|
|
59
|
+
* second thing they could have meant.
|
|
60
|
+
*
|
|
61
|
+
* Deliberately narrow, because a login that writes files in a directory the user did not mean is
|
|
62
|
+
* worse than an extra step:
|
|
63
|
+
* - a `.reticle.json` must already exist. That is what `init` writes and what puts a projectId in
|
|
64
|
+
* the app's BUNDLE, which is the only thing letting the daemon attribute runs to this repo. A
|
|
65
|
+
* bare `.reticle/` directory is NOT the marker: gating on it produced repos that were linked and
|
|
66
|
+
* structurally unable to report, whose runs pooled under the daemon's own root instead.
|
|
67
|
+
* - an existing `cloud.json` is left completely alone. Re-linking would re-resolve the project and
|
|
68
|
+
* is exactly how somebody re-pointing an environment loses a binding they meant to keep.
|
|
69
|
+
* - a failure is reported and swallowed. The login SUCCEEDED and its token is already on disk;
|
|
70
|
+
* turning that into a non-zero exit would make the recoverable half look like the broken one.
|
|
71
|
+
*/
|
|
72
|
+
const linkAfterLogin = async (url, project, link) => {
|
|
73
|
+
const fs = createNodeFileSystem();
|
|
74
|
+
/*
|
|
75
|
+
* The marker is `.reticle.json`, NOT a `.reticle/` directory.
|
|
76
|
+
*
|
|
77
|
+
* `.reticle.json` is what `init` writes and what puts a projectId in the app's bundle, which is
|
|
78
|
+
* the only thing that lets the daemon attribute a session's runs to this repo. A bare `.reticle/`
|
|
79
|
+
* proves nothing — it is also created by artifacts and by older builds — and auto-linking on it
|
|
80
|
+
* produced a repo that was LINKED to a cloud project and yet unable to report to it: runs pooled
|
|
81
|
+
* into the daemon's own ledger, and `reticle sync` here answered "nothing to send" straight after
|
|
82
|
+
* two verdicts had been recorded. Measured, on this very repo's bench app.
|
|
83
|
+
*/
|
|
84
|
+
if (!(await fs.exists(join(process.cwd(), RETICLE_CONFIG_BASENAME)))) {
|
|
85
|
+
hint('next: `reticle init` here first, then `reticle link` — or log in from a project directory');
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
/*
|
|
89
|
+
* An explicit `--project` is an instruction, so it re-binds even a repo that is already linked.
|
|
90
|
+
* Without a project named, an existing binding is left completely alone — re-resolving it is how
|
|
91
|
+
* somebody repointing an environment loses a binding they meant to keep.
|
|
92
|
+
*/
|
|
93
|
+
if (project === undefined &&
|
|
94
|
+
(await fs.exists(join(process.cwd(), RETICLE_DIR, CLOUD_LINK_FILE)))) {
|
|
95
|
+
hint('this repo is already linked — `reticle push` to send what it has recorded');
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
try {
|
|
99
|
+
await link(project === undefined ? ['--url', url] : ['--url', url, '--project', project]);
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
hint('signed in, but linking this repo failed — run `reticle link` to retry');
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
/**
|
|
106
|
+
* Browser device flow — the DEFAULT `reticle login` (like `gh auth login`): fetch a device + user code,
|
|
107
|
+
* open the browser to approve, then poll until the user confirms. No email to type, no code to copy back.
|
|
108
|
+
*/
|
|
109
|
+
const cmdLoginDevice = async (explicitUrl, project, link) => {
|
|
110
|
+
const url = baseUrl(null, explicitUrl);
|
|
111
|
+
const started = DeviceStartSchema.parse(await api('POST', `${url}/v1/auth/device/start`, null, {}));
|
|
112
|
+
hint(`Opening ${started.verificationUri} — confirm this code in the browser: ${started.userCode}`);
|
|
113
|
+
openBrowser(started.verificationUriComplete);
|
|
114
|
+
const intervalMs = Math.max(1, started.interval) * 1000;
|
|
115
|
+
for (;;) {
|
|
116
|
+
await sleep(intervalMs);
|
|
117
|
+
const poll = DevicePollSchema.parse(await api('POST', `${url}/v1/auth/device/token`, null, { deviceCode: started.deviceCode }));
|
|
118
|
+
if ('approved' === poll.status && poll.token !== undefined && poll.org !== undefined) {
|
|
119
|
+
await writeSession(url, poll.token, poll.org.name, poll.org.id, project, link);
|
|
120
|
+
return 0;
|
|
121
|
+
}
|
|
122
|
+
if ('pending' === poll.status) {
|
|
123
|
+
if (Date.now() > started.expiresAt) {
|
|
124
|
+
err('device login expired — run `reticle login` again');
|
|
125
|
+
return 1;
|
|
126
|
+
}
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
err('denied' === poll.status
|
|
130
|
+
? 'device login was denied in the browser'
|
|
131
|
+
: 'device login expired — run `reticle login` again');
|
|
132
|
+
return 1;
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
/**
|
|
136
|
+
* `reticle login` — browser device flow by default; `--email <e>` (or a positional email) keeps the
|
|
137
|
+
* headless two-step code path for CI/servers where opening a browser makes no sense.
|
|
138
|
+
*/
|
|
139
|
+
export const cmdLogin = async (argv, link) => {
|
|
140
|
+
const f = flags(argv);
|
|
141
|
+
const positional = argv[0] !== undefined && !argv[0].startsWith('--') ? argv[0] : undefined;
|
|
142
|
+
const email = f['email'] ?? positional;
|
|
143
|
+
if (email === undefined)
|
|
144
|
+
return cmdLoginDevice(f['url'], f['project'], link);
|
|
145
|
+
const org = f['org'];
|
|
146
|
+
const url = baseUrl(null, f['url']);
|
|
147
|
+
let code = f['code'];
|
|
148
|
+
if (code === undefined) {
|
|
149
|
+
const requested = RequestCodeSchema.parse(await api('POST', `${url}/v1/auth/request-code`, null, {
|
|
150
|
+
email,
|
|
151
|
+
...(org !== undefined ? { orgName: org } : {}),
|
|
152
|
+
}));
|
|
153
|
+
// A real cloud mails the code and never echoes it; a local one cannot mail, so it hands it back.
|
|
154
|
+
if (requested.devCode === undefined) {
|
|
155
|
+
emit({ codeSent: true, to: email });
|
|
156
|
+
hint(`check your inbox, then: \`reticle login --email ${email} --code <the 6-digit code>\``);
|
|
157
|
+
return 0;
|
|
158
|
+
}
|
|
159
|
+
code = requested.devCode;
|
|
160
|
+
}
|
|
161
|
+
const parsed = LoginSchema.parse(await api('POST', `${url}/v1/auth/login`, null, { email, code }));
|
|
162
|
+
await writeSession(url, parsed.token, parsed.org.name, parsed.org.id, f['project'], link);
|
|
163
|
+
return 0;
|
|
164
|
+
};
|
|
165
|
+
/** `reticle logout` — forget the cached session token (per-project keys under credentials.json stay). */
|
|
166
|
+
/**
|
|
167
|
+
* `reticle logout` — sign out of ONE host, not of everywhere.
|
|
168
|
+
*
|
|
169
|
+
* Which host is the same question every other verb asks: `--url`, else the environment, else the
|
|
170
|
+
* active session. Signing out of staging must leave production alone — a logout that quietly
|
|
171
|
+
* cleared every environment would be discovered at the worst possible moment, mid-incident, on the
|
|
172
|
+
* one you did not mean.
|
|
173
|
+
*/
|
|
174
|
+
export const cmdLogout = async (argv = []) => {
|
|
175
|
+
const active = await readSession();
|
|
176
|
+
const url = baseUrl(active, flags(argv)['url']);
|
|
177
|
+
await rm(sessionPath(home(), url), { force: true }).catch(() => undefined);
|
|
178
|
+
// The active pointer only moves if it was pointing at the host just signed out of.
|
|
179
|
+
if (null !== active && normalizeUrl(active.url) === url)
|
|
180
|
+
await writeFile(join(home(), SESSION_FILE), '').catch(() => undefined);
|
|
181
|
+
emit({ loggedOut: true, url });
|
|
182
|
+
return 0;
|
|
183
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The signed-in session on this machine: where it is stored, and how it is read back.
|
|
3
|
+
*
|
|
4
|
+
* Split out of `cloud-cli` for the file-size cap, but the unit is real — every rule about WHICH
|
|
5
|
+
* credential may be sent to WHICH host is decided here, and the safety of that holds by
|
|
6
|
+
* construction rather than by a guard somebody has to remember at each call site.
|
|
7
|
+
*/
|
|
8
|
+
import { z } from 'zod';
|
|
9
|
+
/** Where per-host sessions live under the reticle home directory. */
|
|
10
|
+
export declare const SESSIONS_DIR = "sessions";
|
|
11
|
+
export declare const SessionSchema: z.ZodObject<{
|
|
12
|
+
url: z.ZodString;
|
|
13
|
+
token: z.ZodString;
|
|
14
|
+
orgName: z.ZodString;
|
|
15
|
+
/**
|
|
16
|
+
* WHICH tenant this session signed into. Optional because every session file written before
|
|
17
|
+
* org-scoped credentials lacks it; the link path treats "unknown" as "cannot prove a stored key
|
|
18
|
+
* is ours" and mints, which is the safe direction.
|
|
19
|
+
*/
|
|
20
|
+
orgId: z.ZodOptional<z.ZodString>;
|
|
21
|
+
}, "strip", z.ZodTypeAny, {
|
|
22
|
+
url: string;
|
|
23
|
+
token: string;
|
|
24
|
+
orgName: string;
|
|
25
|
+
orgId?: string | undefined;
|
|
26
|
+
}, {
|
|
27
|
+
url: string;
|
|
28
|
+
token: string;
|
|
29
|
+
orgName: string;
|
|
30
|
+
orgId?: string | undefined;
|
|
31
|
+
}>;
|
|
32
|
+
export type Session = z.infer<typeof SessionSchema>;
|
|
33
|
+
/** Trailing slashes are not identity: `https://x/` and `https://x` are one host. */
|
|
34
|
+
export declare const normalizeUrl: (url: string) => string;
|
|
35
|
+
export declare const sessionPath: (homeDir: string, url: string) => string;
|
|
36
|
+
/** The ACTIVE session — the last host logged in to. What a bare command resolves its URL through. */
|
|
37
|
+
export declare const readSessionFrom: (raw: Promise<unknown>) => Promise<Session | null>;
|
|
38
|
+
/**
|
|
39
|
+
* The session for ONE host, or null.
|
|
40
|
+
*
|
|
41
|
+
* This is the whole safety property, and it holds by construction rather than by a guard somebody
|
|
42
|
+
* has to remember: a token is looked up BY the host it will be sent to, so there is no arrangement
|
|
43
|
+
* of environment variables that fetches one host's credential for a request to another.
|
|
44
|
+
*
|
|
45
|
+
* Falls back to `session.json` when it names this host, so a machine that logged in before per-host
|
|
46
|
+
* sessions existed keeps working and is not silently signed out by an upgrade.
|
|
47
|
+
*/
|
|
48
|
+
export declare const resolveSessionFor: (url: string, perHost: Session | null, active: Session | null) => Session | null;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The signed-in session on this machine: where it is stored, and how it is read back.
|
|
3
|
+
*
|
|
4
|
+
* Split out of `cloud-cli` for the file-size cap, but the unit is real — every rule about WHICH
|
|
5
|
+
* credential may be sent to WHICH host is decided here, and the safety of that holds by
|
|
6
|
+
* construction rather than by a guard somebody has to remember at each call site.
|
|
7
|
+
*/
|
|
8
|
+
import { z } from 'zod';
|
|
9
|
+
import { join } from 'node:path';
|
|
10
|
+
/** Where per-host sessions live under the reticle home directory. */
|
|
11
|
+
export const SESSIONS_DIR = 'sessions';
|
|
12
|
+
export const SessionSchema = z.object({
|
|
13
|
+
url: z.string(),
|
|
14
|
+
token: z.string(),
|
|
15
|
+
orgName: z.string(),
|
|
16
|
+
/**
|
|
17
|
+
* WHICH tenant this session signed into. Optional because every session file written before
|
|
18
|
+
* org-scoped credentials lacks it; the link path treats "unknown" as "cannot prove a stored key
|
|
19
|
+
* is ours" and mints, which is the safe direction.
|
|
20
|
+
*/
|
|
21
|
+
orgId: z.string().optional(),
|
|
22
|
+
});
|
|
23
|
+
/** Trailing slashes are not identity: `https://x/` and `https://x` are one host. */
|
|
24
|
+
export const normalizeUrl = (url) => url.replace(/\/+$/, '');
|
|
25
|
+
/**
|
|
26
|
+
* A filesystem-safe name for one host. `https://app.reticle.sh` → `app.reticle.sh`,
|
|
27
|
+
* `http://localhost:8890` → `localhost_8890`. The scheme is dropped deliberately: nobody runs the
|
|
28
|
+
* same host over both http and https and means two different accounts by it.
|
|
29
|
+
*/
|
|
30
|
+
const hostSlug = (url) => normalizeUrl(url)
|
|
31
|
+
.replace(/^[a-z]+:\/\//i, '')
|
|
32
|
+
.replace(/[^a-zA-Z0-9.-]/g, '_');
|
|
33
|
+
export const sessionPath = (homeDir, url) => join(homeDir, SESSIONS_DIR, `${hostSlug(url)}.json`);
|
|
34
|
+
/** The ACTIVE session — the last host logged in to. What a bare command resolves its URL through. */
|
|
35
|
+
export const readSessionFrom = async (raw) => {
|
|
36
|
+
const parsed = SessionSchema.safeParse(await raw);
|
|
37
|
+
return parsed.success ? parsed.data : null;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* The session for ONE host, or null.
|
|
41
|
+
*
|
|
42
|
+
* This is the whole safety property, and it holds by construction rather than by a guard somebody
|
|
43
|
+
* has to remember: a token is looked up BY the host it will be sent to, so there is no arrangement
|
|
44
|
+
* of environment variables that fetches one host's credential for a request to another.
|
|
45
|
+
*
|
|
46
|
+
* Falls back to `session.json` when it names this host, so a machine that logged in before per-host
|
|
47
|
+
* sessions existed keeps working and is not silently signed out by an upgrade.
|
|
48
|
+
*/
|
|
49
|
+
export const resolveSessionFor = (url, perHost, active) => {
|
|
50
|
+
if (perHost !== null)
|
|
51
|
+
return perHost;
|
|
52
|
+
return null !== active && normalizeUrl(active.url) === normalizeUrl(url) ? active : null;
|
|
53
|
+
};
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* silently is how an agent ends up with a confident verdict about an app it never touched. Callers
|
|
20
20
|
* are handed the list and must name it.
|
|
21
21
|
*/
|
|
22
|
-
|
|
22
|
+
interface FoundConfig {
|
|
23
23
|
/** Absolute path to the `.reticle.json`. */
|
|
24
24
|
path: string;
|
|
25
25
|
/** The directory it configures — what a reader needs in order to act. */
|
|
@@ -33,3 +33,4 @@ export interface ConfigDiscovery {
|
|
|
33
33
|
searched: string[];
|
|
34
34
|
}
|
|
35
35
|
export declare function discoverProjectConfigs(cwd: string): ConfigDiscovery;
|
|
36
|
+
export {};
|
|
@@ -147,6 +147,7 @@ export function discoverProjectConfigs(cwd) {
|
|
|
147
147
|
catch {
|
|
148
148
|
continue; // A declared workspace directory that does not exist is not an error here.
|
|
149
149
|
}
|
|
150
|
+
check(base);
|
|
150
151
|
for (const entry of entries) {
|
|
151
152
|
const candidate = join(base, entry);
|
|
152
153
|
try {
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turn the parsed `_daemon` / `serve` flags into the options `startDaemon` takes.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from `handleDaemonInner` to be testable, because the thing it got wrong was invisible
|
|
5
|
+
* from outside: `headless` used to be spread INSIDE the `driveUrl !== undefined` branch, so
|
|
6
|
+
* `reticle serve --headed` — no `--drive` — parsed the flag correctly and then dropped it on the
|
|
7
|
+
* floor. `startDaemon` fell back to `headless ?? true` and the pool launched hidden, which reads to
|
|
8
|
+
* a user as "--headed does nothing" and cost a real session of being told a run was visible when
|
|
9
|
+
* four chrome-headless-shell processes said otherwise.
|
|
10
|
+
*
|
|
11
|
+
* `headless` and `driveUrl` are independent: the flag governs the shared browser POOL (leases,
|
|
12
|
+
* replay, the spec runner), which exists whether or not a drive URL was named.
|
|
13
|
+
*/
|
|
14
|
+
import type { StartOptions } from '../index.js';
|
|
15
|
+
export type DaemonFlags = {
|
|
16
|
+
port: number;
|
|
17
|
+
driveUrl?: string;
|
|
18
|
+
headless: boolean;
|
|
19
|
+
http: boolean;
|
|
20
|
+
httpPort?: number;
|
|
21
|
+
httpToken?: string;
|
|
22
|
+
};
|
|
23
|
+
export declare function daemonStartOptions(parsed: DaemonFlags): StartOptions;
|
|
24
|
+
/**
|
|
25
|
+
* The argv a parent hands its detached `_daemon` child.
|
|
26
|
+
*
|
|
27
|
+
* `serve` and `mcp` each built this list by hand and each made the SAME mistake as the options
|
|
28
|
+
* builder above — pushing `--headed` only inside the `driveUrl` branch — so the flag survived
|
|
29
|
+
* parsing, was dropped at the spawn, and the child launched a hidden pool. Three sites, one wrong
|
|
30
|
+
* idea: that headless belongs to the drive URL. It belongs to the pool, which exists either way.
|
|
31
|
+
*/
|
|
32
|
+
export declare function daemonSpawnArgs(parsed: DaemonFlags): string[];
|