@reticlehq/server 2.10.0 → 2.12.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 +73 -11
- package/dist/bridge/bridge.d.ts +0 -13
- package/dist/bridge/bridge.js +1 -1
- package/dist/bridge/bridge.test-harness.d.ts +2 -0
- package/dist/bridge/bridge.test-harness.js +8 -1
- package/dist/capsule/capsule-store.d.ts +2 -1
- package/dist/capsule/capsule-store.js +1 -1
- package/dist/capsule/causal-summary.d.ts +2 -2
- package/dist/chromium-launch-options.d.ts +28 -0
- package/dist/chromium-launch-options.js +29 -0
- package/dist/cli/cli-doctor.js +8 -0
- 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-parse.d.ts +3 -1
- package/dist/cli/cli-parse.js +29 -7
- package/dist/cli/cli-update-commands.js +20 -1
- package/dist/cli/cli-verify.d.ts +1 -1
- package/dist/cli/cli-verify.js +2 -2
- package/dist/cli/cloud-cli.js +12 -6
- package/dist/cli/config-discovery.d.ts +2 -1
- package/dist/cli/doctor-rows.d.ts +3 -1
- package/dist/cli/doctor-rows.js +3 -1
- 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 +143 -0
- package/dist/cli/sibling-ports.d.ts +37 -0
- package/dist/cli/sibling-ports.js +56 -0
- package/dist/cli.js +37 -110
- package/dist/cloud/cloud-config.d.ts +2 -1
- package/dist/cloud/cloud-sync.d.ts +11 -0
- package/dist/cloud/cloud-sync.js +32 -0
- package/dist/crawl/crawl.d.ts +9 -13
- package/dist/crawl/crawl.js +20 -3
- package/dist/daemon/daemon-resolve.d.ts +79 -0
- package/dist/daemon/daemon-resolve.js +118 -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/dev/stale-issue-guard.d.ts +41 -0
- package/dist/dev/stale-issue-guard.js +75 -0
- package/dist/domain/domain-tools.js +3 -2
- package/dist/events/contradictions.d.ts +41 -0
- package/dist/events/contradictions.js +125 -12
- package/dist/events/declared.d.ts +14 -0
- package/dist/events/declared.js +33 -0
- package/dist/events/echo-mismatch.js +14 -0
- 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 +30 -0
- package/dist/events/predicate-asks.js +48 -0
- package/dist/events/predicate-eval.js +254 -25
- package/dist/events/predicate-parse.d.ts +0 -6
- package/dist/events/predicate-parse.js +51 -5
- package/dist/events/predicate-schema.d.ts +62 -0
- package/dist/events/predicate-schema.js +162 -4
- package/dist/events/predicate.d.ts +15 -0
- package/dist/events/predicate.js +41 -2
- 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 +0 -2
- 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-replay-run.js +27 -4
- package/dist/flows/flow-success.js +15 -3
- package/dist/flows/flow-tools.js +65 -8
- package/dist/flows/flows.d.ts +6 -1
- package/dist/flows/flows.js +34 -22
- package/dist/flows/predicate-to-expect.js +14 -3
- package/dist/flows/server-verify.js +16 -2
- package/dist/flows/verify-change-tools.js +9 -2
- package/dist/honesty/blind-spots.d.ts +8 -16
- package/dist/honesty/blind-spots.js +18 -5
- package/dist/honesty/feature-capture.d.ts +150 -0
- package/dist/honesty/feature-capture.js +233 -0
- package/dist/honesty/gap-ledger.d.ts +52 -0
- package/dist/honesty/gap-ledger.js +63 -0
- package/dist/honesty/instrumentation-gaps.d.ts +79 -0
- package/dist/honesty/instrumentation-gaps.js +77 -0
- package/dist/honesty/observability.d.ts +37 -0
- package/dist/honesty/observability.js +40 -0
- 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 +15 -0
- package/dist/honesty/verified.js +68 -4
- package/dist/http-server.d.ts +0 -1
- package/dist/http-server.js +1 -1
- package/dist/hunt/hunt-report.d.ts +0 -2
- package/dist/hunt/hunt-report.js +1 -1
- package/dist/impact/impact-store.d.ts +0 -5
- package/dist/impact/impact-store.js +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.js +41 -0
- package/dist/init/agent-rules.d.ts +17 -0
- package/dist/init/agent-rules.js +31 -1
- package/dist/init/capabilities.d.ts +0 -2
- package/dist/init/capabilities.js +1 -1
- package/dist/init/closing-hint.d.ts +36 -0
- package/dist/init/closing-hint.js +120 -0
- package/dist/init/confirm.d.ts +0 -25
- package/dist/init/confirm.js +3 -3
- package/dist/init/cra.d.ts +14 -1
- package/dist/init/cra.js +43 -12
- package/dist/init/dev-script.d.ts +48 -0
- package/dist/init/dev-script.js +63 -0
- package/dist/init/mcp.d.ts +16 -5
- package/dist/init/mcp.js +26 -10
- package/dist/init/node-io.js +3 -0
- package/dist/init/plan-framework.d.ts +0 -5
- package/dist/init/plan-framework.js +83 -9
- package/dist/init/plan.d.ts +10 -30
- package/dist/init/plan.js +30 -15
- package/dist/init/refresh-rules.d.ts +39 -0
- package/dist/init/refresh-rules.js +51 -0
- package/dist/init/run.d.ts +2 -10
- package/dist/init/run.js +26 -68
- package/dist/init/slash-command.d.ts +1 -1
- package/dist/init/slash-command.js +33 -10
- package/dist/init/snippets.js +53 -10
- package/dist/input/real-input.d.ts +2 -0
- package/dist/input/real-input.js +3 -2
- package/dist/input/scroll-find.d.ts +7 -4
- package/dist/input/scroll-find.js +63 -25
- package/dist/intent/inline-intent.d.ts +46 -0
- package/dist/intent/inline-intent.js +103 -0
- package/dist/intent/intent-store.d.ts +37 -0
- package/dist/intent/intent-store.js +112 -0
- package/dist/intent/intent-tools.d.ts +2 -0
- package/dist/intent/intent-tools.js +80 -0
- package/dist/intent/open-intents.d.ts +21 -0
- package/dist/intent/open-intents.js +25 -0
- package/dist/journal/envelope.d.ts +2 -2
- package/dist/journal/journal-recorder.d.ts +23 -0
- package/dist/journal/journal-recorder.js +35 -0
- package/dist/license/license.d.ts +8 -2
- package/dist/license/license.js +10 -15
- package/dist/mcp/attach-memory.d.ts +67 -0
- package/dist/mcp/attach-memory.js +142 -0
- package/dist/mcp/mcp-proxy.d.ts +0 -7
- package/dist/mcp/mcp-proxy.js +14 -2
- package/dist/mcp/mcp.d.ts +2 -0
- package/dist/mcp/mcp.js +45 -23
- package/dist/mcp/server-instructions.js +45 -3
- package/dist/mcp/tool-catalog-cache.d.ts +8 -0
- package/dist/mcp/tool-catalog-cache.js +10 -0
- package/dist/oracles/flow-instrument-gaps.d.ts +4 -4
- package/dist/oracles/flow-instrument-gaps.js +4 -5
- package/dist/oracles/self-instrument.d.ts +9 -9
- package/dist/oracles/self-instrument.js +19 -13
- package/dist/pool/browser-pool.d.ts +12 -1
- package/dist/pool/browser-pool.js +31 -1
- package/dist/pool/playwright-launcher.js +3 -1
- package/dist/project/artifact-root.d.ts +83 -0
- package/dist/project/artifact-root.js +112 -0
- package/dist/project/fs-port.d.ts +3 -0
- package/dist/project/fs-port.js +3 -2
- package/dist/project/memory-fs.d.ts +9 -0
- package/dist/project/memory-fs.js +93 -0
- package/dist/project/project-store.d.ts +17 -0
- package/dist/project/project-store.js +33 -0
- package/dist/project/project-tools.js +3 -32
- package/dist/project/remember-project.d.ts +24 -0
- package/dist/project/remember-project.js +40 -0
- package/dist/project/reticle-dir.d.ts +2 -0
- package/dist/project/reticle-dir.js +1 -0
- package/dist/project/session-root.d.ts +15 -0
- package/dist/project/session-root.js +31 -0
- 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-tools.js +2 -1
- package/dist/runs/verification-sync.js +2 -2
- 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-availability.d.ts +40 -0
- package/dist/session/lease-availability.js +43 -0
- package/dist/session/live-control-tools.js +1 -42
- package/dist/session/no-session-diagnosis.d.ts +20 -0
- package/dist/session/no-session-diagnosis.js +41 -12
- package/dist/session/no-session-next-action.d.ts +5 -0
- package/dist/session/no-session-next-action.js +35 -3
- package/dist/session/no-session-watch.d.ts +8 -0
- package/dist/session/no-session-watch.js +83 -65
- package/dist/session/output-budget.js +4 -1
- package/dist/session/session-health.d.ts +9 -0
- package/dist/session/session-health.js +23 -0
- package/dist/session/session-manager.js +50 -1
- package/dist/session/session-successor.d.ts +37 -0
- package/dist/session/session-successor.js +78 -0
- package/dist/session/session.d.ts +40 -1
- package/dist/session/session.js +84 -2
- package/dist/session/stall-clock.d.ts +18 -0
- package/dist/session/stall-clock.js +48 -0
- package/dist/telemetry/automation-hint.d.ts +2 -1
- package/dist/telemetry/daemon-telemetry.js +12 -23
- package/dist/telemetry/feedback-outbox.d.ts +2 -1
- package/dist/telemetry/project-profile.d.ts +0 -8
- package/dist/telemetry/project-profile.js +1 -1
- package/dist/telemetry/run-telemetry.d.ts +0 -2
- package/dist/telemetry/run-telemetry.js +1 -1
- package/dist/telemetry/session-metrics.d.ts +1 -1
- package/dist/telemetry/session-metrics.js +1 -1
- package/dist/telemetry/telemetry.d.ts +12 -3
- package/dist/telemetry/telemetry.js +25 -5
- package/dist/telemetry/verification-of.js +2 -2
- package/dist/tools/act-tools.js +159 -24
- package/dist/tools/assert-source.d.ts +22 -0
- package/dist/tools/assert-source.js +53 -0
- package/dist/tools/assert-verdict.d.ts +21 -2
- package/dist/tools/assert-verdict.js +59 -4
- package/dist/tools/browser-tools.js +0 -20
- package/dist/tools/complete-snapshot.d.ts +48 -0
- package/dist/tools/complete-snapshot.js +75 -0
- package/dist/tools/contract-tools.js +5 -3
- package/dist/tools/coverage-tools.d.ts +10 -1
- package/dist/tools/coverage-tools.js +124 -37
- package/dist/tools/dynamic-tools.js +29 -8
- package/dist/tools/error-recovery.d.ts +24 -14
- package/dist/tools/error-recovery.js +70 -9
- package/dist/tools/feedback-tools.d.ts +1 -1
- package/dist/tools/feedback-tools.js +2 -2
- package/dist/tools/invoke-tool.js +62 -2
- package/dist/tools/lease-hint.d.ts +10 -0
- package/dist/tools/lease-hint.js +53 -4
- package/dist/tools/lease-tools.d.ts +5 -1
- package/dist/tools/lease-tools.js +22 -8
- package/dist/tools/navigate-arrival.d.ts +0 -2
- package/dist/tools/navigate-arrival.js +1 -1
- package/dist/tools/observe-tools.js +70 -27
- package/dist/tools/read-tools.js +5 -3
- package/dist/tools/real-input-attempt.d.ts +22 -0
- package/dist/tools/real-input-attempt.js +153 -1
- package/dist/tools/reconcile-tools.js +18 -4
- 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 +28 -1
- package/dist/tools/tool-kit.js +16 -0
- package/dist/tools/tool-names.d.ts +3 -0
- package/dist/tools/tool-names.js +3 -0
- package/dist/tools/tool-surface.d.ts +85 -6
- package/dist/tools/tool-surface.js +122 -2
- package/dist/tools/tools.js +22 -4
- 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/update-nudge.d.ts +7 -10
- package/dist/update/update-nudge.js +12 -3
- package/dist/update/updater.d.ts +1 -1
- package/docs/README.md +3 -1
- package/docs/cli/cloud.mdx +1 -1
- package/docs/cli/doctor.mdx +1 -0
- package/docs/cli/verify.mdx +4 -5
- package/docs/desktop.mdx +1 -1
- package/docs/docs.json +3 -3
- package/docs/enterprise.md +3 -1
- package/docs/gates.md +1 -1
- package/docs/install-manual.mdx +1 -1
- package/docs/license-activation.mdx +145 -0
- 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/telemetry-contract.md +1 -12
- package/docs/telemetry-events.mdx +1 -34
- package/docs/telemetry.md +0 -1
- package/docs/tools-tools-and-run.mdx +2 -4
- package/docs/troubleshooting.mdx +9 -0
- package/docs/usage.md +3 -2
- package/package.json +2 -2
- package/dist/session/session-readiness.d.ts +0 -34
- package/dist/session/session-readiness.js +0 -36
- package/dist/telemetry/instrumentation-stall.d.ts +0 -57
- package/dist/telemetry/instrumentation-stall.js +0 -103
|
@@ -36,6 +36,15 @@ export declare const AGENTS_MD_PATH = "AGENTS.md";
|
|
|
36
36
|
export declare const RETICLE_MD_PATH = "RETICLE.md";
|
|
37
37
|
/** Cursor project rule; the frontmatter `alwaysApply` keeps it in context on every turn. */
|
|
38
38
|
export declare const CURSOR_RULE_PATH = ".cursor/rules/reticle.mdc";
|
|
39
|
+
/**
|
|
40
|
+
* Does this file already carry the managed block?
|
|
41
|
+
*
|
|
42
|
+
* Exported so callers cannot invent their own looser test. The begin marker carries explanatory
|
|
43
|
+
* text inside the comment, so a substring check for `reticle:begin` matches files that
|
|
44
|
+
* `mergeMarkedInstruction` would treat as having NO block — and the two disagreeing means a refresh
|
|
45
|
+
* silently appends a second copy instead of updating the first.
|
|
46
|
+
*/
|
|
47
|
+
export declare function hasManagedBlock(text: string): boolean;
|
|
39
48
|
/**
|
|
40
49
|
* How the agent must INVOKE the CLI.
|
|
41
50
|
*
|
|
@@ -67,6 +76,14 @@ export declare const CLI: string;
|
|
|
67
76
|
* Exported because slash-command.ts renders the same rule; there must not be two versions of it.
|
|
68
77
|
*/
|
|
69
78
|
export declare const DEV_SERVER_POLICY = "**If no dev server is listening, start one yourself.** Read the project's own dev script out of `package.json` (`dev`, `start`, whatever this project calls it), run it in the BACKGROUND, tell the user in one line that it is running and how to stop it, then carry on. Stopping to ask is how a verification turn ends with nothing verified.\n\nFive guards, none optional:\n\n1. **Never start a second one.** If something is already listening on the app's port, use it.\n2. **Never guess the command.** It comes from `package.json` scripts. If there is no recognisable dev script, say so and stop rather than inventing one.\n3. **Never kill anything.** Not a dev server, not a daemon, not a port holder \u2014 including one you started.\n4. **Background it, and say so.** The user must know a server is running and how to stop it. A dev server the human does not know about is the same failure one step later.\n5. **The permission prompt belongs to your host.** Never try to bypass, suppress or auto-approve it, and take a refusal as the answer.";
|
|
79
|
+
/**
|
|
80
|
+
* The operative rules, carried by every file that loads on every turn.
|
|
81
|
+
*
|
|
82
|
+
* Short on purpose. This is the text that has to CHANGE BEHAVIOUR, so it states when to verify, when
|
|
83
|
+
* NOT to, how, and the guards against calling something proven when it is not. Everything an agent
|
|
84
|
+
* needs only at the moment it hits a specific situation lives in RETICLE.md, named at the end.
|
|
85
|
+
*/
|
|
86
|
+
export declare const RULE_BODY: string;
|
|
70
87
|
/** The full-rules file, written verbatim (no markers: `init` owns this file entirely). */
|
|
71
88
|
export declare function reticleMdFile(): string;
|
|
72
89
|
export declare const AgentRuleStatus: {
|
package/dist/init/agent-rules.js
CHANGED
|
@@ -41,6 +41,17 @@ export const CURSOR_RULE_PATH = '.cursor/rules/reticle.mdc';
|
|
|
41
41
|
/** Markers delimiting the managed block so a re-run is idempotent and the block stays updatable. */
|
|
42
42
|
const RULE_BEGIN = '<!-- reticle:begin (managed by `reticle init` — edit outside these markers) -->';
|
|
43
43
|
const RULE_END = '<!-- reticle:end -->';
|
|
44
|
+
/**
|
|
45
|
+
* Does this file already carry the managed block?
|
|
46
|
+
*
|
|
47
|
+
* Exported so callers cannot invent their own looser test. The begin marker carries explanatory
|
|
48
|
+
* text inside the comment, so a substring check for `reticle:begin` matches files that
|
|
49
|
+
* `mergeMarkedInstruction` would treat as having NO block — and the two disagreeing means a refresh
|
|
50
|
+
* silently appends a second copy instead of updating the first.
|
|
51
|
+
*/
|
|
52
|
+
export function hasManagedBlock(text) {
|
|
53
|
+
return text.includes(RULE_BEGIN);
|
|
54
|
+
}
|
|
44
55
|
/**
|
|
45
56
|
* How the agent must INVOKE the CLI.
|
|
46
57
|
*
|
|
@@ -87,7 +98,7 @@ Five guards, none optional:
|
|
|
87
98
|
* NOT to, how, and the guards against calling something proven when it is not. Everything an agent
|
|
88
99
|
* needs only at the moment it hits a specific situation lives in RETICLE.md, named at the end.
|
|
89
100
|
*/
|
|
90
|
-
const RULE_BODY = `## Verifying with Reticle
|
|
101
|
+
export const RULE_BODY = `## Verifying with Reticle
|
|
91
102
|
|
|
92
103
|
This app is instrumented by **Reticle**, an in-app verification layer exposed as \`reticle_*\` MCP tools and the \`${CLI}\` CLI (always through npx: Reticle's server is not installed into this project). Verifying is part of "done", not an optional extra.
|
|
93
104
|
|
|
@@ -103,21 +114,40 @@ This app is instrumented by **Reticle**, an in-app verification layer exposed as
|
|
|
103
114
|
- **Only \`reticle_act_and_wait\` and \`reticle_assert\` produce a verdict.** \`reticle_act\` and everything else move or read the app and prove nothing, so a session ending without one of those two has no result however many tools it used.
|
|
104
115
|
- Covered flows: \`${CLI} gate\` reports which recorded flows the changed files affect and whether they still pass.
|
|
105
116
|
|
|
117
|
+
**Setting Reticle up, or just restarted? You are mid-sequence — do not stop until a verdict exists.**
|
|
118
|
+
The whole of it is: instrument the app → get a dev server running → open the app in a browser →
|
|
119
|
+
drive one flow → report the verdict. Every step is yours to do, and none of them needs the user.
|
|
120
|
+
The ONE legitimate pause is a client restart, because a client reads its server list at startup;
|
|
121
|
+
when you come back, you are not finished, you are on the next step — resume it without being asked.
|
|
122
|
+
Stopping anywhere else leaves an app that looks installed and can verify nothing, which is the
|
|
123
|
+
single commonest way this goes wrong. \`/reticle\` carries the full recovery ladder when a step
|
|
124
|
+
does not work; use it rather than guessing, and never report the install as finished without a
|
|
125
|
+
verdict to point at.
|
|
126
|
+
|
|
106
127
|
**Nothing connected? Get the app running.**
|
|
107
128
|
|
|
108
129
|
${DEV_SERVER_POLICY}
|
|
109
130
|
|
|
110
131
|
A dev server that is already running does not pick up an edited build config or a newly created plugin file — restart it and hard-reload the tab. And if a server IS listening and still nothing connects, the cause is the SDK not loading in the page, not a missing dev server; do not tell the user to start one they are already running.
|
|
111
132
|
|
|
133
|
+
**Finish \`src/reticle-dev.ts\` before you claim setup is done.** \`init\` writes it and cannot always fill it in: a store that needs an argument only reading the code supplies (Jotai atoms, an XState actor, a TanStack \`queryClient\`) is left as a commented \`registerStore\` line. A file that registers nothing looks exactly like a finished one, and \`reticle_state\` then returns empty forever — which is indistinguishable from an app that has nothing to report, so it reads as success. Uncomment the line, complete it, and prove it by driving one flow and seeing your keys come back. If \`init\` told you to restart your client, this is the job waiting for you on the other side of that restart.
|
|
134
|
+
|
|
135
|
+
**Verify each feature as you finish it, not all of them at the end.** Asked for four, build one, drive it, get a verdict, then start the second. A red verdict after four builds has four suspects; after one it has none.
|
|
136
|
+
|
|
137
|
+
**Capture what a change is FOR while you are building it, not afterwards.** \`reticle_intent\` records the business outcome a change is meant to produce, and the only moment anybody knows it is while the change is being made. Pass \`intent\` when you save a flow, so the saved flow carries the reason it exists. A flow without one replays for months and then reports "step 3 failed" instead of what stopped being true for a user.
|
|
138
|
+
|
|
112
139
|
**Honesty, which is the whole point:**
|
|
113
140
|
|
|
114
141
|
- **\`verified: "unknown"\` is not a pass.** It means Reticle drove the app and could not tell what happened; \`verifiedReason\` says which clause decided that. Report it as unknown, never as working.
|
|
142
|
+
- **\`verified: "no-fault"\` is not a pass either.** It means nothing was DECLARED to prove: the page settled and no channel complained, but you asserted nothing, so there is no verification. You get it whenever \`until\` is omitted. Name a consequence the action changes — a signal, a request, a route, or store state — and call again.
|
|
115
143
|
- **Never weaken a check to make it green.** Downgrading, skipping or deleting an assertion is a finding, not a fix.
|
|
116
144
|
- **If Reticle cannot run** (no daemon, or this is not a running web app), say so. Do not skip verification silently.
|
|
117
145
|
- **Setup is not finished until one real flow has been driven and produced a verdict.** \`init\` exiting 0, the tools appearing, and a session being listed are all things that happen before anything has been verified.
|
|
118
146
|
|
|
119
147
|
**The \`/reticle\` skill runs this whole loop for you** — detect, connect, drive one flow, report. If your client does not have it, install it once: \`/plugin marketplace add reticlehq/reticle\` then \`/plugin install reticle@reticlehq\` in Claude Code, or \`npx skills add reticlehq/reticle\` anywhere the skills CLI works.
|
|
120
148
|
|
|
149
|
+
**A tool you need is missing? It exists.** The default surface advertises a subset; reach any other by name with \`reticle_run { tool, args }\`, and list them with \`reticle_tools\`. Two worth knowing: \`reticle_context\` returns this run's own memory — what is established, what is proven, what is still unverified — which is what you want after a compaction or when picking up work you did not start; \`reticle_intent\` records what a change was MEANT to do, while somebody still knows.
|
|
150
|
+
|
|
121
151
|
**Report Reticle's own defects with \`reticle_feedback\` the moment you notice**, then carry on with your task. You are the user Reticle is built for and the only one who can say what it cost you, and that knowledge is gone when your context is.
|
|
122
152
|
|
|
123
153
|
📄 **The rest is in [RETICLE.md](./${RETICLE_MD_PATH}): what to do when the tools are missing, when a result carries \`version_skew\` or \`update_available\`, when \`reticle_state\` comes back empty, and how to write a feedback report that can be acted on. Read it when you hit one of those, not before.**`;
|
|
@@ -24,8 +24,6 @@ export interface FoundStore {
|
|
|
24
24
|
/** Module specifier relative to `src/reticle-dev.ts`. */
|
|
25
25
|
importPath: string;
|
|
26
26
|
}
|
|
27
|
-
/** Where the generated dev module lives, for Vite. Next passes its own. */
|
|
28
|
-
export declare const DEFAULT_DEV_MODULE_DIR = "src";
|
|
29
27
|
/**
|
|
30
28
|
* Store instances declared in the app's own source, ready to import and register.
|
|
31
29
|
*
|
|
@@ -102,7 +102,7 @@ function importPathFor(storePath, fromDir) {
|
|
|
102
102
|
return rel.startsWith('.') ? rel : `./${rel}`;
|
|
103
103
|
}
|
|
104
104
|
/** Where the generated dev module lives, for Vite. Next passes its own. */
|
|
105
|
-
|
|
105
|
+
const DEFAULT_DEV_MODULE_DIR = 'src';
|
|
106
106
|
/**
|
|
107
107
|
* Store instances declared in the app's own source, ready to import and register.
|
|
108
108
|
*
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The last thing `init` prints, and the only part of it a user is asked to act on.
|
|
3
|
+
*
|
|
4
|
+
* Split out of `run.ts` because that file sits exactly at the 1000-line cohesion cap, and because
|
|
5
|
+
* this IS a separate concern: everything else in `run.ts` decides what to write, and this decides
|
|
6
|
+
* what to say afterwards. The two failure modes are different too — a wrong write breaks an
|
|
7
|
+
* install, a wrong sentence loses a user quietly.
|
|
8
|
+
*/
|
|
9
|
+
import { Framework } from './detect.js';
|
|
10
|
+
import { StepStatus } from './plan.js';
|
|
11
|
+
/**
|
|
12
|
+
* The standing ask, printed at the end of every `init`.
|
|
13
|
+
*
|
|
14
|
+
* Addressed to the agent as much as the human: whichever of them just ran this command is the one
|
|
15
|
+
* holding the experience, and neither has a Reticle tool surface to file through at this point.
|
|
16
|
+
*
|
|
17
|
+
* Printed by the `runInit` wrapper, NOT by `report()` — see the comment there for why, and for how
|
|
18
|
+
* the workspace redirect is kept from asking twice.
|
|
19
|
+
*/
|
|
20
|
+
export declare const FEEDBACK_HINT: string;
|
|
21
|
+
/**
|
|
22
|
+
* The last two lines a user reads, and the order matters.
|
|
23
|
+
*
|
|
24
|
+
* This used to say only "Restart <dev server>, then ask your agent: List Reticle sessions" — and
|
|
25
|
+
* asking the agent was the one thing that could not work yet. `init` registers the MCP server, but
|
|
26
|
+
* an agent client reads its tool list when it STARTS and never re-reads it, so the session that
|
|
27
|
+
* just ran `init` has no `reticle_*` tools however clean the install was. The user follows the
|
|
28
|
+
* instruction, the agent answers "unknown tool", and the obvious conclusion is that the install
|
|
29
|
+
* failed.
|
|
30
|
+
*
|
|
31
|
+
* So the reload is named FIRST, before the sentence that depends on it. Omitted entirely when this
|
|
32
|
+
* run did not register MCP (`--no-mcp`), where it would be advice about something we did not do.
|
|
33
|
+
*/
|
|
34
|
+
export declare function restartHint(framework: Framework, mcpStatus: StepStatus | undefined,
|
|
35
|
+
/** The project's own dev command, when package.json named one. */
|
|
36
|
+
devCommand?: string): string;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The last thing `init` prints, and the only part of it a user is asked to act on.
|
|
3
|
+
*
|
|
4
|
+
* Split out of `run.ts` because that file sits exactly at the 1000-line cohesion cap, and because
|
|
5
|
+
* this IS a separate concern: everything else in `run.ts` decides what to write, and this decides
|
|
6
|
+
* what to say afterwards. The two failure modes are different too — a wrong write breaks an
|
|
7
|
+
* install, a wrong sentence loses a user quietly.
|
|
8
|
+
*/
|
|
9
|
+
import { Framework } from './detect.js';
|
|
10
|
+
import { StepStatus } from './plan.js';
|
|
11
|
+
import { wasMcpRegistered } from './mcp-registered.js';
|
|
12
|
+
import { CLI } from './agent-rules.js';
|
|
13
|
+
/**
|
|
14
|
+
* The standing ask, printed at the end of every `init`.
|
|
15
|
+
*
|
|
16
|
+
* Addressed to the agent as much as the human: whichever of them just ran this command is the one
|
|
17
|
+
* holding the experience, and neither has a Reticle tool surface to file through at this point.
|
|
18
|
+
*
|
|
19
|
+
* Printed by the `runInit` wrapper, NOT by `report()` — see the comment there for why, and for how
|
|
20
|
+
* the workspace redirect is kept from asking twice.
|
|
21
|
+
*/
|
|
22
|
+
export const FEEDBACK_HINT = 'Anything wrong, missing, or awkward — in this setup or in Reticle itself? Tell us; it is the ' +
|
|
23
|
+
'one thing that decides what gets fixed:\n' +
|
|
24
|
+
` ${CLI} feedback --agent --kind <bug|gap|ambiguity|feature_request|improvement> "what happened" (agents)\n` +
|
|
25
|
+
` ${CLI} feedback "what worked, what didn't" (humans)`;
|
|
26
|
+
/**
|
|
27
|
+
* What to say about the dev server.
|
|
28
|
+
*
|
|
29
|
+
* Names the project's own script when `package.json` has a recognisable one, and otherwise says the
|
|
30
|
+
* THING rather than guessing the command.
|
|
31
|
+
*
|
|
32
|
+
* The guesses that used to live here — `vite`, `next dev`, `astro dev` — were wrong often enough to
|
|
33
|
+
* be worth deleting. Measured on a real app: rowy has no `dev` script at all, so "Restart `vite`"
|
|
34
|
+
* sent the reader to work out what was meant instead of starting a server. Nobody needs the guess:
|
|
35
|
+
* an agent reading this can open package.json, and a human already knows how they start their own
|
|
36
|
+
* app. A wrong command costs more than a missing one.
|
|
37
|
+
*/
|
|
38
|
+
function devServerRestart(_framework, devCommand) {
|
|
39
|
+
return devCommand === undefined
|
|
40
|
+
? 'Restart your dev server'
|
|
41
|
+
: `Restart your dev server (\`${devCommand}\`)`;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* The last two lines a user reads, and the order matters.
|
|
45
|
+
*
|
|
46
|
+
* This used to say only "Restart <dev server>, then ask your agent: List Reticle sessions" — and
|
|
47
|
+
* asking the agent was the one thing that could not work yet. `init` registers the MCP server, but
|
|
48
|
+
* an agent client reads its tool list when it STARTS and never re-reads it, so the session that
|
|
49
|
+
* just ran `init` has no `reticle_*` tools however clean the install was. The user follows the
|
|
50
|
+
* instruction, the agent answers "unknown tool", and the obvious conclusion is that the install
|
|
51
|
+
* failed.
|
|
52
|
+
*
|
|
53
|
+
* So the reload is named FIRST, before the sentence that depends on it. Omitted entirely when this
|
|
54
|
+
* run did not register MCP (`--no-mcp`), where it would be advice about something we did not do.
|
|
55
|
+
*/
|
|
56
|
+
export function restartHint(framework, mcpStatus,
|
|
57
|
+
/** The project's own dev command, when package.json named one. */
|
|
58
|
+
devCommand) {
|
|
59
|
+
const dev = `${devServerRestart(framework, devCommand)}.`;
|
|
60
|
+
// NAME THE COMMAND THAT PROVES IT, not one that merely asks.
|
|
61
|
+
//
|
|
62
|
+
// `init` writes files and stops; the install is not finished until an app carrying the SDK has
|
|
63
|
+
// actually dialled the daemon, and nothing here confirmed that. The field shape is unambiguous:
|
|
64
|
+
// people complete the agent half, never complete the app half, and keep a daemon running for
|
|
65
|
+
// weeks with nothing to drive — so this is the last instruction most of them read.
|
|
66
|
+
//
|
|
67
|
+
// "Ask your agent: List Reticle sessions" asks a question whose failure is a dead end. `reticle
|
|
68
|
+
// status` ANSWERS it: as of 2.7.0 it reports the session, or says why there is none — no app
|
|
69
|
+
// running, an app running that never dialled us, a tab that closed — with the fix for each.
|
|
70
|
+
// The one thing that works BEFORE the restart, so it goes before it.
|
|
71
|
+
//
|
|
72
|
+
// What comes after the restart, named as a JOB rather than as a capability.
|
|
73
|
+
//
|
|
74
|
+
// This used to point at a `reticle demo` command that opened its own browser and drove one
|
|
75
|
+
// control. It was cut: a demo command is a side show. The thing a user needs to see is THEIR app,
|
|
76
|
+
// instrumented in THEIR codebase, driven by THEIR agent through the tools they just installed —
|
|
77
|
+
// because that is the workflow they are deciding whether to adopt, and a bespoke demo path proves
|
|
78
|
+
// nothing about it.
|
|
79
|
+
//
|
|
80
|
+
// So the close hands the agent the sequence instead. It is deliberately one drive of one real
|
|
81
|
+
// flow ending in a verdict, because an install that ends in "the tools are available" is an
|
|
82
|
+
// install that ends in nothing.
|
|
83
|
+
const demo = 'Then drive one real flow of your app end to end and report the verdict — that is the install ' +
|
|
84
|
+
"proving itself, and it is the agent's job, not a separate command.";
|
|
85
|
+
const prove = 'Then run `npx @reticlehq/server status` — it confirms the app connected, or says exactly why ' +
|
|
86
|
+
'it has not.';
|
|
87
|
+
// Nothing registered this run (`--no-mcp`, or a client that needs a manual edit): a restart picks
|
|
88
|
+
// up nothing, so advice about one would be advice about something we did not do.
|
|
89
|
+
if (!wasMcpRegistered(mcpStatus))
|
|
90
|
+
return `${dev}\n${demo}\n${prove}`;
|
|
91
|
+
// ALREADY: the tools are reachable RIGHT NOW, and this is the branch that used to lie.
|
|
92
|
+
//
|
|
93
|
+
// `wasMcpRegistered` is true for both APPLY and ALREADY, which is correct for the funnel field it
|
|
94
|
+
// was written for and wrong here, because the two cases have opposite answers to "must anybody
|
|
95
|
+
// restart". Every second project on a machine, and every Claude Code plugin install, took the
|
|
96
|
+
// APPLY text: "restart your agent — the tools only appear after that". An agent reads this AFTER
|
|
97
|
+
// whatever its skill file said, and it describes the output in front of it, so it wins. The agent
|
|
98
|
+
// stops with the project wired and nothing driven, which is the exact failure the install path
|
|
99
|
+
// exists to prevent, caused by the install path itself.
|
|
100
|
+
if (mcpStatus === StepStatus.ALREADY) {
|
|
101
|
+
return (`${dev}\n` +
|
|
102
|
+
`${demo}\n` +
|
|
103
|
+
'The Reticle MCP server was already registered on this machine, so there is no restart to do ' +
|
|
104
|
+
'and the tools are available now.\n' +
|
|
105
|
+
`${prove}\n` +
|
|
106
|
+
'Once it shows a session, drive a flow — that is the install finished. Carry straight on; ' +
|
|
107
|
+
'nothing here is waiting on a human.');
|
|
108
|
+
}
|
|
109
|
+
return (`${dev}\n` +
|
|
110
|
+
`${demo}\n` +
|
|
111
|
+
'Then restart your agent so it picks up the new MCP server — restart Claude Code, reload the ' +
|
|
112
|
+
'window in Cursor, or hit Start in `.vscode/mcp.json` in VS Code.\n' +
|
|
113
|
+
'The tools only appear after that: your agent read its server list before Reticle existed, ' +
|
|
114
|
+
'and no slash command re-reads it (`/mcp` manages servers already loaded, so it cannot pick ' +
|
|
115
|
+
'up a new one). This is once per machine — Reticle is registered globally, so every later ' +
|
|
116
|
+
'project starts with the tools already there.\n' +
|
|
117
|
+
`${prove}\n` +
|
|
118
|
+
'Once it shows a session, drive one flow and report the verdict — that is the install ' +
|
|
119
|
+
'finished. Do it now; nothing here is waiting on a human.');
|
|
120
|
+
}
|
package/dist/init/confirm.d.ts
CHANGED
|
@@ -27,15 +27,6 @@
|
|
|
27
27
|
*/
|
|
28
28
|
import { InitConfirmation } from '@reticlehq/core';
|
|
29
29
|
import type { InitOutcome } from '@reticlehq/core';
|
|
30
|
-
/**
|
|
31
|
-
* How long a human is asked to wait.
|
|
32
|
-
*
|
|
33
|
-
* Long enough for a page that is already loading to finish dialling, short enough that somebody who
|
|
34
|
-
* has not restarted their dev server yet is not held hostage by a wait that cannot succeed — they
|
|
35
|
-
* have not typed the command that would make it succeed, and the message tells them which one.
|
|
36
|
-
*/
|
|
37
|
-
export declare const CONFIRM_WINDOW_MS = 12000;
|
|
38
|
-
export declare const CONFIRM_POLL_MS = 500;
|
|
39
30
|
export interface ConfirmDeps {
|
|
40
31
|
/** Session ids connected right now, or null when nothing is listening on the bridge port. */
|
|
41
32
|
listSessionIds: () => Promise<readonly string[] | null>;
|
|
@@ -78,22 +69,6 @@ export declare function confirmAppConnected(deps: ConfirmDeps): Promise<InitConf
|
|
|
78
69
|
* up when the agent loads the tools — so it is a notice.
|
|
79
70
|
*/
|
|
80
71
|
export declare function confirmationMessage(confirmation: InitConfirmation, port: number): string;
|
|
81
|
-
/**
|
|
82
|
-
* What a non-TTY run says instead of waiting.
|
|
83
|
-
*
|
|
84
|
-
* `init` deliberately does not block when nobody is at the terminal — a 12-second wait in a script
|
|
85
|
-
* is wrong, and an agent driving `init` through a shell is never a TTY. But silence was the wrong
|
|
86
|
-
* other half of that decision: the agent path IS the prescribed path (every skill and every README
|
|
87
|
-
* block tells an agent to run exactly this command), so the one message joining "init finished" to
|
|
88
|
-
* "an app connected" was withheld from most of the people who run it. They were left with files on
|
|
89
|
-
* disk, an exit code of 0, and nothing saying the page half had not happened.
|
|
90
|
-
*
|
|
91
|
-
* It claims nothing it did not check. It did not look for a session, so it does not say whether one
|
|
92
|
-
* exists — a one-shot look would be worse than none, because a daemon is shared across every project
|
|
93
|
-
* on the machine and "a session exists" is satisfied by somebody else's app, which is the false
|
|
94
|
-
* green `confirmAppConnected` refuses by design.
|
|
95
|
-
*/
|
|
96
|
-
export declare function unwatchedMessage(port: number): string;
|
|
97
72
|
/**
|
|
98
73
|
* Finish `init` on evidence rather than on a write.
|
|
99
74
|
*
|
package/dist/init/confirm.js
CHANGED
|
@@ -35,8 +35,8 @@ import { reportInitOutcome } from '../telemetry/init-telemetry.js';
|
|
|
35
35
|
* has not restarted their dev server yet is not held hostage by a wait that cannot succeed — they
|
|
36
36
|
* have not typed the command that would make it succeed, and the message tells them which one.
|
|
37
37
|
*/
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
const CONFIRM_WINDOW_MS = 12_000;
|
|
39
|
+
const CONFIRM_POLL_MS = 500;
|
|
40
40
|
/**
|
|
41
41
|
* Poll until an app connects or the window runs out.
|
|
42
42
|
*
|
|
@@ -110,7 +110,7 @@ export function confirmationMessage(confirmation, port) {
|
|
|
110
110
|
* on the machine and "a session exists" is satisfied by somebody else's app, which is the false
|
|
111
111
|
* green `confirmAppConnected` refuses by design.
|
|
112
112
|
*/
|
|
113
|
-
|
|
113
|
+
function unwatchedMessage(port) {
|
|
114
114
|
return (' [ℹ] the files are written. An app CONNECTING is what finishes the install, and this run did ' +
|
|
115
115
|
'not wait to see one.\n' +
|
|
116
116
|
' Start the dev server, load the app in a browser, then ' +
|
package/dist/init/cra.d.ts
CHANGED
|
@@ -17,6 +17,19 @@ export declare const CRA_DEV_MODULE_IMPORT = "import './reticle-dev';";
|
|
|
17
17
|
export declare const CRA_DEV_MODULE_PATH = "src/reticle-dev.ts";
|
|
18
18
|
export declare const CRA_ENV_PATH = ".env.development.local";
|
|
19
19
|
export declare const TOKEN_VAR = "REACT_APP_RETICLE_TOKEN";
|
|
20
|
+
/**
|
|
21
|
+
* The bridge URL, carried through the same channel as the token.
|
|
22
|
+
*
|
|
23
|
+
* CRA is the one supported stack with no build-time hook of ours: no plugin, no config wrapper, no
|
|
24
|
+
* `define`. Vite and Astro re-resolve the daemon every dev-server start and Next does it through
|
|
25
|
+
* `withReticle`; CRA has nowhere to run that code. So the URL goes where the token already goes, and
|
|
26
|
+
* is refreshed by the same `init` step that refreshes the token.
|
|
27
|
+
*
|
|
28
|
+
* That makes `reticle init` the heal rather than the dev server, which is weaker and is stated
|
|
29
|
+
* plainly in the step's own detail. It is still strictly better than a URL frozen into a source file
|
|
30
|
+
* the owner has to hand-edit, and `.env.development.local` is already the per-machine file.
|
|
31
|
+
*/
|
|
32
|
+
export declare const URL_VAR = "REACT_APP_RETICLE_URL";
|
|
20
33
|
/**
|
|
21
34
|
* What the app itself says when the token is not there.
|
|
22
35
|
*
|
|
@@ -45,6 +58,6 @@ export declare const CRA_TOKEN_PER_MACHINE_NOTICE: string;
|
|
|
45
58
|
/** Add the import after the last existing one, or null when it is already present. */
|
|
46
59
|
export declare function craImportPatch(source: string): string | null;
|
|
47
60
|
/** Set the token variable, or null when nothing needs to change. */
|
|
48
|
-
export declare function craEnvPatch(existing: string | null, token: string): string | null;
|
|
61
|
+
export declare function craEnvPatch(existing: string | null, token: string, url?: string): string | null;
|
|
49
62
|
/** The dev-only connect module imported from `src/index.tsx`. */
|
|
50
63
|
export declare function craDevModuleFile(port: number | undefined, projectId?: string): string;
|
package/dist/init/cra.js
CHANGED
|
@@ -12,12 +12,26 @@
|
|
|
12
12
|
* `REACT_APP_`. So it travels through `.env.development.local`, which is CRA's own documented
|
|
13
13
|
* mechanism and is gitignored by CRA's own template.
|
|
14
14
|
*/
|
|
15
|
+
import { bridgeWsUrl } from '@reticlehq/core';
|
|
15
16
|
import { CLI } from './agent-rules.js';
|
|
16
17
|
/** The line added to `src/index.tsx`. Side-effect import: the module guards itself on NODE_ENV. */
|
|
17
18
|
export const CRA_DEV_MODULE_IMPORT = "import './reticle-dev';";
|
|
18
19
|
export const CRA_DEV_MODULE_PATH = 'src/reticle-dev.ts';
|
|
19
20
|
export const CRA_ENV_PATH = '.env.development.local';
|
|
20
21
|
export const TOKEN_VAR = 'REACT_APP_RETICLE_TOKEN';
|
|
22
|
+
/**
|
|
23
|
+
* The bridge URL, carried through the same channel as the token.
|
|
24
|
+
*
|
|
25
|
+
* CRA is the one supported stack with no build-time hook of ours: no plugin, no config wrapper, no
|
|
26
|
+
* `define`. Vite and Astro re-resolve the daemon every dev-server start and Next does it through
|
|
27
|
+
* `withReticle`; CRA has nowhere to run that code. So the URL goes where the token already goes, and
|
|
28
|
+
* is refreshed by the same `init` step that refreshes the token.
|
|
29
|
+
*
|
|
30
|
+
* That makes `reticle init` the heal rather than the dev server, which is weaker and is stated
|
|
31
|
+
* plainly in the step's own detail. It is still strictly better than a URL frozen into a source file
|
|
32
|
+
* the owner has to hand-edit, and `.env.development.local` is already the per-machine file.
|
|
33
|
+
*/
|
|
34
|
+
export const URL_VAR = 'REACT_APP_RETICLE_URL';
|
|
21
35
|
/**
|
|
22
36
|
* What the app itself says when the token is not there.
|
|
23
37
|
*
|
|
@@ -64,25 +78,38 @@ export function craImportPatch(source) {
|
|
|
64
78
|
return lines.join('\n');
|
|
65
79
|
}
|
|
66
80
|
/** Set the token variable, or null when nothing needs to change. */
|
|
67
|
-
export function craEnvPatch(existing, token) {
|
|
81
|
+
export function craEnvPatch(existing, token, url) {
|
|
68
82
|
if ('' === token)
|
|
69
83
|
return null;
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
84
|
+
const pairs = [
|
|
85
|
+
[TOKEN_VAR, token],
|
|
86
|
+
...(url !== undefined && url.length > 0 ? [[URL_VAR, url]] : []),
|
|
87
|
+
];
|
|
88
|
+
let next = null === existing || '' === existing.trim() ? '' : existing;
|
|
89
|
+
for (const [name, value] of pairs) {
|
|
90
|
+
const line = `${name}=${value}`;
|
|
91
|
+
if (next.includes(line))
|
|
92
|
+
continue;
|
|
93
|
+
// Replace rather than append: two assignments of one variable is a silent coin flip on which
|
|
94
|
+
// wins, and for the URL that coin decides which daemon the app talks to.
|
|
95
|
+
if (new RegExp(`^${name}=`, 'm').test(next)) {
|
|
96
|
+
next = next.replace(new RegExp(`^${name}=.*$`, 'm'), line);
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
next = `${'' === next || next.endsWith('\n') ? next : `${next}\n`}${line}\n`;
|
|
100
|
+
}
|
|
78
101
|
}
|
|
79
|
-
return
|
|
102
|
+
return next === (existing ?? '') ? null : next;
|
|
80
103
|
}
|
|
81
104
|
/** The dev-only connect module imported from `src/index.tsx`. */
|
|
82
105
|
export function craDevModuleFile(port, projectId) {
|
|
83
106
|
const fields = [];
|
|
107
|
+
// `bridgeWsUrl`, not a hand-written string. This was the only client URL in the product spelling
|
|
108
|
+
// the host as `127.0.0.1` while every other generator said `localhost` — same endpoint, but a
|
|
109
|
+
// difference with no reason behind it is the kind that becomes a real one after somebody edits half
|
|
110
|
+
// of it.
|
|
84
111
|
if (port !== undefined)
|
|
85
|
-
fields.push(`url: '
|
|
112
|
+
fields.push(`url: '${bridgeWsUrl(port)}'`);
|
|
86
113
|
if (projectId !== undefined && projectId.length > 0)
|
|
87
114
|
fields.push(`projectId: '${projectId}'`);
|
|
88
115
|
const inline = fields.length > 0 ? `${fields.join(', ')}, ` : '';
|
|
@@ -95,10 +122,14 @@ if (process.env.NODE_ENV === 'development') {
|
|
|
95
122
|
void import('@reticlehq/react').then(({ reticle, install }) => {
|
|
96
123
|
install();
|
|
97
124
|
const token = process.env.${TOKEN_VAR} ?? '';
|
|
125
|
+
// Written by \`reticle init\` from the daemon that was live when it ran, and refreshed by
|
|
126
|
+
// re-running it. CRA gives us no hook to resolve this at dev-server start, so if the daemon
|
|
127
|
+
// moves, re-run \`reticle init\` rather than editing the url below.
|
|
128
|
+
const url = process.env.${URL_VAR} ?? '';
|
|
98
129
|
// Loud on purpose: without this the only symptom is the bridge's generic auth failure.
|
|
99
130
|
if (token.length === 0) console.error(${JSON.stringify(`[reticle] ${CRA_TOKEN_MISSING_NOTE}`)});
|
|
100
131
|
// Still attempt it — a bridge running without a token pairs fine.
|
|
101
|
-
reticle.connect({ ${inline}...(token.length > 0 ? { token } : {}) });
|
|
132
|
+
reticle.connect({ ${inline}...(url.length > 0 ? { url } : {}), ...(token.length > 0 ? { token } : {}) });
|
|
102
133
|
});
|
|
103
134
|
}
|
|
104
135
|
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which command starts this project, and should we start it ourselves?
|
|
3
|
+
*
|
|
4
|
+
* `init` finishes by telling the human to start or restart their dev server, in a closing
|
|
5
|
+
* paragraph, in a terminal that is about to be cleared by the client restart it also asks for.
|
|
6
|
+
* That is the second unmarked hand-off in the install, after the capabilities file, and it lands
|
|
7
|
+
* on the same surface: prose nobody re-reads.
|
|
8
|
+
*
|
|
9
|
+
* Every fact needed to remove it is already in the project. `package.json` names the script; the
|
|
10
|
+
* framework tells us the conventional port; a probe tells us whether something already answers
|
|
11
|
+
* there. Nothing here needs a human.
|
|
12
|
+
*
|
|
13
|
+
* Kept as a PURE decision, separate from spawning, because the interesting part is the judgement —
|
|
14
|
+
* which script, and whether to touch anything at all — and that is what wants testing. Spawning a
|
|
15
|
+
* process is not.
|
|
16
|
+
*/
|
|
17
|
+
export declare const DevScriptChoice: {
|
|
18
|
+
/** Something already answers on the port. Use it; never replace a server the user is running. */
|
|
19
|
+
readonly ALREADY_SERVING: "already-serving";
|
|
20
|
+
/** We know the command and nothing is listening. Start it. */
|
|
21
|
+
readonly START: "start";
|
|
22
|
+
/** No script we recognise. Say so rather than guess a command. */
|
|
23
|
+
readonly NO_SCRIPT: "no-script";
|
|
24
|
+
};
|
|
25
|
+
export type DevScriptChoice = (typeof DevScriptChoice)[keyof typeof DevScriptChoice];
|
|
26
|
+
export interface DevScriptPlan {
|
|
27
|
+
choice: DevScriptChoice;
|
|
28
|
+
/** The npm script name, when there is one. */
|
|
29
|
+
script?: string;
|
|
30
|
+
/** What a human would type, for the line we print. */
|
|
31
|
+
command?: string;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Pick the script and decide whether to run it.
|
|
35
|
+
*
|
|
36
|
+
* `serving` is the probe result, passed in rather than measured here so the decision stays pure.
|
|
37
|
+
* When something is already answering we do not care which script exists: the rule is never to
|
|
38
|
+
* start a second server, because the one that is running may be the user's, with their state in it.
|
|
39
|
+
*/
|
|
40
|
+
export declare function planDevScript(scripts: Readonly<Record<string, string>>, packageManager: string, serving: boolean): DevScriptPlan;
|
|
41
|
+
/**
|
|
42
|
+
* The command to print for this project, straight from its `package.json`.
|
|
43
|
+
*
|
|
44
|
+
* Lives here rather than in `run.ts` because it is the same judgement as `planDevScript` with the
|
|
45
|
+
* parsing attached, and `run.ts` is at its cohesion limit. Returns undefined for anything it cannot
|
|
46
|
+
* read: this phrasing decides nothing, so it must never be able to break an install.
|
|
47
|
+
*/
|
|
48
|
+
export declare function devCommandFrom(pkgRaw: string, packageManager: string): string | undefined;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which command starts this project, and should we start it ourselves?
|
|
3
|
+
*
|
|
4
|
+
* `init` finishes by telling the human to start or restart their dev server, in a closing
|
|
5
|
+
* paragraph, in a terminal that is about to be cleared by the client restart it also asks for.
|
|
6
|
+
* That is the second unmarked hand-off in the install, after the capabilities file, and it lands
|
|
7
|
+
* on the same surface: prose nobody re-reads.
|
|
8
|
+
*
|
|
9
|
+
* Every fact needed to remove it is already in the project. `package.json` names the script; the
|
|
10
|
+
* framework tells us the conventional port; a probe tells us whether something already answers
|
|
11
|
+
* there. Nothing here needs a human.
|
|
12
|
+
*
|
|
13
|
+
* Kept as a PURE decision, separate from spawning, because the interesting part is the judgement —
|
|
14
|
+
* which script, and whether to touch anything at all — and that is what wants testing. Spawning a
|
|
15
|
+
* process is not.
|
|
16
|
+
*/
|
|
17
|
+
/** Script names a JS project uses to run itself, in the order we would pick them. */
|
|
18
|
+
const CANDIDATES = ['dev', 'start', 'serve'];
|
|
19
|
+
export const DevScriptChoice = {
|
|
20
|
+
/** Something already answers on the port. Use it; never replace a server the user is running. */
|
|
21
|
+
ALREADY_SERVING: 'already-serving',
|
|
22
|
+
/** We know the command and nothing is listening. Start it. */
|
|
23
|
+
START: 'start',
|
|
24
|
+
/** No script we recognise. Say so rather than guess a command. */
|
|
25
|
+
NO_SCRIPT: 'no-script',
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Pick the script and decide whether to run it.
|
|
29
|
+
*
|
|
30
|
+
* `serving` is the probe result, passed in rather than measured here so the decision stays pure.
|
|
31
|
+
* When something is already answering we do not care which script exists: the rule is never to
|
|
32
|
+
* start a second server, because the one that is running may be the user's, with their state in it.
|
|
33
|
+
*/
|
|
34
|
+
export function planDevScript(scripts, packageManager, serving) {
|
|
35
|
+
if (serving)
|
|
36
|
+
return { choice: DevScriptChoice.ALREADY_SERVING };
|
|
37
|
+
const name = CANDIDATES.find((c) => 'string' === typeof scripts[c] && scripts[c] !== '');
|
|
38
|
+
if (name === undefined)
|
|
39
|
+
return { choice: DevScriptChoice.NO_SCRIPT };
|
|
40
|
+
// `npm` needs `run`; the others take the script name directly. Getting this wrong prints a
|
|
41
|
+
// command that fails, which is worse than printing nothing.
|
|
42
|
+
const command = 'npm' === packageManager ? `npm run ${name}` : `${packageManager} ${name}`;
|
|
43
|
+
return { choice: DevScriptChoice.START, script: name, command };
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The command to print for this project, straight from its `package.json`.
|
|
47
|
+
*
|
|
48
|
+
* Lives here rather than in `run.ts` because it is the same judgement as `planDevScript` with the
|
|
49
|
+
* parsing attached, and `run.ts` is at its cohesion limit. Returns undefined for anything it cannot
|
|
50
|
+
* read: this phrasing decides nothing, so it must never be able to break an install.
|
|
51
|
+
*/
|
|
52
|
+
export function devCommandFrom(pkgRaw, packageManager) {
|
|
53
|
+
try {
|
|
54
|
+
const parsed = JSON.parse(pkgRaw);
|
|
55
|
+
const scripts = 'object' === typeof parsed && null !== parsed
|
|
56
|
+
? (parsed.scripts ?? {})
|
|
57
|
+
: {};
|
|
58
|
+
return planDevScript(scripts, packageManager, false).command;
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
}
|
package/dist/init/mcp.d.ts
CHANGED
|
@@ -15,11 +15,16 @@ export { MCP_SERVER_NAME } from '@reticlehq/core';
|
|
|
15
15
|
/**
|
|
16
16
|
* The registered command is STILL bare `npx` on every platform, deliberately.
|
|
17
17
|
*
|
|
18
|
-
* Windows is
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
18
|
+
* Windows is most of Reticle's users and, when this was written, had no CI. There is Windows CI
|
|
19
|
+
* now, so the `cmd /c` fallback is testable. Defaulting the majority platform to that launch
|
|
20
|
+
* command is still the riskier change: `.cmd` has spawn caveats in some hosts, and a registration
|
|
21
|
+
* that works on the machines we can test can still fail in the hosts we cannot. So the fallback
|
|
22
|
+
* stays documented rather than defaulted.
|
|
23
|
+
*
|
|
24
|
+
* What DID change is when it is printed. `mcpManual` used to run only when the `claude` CLI was
|
|
25
|
+
* absent. The reported Windows install had `claude mcp add` succeed, so the one paragraph that
|
|
26
|
+
* would have unblocked zero tools never appeared. `mcpWindowsNote` is that paragraph, and init
|
|
27
|
+
* prints it on every Windows run — including the ones that look like a clean registration.
|
|
23
28
|
*/
|
|
24
29
|
export declare const NPX = "npx";
|
|
25
30
|
/**
|
|
@@ -74,5 +79,11 @@ export declare function claudeAvailableProbe(): {
|
|
|
74
79
|
command: string;
|
|
75
80
|
args: string[];
|
|
76
81
|
};
|
|
82
|
+
/**
|
|
83
|
+
* The Windows `cmd /c npx` registration. Printed on every Windows init, not only when the `claude`
|
|
84
|
+
* CLI is missing — a successful `claude mcp add` of bare `npx` is exactly the failure that reported
|
|
85
|
+
* as a clean install with zero tools.
|
|
86
|
+
*/
|
|
87
|
+
export declare function mcpWindowsNote(): string;
|
|
77
88
|
/** Printed when the `claude` CLI isn't available — register Reticle globally once, by hand. */
|
|
78
89
|
export declare function mcpManual(): string;
|