@reticlehq/server 2.12.0 → 2.13.1
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 +67 -109
- 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 -1
- package/dist/bridge/bridge.js +247 -164
- 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 +6 -6
- package/dist/capsule/causal-summary.js +7 -2
- package/dist/cli/cli-doctor.js +31 -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 +15 -1
- package/dist/cli/cli-parse.js +212 -8
- package/dist/cli/cli-verify.d.ts +1 -0
- package/dist/cli/cli-verify.js +3 -2
- package/dist/cli/cloud-cli.js +428 -259
- 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.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 +9 -0
- package/dist/cli/doctor-rows.js +9 -0
- 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/mcp-command.js +51 -20
- package/dist/cli/project-name.d.ts +30 -0
- package/dist/cli/project-name.js +44 -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 +134 -80
- package/dist/cloud/cloud-config.d.ts +22 -1
- package/dist/cloud/cloud-config.js +64 -5
- package/dist/cloud/cloud-sync.d.ts +5 -5
- package/dist/cloud/cloud-sync.js +33 -2
- 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 +3 -0
- package/dist/crawl/crawl.js +5 -0
- package/dist/daemon/daemon-resolve.d.ts +43 -0
- package/dist/daemon/daemon-resolve.js +69 -0
- package/dist/daemon/dev-servers.d.ts +15 -0
- package/dist/daemon/dev-servers.js +68 -0
- package/dist/daemon/wake-decision.d.ts +32 -0
- package/dist/daemon/wake-decision.js +38 -0
- package/dist/domain/domain-tools.js +2 -2
- package/dist/events/contradictions.d.ts +18 -0
- package/dist/events/contradictions.js +134 -21
- package/dist/events/declared.d.ts +7 -4
- package/dist/events/declared.js +15 -6
- package/dist/events/echo-mismatch.js +64 -14
- package/dist/events/event-filters.d.ts +8 -0
- package/dist/events/event-filters.js +19 -2
- package/dist/events/predicate-eval.js +53 -5
- 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.d.ts +5 -0
- package/dist/events/predicate.js +37 -5
- package/dist/events/split-text-miss.d.ts +30 -0
- package/dist/events/split-text-miss.js +44 -0
- package/dist/flows/flake.d.ts +6 -6
- 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 +239 -22
- 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-tools.js +58 -32
- package/dist/flows/flows.d.ts +8 -0
- package/dist/flows/flows.js +468 -2
- 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 +30 -12
- 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/verify-change-tools.js +5 -2
- package/dist/honesty/blind-spots.d.ts +55 -2
- package/dist/honesty/blind-spots.js +63 -3
- package/dist/honesty/honesty.d.ts +24 -0
- package/dist/honesty/honesty.js +25 -1
- package/dist/honesty/instrumentation-gaps.d.ts +9 -0
- package/dist/honesty/instrumentation-gaps.js +29 -2
- package/dist/honesty/verified.d.ts +2 -1
- package/dist/honesty/verified.js +16 -1
- package/dist/http-server.js +12 -0
- package/dist/impact/impact-recorder.d.ts +31 -8
- package/dist/impact/impact-recorder.js +104 -21
- package/dist/impact/impact-store.d.ts +15 -1
- package/dist/impact/impact-store.js +43 -2
- package/dist/index.js +113 -7
- package/dist/init/agent-rules.d.ts +13 -6
- package/dist/init/agent-rules.js +23 -12
- package/dist/init/astro-patch.d.ts +16 -0
- package/dist/init/astro-patch.js +28 -0
- package/dist/init/capabilities.d.ts +4 -1
- package/dist/init/capabilities.js +22 -2
- package/dist/init/confirm.d.ts +31 -7
- package/dist/init/confirm.js +48 -8
- package/dist/init/cra.d.ts +20 -4
- package/dist/init/cra.js +57 -17
- 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 +3 -1
- package/dist/init/dev-script.js +8 -5
- 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 +1 -1
- package/dist/init/mcp.js +1 -1
- package/dist/init/node-io.js +11 -1
- package/dist/init/plan-framework.js +81 -45
- package/dist/init/plan.d.ts +20 -7
- package/dist/init/plan.js +20 -45
- package/dist/init/preflight.d.ts +28 -0
- package/dist/init/preflight.js +38 -0
- package/dist/init/run.d.ts +32 -9
- package/dist/init/run.js +141 -119
- package/dist/init/slash-command.d.ts +1 -1
- package/dist/init/snippets.js +41 -25
- 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.js +25 -5
- package/dist/input/scroll-tools.js +4 -6
- package/dist/input/viewport-tools.js +5 -4
- package/dist/intent/inline-intent.d.ts +10 -19
- package/dist/intent/inline-intent.js +91 -1
- 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 +12 -0
- package/dist/intent/intent-store.js +21 -0
- package/dist/intent/intent-subject.d.ts +45 -0
- package/dist/intent/intent-subject.js +177 -0
- package/dist/intent/intent-tools.js +65 -2
- package/dist/intent/open-intents.d.ts +21 -0
- package/dist/intent/open-intents.js +15 -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 +18 -2
- package/dist/mcp/attach-memory.js +50 -7
- 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 -1
- package/dist/mcp/mcp-proxy.js +52 -62
- package/dist/mcp/mcp.js +7 -1
- 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/tool-catalog-cache.d.ts +8 -0
- package/dist/mcp/tool-catalog-cache.js +12 -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/propose-consequences.js +12 -3
- package/dist/pool/browser-pool.d.ts +63 -4
- package/dist/pool/browser-pool.js +113 -4
- package/dist/pool/playwright-launcher.js +9 -0
- package/dist/project/project-tools.js +3 -3
- package/dist/project/reticle-dir.d.ts +15 -2
- package/dist/project/reticle-dir.js +21 -4
- 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 +22 -1
- 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/lease-visibility.d.ts +67 -0
- package/dist/session/lease-visibility.js +76 -0
- package/dist/session/no-session-diagnosis.d.ts +23 -0
- package/dist/session/no-session-diagnosis.js +58 -17
- package/dist/session/no-session-next-action.d.ts +7 -0
- package/dist/session/no-session-next-action.js +42 -3
- package/dist/session/no-session-watch.d.ts +10 -6
- package/dist/session/no-session-watch.js +51 -19
- 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-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 +61 -3
- 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.js +36 -1
- package/dist/session/session-tools.js +2 -2
- package/dist/session/session.d.ts +22 -2
- package/dist/session/session.js +87 -7
- 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 +68 -0
- package/dist/setup/dev-server-wait.js +67 -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 +203 -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/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.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 -1
- package/dist/telemetry/project-profile.js +11 -6
- package/dist/telemetry/proxy-telemetry.d.ts +3 -0
- package/dist/telemetry/proxy-telemetry.js +21 -0
- 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/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 +100 -213
- package/dist/tools/assert-verdict.js +22 -16
- package/dist/tools/browser-tools.js +6 -2
- package/dist/tools/dynamic-tools.js +24 -5
- package/dist/tools/error-recovery.d.ts +1 -0
- package/dist/tools/error-recovery.js +8 -0
- package/dist/tools/invoke-tool.js +59 -9
- package/dist/tools/lease-tools.d.ts +30 -4
- package/dist/tools/lease-tools.js +169 -5
- package/dist/tools/merged-name-redirect.d.ts +13 -0
- package/dist/tools/merged-name-redirect.js +22 -0
- package/dist/tools/numeric-bounds.d.ts +100 -0
- package/dist/tools/numeric-bounds.js +125 -0
- package/dist/tools/observe-tools.js +47 -40
- package/dist/tools/read-tools.js +28 -7
- package/dist/tools/real-input-attempt.d.ts +10 -0
- package/dist/tools/real-input-attempt.js +59 -1
- package/dist/tools/reconcile-tools.js +4 -4
- package/dist/tools/tool-kit.d.ts +12 -0
- package/dist/tools/tool-kit.js +6 -1
- package/dist/tools/tool-names.d.ts +2 -0
- package/dist/tools/tool-names.js +2 -0
- 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 +56 -8
- 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 +18 -0
- package/dist/update/update-nudge.js +20 -0
- 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/cli/doctor.mdx +1 -0
- package/docs/cli/serve.mdx +4 -2
- package/docs/cli.mdx +7 -3
- package/docs/desktop.mdx +1 -1
- package/docs/docs.json +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.mdx +1 -1
- package/docs/skill-file.mdx +3 -3
- package/docs/telemetry-contract.md +108 -4
- package/docs/tools-act-sequence.mdx +16 -0
- package/docs/usage.md +1 -1
- package/package.json +4 -3
- package/dist/init/cursor.d.ts +0 -24
- package/dist/init/cursor.js +0 -80
|
@@ -31,21 +31,21 @@ declare const CapsuleSchema: z.ZodObject<{
|
|
|
31
31
|
/** Minimal steps to reproduce (prefix-trimmed). */
|
|
32
32
|
steps: z.ZodArray<z.ZodType<FlowStep, z.ZodTypeDef, FlowStep>, "many">;
|
|
33
33
|
}, "strip", z.ZodTypeAny, {
|
|
34
|
+
id: string;
|
|
35
|
+
version: 1;
|
|
34
36
|
expected: string;
|
|
35
37
|
origin: string;
|
|
36
|
-
version: 1;
|
|
37
|
-
id: string;
|
|
38
|
-
observed: string;
|
|
39
38
|
steps: FlowStep[];
|
|
39
|
+
observed: string;
|
|
40
40
|
createdAt: number;
|
|
41
41
|
flow?: string | undefined;
|
|
42
42
|
}, {
|
|
43
|
+
id: string;
|
|
44
|
+
version: 1;
|
|
43
45
|
expected: string;
|
|
44
46
|
origin: string;
|
|
45
|
-
version: 1;
|
|
46
|
-
id: string;
|
|
47
|
-
observed: string;
|
|
48
47
|
steps: FlowStep[];
|
|
48
|
+
observed: string;
|
|
49
49
|
createdAt: number;
|
|
50
50
|
flow?: string | undefined;
|
|
51
51
|
}>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { EventType, PerfMetric, isDevToolingUrl } from '@reticlehq/core';
|
|
2
|
+
import { routePathOf } from '../events/predicate-route.js';
|
|
2
3
|
/**
|
|
3
4
|
* THE INVARIANT: every field of this summary has a bounded size that does not depend on how much the
|
|
4
5
|
* app did. A verdict travels in the same payload as its evidence, so an unbounded evidence field is
|
|
@@ -119,8 +120,12 @@ export function causalSummary(events, context = {}) {
|
|
|
119
120
|
break;
|
|
120
121
|
}
|
|
121
122
|
case EventType.ROUTE_CHANGE:
|
|
122
|
-
|
|
123
|
-
|
|
123
|
+
// The ROUTER's path. Reporting the document pathname left `route: "/"` on every action of a
|
|
124
|
+
// hash-routed app — the same reading that made a route predicate unsatisfiable there, and an
|
|
125
|
+
// agent reasoning off this field would conclude the route never changed. See routePathOf.
|
|
126
|
+
if ('string' === typeof data['pathname']) {
|
|
127
|
+
route = routePathOf(data['pathname'], 'string' === typeof data['hash'] ? data['hash'] : '');
|
|
128
|
+
}
|
|
124
129
|
break;
|
|
125
130
|
case EventType.SIGNAL:
|
|
126
131
|
pushUnique(signals, data['name']);
|
package/dist/cli/cli-doctor.js
CHANGED
|
@@ -17,7 +17,10 @@ import { diagnoseDesktop, isDesktopProject } from '../init/desktop-doctor.js';
|
|
|
17
17
|
import { diagnoseWebCsp } from '../init/csp-doctor.js';
|
|
18
18
|
import { RETICLE_CONFIG_BASENAME, diagnosePortMismatch, readProjectId, readProjectPort, } from './cli-port.js';
|
|
19
19
|
import { DoctorRow, doctorRow } from './doctor-rows.js';
|
|
20
|
+
import { attachState, describeAttachState } from '../mcp/attach-memory.js';
|
|
20
21
|
import { findOccupiedSiblings, siblingListenerNote } from './sibling-ports.js';
|
|
22
|
+
import { daemonsServingProjectElsewhere, resolveDaemonForProject, splitBrainNote, wrongDaemonNote, } from '../daemon/daemon-resolve.js';
|
|
23
|
+
import { isAlive } from '../daemon/daemon.js';
|
|
21
24
|
/**
|
|
22
25
|
* `reticle doctor` — collapse the ~6 independent first-run failure modes into one command. Checks the
|
|
23
26
|
* Chromium install (the #1 silent failure), whether a daemon is up on the resolved bridge port, and
|
|
@@ -41,6 +44,18 @@ function asIdentity(payload) {
|
|
|
41
44
|
...(contract === undefined ? {} : { contract }),
|
|
42
45
|
};
|
|
43
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* One project, two daemons — asked from whichever end this command is standing on.
|
|
49
|
+
*
|
|
50
|
+
* Shares the rule with `reticle status` rather than restating it: the empty daemon can see that the
|
|
51
|
+
* app connected somewhere else, the daemon holding the app can only see that this project owns
|
|
52
|
+
* another one, and a second copy of either half would be a second opinion waiting to disagree.
|
|
53
|
+
*/
|
|
54
|
+
function splitBrainLine(port, projectId) {
|
|
55
|
+
const home = reticleStateHome();
|
|
56
|
+
const elsewhere = splitBrainNote(port, daemonsServingProjectElsewhere(projectId, port, home, isAlive, (other) => hasProjectConnectedBefore(home, other, projectId)));
|
|
57
|
+
return elsewhere ?? wrongDaemonNote(port, resolveDaemonForProject(projectId, home, isAlive));
|
|
58
|
+
}
|
|
44
59
|
export async function handleDoctor(port) {
|
|
45
60
|
const line = (s) => {
|
|
46
61
|
process.stdout.write(`${s}\n`);
|
|
@@ -92,6 +107,22 @@ export async function handleDoctor(port) {
|
|
|
92
107
|
const mismatch = diagnosePortMismatch(port, projectPort);
|
|
93
108
|
if (mismatch !== undefined)
|
|
94
109
|
line(doctorRow(DoctorRow.PORT_CHECK, `✗ ${mismatch}`));
|
|
110
|
+
const projectId = readProjectId(process.cwd());
|
|
111
|
+
// The hop between the agent and this daemon, stated as its own row rather than inferred from the
|
|
112
|
+
// ones above it. `sessions` says the BROWSER reached the daemon; nothing said whether the AGENT
|
|
113
|
+
// did, and those two are exactly the pair a user cannot tell apart when one of them is broken.
|
|
114
|
+
const attach = attachState(reticleStateHome(), port);
|
|
115
|
+
const attachAction = describeAttachState(attach);
|
|
116
|
+
// A SPLIT outranks the attach record, and this is the one place it can. Both records are per PORT,
|
|
117
|
+
// so on a machine whose daemons have split in two the attach row reports "nothing has ever
|
|
118
|
+
// attached here" and sends the reader to re-register a server that is registered and running —
|
|
119
|
+
// the confident wrong answer for a link that is not missing but misaimed.
|
|
120
|
+
const split = splitBrainLine(port, projectId);
|
|
121
|
+
line(doctorRow(DoctorRow.AGENT_LINK, split !== undefined
|
|
122
|
+
? `✗ ${split}`
|
|
123
|
+
: attachAction === undefined
|
|
124
|
+
? "✓ an MCP client has listed and called Reticle's tools on this port"
|
|
125
|
+
: `✗ ${attachAction}`));
|
|
95
126
|
// Remaining half of #261. The SDK dialling a port we are not on is invisible as a refused inbound,
|
|
96
127
|
// but a listener on a well-known Reticle port is visible. Report the observation; do not conclude
|
|
97
128
|
// it is the daemon this app wants — somebody else's daemon looks the same from here.
|
|
@@ -102,7 +133,6 @@ export async function handleDoctor(port) {
|
|
|
102
133
|
// The check this checklist was missing: is the APP wired, not just the tools. Everything above can
|
|
103
134
|
// be green in a project that has never been through `init`, and that combination is precisely the
|
|
104
135
|
// one `doctor` gets run to explain.
|
|
105
|
-
const projectId = readProjectId(process.cwd());
|
|
106
136
|
line(projectWiringLine({
|
|
107
137
|
projectId,
|
|
108
138
|
previouslyConnected: hasProjectConnectedBefore(reticleStateHome(), port, projectId),
|
package/dist/cli/cli-launch.d.ts
CHANGED
|
@@ -10,6 +10,8 @@ interface StatusSession {
|
|
|
10
10
|
throttled: boolean;
|
|
11
11
|
stale: boolean;
|
|
12
12
|
pendingMarks: number;
|
|
13
|
+
/** The tab is attached and answering nothing — see Session.unresponsive. Absent on an older daemon. */
|
|
14
|
+
unresponsive?: true;
|
|
13
15
|
}
|
|
14
16
|
/**
|
|
15
17
|
* Reduce the daemon's /status JSON to the compact view `reticle status` prints. Pure: narrows the
|
|
@@ -62,8 +64,9 @@ type OpenDecision = {
|
|
|
62
64
|
* is run by a human whose browser this is, and yanking the page they are looking at to somewhere else
|
|
63
65
|
* is a bigger surprise than being told where the tab actually is.
|
|
64
66
|
*/
|
|
65
|
-
export declare function decideOpen(
|
|
67
|
+
export declare function decideOpen(all: {
|
|
66
68
|
url: string;
|
|
69
|
+
unresponsive?: boolean;
|
|
67
70
|
}[], url: string | undefined): OpenDecision;
|
|
68
71
|
/** The OS command that opens a URL in the default browser, per platform. Pure — unit-tested. */
|
|
69
72
|
export declare function openCommand(url: string, platform: NodeJS.Platform): {
|
|
@@ -79,4 +82,11 @@ export declare function openCommand(url: string, platform: NodeJS.Platform): {
|
|
|
79
82
|
* port problem while nothing had ever opened.
|
|
80
83
|
*/
|
|
81
84
|
export declare function openInBrowser(url: string, platform?: NodeJS.Platform, run?: (cmd: string, args: string[]) => Promise<string | null>): Promise<string | null>;
|
|
85
|
+
/**
|
|
86
|
+
* What a finished launcher means, or null when nothing is wrong.
|
|
87
|
+
*
|
|
88
|
+
* Exported for its own tests: the headless case is the one that matters and it cannot be produced
|
|
89
|
+
* by spawning a real launcher on a developer machine.
|
|
90
|
+
*/
|
|
91
|
+
export declare function launcherFailure(code: number | null, signal: NodeJS.Signals | null): string | null;
|
|
82
92
|
export {};
|
package/dist/cli/cli-launch.js
CHANGED
|
@@ -2,10 +2,11 @@ import * as http from 'node:http';
|
|
|
2
2
|
import { NodePlatform } from '../platform.js';
|
|
3
3
|
import { spawn } from 'node:child_process';
|
|
4
4
|
import { isOpaqueOrigin, LOOPBACK_HOST, STATUS_PATH } from '@reticlehq/core';
|
|
5
|
-
import {
|
|
5
|
+
import { daemonFix, describeSkew } from '../version/version-skew.js';
|
|
6
6
|
import { CONTRACT_FINGERPRINT } from '@reticlehq/core';
|
|
7
7
|
import { SERVER_VERSION } from '../version/server-version.js';
|
|
8
8
|
import { log } from '../log.js';
|
|
9
|
+
import { loopbackAgent } from '../loopback-agent.js';
|
|
9
10
|
/**
|
|
10
11
|
* Reduce the daemon's /status JSON to the compact view `reticle status` prints. Pure: narrows the
|
|
11
12
|
* untrusted wire payload (never `any`) and tolerates a missing/partial body so a malformed response
|
|
@@ -33,6 +34,7 @@ export function summarizeStatus(payload) {
|
|
|
33
34
|
throttled: true === r['throttled'],
|
|
34
35
|
stale: true === r['stale'],
|
|
35
36
|
pendingMarks: 'number' === typeof r['pendingMarks'] ? r['pendingMarks'] : 0,
|
|
37
|
+
...(true === r['unresponsive'] ? { unresponsive: true } : {}),
|
|
36
38
|
};
|
|
37
39
|
})
|
|
38
40
|
.filter((s) => s !== null);
|
|
@@ -55,11 +57,13 @@ function statusField(payload, key) {
|
|
|
55
57
|
*/
|
|
56
58
|
export async function warnOnDaemonSkew(port) {
|
|
57
59
|
const status = await fetchStatus(port);
|
|
60
|
+
const daemonVersion = statusField(status, 'version');
|
|
58
61
|
const skew = describeSkew({
|
|
59
62
|
what: 'the daemon already running on this port',
|
|
60
|
-
version:
|
|
63
|
+
version: daemonVersion,
|
|
61
64
|
contract: statusField(status, 'contract'),
|
|
62
|
-
|
|
65
|
+
// The daemon is the peer here and this process is the agent side.
|
|
66
|
+
fix: daemonFix(daemonVersion, SERVER_VERSION),
|
|
63
67
|
}, { version: SERVER_VERSION, contract: CONTRACT_FINGERPRINT });
|
|
64
68
|
if (skew !== undefined)
|
|
65
69
|
log('reticle_daemon_skew', { port, warning: skew });
|
|
@@ -69,7 +73,15 @@ const STATUS_PROBE_TIMEOUT_MS = 1000;
|
|
|
69
73
|
/** GET the daemon's /status JSON. Resolves to the parsed body, or undefined on any failure. */
|
|
70
74
|
export function fetchStatus(port) {
|
|
71
75
|
return new Promise((resolve) => {
|
|
72
|
-
const req = http.get({
|
|
76
|
+
const req = http.get({
|
|
77
|
+
host: LOOPBACK_HOST,
|
|
78
|
+
port,
|
|
79
|
+
path: STATUS_PATH,
|
|
80
|
+
timeout: STATUS_PROBE_TIMEOUT_MS,
|
|
81
|
+
// Shares the proxy's keep-alive agent: the proxy calls this on every reconnect, and a socket
|
|
82
|
+
// per probe is the same churn the POST leg was fixed for.
|
|
83
|
+
agent: loopbackAgent,
|
|
84
|
+
}, (res) => {
|
|
73
85
|
let body = '';
|
|
74
86
|
res.setEncoding('utf8');
|
|
75
87
|
res.on('data', (chunk) => (body += chunk));
|
|
@@ -117,7 +129,11 @@ function sameOrigin(a, b) {
|
|
|
117
129
|
* is run by a human whose browser this is, and yanking the page they are looking at to somewhere else
|
|
118
130
|
* is a bigger surprise than being told where the tab actually is.
|
|
119
131
|
*/
|
|
120
|
-
export function decideOpen(
|
|
132
|
+
export function decideOpen(all, url) {
|
|
133
|
+
// A tab that has stopped answering is not a tab you can be handed. `open` is the command a caller
|
|
134
|
+
// reaches for to RECOVER, and reusing the wedged one left no way out short of killing the daemon:
|
|
135
|
+
// ending the session only flips a flag on the record, it does not make the page answer.
|
|
136
|
+
const sessions = all.filter((s) => true !== s.unresponsive);
|
|
121
137
|
if (url === undefined) {
|
|
122
138
|
const first = sessions[0];
|
|
123
139
|
return first !== undefined ? { action: 'reuse', url: first.url } : { action: 'need-url' };
|
|
@@ -159,16 +175,49 @@ export async function openInBrowser(url, platform = process.platform, run = defa
|
|
|
159
175
|
const { cmd, args } = openCommand(url, platform);
|
|
160
176
|
return run(cmd, args);
|
|
161
177
|
}
|
|
162
|
-
/**
|
|
178
|
+
/**
|
|
179
|
+
* What a finished launcher means, or null when nothing is wrong.
|
|
180
|
+
*
|
|
181
|
+
* Exported for its own tests: the headless case is the one that matters and it cannot be produced
|
|
182
|
+
* by spawning a real launcher on a developer machine.
|
|
183
|
+
*/
|
|
184
|
+
export function launcherFailure(code, signal) {
|
|
185
|
+
if (null !== signal)
|
|
186
|
+
return `the browser launcher was killed by ${signal}`;
|
|
187
|
+
// null code with no signal: it never reported an outcome in the window we waited. Some desktop
|
|
188
|
+
// launchers stay attached rather than handing off, and inventing a failure there would be worse
|
|
189
|
+
// than the silence — it would send someone to fix a browser that opened.
|
|
190
|
+
if (null === code || 0 === code)
|
|
191
|
+
return null;
|
|
192
|
+
return (`the browser launcher exited ${String(code)} — on a machine with no browser (CI, a container, ` +
|
|
193
|
+
'an SSH session, WSL with no host browser) it starts fine and then has nothing to open');
|
|
194
|
+
}
|
|
195
|
+
/** How long to wait for the launcher to hand off before assuming it did. */
|
|
196
|
+
const LAUNCHER_EXIT_MS = 2_000;
|
|
197
|
+
/** null when the launcher opened something; the failure message when it did not. */
|
|
163
198
|
function defaultRun(cmd, args) {
|
|
164
199
|
return new Promise((resolve) => {
|
|
165
200
|
const child = spawn(cmd, args, { detached: true, stdio: 'ignore' });
|
|
201
|
+
let settled = false;
|
|
202
|
+
const finish = (value) => {
|
|
203
|
+
if (settled)
|
|
204
|
+
return;
|
|
205
|
+
settled = true;
|
|
206
|
+
resolve(value);
|
|
207
|
+
};
|
|
166
208
|
// An unhandled 'error' on a ChildProcess throws — and ENOENT on the launcher is exactly the case
|
|
167
209
|
// this function exists to report, so it must be listened for either way.
|
|
168
|
-
child.on('error', (err) =>
|
|
169
|
-
|
|
210
|
+
child.on('error', (err) => finish(err.message));
|
|
211
|
+
// The EXIT, not the spawn. Answering "did the command begin" reported success on every headless
|
|
212
|
+
// box, where the launcher begins perfectly well and then finds nothing to open — see
|
|
213
|
+
// launcherFailure. Every launcher we use hands off and exits immediately, so this costs nothing
|
|
214
|
+
// on the path where it works.
|
|
215
|
+
child.on('exit', (code, signal) => finish(launcherFailure(code, signal)));
|
|
216
|
+
// ...and a launcher that stays attached must not hold up the install.
|
|
217
|
+
const timer = setTimeout(() => {
|
|
170
218
|
child.unref();
|
|
171
|
-
|
|
172
|
-
});
|
|
219
|
+
finish(null);
|
|
220
|
+
}, LAUNCHER_EXIT_MS);
|
|
221
|
+
timer.unref?.();
|
|
173
222
|
});
|
|
174
223
|
}
|
package/dist/cli/cli-parse.d.ts
CHANGED
|
@@ -13,7 +13,7 @@ export { AGENT_FLAG, BUG_FLAG, FEEDBACK_KINDS, KIND_FLAG, RATING_FLAG, } from '.
|
|
|
13
13
|
* telling readers to do exactly that on 110 lines before it was caught, so the invocation now leads
|
|
14
14
|
* the usage block rather than being a footnote somewhere else.
|
|
15
15
|
*/
|
|
16
|
-
export declare const CLI_USAGE = "usage: npx @reticlehq/server <command> (or `reticle <command>` once the bin is on your PATH)\n\n reticle init [--dry-run] [--port N] [--no-mcp] [--no-install] [--app <dir>] (wire Reticle into the project in this directory)\n --app picks WHICH app in a monorepo, when several are found\n --no-mcp skips MORE than the server registration: also the agent rule files\n (CLAUDE.md / AGENTS.md / .cursor) and the /reticle command, because all three\n only make sense once the tools are reachable.\n reticle serve [--port N] [--drive <url>] [--headless] [--http] [--http-port N] [--http-token T]\n reticle stop [--port N] [--quiet] (stop the daemon we started, by its recorded pid)\n reticle kill [--port N] [--force] (free the port by its LISTENER, never the agent's mcp proxy)\n reticle restart [--port N] [--force] (kill, then start a daemon and wait for a real bind)\n reticle status [--port N]\n reticle doctor [--port N] (one command to diagnose setup: Chromium, daemon, port)\n reticle open [url] [--port N] (show the app: reuse the connected tab, else open one)\n reticle verify <url> [--port N] [--headed] [--timeout N] [--storage-state <file>] (one-shot: drive the URL, verify saved flows, exit 0=pass)\n reticle affected [--since <ref>] [file...] (which saved flows must re-verify for the changed files)\n reticle gate [--since <ref>] [file...] (exit non-zero unless passing artifacts cover the affected flows)\n reticle watch [url] (on save, report which saved flows must re-verify)\n reticle drive <url> [--headless] (foreground mode, for debugging)\n reticle mcp [--port N] [--drive <url>] [--headless] (MCP stdio proxy; auto-starts daemon if needed)\n reticle update (install the latest server version and restart)\n reticle rollback (restore the previous server version and restart)\n reticle license (show enterprise license status: active | eval | missing)\n reticle telemetry [status|enable|disable] (anonymous usage metrics; status shows what's sent + the policy)\n reticle feedback [--rating 1-5] [--bug] \"message\" (tell us what worked and what didn't; prints exactly what it sends)\n reticle feedback --agent --kind <bug|gap|ambiguity|feature_request|improvement|experience> \"message\"\n (agents: file from anywhere, including a setup that never finished)\n reticle identify --context company|side_project|open_source|learning [--company N] [--email E] [--forget]\n (OPT-IN: tell us who you are, e.g. for support or an enterprise trial)\n\nCloud (link this project to Reticle
|
|
16
|
+
export declare const CLI_USAGE = "usage: npx @reticlehq/server <command> (or `reticle <command>` once the bin is on your PATH)\n\n reticle init [--dry-run] [--port N] [--no-mcp] [--no-install] [--app <dir>]\n [--flow \"<what to drive>\"] [--env KEY=VALUE]... [--files-only] (wire Reticle into the project in this directory)\n --app picks WHICH app in a monorepo, when several are found\n --flow names the journey worth proving, in your own words. Only you know\n which one matters, and naming it is the difference between a drive that\n finishes and one that spends its budget looking for something to do\n --env is what the app needs to reach a usable state: the key from\n .env.example, the mock backend, the variable that skips an auth wall.\n Repeatable, and a value may contain spaces and equals signs\n --files-only writes the files and stops, which is what init did before it\n learned to boot the app and prove the install works\n --license writes the key to .env and keeps .env out of git\n --json puts the result on stdout, so an agent reads one object\n --no-drive / --no-open / --no-agents / --url / --timeout / --drive-model\n are the runtime dials: CI, a headless box, or an app you already run\n --no-mcp skips MORE than the server registration: also the agent rule files\n (CLAUDE.md / AGENTS.md / .cursor) and the /reticle command, because all three\n only make sense once the tools are reachable.\n reticle serve [--port N] [--drive <url>] [--headless] [--http] [--http-port N] [--http-token T]\n reticle stop [--port N] [--quiet] (stop the daemon we started, by its recorded pid)\n reticle kill [--port N] [--force] (free the port by its LISTENER, never the agent's mcp proxy)\n reticle restart [--port N] [--force] (kill, then start a daemon and wait for a real bind)\n reticle status [--port N]\n reticle doctor [--port N] (one command to diagnose setup: Chromium, daemon, port)\n reticle open [url] [--port N] (show the app: reuse the connected tab, else open one)\n reticle verify <url> [--port N] [--headed] [--timeout N] [--storage-state <file>] [--session-id <id>] (one-shot: drive the URL, verify saved flows, exit 0=pass)\n reticle affected [--since <ref>] [file...] (which saved flows must re-verify for the changed files)\n reticle gate [--since <ref>] [file...] (exit non-zero unless passing artifacts cover the affected flows)\n reticle watch [url] (on save, report which saved flows must re-verify)\n reticle drive <url> [--headless] (foreground mode, for debugging)\n reticle mcp [--port N] [--drive <url>] [--headless] (MCP stdio proxy; auto-starts daemon if needed)\n reticle update (install the latest server version and restart)\n reticle rollback (restore the previous server version and restart)\n reticle license (show enterprise license status: active | eval | missing)\n reticle telemetry [status|enable|disable] (anonymous usage metrics; status shows what's sent + the policy)\n reticle feedback [--rating 1-5] [--bug] \"message\" (tell us what worked and what didn't; prints exactly what it sends)\n reticle feedback --agent --kind <bug|gap|ambiguity|feature_request|improvement|experience> \"message\"\n (agents: file from anywhere, including a setup that never finished)\n reticle identify --context company|side_project|open_source|learning [--company N] [--email E] [--forget]\n (OPT-IN: tell us who you are, e.g. for support or an enterprise trial)\n\nCloud (link this project to Reticle; runs/flows recorded on the dashboard):\n reticle login [--url <u>] [--email <e>] [--code <c>] [--org <n>]\n (browser device flow by default; --email mails a code)\n reticle logout [--url <u>] (sign out of ONE host; others stay signed in)\n reticle link [--project <name|id>] (bind this repo: mints a scoped key, writes .reticle/cloud.json)\n reticle whoami (who am I signed in as, and is this repo attached?)\n reticle project <ls|create <name>> (list or create cloud projects)\n reticle config [--runs on|off] [--memory on|off] [--flows on|off] [--verify local|server]\n reticle push | sync [--watch] (one sync cycle: send the difference, collect decisions)\n reticle runs | regression | share <runId> (read cloud state; regression exits 3 if any flow broke)\n\n'drive' shows the browser; everything else is hidden (serve/mcp own the pool behind leases, replay\nand the spec runner: batch work). --headed opts any of them in, --headless hides drive, CI hides it.";
|
|
17
17
|
/** The `reticle telemetry` sub-actions. Bare `reticle telemetry` means `status`. */
|
|
18
18
|
export declare const TelemetryAction: {
|
|
19
19
|
readonly STATUS: "status";
|
|
@@ -47,6 +47,19 @@ export type CliResult = {
|
|
|
47
47
|
dryRun: boolean;
|
|
48
48
|
install: boolean;
|
|
49
49
|
app: string | undefined;
|
|
50
|
+
flow: string | undefined;
|
|
51
|
+
env: string[];
|
|
52
|
+
filesOnly: boolean;
|
|
53
|
+
captureBodies: boolean;
|
|
54
|
+
json: boolean;
|
|
55
|
+
drive: boolean;
|
|
56
|
+
relaunch: boolean;
|
|
57
|
+
open: boolean;
|
|
58
|
+
agents: boolean;
|
|
59
|
+
url: string | undefined;
|
|
60
|
+
timeoutSeconds: number | undefined;
|
|
61
|
+
driveModel: string | undefined;
|
|
62
|
+
licenseKey: string | undefined;
|
|
50
63
|
} | {
|
|
51
64
|
kind: 'serve';
|
|
52
65
|
port: number;
|
|
@@ -114,6 +127,7 @@ export type CliResult = {
|
|
|
114
127
|
port: number;
|
|
115
128
|
timeoutMs?: number;
|
|
116
129
|
storageState?: string;
|
|
130
|
+
sessionId?: string;
|
|
117
131
|
} | {
|
|
118
132
|
kind: 'affected';
|
|
119
133
|
files: string[];
|
package/dist/cli/cli-parse.js
CHANGED
|
@@ -17,8 +17,21 @@ export { AGENT_FLAG, BUG_FLAG, FEEDBACK_KINDS, KIND_FLAG, RATING_FLAG, } from '.
|
|
|
17
17
|
*/
|
|
18
18
|
export const CLI_USAGE = `usage: npx @reticlehq/server <command> (or \`reticle <command>\` once the bin is on your PATH)
|
|
19
19
|
|
|
20
|
-
reticle init [--dry-run] [--port N] [--no-mcp] [--no-install] [--app <dir>]
|
|
20
|
+
reticle init [--dry-run] [--port N] [--no-mcp] [--no-install] [--app <dir>]
|
|
21
|
+
[--flow "<what to drive>"] [--env KEY=VALUE]... [--files-only] (wire Reticle into the project in this directory)
|
|
21
22
|
--app picks WHICH app in a monorepo, when several are found
|
|
23
|
+
--flow names the journey worth proving, in your own words. Only you know
|
|
24
|
+
which one matters, and naming it is the difference between a drive that
|
|
25
|
+
finishes and one that spends its budget looking for something to do
|
|
26
|
+
--env is what the app needs to reach a usable state: the key from
|
|
27
|
+
.env.example, the mock backend, the variable that skips an auth wall.
|
|
28
|
+
Repeatable, and a value may contain spaces and equals signs
|
|
29
|
+
--files-only writes the files and stops, which is what init did before it
|
|
30
|
+
learned to boot the app and prove the install works
|
|
31
|
+
--license writes the key to .env and keeps .env out of git
|
|
32
|
+
--json puts the result on stdout, so an agent reads one object
|
|
33
|
+
--no-drive / --no-open / --no-agents / --url / --timeout / --drive-model
|
|
34
|
+
are the runtime dials: CI, a headless box, or an app you already run
|
|
22
35
|
--no-mcp skips MORE than the server registration: also the agent rule files
|
|
23
36
|
(CLAUDE.md / AGENTS.md / .cursor) and the /reticle command, because all three
|
|
24
37
|
only make sense once the tools are reachable.
|
|
@@ -29,7 +42,7 @@ export const CLI_USAGE = `usage: npx @reticlehq/server <command> (or \`reticl
|
|
|
29
42
|
reticle status [--port N]
|
|
30
43
|
reticle doctor [--port N] (one command to diagnose setup: Chromium, daemon, port)
|
|
31
44
|
reticle open [url] [--port N] (show the app: reuse the connected tab, else open one)
|
|
32
|
-
reticle verify <url> [--port N] [--headed] [--timeout N] [--storage-state <file>] (one-shot: drive the URL, verify saved flows, exit 0=pass)
|
|
45
|
+
reticle verify <url> [--port N] [--headed] [--timeout N] [--storage-state <file>] [--session-id <id>] (one-shot: drive the URL, verify saved flows, exit 0=pass)
|
|
33
46
|
reticle affected [--since <ref>] [file...] (which saved flows must re-verify for the changed files)
|
|
34
47
|
reticle gate [--since <ref>] [file...] (exit non-zero unless passing artifacts cover the affected flows)
|
|
35
48
|
reticle watch [url] (on save, report which saved flows must re-verify)
|
|
@@ -45,13 +58,15 @@ export const CLI_USAGE = `usage: npx @reticlehq/server <command> (or \`reticl
|
|
|
45
58
|
reticle identify --context company|side_project|open_source|learning [--company N] [--email E] [--forget]
|
|
46
59
|
(OPT-IN: tell us who you are, e.g. for support or an enterprise trial)
|
|
47
60
|
|
|
48
|
-
Cloud (link this project to Reticle
|
|
49
|
-
reticle login --email <e> [--code <c>] [--org <n>]
|
|
61
|
+
Cloud (link this project to Reticle; runs/flows recorded on the dashboard):
|
|
62
|
+
reticle login [--url <u>] [--email <e>] [--code <c>] [--org <n>]
|
|
63
|
+
(browser device flow by default; --email mails a code)
|
|
64
|
+
reticle logout [--url <u>] (sign out of ONE host; others stay signed in)
|
|
50
65
|
reticle link [--project <name|id>] (bind this repo: mints a scoped key, writes .reticle/cloud.json)
|
|
51
66
|
reticle whoami (who am I signed in as, and is this repo attached?)
|
|
52
67
|
reticle project <ls|create <name>> (list or create cloud projects)
|
|
53
68
|
reticle config [--runs on|off] [--memory on|off] [--flows on|off] [--verify local|server]
|
|
54
|
-
reticle push
|
|
69
|
+
reticle push | sync [--watch] (one sync cycle: send the difference, collect decisions)
|
|
55
70
|
reticle runs | regression | share <runId> (read cloud state; regression exits 3 if any flow broke)
|
|
56
71
|
|
|
57
72
|
'drive' shows the browser; everything else is hidden (serve/mcp own the pool behind leases, replay
|
|
@@ -134,6 +149,8 @@ const KNOWN_COMMANDS = new Set([
|
|
|
134
149
|
'config',
|
|
135
150
|
'push',
|
|
136
151
|
'runs',
|
|
152
|
+
'issues',
|
|
153
|
+
'memory',
|
|
137
154
|
'regression',
|
|
138
155
|
'share',
|
|
139
156
|
'doctor',
|
|
@@ -159,12 +176,64 @@ const DRY_RUN_FLAG = '--dry-run';
|
|
|
159
176
|
const YES_FLAG = '--yes';
|
|
160
177
|
const NO_MCP_FLAG = '--no-mcp';
|
|
161
178
|
const APP_FLAG = '--app';
|
|
179
|
+
/**
|
|
180
|
+
* What only the caller can know, and what it may switch off.
|
|
181
|
+
*
|
|
182
|
+
* `--flow`, `--env` and `--app` are an AGENT's answers: which journey proves the thing the user
|
|
183
|
+
* cares about, what the app needs to reach a usable state, and which app in a monorepo. None is
|
|
184
|
+
* inferable from the repository alone, and a run without them either guesses or stops.
|
|
185
|
+
*
|
|
186
|
+
* `--files-only` is the escape hatch for a caller that wants what init used to do and nothing more.
|
|
187
|
+
*/
|
|
188
|
+
const FLOW_FLAG = '--flow';
|
|
189
|
+
const ENV_FLAG = '--env';
|
|
190
|
+
const FILES_ONLY_FLAG = '--files-only';
|
|
191
|
+
/**
|
|
192
|
+
* Write `captureNetworkBodies: true` into the app's config. OFF unless asked (#705).
|
|
193
|
+
*
|
|
194
|
+
* Body capture is what makes a 2xx write's OUTCOME readable rather than just its transport, so it
|
|
195
|
+
* is worth having — but a body is the one part of a request that routinely carries personal data,
|
|
196
|
+
* and `init` runs unattended. Whoever passes this has decided; nothing decides it for them.
|
|
197
|
+
*/
|
|
198
|
+
const CAPTURE_BODIES_FLAG = '--capture-bodies';
|
|
199
|
+
/**
|
|
200
|
+
* The rest of the runtime surface.
|
|
201
|
+
*
|
|
202
|
+
* `--json` is not a convenience: SKILL.md tells an agent to read the result and act on `agentTodo`,
|
|
203
|
+
* and reading one object is one turn where interpreting a report is several. The three opt-outs are
|
|
204
|
+
* what a machine without a browser, or a caller already running their app, actually needs.
|
|
205
|
+
*/
|
|
206
|
+
const JSON_FLAG = '--json';
|
|
207
|
+
const NO_DRIVE_FLAG = '--no-drive';
|
|
208
|
+
const NO_OPEN_FLAG = '--no-open';
|
|
209
|
+
const NO_AGENTS_FLAG = '--no-agents';
|
|
210
|
+
/**
|
|
211
|
+
* Restart the calling client so IT gets the tools.
|
|
212
|
+
*
|
|
213
|
+
* The flag was accepted by the prototype and not by `init`, so passing it printed the whole usage
|
|
214
|
+
* text. `init` decides what a restart should do — including refusing an id with no transcript, which
|
|
215
|
+
* is the case that looks exactly like success — and prints it; performing the restart is the
|
|
216
|
+
* caller's, because opening a terminal is not something a one-shot command should do behind a flag.
|
|
217
|
+
*/
|
|
218
|
+
const RELAUNCH_FLAG = '--relaunch';
|
|
219
|
+
const URL_FLAG = '--url';
|
|
220
|
+
const TIMEOUT_FLAG_INIT = '--timeout';
|
|
221
|
+
const DRIVE_MODEL_FLAG = '--drive-model';
|
|
222
|
+
/**
|
|
223
|
+
* The key, written to .env by the command rather than by hand.
|
|
224
|
+
*
|
|
225
|
+
* The instructions used to ask an agent for three steps: append to .env, check .gitignore, confirm.
|
|
226
|
+
* All three are deterministic, and the second is the one that costs something when skipped, because
|
|
227
|
+
* a key committed to git is leaked and stays leaked after the file is removed.
|
|
228
|
+
*/
|
|
229
|
+
const LICENSE_FLAG = '--license';
|
|
162
230
|
const NO_INSTALL_FLAG = '--no-install';
|
|
163
231
|
export const HTTP_FLAG = '--http';
|
|
164
232
|
export const HTTP_PORT_FLAG = '--http-port';
|
|
165
233
|
export const HTTP_TOKEN_FLAG = '--http-token';
|
|
166
234
|
const TIMEOUT_FLAG = '--timeout';
|
|
167
235
|
const STORAGE_STATE_FLAG = '--storage-state';
|
|
236
|
+
const SESSION_ID_FLAG = '--session-id';
|
|
168
237
|
const unknownArgument = (arg) => ({
|
|
169
238
|
kind: 'error',
|
|
170
239
|
message: `unknown argument '${arg}'`,
|
|
@@ -181,6 +250,10 @@ const missingOperand = (command, what) => ({
|
|
|
181
250
|
kind: 'error',
|
|
182
251
|
message: `${command} needs ${what}`,
|
|
183
252
|
});
|
|
253
|
+
const requiresHttp = (flag) => ({
|
|
254
|
+
kind: 'error',
|
|
255
|
+
message: `${flag} requires ${HTTP_FLAG} — it configures the verify endpoint ${HTTP_FLAG} starts`,
|
|
256
|
+
});
|
|
184
257
|
const unknownCommand = (command) => ({
|
|
185
258
|
kind: 'error',
|
|
186
259
|
message: `unknown command '${command}'`,
|
|
@@ -245,6 +318,13 @@ function parseServeFlags(args, defaultPort, _defaultHeadless) {
|
|
|
245
318
|
}
|
|
246
319
|
i++;
|
|
247
320
|
}
|
|
321
|
+
// Without `--http` these flags configure an endpoint nothing starts, and they were accepted and
|
|
322
|
+
// silently ignored — worse than being rejected, because the whole reason to pass them is to be
|
|
323
|
+
// honoured (#687).
|
|
324
|
+
if (!http && httpPort !== undefined)
|
|
325
|
+
return requiresHttp(HTTP_PORT_FLAG);
|
|
326
|
+
if (!http && httpToken !== undefined)
|
|
327
|
+
return requiresHttp(HTTP_TOKEN_FLAG);
|
|
248
328
|
return {
|
|
249
329
|
kind: 'ok',
|
|
250
330
|
port,
|
|
@@ -290,14 +370,16 @@ function parseDriveSuffix(args, port, defaultHeadless) {
|
|
|
290
370
|
return { kind: 'ok', port, driveUrl, headless };
|
|
291
371
|
}
|
|
292
372
|
/**
|
|
293
|
-
* Parse `verify <url> [--port N] [--headed] [--timeout N] [--storage-state <file>]
|
|
294
|
-
* non-flag token is the preview URL. `defaultPort` is already
|
|
373
|
+
* Parse `verify <url> [--port N] [--headed] [--timeout N] [--storage-state <file>]
|
|
374
|
+
* [--session-id <id>]`. The first non-flag token is the preview URL. `defaultPort` is already
|
|
375
|
+
* env + `.reticle.json` + 4400.
|
|
295
376
|
*/
|
|
296
377
|
function parseVerifySuffix(args, defaultPort) {
|
|
297
378
|
let headless = true;
|
|
298
379
|
let url;
|
|
299
380
|
let timeoutMs;
|
|
300
381
|
let storageState;
|
|
382
|
+
let sessionId;
|
|
301
383
|
let port = defaultPort;
|
|
302
384
|
let i = 0;
|
|
303
385
|
while (i < args.length) {
|
|
@@ -334,6 +416,13 @@ function parseVerifySuffix(args, defaultPort) {
|
|
|
334
416
|
return missingValue(STORAGE_STATE_FLAG);
|
|
335
417
|
storageState = v;
|
|
336
418
|
}
|
|
419
|
+
else if (arg === SESSION_ID_FLAG) {
|
|
420
|
+
i++;
|
|
421
|
+
const v = args[i];
|
|
422
|
+
if (v === undefined)
|
|
423
|
+
return missingValue(SESSION_ID_FLAG);
|
|
424
|
+
sessionId = v;
|
|
425
|
+
}
|
|
337
426
|
else if (arg.startsWith('--')) {
|
|
338
427
|
return unknownArgument(arg);
|
|
339
428
|
}
|
|
@@ -354,6 +443,7 @@ function parseVerifySuffix(args, defaultPort) {
|
|
|
354
443
|
port,
|
|
355
444
|
...(timeoutMs !== undefined ? { timeoutMs } : {}),
|
|
356
445
|
...(storageState !== undefined ? { storageState } : {}),
|
|
446
|
+
...(sessionId !== undefined ? { sessionId } : {}),
|
|
357
447
|
};
|
|
358
448
|
}
|
|
359
449
|
function parseInitFlags(args) {
|
|
@@ -362,6 +452,20 @@ function parseInitFlags(args) {
|
|
|
362
452
|
let dryRun = false;
|
|
363
453
|
let install = true;
|
|
364
454
|
let app;
|
|
455
|
+
let flow;
|
|
456
|
+
// Repeatable: one variable per flag, so a value containing spaces or `=` needs no quoting rules.
|
|
457
|
+
const env = [];
|
|
458
|
+
let filesOnly = false;
|
|
459
|
+
let captureBodies = false;
|
|
460
|
+
let json = false;
|
|
461
|
+
let drive = true;
|
|
462
|
+
let open = true;
|
|
463
|
+
let relaunch = false;
|
|
464
|
+
let agents = true;
|
|
465
|
+
let url;
|
|
466
|
+
let timeoutSeconds;
|
|
467
|
+
let driveModel;
|
|
468
|
+
let licenseKey;
|
|
365
469
|
let i = 0;
|
|
366
470
|
while (i < args.length) {
|
|
367
471
|
const arg = args[i];
|
|
@@ -386,6 +490,72 @@ function parseInitFlags(args) {
|
|
|
386
490
|
return missingValue(APP_FLAG);
|
|
387
491
|
app = value;
|
|
388
492
|
}
|
|
493
|
+
else if (arg === FLOW_FLAG) {
|
|
494
|
+
i++;
|
|
495
|
+
const value = args[i];
|
|
496
|
+
if (value === undefined)
|
|
497
|
+
return missingValue(FLOW_FLAG);
|
|
498
|
+
flow = value;
|
|
499
|
+
}
|
|
500
|
+
else if (arg === ENV_FLAG) {
|
|
501
|
+
i++;
|
|
502
|
+
const value = args[i];
|
|
503
|
+
if (value === undefined)
|
|
504
|
+
return missingValue(ENV_FLAG);
|
|
505
|
+
env.push(value);
|
|
506
|
+
}
|
|
507
|
+
else if (arg === FILES_ONLY_FLAG) {
|
|
508
|
+
filesOnly = true;
|
|
509
|
+
}
|
|
510
|
+
else if (arg === CAPTURE_BODIES_FLAG) {
|
|
511
|
+
captureBodies = true;
|
|
512
|
+
}
|
|
513
|
+
else if (arg === JSON_FLAG) {
|
|
514
|
+
json = true;
|
|
515
|
+
}
|
|
516
|
+
else if (arg === NO_DRIVE_FLAG) {
|
|
517
|
+
drive = false;
|
|
518
|
+
}
|
|
519
|
+
else if (arg === NO_OPEN_FLAG) {
|
|
520
|
+
open = false;
|
|
521
|
+
}
|
|
522
|
+
else if (arg === RELAUNCH_FLAG) {
|
|
523
|
+
relaunch = true;
|
|
524
|
+
}
|
|
525
|
+
else if (arg === NO_AGENTS_FLAG) {
|
|
526
|
+
agents = false;
|
|
527
|
+
}
|
|
528
|
+
else if (arg === URL_FLAG) {
|
|
529
|
+
i++;
|
|
530
|
+
const value = args[i];
|
|
531
|
+
if (value === undefined)
|
|
532
|
+
return missingValue(URL_FLAG);
|
|
533
|
+
url = value;
|
|
534
|
+
}
|
|
535
|
+
else if (arg === LICENSE_FLAG) {
|
|
536
|
+
i++;
|
|
537
|
+
const value = args[i];
|
|
538
|
+
if (value === undefined)
|
|
539
|
+
return missingValue(LICENSE_FLAG);
|
|
540
|
+
licenseKey = value;
|
|
541
|
+
}
|
|
542
|
+
else if (arg === DRIVE_MODEL_FLAG) {
|
|
543
|
+
i++;
|
|
544
|
+
const value = args[i];
|
|
545
|
+
if (value === undefined)
|
|
546
|
+
return missingValue(DRIVE_MODEL_FLAG);
|
|
547
|
+
driveModel = value;
|
|
548
|
+
}
|
|
549
|
+
else if (arg === TIMEOUT_FLAG_INIT) {
|
|
550
|
+
i++;
|
|
551
|
+
const value = args[i];
|
|
552
|
+
if (value === undefined)
|
|
553
|
+
return missingValue(TIMEOUT_FLAG_INIT);
|
|
554
|
+
const seconds = parseInt(value, 10);
|
|
555
|
+
if (isNaN(seconds))
|
|
556
|
+
return notANumber(TIMEOUT_FLAG_INIT, value);
|
|
557
|
+
timeoutSeconds = seconds;
|
|
558
|
+
}
|
|
389
559
|
else if (arg === NO_MCP_FLAG) {
|
|
390
560
|
mcp = false;
|
|
391
561
|
}
|
|
@@ -403,7 +573,27 @@ function parseInitFlags(args) {
|
|
|
403
573
|
}
|
|
404
574
|
i++;
|
|
405
575
|
}
|
|
406
|
-
return {
|
|
576
|
+
return {
|
|
577
|
+
kind: 'ok',
|
|
578
|
+
port,
|
|
579
|
+
mcp,
|
|
580
|
+
dryRun,
|
|
581
|
+
install,
|
|
582
|
+
app,
|
|
583
|
+
flow,
|
|
584
|
+
env,
|
|
585
|
+
filesOnly,
|
|
586
|
+
captureBodies,
|
|
587
|
+
json,
|
|
588
|
+
drive,
|
|
589
|
+
open,
|
|
590
|
+
relaunch,
|
|
591
|
+
agents,
|
|
592
|
+
url,
|
|
593
|
+
timeoutSeconds,
|
|
594
|
+
driveModel,
|
|
595
|
+
licenseKey,
|
|
596
|
+
};
|
|
407
597
|
}
|
|
408
598
|
/** Pure CLI arg parser — exported for unit tests. argv = process.argv.slice(2). */
|
|
409
599
|
const SINCE_FLAG = '--since';
|
|
@@ -471,6 +661,19 @@ export function parseCliArgs(argv, defaultPort, defaultHeadless = false) {
|
|
|
471
661
|
dryRun: r.dryRun,
|
|
472
662
|
install: r.install,
|
|
473
663
|
app: r.app,
|
|
664
|
+
flow: r.flow,
|
|
665
|
+
env: r.env,
|
|
666
|
+
filesOnly: r.filesOnly,
|
|
667
|
+
captureBodies: r.captureBodies,
|
|
668
|
+
json: r.json,
|
|
669
|
+
drive: r.drive,
|
|
670
|
+
open: r.open,
|
|
671
|
+
relaunch: r.relaunch,
|
|
672
|
+
agents: r.agents,
|
|
673
|
+
url: r.url,
|
|
674
|
+
timeoutSeconds: r.timeoutSeconds,
|
|
675
|
+
driveModel: r.driveModel,
|
|
676
|
+
licenseKey: r.licenseKey,
|
|
474
677
|
};
|
|
475
678
|
}
|
|
476
679
|
case SERVE_COMMAND: {
|
|
@@ -574,6 +777,7 @@ export function parseCliArgs(argv, defaultPort, defaultHeadless = false) {
|
|
|
574
777
|
port: r.port,
|
|
575
778
|
...(r.timeoutMs !== undefined ? { timeoutMs: r.timeoutMs } : {}),
|
|
576
779
|
...(r.storageState !== undefined ? { storageState: r.storageState } : {}),
|
|
780
|
+
...(r.sessionId !== undefined ? { sessionId: r.sessionId } : {}),
|
|
577
781
|
};
|
|
578
782
|
}
|
|
579
783
|
case CAPSULES_COMMAND:
|
package/dist/cli/cli-verify.d.ts
CHANGED
|
@@ -62,6 +62,7 @@ export declare function handleVerify(parsed: {
|
|
|
62
62
|
headless: boolean;
|
|
63
63
|
timeoutMs?: number;
|
|
64
64
|
storageState?: string;
|
|
65
|
+
sessionId?: string;
|
|
65
66
|
/** Bridge port — parseCliArgs already resolves --port / RETICLE_PORT / .reticle.json into this. */
|
|
66
67
|
port: number;
|
|
67
68
|
}): void;
|