@reticlehq/server 2.8.0 → 2.9.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 +130 -813
- package/dist/bridge/bridge.d.ts +23 -0
- package/dist/bridge/bridge.js +80 -2
- package/dist/bridge/bridge.test-harness.d.ts +5 -0
- package/dist/bridge/bridge.test-harness.js +6 -4
- package/dist/capsule/causal-summary.d.ts +16 -1
- package/dist/capsule/causal-summary.js +2 -1
- package/dist/cli/cli-doctor.js +41 -13
- package/dist/cli/cli-flow-commands.js +8 -8
- package/dist/cli/cli-launch.d.ts +0 -17
- package/dist/cli/cli-launch.js +0 -28
- package/dist/cli/cli-port.d.ts +24 -0
- package/dist/cli/cli-port.js +66 -35
- package/dist/cli/cli-verify.d.ts +21 -0
- package/dist/cli/cli-verify.js +45 -1
- package/dist/cli/config-discovery.d.ts +35 -0
- package/dist/cli/config-discovery.js +163 -0
- package/dist/cli/doctor-daemon-line.js +4 -1
- package/dist/cli/doctor-project-line.d.ts +37 -0
- package/dist/cli/doctor-project-line.js +35 -0
- package/dist/cli/doctor-rows.d.ts +51 -0
- package/dist/cli/doctor-rows.js +54 -0
- package/dist/cli/doctor-sessions-line.js +6 -3
- package/dist/cli/drive-attach.d.ts +99 -0
- package/dist/cli/drive-attach.js +203 -0
- package/dist/cli/drive-command.d.ts +13 -0
- package/dist/cli/drive-command.js +103 -0
- package/dist/cli/open-note.d.ts +20 -0
- package/dist/cli/open-note.js +37 -0
- package/dist/cli/status-next-action.d.ts +37 -0
- package/dist/cli/status-next-action.js +61 -0
- package/dist/cli.js +65 -70
- package/dist/events/contradictions.d.ts +32 -0
- package/dist/events/contradictions.js +9 -5
- package/dist/events/declared.d.ts +45 -0
- package/dist/events/declared.js +73 -0
- package/dist/events/echo-mismatch.d.ts +8 -2
- package/dist/events/echo-mismatch.js +33 -5
- package/dist/events/event-filters.d.ts +1 -1
- package/dist/events/event-filters.js +7 -4
- package/dist/events/predicate-eval.d.ts +17 -91
- package/dist/events/predicate-eval.js +85 -326
- package/dist/events/predicate-parse.js +31 -3
- package/dist/events/predicate-route.d.ts +32 -0
- package/dist/events/predicate-route.js +102 -0
- package/dist/events/predicate-schema.d.ts +99 -0
- package/dist/events/predicate-schema.js +309 -0
- package/dist/events/predicate.d.ts +8 -0
- package/dist/events/predicate.js +127 -5
- package/dist/flows/annotate-tools.js +3 -0
- package/dist/flows/annotate.js +18 -4
- package/dist/flows/coverage.d.ts +38 -5
- package/dist/flows/coverage.js +30 -4
- package/dist/flows/decision.d.ts +15 -0
- package/dist/flows/decision.js +1 -1
- package/dist/flows/flow-replay-run.js +8 -1
- package/dist/flows/flow-tools.js +9 -1
- package/dist/flows/replay.js +3 -0
- package/dist/honesty/accepted-write.d.ts +3 -0
- package/dist/honesty/accepted-write.js +8 -3
- package/dist/honesty/already-true.js +15 -0
- package/dist/honesty/blind-spots.js +4 -0
- package/dist/honesty/honesty.d.ts +11 -0
- package/dist/honesty/honesty.js +1 -0
- package/dist/honesty/uncaptured-bodies.d.ts +4 -2
- package/dist/honesty/uncaptured-bodies.js +9 -2
- package/dist/honesty/unread-outcome.d.ts +8 -0
- package/dist/honesty/unread-outcome.js +36 -9
- package/dist/honesty/unsettled.d.ts +9 -0
- package/dist/honesty/unsettled.js +7 -3
- package/dist/honesty/verified.d.ts +29 -3
- package/dist/honesty/verified.js +51 -8
- package/dist/http-server.d.ts +19 -0
- package/dist/http-server.js +202 -4
- package/dist/impact/impact-recorder.d.ts +23 -0
- package/dist/impact/impact-recorder.js +76 -0
- package/dist/impact/impact-store.d.ts +47 -0
- package/dist/impact/impact-store.js +171 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +44 -10
- package/dist/init/agent-rules.d.ts +20 -0
- package/dist/init/agent-rules.js +36 -0
- package/dist/init/astro-patch.js +27 -1
- package/dist/init/confirm.d.ts +21 -3
- package/dist/init/confirm.js +32 -5
- package/dist/init/connect-steps.d.ts +2 -0
- package/dist/init/connect-steps.js +50 -0
- package/dist/init/csp-check.d.ts +38 -0
- package/dist/init/csp-check.js +92 -0
- package/dist/init/csp-doctor.d.ts +25 -0
- package/dist/init/csp-doctor.js +49 -0
- package/dist/init/cursor.js +6 -9
- package/dist/init/detect.d.ts +8 -0
- package/dist/init/detect.js +33 -3
- package/dist/init/mcp-clients.d.ts +1 -0
- package/dist/init/mcp-clients.js +51 -7
- package/dist/init/mcp.d.ts +15 -0
- package/dist/init/mcp.js +19 -0
- package/dist/init/plan-framework.d.ts +10 -0
- package/dist/init/plan-framework.js +25 -1
- package/dist/init/plan.d.ts +7 -2
- package/dist/init/plan.js +66 -30
- package/dist/init/run.js +11 -5
- package/dist/init/slash-command.d.ts +1 -1
- package/dist/init/slash-command.js +12 -4
- package/dist/init/snippets.d.ts +1 -1
- package/dist/init/snippets.js +35 -7
- package/dist/init/vite-config.js +24 -2
- package/dist/input/network-mock-tools.js +8 -1
- package/dist/input/scroll-find.d.ts +6 -0
- package/dist/input/scroll-find.js +17 -2
- package/dist/input/scroll-tools.js +4 -0
- package/dist/journal/attach-journal.d.ts +9 -1
- package/dist/journal/attach-journal.js +13 -2
- package/dist/journal/journal-query.d.ts +13 -0
- package/dist/journal/journal-query.js +28 -3
- package/dist/journal/workspace-gitignore.d.ts +10 -0
- package/dist/journal/workspace-gitignore.js +59 -0
- package/dist/mcp/mcp-proxy.d.ts +5 -66
- package/dist/mcp/mcp-proxy.js +180 -163
- package/dist/mcp/mcp.d.ts +10 -1
- package/dist/mcp/mcp.js +47 -27
- package/dist/mcp/proxy-handshake.d.ts +82 -1
- package/dist/mcp/proxy-handshake.js +109 -2
- package/dist/mcp/proxy-lifecycle.d.ts +10 -1
- package/dist/mcp/proxy-lifecycle.js +11 -2
- package/dist/mcp/proxy-log.d.ts +63 -0
- package/dist/mcp/proxy-log.js +132 -0
- package/dist/mcp/server-instructions.d.ts +17 -0
- package/dist/mcp/server-instructions.js +56 -0
- package/dist/mcp/shared-params.d.ts +27 -0
- package/dist/mcp/shared-params.js +36 -0
- package/dist/project/reticle-dir.d.ts +2 -0
- package/dist/project/reticle-dir.js +1 -0
- package/dist/session/connection-memory.d.ts +59 -0
- package/dist/session/connection-memory.js +108 -0
- package/dist/session/dev-command.d.ts +26 -0
- package/dist/session/dev-command.js +89 -0
- package/dist/session/dev-server-probe.d.ts +68 -0
- package/dist/session/dev-server-probe.js +102 -7
- package/dist/session/last-act.d.ts +26 -0
- package/dist/session/last-act.js +35 -1
- package/dist/session/live-control-tools.js +42 -5
- package/dist/session/no-session-diagnosis.d.ts +48 -2
- package/dist/session/no-session-diagnosis.js +176 -20
- package/dist/session/no-session-next-action.d.ts +46 -0
- package/dist/session/no-session-next-action.js +85 -0
- package/dist/session/no-session-watch.d.ts +40 -1
- package/dist/session/no-session-watch.js +165 -11
- package/dist/session/pending-commands.d.ts +12 -0
- package/dist/session/pending-commands.js +13 -1
- package/dist/session/session-manager.d.ts +4 -0
- package/dist/session/session-manager.js +35 -5
- package/dist/session/session.d.ts +10 -1
- package/dist/session/session.js +146 -5
- package/dist/telemetry/app-instrumented.d.ts +8 -0
- package/dist/telemetry/app-instrumented.js +10 -0
- package/dist/telemetry/bug-found.d.ts +18 -0
- package/dist/telemetry/bug-found.js +14 -0
- package/dist/telemetry/daemon-telemetry.js +24 -0
- package/dist/telemetry/install-source.d.ts +20 -0
- package/dist/telemetry/install-source.js +34 -0
- package/dist/telemetry/instrumentation-stall.d.ts +50 -0
- package/dist/telemetry/instrumentation-stall.js +90 -0
- package/dist/telemetry/session-metrics.d.ts +8 -0
- package/dist/telemetry/session-metrics.js +10 -0
- package/dist/telemetry/telemetry.d.ts +3 -1
- package/dist/telemetry/telemetry.js +13 -2
- package/dist/tools/act-tools.js +166 -29
- package/dist/tools/assert-verdict.js +32 -7
- package/dist/tools/browser-tools.js +10 -0
- package/dist/tools/dynamic-tools.js +16 -7
- package/dist/tools/error-recovery.js +9 -5
- package/dist/tools/invoke-tool.js +20 -3
- package/dist/tools/lease-hint.d.ts +30 -8
- package/dist/tools/lease-hint.js +65 -14
- package/dist/tools/lease-tools.d.ts +11 -0
- package/dist/tools/lease-tools.js +141 -96
- package/dist/tools/observe-tools.js +9 -3
- package/dist/tools/react-grace.d.ts +4 -0
- package/dist/tools/react-grace.js +6 -3
- package/dist/tools/read-tools.js +47 -1
- package/dist/tools/resolve-target.d.ts +33 -0
- package/dist/tools/resolve-target.js +53 -0
- package/dist/tools/settle-in-flight.d.ts +15 -0
- package/dist/tools/settle-in-flight.js +42 -2
- package/dist/tools/snapshot-delta.d.ts +3 -0
- package/dist/tools/snapshot-delta.js +67 -6
- package/dist/tools/tool-kit.d.ts +8 -0
- package/dist/tools/tool-surface.d.ts +60 -0
- package/dist/tools/tool-surface.js +85 -19
- package/dist/tools/tools.js +45 -4
- package/dist/tools/unadvertised-help.js +9 -6
- package/docs/README.md +1 -2
- package/docs/actions.mdx +41 -8
- package/docs/agent-cheatsheet.md +4 -2
- package/docs/architecture.md +6 -0
- package/docs/benchmarks.md +60 -12
- package/docs/best-practices.mdx +11 -6
- package/docs/capabilities.mdx +201 -54
- package/docs/cli/affected.mdx +6 -4
- package/docs/cli/capsules.mdx +9 -8
- package/docs/cli/cloud.mdx +30 -4
- package/docs/cli/doctor.mdx +41 -8
- package/docs/cli/drive.mdx +34 -15
- package/docs/cli/gate.mdx +6 -6
- package/docs/cli/help.mdx +8 -6
- package/docs/cli/hunt.mdx +18 -2
- package/docs/cli/identify.mdx +16 -2
- package/docs/cli/init.mdx +35 -8
- package/docs/cli/kill.mdx +5 -5
- package/docs/cli/license.mdx +1 -1
- package/docs/cli/open.mdx +6 -0
- package/docs/cli/restart.mdx +6 -6
- package/docs/cli/serve.mdx +10 -3
- package/docs/cli/status.mdx +29 -5
- package/docs/cli/stop.mdx +8 -0
- package/docs/cli/verify.mdx +20 -1
- package/docs/cli/version.mdx +1 -1
- package/docs/cli/watch.mdx +9 -2
- package/docs/cli.mdx +44 -22
- package/docs/debugging.md +3 -1
- package/docs/deploy-checks.md +30 -1
- package/docs/desktop.mdx +276 -80
- package/docs/docs.json +13 -12
- package/docs/enterprise.md +2 -0
- package/docs/faq.mdx +49 -35
- package/docs/fixtures.md +2 -0
- package/docs/flows.md +3 -1
- package/docs/for-agents.md +3 -3
- package/docs/frameworks.mdx +46 -7
- package/docs/gate-plan.md +4 -2
- package/docs/gates.md +7 -5
- package/docs/getting-started.md +53 -12
- package/docs/human-control.md +2 -0
- package/docs/index.mdx +43 -1
- package/docs/install-agentic.mdx +13 -4
- package/docs/install-manual.mdx +6 -4
- package/docs/instrumentation.mdx +7 -1
- package/docs/integration-patterns.md +3 -1
- package/docs/local-registry.md +4 -2
- package/docs/multi-agent-testing.md +3 -1
- package/docs/packages/babel-plugin.mdx +15 -6
- package/docs/packages/browser.mdx +2 -2
- package/docs/packages/core.mdx +26 -16
- package/docs/packages/electron.mdx +5 -7
- package/docs/packages/eslint-plugin.mdx +5 -3
- package/docs/packages/next.mdx +3 -5
- package/docs/packages/react.mdx +3 -3
- package/docs/packages/server.mdx +2 -2
- package/docs/packages/tauri.mdx +6 -6
- package/docs/packages/test.mdx +18 -7
- package/docs/packages/vite-plugin.mdx +4 -6
- package/docs/packages.mdx +14 -13
- package/docs/platform-integration.md +2 -0
- package/docs/predicates.mdx +76 -7
- package/docs/quickstart.mdx +51 -5
- package/docs/recipes.mdx +62 -22
- package/docs/skill-file.mdx +3 -1
- package/docs/state-management.mdx +2 -0
- package/docs/system-map.md +3 -1
- package/docs/telemetry-contract.md +16 -5
- package/docs/telemetry-events.mdx +63 -18
- package/docs/telemetry.md +8 -5
- package/docs/testing.md +35 -1
- package/docs/token-efficiency.md +8 -2
- package/docs/tools-act-and-wait.mdx +64 -14
- package/docs/tools-act-sequence.mdx +34 -17
- package/docs/tools-act.mdx +24 -11
- package/docs/tools-assert.mdx +69 -10
- package/docs/tools-console.mdx +11 -7
- package/docs/tools-inspect.mdx +11 -8
- package/docs/tools-navigate.mdx +41 -5
- package/docs/tools-network.mdx +42 -9
- package/docs/tools-observe.mdx +66 -10
- package/docs/tools-overview.mdx +26 -23
- package/docs/tools-query.mdx +52 -8
- package/docs/tools-session-and-feedback.mdx +30 -9
- package/docs/tools-sessions.mdx +39 -10
- package/docs/tools-snapshot.mdx +57 -3
- package/docs/tools-state.mdx +24 -1
- package/docs/tools-tools-and-run.mdx +40 -9
- package/docs/tools-wait-for.mdx +34 -15
- package/docs/troubleshooting.mdx +80 -10
- package/docs/usage.md +43 -21
- package/docs/vs-chrome-devtools-mcp.mdx +41 -0
- package/docs/vs-playwright-mcp.mdx +42 -2
- package/docs/vs-screenshots.mdx +42 -1
- package/docs/what-is-reticle.mdx +1 -1
- package/docs/why-reticle.mdx +3 -1
- package/package.json +5 -5
- package/docs/desktop-apps.md +0 -265
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which `init` steps decide whether the app can dial the daemon.
|
|
3
|
+
*
|
|
4
|
+
* Lifted out of `plan.ts` when that file reached the size backstop. A cohesive unit on its own
|
|
5
|
+
* terms: one question, asked of a step title, with one consequence — a manual step in this set makes
|
|
6
|
+
* `init` exit non-zero instead of reporting a success over an app that can never connect.
|
|
7
|
+
*
|
|
8
|
+
* Matched on TITLES, which is the fragile part and worth stating: renaming a step in
|
|
9
|
+
* `plan-framework.ts` silently drops it out of this set and nothing goes red. `connect-steps.test.ts`
|
|
10
|
+
* pins the membership for exactly that reason.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Titles of the steps WITHOUT which no session ever appears.
|
|
14
|
+
*
|
|
15
|
+
* A ⚠ on one of these is not a warning, it is a guaranteed failure: nothing performs the manual step,
|
|
16
|
+
* so the app will not connect and every Reticle tool will answer "no browser session connected".
|
|
17
|
+
* Reported from a field sweep, where the ⚠ count and "did it connect" were treated as independent
|
|
18
|
+
* signals and are not.
|
|
19
|
+
*/
|
|
20
|
+
const CONNECT_STEP_TITLES = new Set([
|
|
21
|
+
'Connect snippet',
|
|
22
|
+
'Connect snippet (CRA)',
|
|
23
|
+
'Connect snippet (Astro)',
|
|
24
|
+
'Connect snippet (Nuxt)',
|
|
25
|
+
'Reticle client hook',
|
|
26
|
+
'Reticle connect module',
|
|
27
|
+
'ReticleDev component',
|
|
28
|
+
// Writing the component and MOUNTING it are two steps, and only the write was here. A root layout
|
|
29
|
+
// whose shape `init` does not recognise leaves the component on disk and never rendered: the SDK
|
|
30
|
+
// is in the project, nothing imports it, and `init` exited 0 over an app that could not connect.
|
|
31
|
+
'Mount ReticleDev',
|
|
32
|
+
// NOT here, and the reason is worth keeping: 'Pairing token'.
|
|
33
|
+
//
|
|
34
|
+
// It is a genuine connect step — CRA inlines only REACT_APP_*, so without the token in the env
|
|
35
|
+
// file the bridge refuses every connection and the app boots, looks correct, and never pairs. But
|
|
36
|
+
// it goes MANUAL in exactly one situation: no daemon has ever run on this machine, so there is no
|
|
37
|
+
// token to inline. That is the FIRST CRA install on a fresh machine, i.e. the first-time user —
|
|
38
|
+
// and making that exit non-zero reports a broken install to the one person least able to tell
|
|
39
|
+
// that it is not.
|
|
40
|
+
//
|
|
41
|
+
// The real fix is for `init` to mint the token rather than only read it, which is what both the
|
|
42
|
+
// daemon and the Vite plugin already do. `runInit` is synchronous and the existing helpers are
|
|
43
|
+
// not, and `gate:install` scaffolds no CRA app, so that change would ship with no coverage of the
|
|
44
|
+
// path it changes. It is worth doing, and worth doing with a scaffold behind it.
|
|
45
|
+
'Vite plugin',
|
|
46
|
+
]);
|
|
47
|
+
/** True when this step is what makes the app dial the daemon. */
|
|
48
|
+
export function isConnectStep(title) {
|
|
49
|
+
return CONNECT_STEP_TITLES.has(title);
|
|
50
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Does this app's Content-Security-Policy let the browser reach the Reticle bridge?
|
|
3
|
+
*
|
|
4
|
+
* Two independent field reports, both Next: `init` reported every step successful, the SDK mounted,
|
|
5
|
+
* the dial URL was correct, and the app never connected because a strict `connect-src` excluded
|
|
6
|
+
* `ws://localhost:<port>`. The browser blocks the WebSocket and says so in its own console, which
|
|
7
|
+
* nothing on the Reticle side reads — so `status` and `doctor` reported perfect health at an app
|
|
8
|
+
* that could not connect at all, forever. A setup step that exits 0 while leaving the user broken is
|
|
9
|
+
* the worst failure mode this repo has.
|
|
10
|
+
*
|
|
11
|
+
* A TEXT SCAN, deliberately. `headers()` is a function; `init` cannot execute a user's Next config
|
|
12
|
+
* without importing their whole app, and the thing worth finding is the policy string a developer
|
|
13
|
+
* typed — in `next.config.*`, in middleware, or in a `<meta http-equiv>` tag. The scan finds the
|
|
14
|
+
* string wherever it is written.
|
|
15
|
+
*
|
|
16
|
+
* NARROW on purpose: it fires only when a `connect-src` exists AND does not admit the bridge. No
|
|
17
|
+
* CSP, or one that already covers us, produces nothing. A warning that fires on a working setup
|
|
18
|
+
* costs precisely what a green check on a broken one costs, and this file exists because of the
|
|
19
|
+
* second kind.
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* The problem with this app's `connect-src`, or undefined if there is not one.
|
|
23
|
+
*
|
|
24
|
+
* `https:` counts as a wildcard here only in the sense that a policy written that loosely will not
|
|
25
|
+
* be the thing blocking a dev WebSocket; the check stays quiet rather than arguing about it.
|
|
26
|
+
*/
|
|
27
|
+
export declare function cspConnectSrcProblem(text: string, port: number): string | undefined;
|
|
28
|
+
/** The exact addition to paste. Text to copy, not advice to interpret. */
|
|
29
|
+
export declare function devCspAddition(port: number): string;
|
|
30
|
+
/** The step title, named here so `doctor` and the plan cannot drift apart on what this check is called. */
|
|
31
|
+
export declare const CSP_STEP_TITLE = "Content-Security-Policy";
|
|
32
|
+
/**
|
|
33
|
+
* The first blocking policy among the sources `init` already has in hand, or undefined.
|
|
34
|
+
*
|
|
35
|
+
* At most one finding: a second copy of the same policy in a second file is the same problem, and
|
|
36
|
+
* two identical notices read as two problems.
|
|
37
|
+
*/
|
|
38
|
+
export declare function cspPlanProblem(sources: readonly (string | null | undefined)[], port: number): string | undefined;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Does this app's Content-Security-Policy let the browser reach the Reticle bridge?
|
|
3
|
+
*
|
|
4
|
+
* Two independent field reports, both Next: `init` reported every step successful, the SDK mounted,
|
|
5
|
+
* the dial URL was correct, and the app never connected because a strict `connect-src` excluded
|
|
6
|
+
* `ws://localhost:<port>`. The browser blocks the WebSocket and says so in its own console, which
|
|
7
|
+
* nothing on the Reticle side reads — so `status` and `doctor` reported perfect health at an app
|
|
8
|
+
* that could not connect at all, forever. A setup step that exits 0 while leaving the user broken is
|
|
9
|
+
* the worst failure mode this repo has.
|
|
10
|
+
*
|
|
11
|
+
* A TEXT SCAN, deliberately. `headers()` is a function; `init` cannot execute a user's Next config
|
|
12
|
+
* without importing their whole app, and the thing worth finding is the policy string a developer
|
|
13
|
+
* typed — in `next.config.*`, in middleware, or in a `<meta http-equiv>` tag. The scan finds the
|
|
14
|
+
* string wherever it is written.
|
|
15
|
+
*
|
|
16
|
+
* NARROW on purpose: it fires only when a `connect-src` exists AND does not admit the bridge. No
|
|
17
|
+
* CSP, or one that already covers us, produces nothing. A warning that fires on a working setup
|
|
18
|
+
* costs precisely what a green check on a broken one costs, and this file exists because of the
|
|
19
|
+
* second kind.
|
|
20
|
+
*/
|
|
21
|
+
/** Sources that admit any ws origin, so the bridge is already reachable. */
|
|
22
|
+
const WILDCARDS = ['*', 'ws:', 'wss:', 'ws://*', 'https:'];
|
|
23
|
+
/** The two hosts the SDK may dial. `localhost` and the IPv4 loopback are different CSP sources. */
|
|
24
|
+
function bridgeOrigins(port) {
|
|
25
|
+
return [`ws://localhost:${String(port)}`, `ws://127.0.0.1:${String(port)}`];
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* The `connect-src` directive's source list, or undefined when the text declares none.
|
|
29
|
+
*
|
|
30
|
+
* Matches to the end of the directive (`;`) or the end of the enclosing quoted string, which is how
|
|
31
|
+
* these are written in both a Next `headers()` value and a `<meta content="...">`.
|
|
32
|
+
*/
|
|
33
|
+
function connectSrcSources(text) {
|
|
34
|
+
// Stops at the directive separator or the closing double quote of the enclosing string. Single
|
|
35
|
+
// quotes are NOT terminators: `'self'` is a source, not the end of the list.
|
|
36
|
+
const match = /connect-src([^;"]*)/i.exec(text);
|
|
37
|
+
const list = match?.[1];
|
|
38
|
+
if (list === undefined)
|
|
39
|
+
return undefined;
|
|
40
|
+
return list
|
|
41
|
+
.split(/\s+/)
|
|
42
|
+
.map((source) => source.trim())
|
|
43
|
+
.filter((source) => source.length > 0);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The problem with this app's `connect-src`, or undefined if there is not one.
|
|
47
|
+
*
|
|
48
|
+
* `https:` counts as a wildcard here only in the sense that a policy written that loosely will not
|
|
49
|
+
* be the thing blocking a dev WebSocket; the check stays quiet rather than arguing about it.
|
|
50
|
+
*/
|
|
51
|
+
export function cspConnectSrcProblem(text, port) {
|
|
52
|
+
const sources = connectSrcSources(text);
|
|
53
|
+
if (sources === undefined || 0 === sources.length)
|
|
54
|
+
return undefined;
|
|
55
|
+
if (sources.some((source) => WILDCARDS.includes(source)))
|
|
56
|
+
return undefined;
|
|
57
|
+
const wanted = bridgeOrigins(port);
|
|
58
|
+
// BOTH, not either: the SDK picks its host from how the page was served, and a policy that admits
|
|
59
|
+
// one is a coin flip. A coin flip that fails is indistinguishable from every other silent
|
|
60
|
+
// non-connect, which is the whole cost being avoided here.
|
|
61
|
+
if (wanted.every((origin) => sources.includes(origin)))
|
|
62
|
+
return undefined;
|
|
63
|
+
const missing = wanted.filter((origin) => !sources.includes(origin));
|
|
64
|
+
return (`this app declares a Content-Security-Policy whose \`connect-src\` does not admit the Reticle ` +
|
|
65
|
+
`bridge: ${missing.join(' and ')} ${1 === missing.length ? 'is' : 'are'} missing. The browser ` +
|
|
66
|
+
`will block the WebSocket and report it in ITS console only — every check on the Reticle side ` +
|
|
67
|
+
`will pass while the app never connects. ${devCspAddition(port)}`);
|
|
68
|
+
}
|
|
69
|
+
/** The exact addition to paste. Text to copy, not advice to interpret. */
|
|
70
|
+
export function devCspAddition(port) {
|
|
71
|
+
return (`Add to \`connect-src\` in development only (e.g. behind ` +
|
|
72
|
+
`\`process.env.NODE_ENV === 'development'\`): ` +
|
|
73
|
+
`${bridgeOrigins(port).join(' ')}`);
|
|
74
|
+
}
|
|
75
|
+
/** The step title, named here so `doctor` and the plan cannot drift apart on what this check is called. */
|
|
76
|
+
export const CSP_STEP_TITLE = 'Content-Security-Policy';
|
|
77
|
+
/**
|
|
78
|
+
* The first blocking policy among the sources `init` already has in hand, or undefined.
|
|
79
|
+
*
|
|
80
|
+
* At most one finding: a second copy of the same policy in a second file is the same problem, and
|
|
81
|
+
* two identical notices read as two problems.
|
|
82
|
+
*/
|
|
83
|
+
export function cspPlanProblem(sources, port) {
|
|
84
|
+
for (const source of sources) {
|
|
85
|
+
if ('string' !== typeof source)
|
|
86
|
+
continue;
|
|
87
|
+
const problem = cspConnectSrcProblem(source, port);
|
|
88
|
+
if (problem !== undefined)
|
|
89
|
+
return problem;
|
|
90
|
+
}
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `doctor`'s web CSP check — the sibling of the desktop one next door.
|
|
3
|
+
*
|
|
4
|
+
* The desktop findings exist because a Tauri app with the default CSP runs perfectly and never
|
|
5
|
+
* connects. The WEB version of that failure had no check at all, and it arrived twice in one batch
|
|
6
|
+
* of field reports: two Next apps where `init` printed success for every step, the SDK mounted, the
|
|
7
|
+
* dial URL was right, and the browser silently refused the WebSocket because `connect-src` excluded
|
|
8
|
+
* the bridge. The violation is reported in the browser's console and nowhere else, so every check on
|
|
9
|
+
* this side stayed green while the app was permanently unreachable.
|
|
10
|
+
*
|
|
11
|
+
* Reads the files a CSP is actually written in, by name — a full source scan would be slower and
|
|
12
|
+
* would find policy strings in test fixtures and documentation.
|
|
13
|
+
*/
|
|
14
|
+
/** Read a project-relative file, or undefined when it is absent/unreadable. */
|
|
15
|
+
type ReadFile = (relative: string) => string | undefined;
|
|
16
|
+
export interface CspDiagnosis {
|
|
17
|
+
/** Which file to look at. */
|
|
18
|
+
file: string;
|
|
19
|
+
/** What is wrong, in one line. */
|
|
20
|
+
problem: string;
|
|
21
|
+
/** Exactly what to add — copy-pasteable, with the port actually in use. */
|
|
22
|
+
fix: string;
|
|
23
|
+
}
|
|
24
|
+
export declare function diagnoseWebCsp(read: ReadFile, port: number): CspDiagnosis[];
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `doctor`'s web CSP check — the sibling of the desktop one next door.
|
|
3
|
+
*
|
|
4
|
+
* The desktop findings exist because a Tauri app with the default CSP runs perfectly and never
|
|
5
|
+
* connects. The WEB version of that failure had no check at all, and it arrived twice in one batch
|
|
6
|
+
* of field reports: two Next apps where `init` printed success for every step, the SDK mounted, the
|
|
7
|
+
* dial URL was right, and the browser silently refused the WebSocket because `connect-src` excluded
|
|
8
|
+
* the bridge. The violation is reported in the browser's console and nowhere else, so every check on
|
|
9
|
+
* this side stayed green while the app was permanently unreachable.
|
|
10
|
+
*
|
|
11
|
+
* Reads the files a CSP is actually written in, by name — a full source scan would be slower and
|
|
12
|
+
* would find policy strings in test fixtures and documentation.
|
|
13
|
+
*/
|
|
14
|
+
import { cspConnectSrcProblem, devCspAddition } from './csp-check.js';
|
|
15
|
+
/**
|
|
16
|
+
* The places a Content-Security-Policy gets written in a JS app.
|
|
17
|
+
*
|
|
18
|
+
* Next config and middleware cover `headers()` and the edge-middleware style; the layouts and the
|
|
19
|
+
* plain `index.html` cover the `<meta http-equiv>` style. Both reported cases were in this list.
|
|
20
|
+
*/
|
|
21
|
+
const CSP_FILES = [
|
|
22
|
+
'next.config.js',
|
|
23
|
+
'next.config.mjs',
|
|
24
|
+
'next.config.ts',
|
|
25
|
+
'middleware.ts',
|
|
26
|
+
'middleware.js',
|
|
27
|
+
'src/middleware.ts',
|
|
28
|
+
'app/layout.tsx',
|
|
29
|
+
'app/layout.js',
|
|
30
|
+
'pages/_document.tsx',
|
|
31
|
+
'pages/_document.js',
|
|
32
|
+
'index.html',
|
|
33
|
+
'public/index.html',
|
|
34
|
+
'vercel.json',
|
|
35
|
+
'netlify.toml',
|
|
36
|
+
];
|
|
37
|
+
export function diagnoseWebCsp(read, port) {
|
|
38
|
+
const findings = [];
|
|
39
|
+
for (const file of CSP_FILES) {
|
|
40
|
+
const source = read(file);
|
|
41
|
+
if (source === undefined)
|
|
42
|
+
continue;
|
|
43
|
+
const problem = cspConnectSrcProblem(source, port);
|
|
44
|
+
if (problem === undefined)
|
|
45
|
+
continue;
|
|
46
|
+
findings.push({ file, problem, fix: devCspAddition(port) });
|
|
47
|
+
}
|
|
48
|
+
return findings;
|
|
49
|
+
}
|
package/dist/init/cursor.js
CHANGED
|
@@ -4,8 +4,7 @@
|
|
|
4
4
|
* We add the `reticle` server once at this global path so every project picks it up. Unparseable
|
|
5
5
|
* (jsonc/comment) files bail to manual rather than being rewritten.
|
|
6
6
|
*/
|
|
7
|
-
import { NPX, MCP_SERVER_NAME, npxServerArgs } from './mcp.js';
|
|
8
|
-
import { RETICLE_NPM_PACKAGE } from '../version/server-version.js';
|
|
7
|
+
import { NPX, MCP_SERVER_NAME, npxServerArgs, isReticleRegistration } from './mcp.js';
|
|
9
8
|
/** Path of Cursor's global MCP config, relative to the user's home directory. */
|
|
10
9
|
export const CURSOR_MCP_RELPATH = '.cursor/mcp.json';
|
|
11
10
|
/** The directory whose presence signals Cursor is installed for this user. */
|
|
@@ -46,19 +45,17 @@ function parseConfig(existing) {
|
|
|
46
45
|
*
|
|
47
46
|
* What IS repaired is a stale entry of our own shape — an old pin, a command that has moved — which
|
|
48
47
|
* presence-only idempotency reported as "already registered" and never fixed, so an upgrade could
|
|
49
|
-
* not repair the thing an upgrade exists to repair.
|
|
48
|
+
* not repair the thing an upgrade exists to repair. A WRONG entry counts as ours to repair too:
|
|
49
|
+
* `["@reticlehq/core","mcp"]` was reported from the field, and `core` has no MCP server in it.
|
|
50
50
|
*/
|
|
51
51
|
function leaveEntryAlone(existing) {
|
|
52
52
|
if (JSON.stringify(existing) === JSON.stringify(cursorServerEntry()))
|
|
53
53
|
return true;
|
|
54
54
|
if (typeof existing !== 'object' || null === existing)
|
|
55
55
|
return true;
|
|
56
|
-
const
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
Array.isArray(args) &&
|
|
60
|
-
args.some((arg) => 'string' === typeof arg && arg.includes(RETICLE_NPM_PACKAGE));
|
|
61
|
-
return !ours;
|
|
56
|
+
const record = existing;
|
|
57
|
+
const tokens = [record.command, record.args].flat().filter((v) => 'string' === typeof v);
|
|
58
|
+
return !isReticleRegistration(tokens);
|
|
62
59
|
}
|
|
63
60
|
export function mergeCursorConfig(existing) {
|
|
64
61
|
const parsed = parseConfig(existing);
|
package/dist/init/detect.d.ts
CHANGED
|
@@ -84,5 +84,13 @@ interface InstallCommand {
|
|
|
84
84
|
}
|
|
85
85
|
/** Build a dev-dependency install command for one or more packages (e.g. the kit + its build plugin). */
|
|
86
86
|
export declare function installCommandParts(pm: PackageManager, pkgs: string | readonly string[]): InstallCommand;
|
|
87
|
+
/**
|
|
88
|
+
* The install command as a human reads it — and deliberately NOT `installCommandParts` joined.
|
|
89
|
+
*
|
|
90
|
+
* This string is what the plan prints and what a user retypes when running the step by hand, so it
|
|
91
|
+
* must not carry the flags we add only to quieten our own child process. Built from `INSTALL_ARGS`
|
|
92
|
+
* directly for that reason; routing it through the parts would put `--no-audit --no-fund` in front of
|
|
93
|
+
* every reader and teach them our noise-suppression as if it were part of installing Reticle.
|
|
94
|
+
*/
|
|
87
95
|
export declare function installCommand(pm: PackageManager, pkgs: string | readonly string[]): string;
|
|
88
96
|
export {};
|
package/dist/init/detect.js
CHANGED
|
@@ -168,12 +168,42 @@ const INSTALL_ARGS = {
|
|
|
168
168
|
[PackageManager.BUN]: ['add', '-d'],
|
|
169
169
|
[PackageManager.NPM]: ['i', '-D'],
|
|
170
170
|
};
|
|
171
|
+
/**
|
|
172
|
+
* Flags added only to the install we RUN, never to the one we print.
|
|
173
|
+
*
|
|
174
|
+
* The install is a child process whose output lands above ours. Measured on a real install: the run
|
|
175
|
+
* opened with "added 602 packages", a funding notice, and "14 vulnerabilities (7 moderate, 7 high)"
|
|
176
|
+
* with `npm audit fix` advice, before one line of Reticle output. A user's first impression of a
|
|
177
|
+
* verification tool was a wall of somebody else's security warnings, at exactly the moment they are
|
|
178
|
+
* deciding whether this tool is careful — and that audit summary describes their existing dependency
|
|
179
|
+
* tree, which our two dev packages neither caused nor can fix.
|
|
180
|
+
*
|
|
181
|
+
* Kept off `installCommand`, which is the string shown in the plan and the one a user copies when
|
|
182
|
+
* running it by hand: nobody should be taught to type our noise-suppression flags.
|
|
183
|
+
*
|
|
184
|
+
* Quieted, never silenced — the exit code and stderr are untouched, so a real failure of the step
|
|
185
|
+
* everything downstream depends on stays as loud as it was.
|
|
186
|
+
*
|
|
187
|
+
* npm only: pnpm, yarn and bun reject these, and an unknown flag would turn a working install into a
|
|
188
|
+
* hard failure, which is the opposite of the problem being fixed.
|
|
189
|
+
*/
|
|
190
|
+
const QUIET_INSTALL_ARGS = {
|
|
191
|
+
[PackageManager.NPM]: ['--no-audit', '--no-fund'],
|
|
192
|
+
};
|
|
171
193
|
/** Build a dev-dependency install command for one or more packages (e.g. the kit + its build plugin). */
|
|
172
194
|
export function installCommandParts(pm, pkgs) {
|
|
173
195
|
const list = 'string' === typeof pkgs ? [pkgs] : pkgs;
|
|
174
|
-
return { command: pm, args: [...INSTALL_ARGS[pm], ...list] };
|
|
196
|
+
return { command: pm, args: [...INSTALL_ARGS[pm], ...list, ...(QUIET_INSTALL_ARGS[pm] ?? [])] };
|
|
175
197
|
}
|
|
198
|
+
/**
|
|
199
|
+
* The install command as a human reads it — and deliberately NOT `installCommandParts` joined.
|
|
200
|
+
*
|
|
201
|
+
* This string is what the plan prints and what a user retypes when running the step by hand, so it
|
|
202
|
+
* must not carry the flags we add only to quieten our own child process. Built from `INSTALL_ARGS`
|
|
203
|
+
* directly for that reason; routing it through the parts would put `--no-audit --no-fund` in front of
|
|
204
|
+
* every reader and teach them our noise-suppression as if it were part of installing Reticle.
|
|
205
|
+
*/
|
|
176
206
|
export function installCommand(pm, pkgs) {
|
|
177
|
-
const
|
|
178
|
-
return `${
|
|
207
|
+
const list = 'string' === typeof pkgs ? [pkgs] : pkgs;
|
|
208
|
+
return `${pm} ${[...INSTALL_ARGS[pm], ...list].join(' ')}`;
|
|
179
209
|
}
|
package/dist/init/mcp-clients.js
CHANGED
|
@@ -18,13 +18,13 @@
|
|
|
18
18
|
* an install that reports success and registers nothing, which is the exact failure class this
|
|
19
19
|
* repo's install gate exists to catch.
|
|
20
20
|
*/
|
|
21
|
-
import { NPX, MCP_SERVER_NAME, npxServerArgs } from './mcp.js';
|
|
22
|
-
import { RETICLE_NPM_PACKAGE } from '../version/server-version.js';
|
|
21
|
+
import { NPX, MCP_SERVER_NAME, npxServerArgs, isReticleRegistration } from './mcp.js';
|
|
23
22
|
export const McpClient = {
|
|
24
23
|
CLAUDE_CODE: 'claude-code',
|
|
25
24
|
CURSOR: 'cursor',
|
|
26
25
|
WINDSURF: 'windsurf',
|
|
27
26
|
OPENCODE: 'opencode',
|
|
27
|
+
ANTIGRAVITY: 'antigravity',
|
|
28
28
|
CODEX: 'codex',
|
|
29
29
|
GEMINI: 'gemini',
|
|
30
30
|
VSCODE: 'vscode',
|
|
@@ -107,13 +107,57 @@ export const MCP_CLIENTS = [
|
|
|
107
107
|
{
|
|
108
108
|
id: McpClient.OPENCODE,
|
|
109
109
|
label: 'OpenCode',
|
|
110
|
-
|
|
111
|
-
|
|
110
|
+
/**
|
|
111
|
+
* HOME, not PROJECT — OpenCode is installed globally and configured globally, like Cursor.
|
|
112
|
+
*
|
|
113
|
+
* It was project-scoped as `opencode.json`, and registration is gated on the marker already
|
|
114
|
+
* existing, so a project that had never written one was skipped in SILENCE: the user has OpenCode
|
|
115
|
+
* installed, `init` says nothing about it, and the tools never appear. In the field every OpenCode
|
|
116
|
+
* user connected a client and produced zero tool calls and zero app connections, which is what
|
|
117
|
+
* "you were never wired" looks like from outside.
|
|
118
|
+
*
|
|
119
|
+
* Path verified against a real install (OpenCode 1.3.17), not recalled. The extension is `.jsonc`,
|
|
120
|
+
* which the old project marker could not have matched even where a project config existed. JSONC
|
|
121
|
+
* permits comments, and a config that genuinely has them fails to parse — which `mergeClientConfig`
|
|
122
|
+
* already answers with MANUAL and a printed block, rather than rewriting somebody's file and
|
|
123
|
+
* dropping their comments.
|
|
124
|
+
*/
|
|
125
|
+
scope: ConfigScope.HOME,
|
|
126
|
+
relPath: '.config/opencode/opencode.jsonc',
|
|
112
127
|
format: ConfigFormat.JSON,
|
|
113
128
|
serversKey: 'mcp',
|
|
114
129
|
entry: openCodeEntry,
|
|
115
130
|
docs: 'https://opencode.ai/docs/config — `mcp` key, type:"local", command is an array',
|
|
116
131
|
},
|
|
132
|
+
{
|
|
133
|
+
id: McpClient.ANTIGRAVITY,
|
|
134
|
+
label: 'Antigravity',
|
|
135
|
+
/**
|
|
136
|
+
* Nine users connected an MCP client through Antigravity and drove nothing at all.
|
|
137
|
+
*
|
|
138
|
+
* `init` did not know this client, so they hand-wired a registration - the connections are in the
|
|
139
|
+
* field data, so they got that far - and then never ran `init`, never instrumented an app, and
|
|
140
|
+
* never called a tool. Nothing in that state tells anyone there is a second half.
|
|
141
|
+
*
|
|
142
|
+
* Path and shape from Google's documentation rather than recall: `mcpServers`, at
|
|
143
|
+
* `~/.gemini/config/mcp_config.json`, and one file serves the 2.0 IDE, the CLI and the SDK. Same
|
|
144
|
+
* `command`/`args` object Cursor, Windsurf and Gemini CLI already use.
|
|
145
|
+
*
|
|
146
|
+
* It shares the `~/.gemini` tree with Gemini CLI, which is why the marker matters: registration is
|
|
147
|
+
* gated on the marker DIRECTORY, and these resolve to `.gemini/config` and `.gemini` respectively,
|
|
148
|
+
* so having one installed cannot make `init` write a config for the other.
|
|
149
|
+
*
|
|
150
|
+
* NOT verified against a running install. The path is documented, the merge is the shared JSON
|
|
151
|
+
* path that every other client here uses, and an unparseable config still degrades to a printed
|
|
152
|
+
* block rather than a rewrite.
|
|
153
|
+
*/
|
|
154
|
+
scope: ConfigScope.HOME,
|
|
155
|
+
relPath: '.gemini/config/mcp_config.json',
|
|
156
|
+
format: ConfigFormat.JSON,
|
|
157
|
+
serversKey: 'mcpServers',
|
|
158
|
+
entry: commandArgsEntry,
|
|
159
|
+
docs: 'https://antigravity.google/docs/mcp — ~/.gemini/config/mcp_config.json, `mcpServers` key',
|
|
160
|
+
},
|
|
117
161
|
{
|
|
118
162
|
id: McpClient.CODEX,
|
|
119
163
|
label: 'Codex CLI',
|
|
@@ -163,7 +207,8 @@ function parseConfig(existing) {
|
|
|
163
207
|
*
|
|
164
208
|
* What IS repaired is a stale entry of our own shape — an old pin, a moved command — which
|
|
165
209
|
* presence-only idempotency reported as "already registered" and never fixed, so an upgrade could
|
|
166
|
-
* not repair the thing an upgrade exists to repair
|
|
210
|
+
* not repair the thing an upgrade exists to repair — and a WRONG one, naming a Reticle package that
|
|
211
|
+
* has no MCP server in it (see isReticleRegistration).
|
|
167
212
|
*/
|
|
168
213
|
function leaveEntryAlone(existing, spec) {
|
|
169
214
|
if (JSON.stringify(existing) === JSON.stringify(spec.entry()))
|
|
@@ -173,8 +218,7 @@ function leaveEntryAlone(existing, spec) {
|
|
|
173
218
|
const record = existing;
|
|
174
219
|
// Both shapes carry the package name somewhere in a string; find it without assuming which field.
|
|
175
220
|
const tokens = [record.command, record.args].flat().filter((v) => 'string' === typeof v);
|
|
176
|
-
|
|
177
|
-
return !ours;
|
|
221
|
+
return !isReticleRegistration(tokens);
|
|
178
222
|
}
|
|
179
223
|
/**
|
|
180
224
|
* Merge our server into this client's config.
|
package/dist/init/mcp.d.ts
CHANGED
|
@@ -41,6 +41,21 @@ export declare const NPX = "npx";
|
|
|
41
41
|
* shape is now repaired on re-run rather than reported "already registered" (see cursor.ts).
|
|
42
42
|
*/
|
|
43
43
|
export declare function npxServerArgs(): string[];
|
|
44
|
+
/**
|
|
45
|
+
* Is this existing registration one WE are responsible for?
|
|
46
|
+
*
|
|
47
|
+
* Deliberately the SCOPE and not the server package. An entry naming any `@reticlehq/*` package is
|
|
48
|
+
* ours to repair — including one naming the WRONG package, which is the case this widened for:
|
|
49
|
+
* `args: ["@reticlehq/core","mcp"]` was reported from the field, where `core` has no `mcp` bin, so
|
|
50
|
+
* the client showed the server errored with zero tools while `init` reported it already wired and
|
|
51
|
+
* left it alone on every re-run. Every `reticle_*` tool was blocked until a human edited that file.
|
|
52
|
+
*
|
|
53
|
+
* An entry that names no Reticle package at all — a local build, a wrapper script — is still someone
|
|
54
|
+
* else's deliberate choice and is left exactly as it is.
|
|
55
|
+
*
|
|
56
|
+
* @param tokens every string in the entry's command + args, in any of the client shapes.
|
|
57
|
+
*/
|
|
58
|
+
export declare function isReticleRegistration(tokens: readonly string[]): boolean;
|
|
44
59
|
interface ClaudeAddCommand {
|
|
45
60
|
command: string;
|
|
46
61
|
args: string[];
|
package/dist/init/mcp.js
CHANGED
|
@@ -49,6 +49,25 @@ const CLAUDE_CLI = 'claude';
|
|
|
49
49
|
export function npxServerArgs() {
|
|
50
50
|
return [RETICLE_PACKAGE, MCP_SUBCOMMAND];
|
|
51
51
|
}
|
|
52
|
+
/** The npm scope every Reticle package shares — the marker of a registration we are entitled to fix. */
|
|
53
|
+
const RETICLE_SCOPE = '@reticlehq/';
|
|
54
|
+
/**
|
|
55
|
+
* Is this existing registration one WE are responsible for?
|
|
56
|
+
*
|
|
57
|
+
* Deliberately the SCOPE and not the server package. An entry naming any `@reticlehq/*` package is
|
|
58
|
+
* ours to repair — including one naming the WRONG package, which is the case this widened for:
|
|
59
|
+
* `args: ["@reticlehq/core","mcp"]` was reported from the field, where `core` has no `mcp` bin, so
|
|
60
|
+
* the client showed the server errored with zero tools while `init` reported it already wired and
|
|
61
|
+
* left it alone on every re-run. Every `reticle_*` tool was blocked until a human edited that file.
|
|
62
|
+
*
|
|
63
|
+
* An entry that names no Reticle package at all — a local build, a wrapper script — is still someone
|
|
64
|
+
* else's deliberate choice and is left exactly as it is.
|
|
65
|
+
*
|
|
66
|
+
* @param tokens every string in the entry's command + args, in any of the client shapes.
|
|
67
|
+
*/
|
|
68
|
+
export function isReticleRegistration(tokens) {
|
|
69
|
+
return tokens.includes(NPX) && tokens.some((t) => t.includes(RETICLE_SCOPE));
|
|
70
|
+
}
|
|
52
71
|
/** The full `npx …` invocation — the tail after `claude mcp add … --`. */
|
|
53
72
|
function serverInvocation() {
|
|
54
73
|
return [NPX, ...npxServerArgs()];
|
|
@@ -57,3 +57,13 @@ export declare function svelteKitSteps(input: PlanInput): Step[];
|
|
|
57
57
|
* and a half-edited build config is worse than a documented manual step.
|
|
58
58
|
*/
|
|
59
59
|
export declare function astroSteps(input: PlanInput): Step[];
|
|
60
|
+
/**
|
|
61
|
+
* A Content-Security-Policy whose `connect-src` excludes the bridge, said out loud at install time.
|
|
62
|
+
*
|
|
63
|
+
* Both reported cases were Next apps where `init` printed success for every step and the app then
|
|
64
|
+
* never connected: the browser blocked the WebSocket and reported it in its own console, which
|
|
65
|
+
* nothing on this side reads. A NOTICE rather than a step, because editing someone's security policy
|
|
66
|
+
* is theirs to do — but it carries the exact text to paste, which is the difference between a
|
|
67
|
+
* warning and a fix.
|
|
68
|
+
*/
|
|
69
|
+
export declare function cspStep(input: PlanInput): Step[];
|
|
@@ -9,7 +9,9 @@ import { patchAstroConfig, patchAstroLayout } from './astro-patch.js';
|
|
|
9
9
|
import { CRA_DEV_MODULE_IMPORT, CRA_DEV_MODULE_PATH, CRA_ENV_PATH, CRA_TOKEN_PER_MACHINE_NOTICE, TOKEN_VAR, craDevModuleFile, craEnvPatch, craImportPatch, } from './cra.js';
|
|
10
10
|
import { PatchKind } from './patch-kind.js';
|
|
11
11
|
import { viteManual, NEXT_LAYOUT_MANUAL, NEXT_LAYOUT_PATH, viteDevModuleFile, VITE_DEV_MODULE_PATH, nextReticleDevFile, NEXT_RETICLE_DEV_PATH, nextConfigManual, svelteKitHooksFile, SVELTEKIT_HOOKS_PATH, UNVERIFIED_FRAMEWORK_NOTE, astroManual, nuxtManual, NUXT_PLUGIN_PATH, } from './snippets.js';
|
|
12
|
-
import { StepStatus } from './plan.js';
|
|
12
|
+
import { RETICLE_CONFIG_FILE, StepStatus } from './plan.js';
|
|
13
|
+
import { RETICLE_DEFAULT_PORT } from '@reticlehq/core';
|
|
14
|
+
import { CSP_STEP_TITLE, cspPlanProblem } from './csp-check.js';
|
|
13
15
|
/** What adding `reticle()` to a Vite config buys, which differs by framework. */
|
|
14
16
|
export const VITE_PLUGIN_DETAIL = {
|
|
15
17
|
/** A plain Vite app gets both halves from the plugin. */
|
|
@@ -429,3 +431,25 @@ export function astroSteps(input) {
|
|
|
429
431
|
patchStep('Connect snippet (Astro)', layout.path, layoutPatch, 'add the dev-only connect <script> before </body>', manualWithLayout),
|
|
430
432
|
];
|
|
431
433
|
}
|
|
434
|
+
/**
|
|
435
|
+
* A Content-Security-Policy whose `connect-src` excludes the bridge, said out loud at install time.
|
|
436
|
+
*
|
|
437
|
+
* Both reported cases were Next apps where `init` printed success for every step and the app then
|
|
438
|
+
* never connected: the browser blocked the WebSocket and reported it in its own console, which
|
|
439
|
+
* nothing on this side reads. A NOTICE rather than a step, because editing someone's security policy
|
|
440
|
+
* is theirs to do — but it carries the exact text to paste, which is the difference between a
|
|
441
|
+
* warning and a fix.
|
|
442
|
+
*/
|
|
443
|
+
export function cspStep(input) {
|
|
444
|
+
const problem = cspPlanProblem([input.nextConfigSource, input.nextLayout?.source], input.options.port ?? RETICLE_DEFAULT_PORT);
|
|
445
|
+
if (problem === undefined)
|
|
446
|
+
return [];
|
|
447
|
+
return [
|
|
448
|
+
{
|
|
449
|
+
title: CSP_STEP_TITLE,
|
|
450
|
+
target: input.nextConfigFile ?? RETICLE_CONFIG_FILE,
|
|
451
|
+
status: StepStatus.NOTICE,
|
|
452
|
+
detail: problem,
|
|
453
|
+
},
|
|
454
|
+
];
|
|
455
|
+
}
|
package/dist/init/plan.d.ts
CHANGED
|
@@ -27,10 +27,9 @@ export declare function pinnedPackages(packages: readonly string[], version: str
|
|
|
27
27
|
export declare function frameworkPackages(framework: Framework, uiLibrary?: UiLibrary): readonly string[];
|
|
28
28
|
/** Exported so the init telemetry can tell an MCP-registration failure from a dependency install. */
|
|
29
29
|
export declare const MCP_TARGET = "global (claude user scope)";
|
|
30
|
-
/** True when this step is what makes the app dial the daemon. */
|
|
31
|
-
export declare function isConnectStep(title: string): boolean;
|
|
32
30
|
/** The step that runs the package manager — the other thing that commonly fails on a user's machine. */
|
|
33
31
|
export declare const DEPS_TARGET = "package.json";
|
|
32
|
+
export declare const RETICLE_CONFIG_FILE = ".reticle.json";
|
|
34
33
|
export declare const StepStatus: {
|
|
35
34
|
readonly APPLY: "apply";
|
|
36
35
|
readonly MANUAL: "manual";
|
|
@@ -211,6 +210,12 @@ export interface PlanInput {
|
|
|
211
210
|
* (the single-package case) leaves every path project-relative exactly as before.
|
|
212
211
|
*/
|
|
213
212
|
agentFileRoot?: string | undefined;
|
|
213
|
+
/**
|
|
214
|
+
* Content of the `.reticle.json` already sitting at that agent root, or null/undefined when there
|
|
215
|
+
* is none. Content rather than existence, for the reason `reticleConfigSource` is: a root config
|
|
216
|
+
* that disagrees with the app's is worse than no root config, and only reading it can tell.
|
|
217
|
+
*/
|
|
218
|
+
agentRootConfigSource?: string | null | undefined;
|
|
214
219
|
options: {
|
|
215
220
|
port: number | undefined;
|
|
216
221
|
mcp: boolean;
|