@reticlehq/server 2.6.0 → 2.8.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/README.md +14 -5
- package/SKILL.md +251 -33
- package/dist/bridge/bridge.d.ts +6 -1
- package/dist/bridge/bridge.js +27 -4
- package/dist/capsule/causal-summary.d.ts +20 -0
- package/dist/capsule/causal-summary.js +31 -6
- package/dist/capsule/divergence.js +7 -1
- package/dist/cli/chromium-hint.d.ts +68 -0
- package/dist/cli/chromium-hint.js +145 -0
- package/dist/cli/cli-doctor.js +31 -36
- package/dist/cli/cli-kill.d.ts +58 -0
- package/dist/cli/cli-kill.js +162 -0
- package/dist/cli/cli-launch.d.ts +18 -0
- package/dist/cli/cli-launch.js +32 -1
- package/dist/cli/cli-parse.d.ts +16 -1
- package/dist/cli/cli-parse.js +32 -9
- package/dist/cli/cli-port.d.ts +8 -0
- package/dist/cli/cli-port.js +36 -1
- package/dist/cli/cli-verify.js +2 -2
- package/dist/cli/doctor-daemon-line.js +10 -1
- package/dist/cli/doctor-sessions-line.d.ts +12 -0
- package/dist/cli/doctor-sessions-line.js +66 -0
- package/dist/cli/port-holder.d.ts +10 -0
- package/dist/cli/port-holder.js +26 -2
- package/dist/cli.js +139 -23
- package/dist/crawl/crawl-tools.js +12 -2
- package/dist/crawl/crawl.d.ts +13 -0
- package/dist/crawl/crawl.js +50 -2
- package/dist/daemon/daemon-resilience.js +2 -2
- package/dist/daemon/loopback-alias.d.ts +32 -0
- package/dist/daemon/loopback-alias.js +80 -0
- package/dist/events/contradictions.d.ts +14 -0
- package/dist/events/contradictions.js +26 -17
- package/dist/events/predicate-eval.d.ts +26 -3
- package/dist/events/predicate-eval.js +284 -95
- package/dist/events/predicate-parse.d.ts +1 -1
- package/dist/events/predicate-parse.js +18 -4
- package/dist/events/predicate.js +170 -18
- package/dist/events/ring-buffer.d.ts +8 -0
- package/dist/events/ring-buffer.js +34 -1
- package/dist/flows/flow-classify.js +1 -1
- package/dist/flows/flow-tools.js +12 -4
- package/dist/flows/heal-run.js +1 -1
- package/dist/honesty/blind-spots.js +6 -2
- package/dist/honesty/honesty.d.ts +11 -0
- package/dist/honesty/honesty.js +9 -1
- package/dist/honesty/unsettled.d.ts +40 -0
- package/dist/honesty/unsettled.js +78 -0
- package/dist/honesty/verified.d.ts +7 -0
- package/dist/honesty/verified.js +27 -4
- package/dist/http-server.js +11 -1
- package/dist/index.js +29 -2
- package/dist/init/agent-rules.d.ts +16 -0
- package/dist/init/agent-rules.js +96 -17
- package/dist/init/astro-patch.d.ts +2 -1
- package/dist/init/astro-patch.js +11 -5
- package/dist/init/capabilities.d.ts +22 -0
- package/dist/init/capabilities.js +76 -0
- package/dist/init/confirm.d.ts +96 -0
- package/dist/init/confirm.js +138 -0
- package/dist/init/cra.js +3 -2
- package/dist/init/detect.d.ts +8 -0
- package/dist/init/detect.js +15 -0
- package/dist/init/non-js-project.d.ts +27 -0
- package/dist/init/non-js-project.js +84 -0
- package/dist/init/plan-framework.d.ts +9 -0
- package/dist/init/plan-framework.js +72 -16
- package/dist/init/plan.d.ts +40 -4
- package/dist/init/plan.js +183 -43
- package/dist/init/run.d.ts +24 -2
- package/dist/init/run.js +181 -46
- package/dist/init/slash-command.d.ts +1 -1
- package/dist/init/slash-command.js +46 -6
- package/dist/init/snippets.d.ts +64 -5
- package/dist/init/snippets.js +220 -31
- package/dist/init/vite-config.js +29 -3
- package/dist/input/network-mock-tools.d.ts +19 -0
- package/dist/input/network-mock-tools.js +29 -4
- package/dist/license/license.js +1 -1
- package/dist/mcp/mcp-proxy.d.ts +69 -3
- package/dist/mcp/mcp-proxy.js +215 -23
- package/dist/mcp/mcp.js +58 -7
- package/dist/mcp/proxy-lifecycle.d.ts +20 -1
- package/dist/mcp/proxy-lifecycle.js +22 -1
- package/dist/pool/browser-pool.d.ts +22 -2
- package/dist/pool/browser-pool.js +44 -4
- package/dist/pool/playwright-launcher.js +10 -2
- package/dist/project/learned-routes.d.ts +24 -0
- package/dist/project/learned-routes.js +63 -0
- package/dist/project/project-store.d.ts +6 -0
- package/dist/project/project-store.js +25 -1
- package/dist/session/live-control-tools.js +39 -2
- package/dist/session/live-control.d.ts +7 -9
- package/dist/session/live-control.js +34 -1
- package/dist/session/no-session-diagnosis.d.ts +23 -4
- package/dist/session/no-session-diagnosis.js +128 -44
- package/dist/session/no-session-watch.js +12 -0
- package/dist/session/ref-provenance.d.ts +54 -0
- package/dist/session/ref-provenance.js +61 -0
- package/dist/session/review-store.d.ts +19 -0
- package/dist/session/review-store.js +34 -5
- package/dist/session/session-lease.js +1 -1
- package/dist/session/session-manager.d.ts +9 -0
- package/dist/session/session-manager.js +11 -0
- package/dist/session/session.d.ts +8 -0
- package/dist/session/session.js +12 -0
- package/dist/status-payload.d.ts +13 -1
- package/dist/status-payload.js +6 -1
- package/dist/telemetry/app-instrumented.d.ts +37 -0
- package/dist/telemetry/app-instrumented.js +54 -0
- package/dist/telemetry/automation-hint.d.ts +17 -0
- package/dist/telemetry/automation-hint.js +71 -0
- package/dist/telemetry/bug-found.d.ts +0 -20
- package/dist/telemetry/bug-found.js +38 -21
- package/dist/telemetry/cli-telemetry.js +7 -1
- package/dist/telemetry/daemon-telemetry.d.ts +4 -4
- package/dist/telemetry/daemon-telemetry.js +8 -4
- package/dist/telemetry/feedback.js +1 -1
- package/dist/telemetry/init-telemetry.js +9 -1
- package/dist/telemetry/install-source.d.ts +36 -0
- package/dist/telemetry/install-source.js +40 -0
- package/dist/telemetry/run-telemetry.js +4 -1
- package/dist/telemetry/session-metrics.d.ts +3 -3
- package/dist/telemetry/session-metrics.js +9 -4
- package/dist/telemetry/telemetry.d.ts +7 -1
- package/dist/telemetry/telemetry.js +17 -2
- package/dist/telemetry/tool-refused.d.ts +19 -0
- package/dist/telemetry/tool-refused.js +55 -0
- package/dist/telemetry/verification-of.js +72 -8
- package/dist/temp-dir.d.ts +27 -0
- package/dist/temp-dir.js +46 -0
- package/dist/tools/act-tools.js +77 -11
- package/dist/tools/assert-grade.d.ts +1 -1
- package/dist/tools/assert-grade.js +27 -5
- package/dist/tools/assert-verdict.js +25 -1
- package/dist/tools/contract-tools.js +4 -0
- package/dist/tools/dynamic-tools.js +1 -1
- package/dist/tools/error-recovery.d.ts +10 -0
- package/dist/tools/error-recovery.js +70 -5
- package/dist/tools/feedback-invite.js +1 -1
- package/dist/tools/invoke-tool.js +149 -32
- package/dist/tools/lease-tools.d.ts +29 -1
- package/dist/tools/lease-tools.js +58 -5
- package/dist/tools/navigate-result.js +1 -1
- package/dist/tools/observe-tools.js +1 -1
- package/dist/tools/observed-nothing.js +1 -1
- package/dist/tools/react-grace.d.ts +48 -0
- package/dist/tools/react-grace.js +87 -0
- package/dist/tools/read-tools.js +20 -4
- package/dist/tools/settle-in-flight.d.ts +79 -0
- package/dist/tools/settle-in-flight.js +106 -0
- package/dist/tools/snapshot-delta.d.ts +7 -0
- package/dist/tools/snapshot-delta.js +130 -6
- package/dist/tools/tool-kit.d.ts +52 -5
- package/dist/tools/tool-kit.js +55 -24
- package/dist/tools/tool-surface.js +28 -9
- package/dist/tools/tools.js +38 -4
- package/dist/visual/capture-cleanup.d.ts +16 -0
- package/dist/visual/capture-cleanup.js +39 -0
- package/dist/visual/visual-tools.d.ts +18 -0
- package/dist/visual/visual-tools.js +24 -5
- package/docs/README.md +56 -11
- package/docs/actions.mdx +87 -0
- package/docs/agent-cheatsheet.md +102 -26
- package/docs/architecture.md +46 -35
- package/docs/benchmarks.md +76 -72
- package/docs/best-practices.mdx +211 -0
- package/docs/capabilities.mdx +387 -0
- package/docs/cli/affected.mdx +51 -0
- package/docs/cli/capsules.mdx +69 -0
- package/docs/cli/cloud.mdx +127 -0
- package/docs/cli/doctor.mdx +76 -0
- package/docs/cli/drive.mdx +55 -0
- package/docs/cli/feedback.mdx +67 -0
- package/docs/cli/gate.mdx +64 -0
- package/docs/cli/help.mdx +92 -0
- package/docs/cli/hunt.mdx +57 -0
- package/docs/cli/identify.mdx +62 -0
- package/docs/cli/init.mdx +94 -0
- package/docs/cli/kill.mdx +91 -0
- package/docs/cli/license.mdx +48 -0
- package/docs/cli/mcp.mdx +61 -0
- package/docs/cli/open.mdx +68 -0
- package/docs/cli/restart.mdx +60 -0
- package/docs/cli/rollback.mdx +35 -0
- package/docs/cli/serve.mdx +62 -0
- package/docs/cli/status.mdx +64 -0
- package/docs/cli/stop.mdx +56 -0
- package/docs/cli/telemetry.mdx +57 -0
- package/docs/cli/update.mdx +52 -0
- package/docs/cli/verify.mdx +57 -0
- package/docs/cli/version.mdx +39 -0
- package/docs/cli/watch.mdx +55 -0
- package/docs/cli.mdx +219 -0
- package/docs/debugging.md +13 -9
- package/docs/deploy-checks.md +15 -11
- package/docs/desktop-apps.md +49 -40
- package/docs/desktop.mdx +188 -0
- package/docs/docs.json +363 -0
- package/docs/enterprise.md +20 -16
- package/docs/faq.mdx +88 -0
- package/docs/fixtures.md +11 -7
- package/docs/flows.md +26 -22
- package/docs/for-agents.md +71 -0
- package/docs/frameworks.mdx +199 -0
- package/docs/gate-plan.md +45 -41
- package/docs/gates.md +23 -19
- package/docs/getting-started.md +76 -65
- package/docs/human-control.md +22 -18
- package/docs/index.mdx +101 -0
- package/docs/install-agentic.mdx +215 -0
- package/docs/install-manual.mdx +194 -0
- package/docs/instrumentation.mdx +232 -0
- package/docs/integration-patterns.md +38 -34
- package/docs/local-registry.md +16 -12
- package/docs/multi-agent-testing.md +16 -12
- package/docs/packages/babel-plugin.mdx +70 -0
- package/docs/packages/browser.mdx +111 -0
- package/docs/packages/core.mdx +106 -0
- package/docs/packages/electron.mdx +87 -0
- package/docs/packages/eslint-plugin.mdx +84 -0
- package/docs/packages/next.mdx +91 -0
- package/docs/packages/react.mdx +91 -0
- package/docs/packages/server.mdx +98 -0
- package/docs/packages/tauri.mdx +63 -0
- package/docs/packages/test.mdx +108 -0
- package/docs/packages/vite-plugin.mdx +96 -0
- package/docs/packages.mdx +217 -0
- package/docs/platform-integration.md +33 -26
- package/docs/predicates.mdx +179 -0
- package/docs/quickstart.mdx +211 -0
- package/docs/recipes.mdx +242 -0
- package/docs/skill-file.mdx +105 -0
- package/docs/state-management.mdx +197 -0
- package/docs/style.css +14 -0
- package/docs/system-map.md +47 -32
- package/docs/telemetry-contract.md +101 -46
- package/docs/telemetry-events.mdx +430 -0
- package/docs/telemetry.md +63 -50
- package/docs/testing.md +10 -6
- package/docs/token-efficiency.md +30 -26
- package/docs/tools-act-and-wait.mdx +123 -0
- package/docs/tools-act-sequence.mdx +120 -0
- package/docs/tools-act.mdx +82 -0
- package/docs/tools-assert.mdx +70 -0
- package/docs/tools-console.mdx +59 -0
- package/docs/tools-inspect.mdx +85 -0
- package/docs/tools-navigate.mdx +63 -0
- package/docs/tools-network.mdx +86 -0
- package/docs/tools-observe.mdx +89 -0
- package/docs/tools-overview.mdx +157 -0
- package/docs/tools-query.mdx +82 -0
- package/docs/tools-session-and-feedback.mdx +91 -0
- package/docs/tools-sessions.mdx +65 -0
- package/docs/tools-snapshot.mdx +75 -0
- package/docs/tools-state.mdx +81 -0
- package/docs/tools-tools-and-run.mdx +125 -0
- package/docs/tools-wait-for.mdx +66 -0
- package/docs/troubleshooting.mdx +193 -0
- package/docs/usage.md +175 -153
- package/docs/vs-chrome-devtools-mcp.mdx +82 -0
- package/docs/vs-playwright-mcp.mdx +85 -0
- package/docs/vs-screenshots.mdx +96 -0
- package/docs/what-is-reticle.mdx +63 -0
- package/docs/why-reticle.mdx +107 -0
- package/package.json +2 -2
- package/docs/matrix/2.5.0/claude-code-machine.json +0 -19
- package/docs/matrix/2.5.0/codex-machine.json +0 -20
- package/docs/matrix/2.5.0/cursor-machine.json +0 -22
- package/docs/matrix/2.5.0/gemini-machine.json +0 -22
- package/docs/matrix/2.5.0/opencode-machine.json +0 -22
- package/docs/matrix/2.5.0/vscode-machine.json +0 -22
- package/docs/matrix/2.5.0/windsurf-machine.json +0 -22
- package/docs/matrix/MATRIX.md +0 -26
- package/docs/matrix/README.md +0 -59
- package/docs/mint.json +0 -61
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The step `init` never took: stay and watch for an app to connect.
|
|
3
|
+
*
|
|
4
|
+
* The install has two halves. Registering the MCP server gives the agent the tools; getting the SDK
|
|
5
|
+
* into a running page gives it something to look at. Almost everyone finishes the first, and nothing
|
|
6
|
+
* joined "init finished" to "an app connected" — so `init` exited 0 having written files, and the
|
|
7
|
+
* person left with a config file and no instrumented page was never told (#269).
|
|
8
|
+
*
|
|
9
|
+
* The capability existed in three places and was used together by none of them: the poll behind
|
|
10
|
+
* `reticle open`, the install gate (which calls this exact check "the only step that proves the
|
|
11
|
+
* install works"), and the event that measures whether it ever happened.
|
|
12
|
+
*
|
|
13
|
+
* WHY NO FLAG, and no dev-server management:
|
|
14
|
+
*
|
|
15
|
+
* - No flag, because the people this is for are the ones who do not know to ask. `--verify` would
|
|
16
|
+
* be typed by the users who already run `status` afterwards, which is exactly the cohort that is
|
|
17
|
+
* not being lost. The gate instead is whether a HUMAN IS AT THE TERMINAL: `init` waits when
|
|
18
|
+
* stdout is a TTY and skips the wait entirely otherwise, so a scripted or agent-driven `init`
|
|
19
|
+
* keeps today's cost and today's exit behaviour, and blocking never happens where blocking is
|
|
20
|
+
* wrong. The signal is the standard "am I being piped" one and needs no argument parsing, which
|
|
21
|
+
* also keeps `init`'s documented flag surface where it is.
|
|
22
|
+
* - It never starts anything. Reticle attaches to what is already running and does not manage a
|
|
23
|
+
* dev server; that rule holds here too. So the honest shape is: confirm the app if it connects,
|
|
24
|
+
* otherwise name the ONE command that is outstanding and the one that proves it worked.
|
|
25
|
+
*/
|
|
26
|
+
import { InitConfirmation, RETICLE_DEFAULT_PORT } from '@reticlehq/core';
|
|
27
|
+
import { fetchStatus, summarizeStatus } from '../cli/cli-launch.js';
|
|
28
|
+
import { reportInitOutcome } from '../telemetry/init-telemetry.js';
|
|
29
|
+
/**
|
|
30
|
+
* How long a human is asked to wait.
|
|
31
|
+
*
|
|
32
|
+
* Long enough for a page that is already loading to finish dialling, short enough that somebody who
|
|
33
|
+
* has not restarted their dev server yet is not held hostage by a wait that cannot succeed — they
|
|
34
|
+
* have not typed the command that would make it succeed, and the message tells them which one.
|
|
35
|
+
*/
|
|
36
|
+
export const CONFIRM_WINDOW_MS = 12_000;
|
|
37
|
+
export const CONFIRM_POLL_MS = 500;
|
|
38
|
+
/**
|
|
39
|
+
* Poll until an app connects or the window runs out.
|
|
40
|
+
*
|
|
41
|
+
* A session that was ALREADY there when the wait began does not count, and this is the whole reason
|
|
42
|
+
* the check reads ids rather than a count. A daemon is shared across every project on the machine,
|
|
43
|
+
* so "a session exists" is satisfied by another app, by a stray tab, by yesterday's work — and
|
|
44
|
+
* confirming this install on somebody else's session is a false green in the one message a user
|
|
45
|
+
* reads to decide whether they are done. `reticle open` refuses the same way, for the same reason.
|
|
46
|
+
*
|
|
47
|
+
* The conservative direction is deliberate: an app that reconnects under an id it already held is
|
|
48
|
+
* reported as not-yet-connected, and the message it gets sends them to `status`, which shows it. A
|
|
49
|
+
* missed confirmation costs one command; a false one costs the whole point of confirming.
|
|
50
|
+
*/
|
|
51
|
+
export async function confirmAppConnected(deps) {
|
|
52
|
+
const deadline = deps.now() + (deps.windowMs ?? CONFIRM_WINDOW_MS);
|
|
53
|
+
let before;
|
|
54
|
+
for (;;) {
|
|
55
|
+
const ids = await deps.listSessionIds();
|
|
56
|
+
// A port nobody is listening on cannot receive a session, and no amount of waiting changes that.
|
|
57
|
+
// One look settles it; polling on would spend the whole window to re-learn the first answer.
|
|
58
|
+
if (null === ids)
|
|
59
|
+
return InitConfirmation.NO_DAEMON;
|
|
60
|
+
const baseline = before;
|
|
61
|
+
if (baseline === undefined)
|
|
62
|
+
before = new Set(ids);
|
|
63
|
+
else if (ids.some((id) => !baseline.has(id)))
|
|
64
|
+
return InitConfirmation.CONNECTED;
|
|
65
|
+
if (deadline <= deps.now())
|
|
66
|
+
return InitConfirmation.NO_SESSION;
|
|
67
|
+
await deps.sleep(deps.pollMs ?? CONFIRM_POLL_MS);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const PROVE_COMMAND = '`npx @reticlehq/server status`';
|
|
71
|
+
/**
|
|
72
|
+
* What the user reads, in the marks the rest of the report already uses: `⚠` means work left to do
|
|
73
|
+
* and nothing else, `ℹ` is worth reading but is not work, `✓` happened.
|
|
74
|
+
*
|
|
75
|
+
* Only NO_SESSION earns a `⚠`, and it earns it honestly: the app is wired and has not connected, so
|
|
76
|
+
* there is a command outstanding. A missing daemon is not a defect in the install — the daemon comes
|
|
77
|
+
* up when the agent loads the tools — so it is a notice.
|
|
78
|
+
*/
|
|
79
|
+
export function confirmationMessage(confirmation, port) {
|
|
80
|
+
if (confirmation === InitConfirmation.CONNECTED) {
|
|
81
|
+
return (' [✓] an app is connected — that is the install finished, not just written.\n' +
|
|
82
|
+
' Ask your agent to drive a flow.');
|
|
83
|
+
}
|
|
84
|
+
if (confirmation === InitConfirmation.NO_DAEMON) {
|
|
85
|
+
return (` [ℹ] nothing is listening on port ${String(port)} yet, so no app could have connected and ` +
|
|
86
|
+
'this run did not wait for one.\n' +
|
|
87
|
+
' The daemon starts on its own when your agent loads the Reticle tools. After that, ' +
|
|
88
|
+
`${PROVE_COMMAND} confirms the app.`);
|
|
89
|
+
}
|
|
90
|
+
return (' [⚠] no app has connected yet — the files are written, the page half of the install is not ' +
|
|
91
|
+
'done.\n' +
|
|
92
|
+
' Restart your dev server (Reticle attaches to it, it never starts it for you) and load ' +
|
|
93
|
+
'the app in a browser.\n' +
|
|
94
|
+
` ${PROVE_COMMAND} then confirms it, or says exactly why it has not connected.`);
|
|
95
|
+
}
|
|
96
|
+
const WATCHING = (windowMs) => ` watching for an app to connect (up to ${String(Math.round(windowMs / 1000))}s) — Ctrl-C is safe, ` +
|
|
97
|
+
'everything above is already written.';
|
|
98
|
+
/**
|
|
99
|
+
* Finish `init` on evidence rather than on a write.
|
|
100
|
+
*
|
|
101
|
+
* The telemetry report happens HERE rather than inside `runInit`, so the one `init_completed` event
|
|
102
|
+
* carries what was seen. Emitting inside `runInit` and again after the wait would double-count the
|
|
103
|
+
* funnel it exists to measure, and a second event kind for the same command would need joining back
|
|
104
|
+
* to the first before anybody could read either.
|
|
105
|
+
*/
|
|
106
|
+
export async function confirmInstall(result, io, deps, report = reportInitOutcome) {
|
|
107
|
+
const outcome = result.outcome;
|
|
108
|
+
// No outcome means a dry run or an exit that already reported for itself (no package.json). A
|
|
109
|
+
// preview is not an install and must not land in the funnel as either a success or a failure.
|
|
110
|
+
if (outcome === undefined)
|
|
111
|
+
return;
|
|
112
|
+
if (!deps.interactive) {
|
|
113
|
+
report(outcome);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
io.print('');
|
|
117
|
+
io.print(WATCHING(deps.windowMs ?? CONFIRM_WINDOW_MS));
|
|
118
|
+
const confirmation = await confirmAppConnected(deps);
|
|
119
|
+
io.print(confirmationMessage(confirmation, deps.port));
|
|
120
|
+
report({ ...outcome, confirmation });
|
|
121
|
+
}
|
|
122
|
+
/** The real deps: the daemon's own status endpoint, and whether a human is watching. */
|
|
123
|
+
export function nodeConfirmDeps(port = RETICLE_DEFAULT_PORT) {
|
|
124
|
+
return {
|
|
125
|
+
listSessionIds: async () => {
|
|
126
|
+
const payload = await fetchStatus(port);
|
|
127
|
+
// `fetchStatus` resolves undefined when nothing answered, which is precisely the "no daemon"
|
|
128
|
+
// case — a zero-session daemon and an absent one need opposite messages.
|
|
129
|
+
if (payload === undefined)
|
|
130
|
+
return null;
|
|
131
|
+
return summarizeStatus(payload).sessions.map((s) => s.sessionId);
|
|
132
|
+
},
|
|
133
|
+
now: () => Date.now(),
|
|
134
|
+
sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
135
|
+
interactive: true === process.stdout.isTTY,
|
|
136
|
+
port,
|
|
137
|
+
};
|
|
138
|
+
}
|
package/dist/init/cra.js
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
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 { CLI } from './agent-rules.js';
|
|
15
16
|
/** The line added to `src/index.tsx`. Side-effect import: the module guards itself on NODE_ENV. */
|
|
16
17
|
export const CRA_DEV_MODULE_IMPORT = "import './reticle-dev';";
|
|
17
18
|
export const CRA_DEV_MODULE_PATH = 'src/reticle-dev.ts';
|
|
@@ -30,7 +31,7 @@ export const TOKEN_VAR = 'REACT_APP_RETICLE_TOKEN';
|
|
|
30
31
|
*/
|
|
31
32
|
export const CRA_TOKEN_MISSING_NOTE = `${TOKEN_VAR} is not set, so Reticle cannot pair with the daemon. ` +
|
|
32
33
|
`The pairing token is per-machine and ${CRA_ENV_PATH} is gitignored by CRA's template, ` +
|
|
33
|
-
|
|
34
|
+
`so it does not survive a clone. Run \`${CLI} init\` in this project to write it for this machine.`;
|
|
34
35
|
/**
|
|
35
36
|
* Said as its own NOTICE line, beside the write — not inside it.
|
|
36
37
|
*
|
|
@@ -46,7 +47,7 @@ export const CRA_TOKEN_MISSING_NOTE = `${TOKEN_VAR} is not set, so Reticle canno
|
|
|
46
47
|
export const CRA_TOKEN_PER_MACHINE_NOTICE = `${CRA_ENV_PATH} is in CRA's own .gitignore, so the token just written does not travel. ` +
|
|
47
48
|
'It works on THIS machine and nowhere else: a teammate cloning the repo, CI, or a container ' +
|
|
48
49
|
'gets an app that boots, never pairs, and reports it only in the browser console. ' +
|
|
49
|
-
|
|
50
|
+
`Each developer runs \`${CLI} init\` once locally.`;
|
|
50
51
|
/** Add the import after the last existing one, or null when it is already present. */
|
|
51
52
|
export function craImportPatch(source) {
|
|
52
53
|
if (source.includes(CRA_DEV_MODULE_IMPORT))
|
package/dist/init/detect.d.ts
CHANGED
|
@@ -4,6 +4,14 @@
|
|
|
4
4
|
*/
|
|
5
5
|
export declare const Framework: {
|
|
6
6
|
readonly NEXT: "next";
|
|
7
|
+
/**
|
|
8
|
+
* Nuxt owns its own Vite instance and has no `vite.config`, so it used to fall all the way through
|
|
9
|
+
* to HTML — and then be handed a React kit and a connect snippet guarded on
|
|
10
|
+
* `window.location.hostname === 'localhost'`, which cannot work in a Vue app (the guard throws
|
|
11
|
+
* during SSR, and never runs at all on a non-localhost dev host). Detected in its own right so it
|
|
12
|
+
* gets the package and the recipe that actually fit it.
|
|
13
|
+
*/
|
|
14
|
+
readonly NUXT: "nuxt";
|
|
7
15
|
readonly VITE: "vite";
|
|
8
16
|
readonly SVELTEKIT: "sveltekit";
|
|
9
17
|
readonly ASTRO: "astro";
|
package/dist/init/detect.js
CHANGED
|
@@ -4,6 +4,14 @@
|
|
|
4
4
|
*/
|
|
5
5
|
export const Framework = {
|
|
6
6
|
NEXT: 'next',
|
|
7
|
+
/**
|
|
8
|
+
* Nuxt owns its own Vite instance and has no `vite.config`, so it used to fall all the way through
|
|
9
|
+
* to HTML — and then be handed a React kit and a connect snippet guarded on
|
|
10
|
+
* `window.location.hostname === 'localhost'`, which cannot work in a Vue app (the guard throws
|
|
11
|
+
* during SSR, and never runs at all on a non-localhost dev host). Detected in its own right so it
|
|
12
|
+
* gets the package and the recipe that actually fit it.
|
|
13
|
+
*/
|
|
14
|
+
NUXT: 'nuxt',
|
|
7
15
|
VITE: 'vite',
|
|
8
16
|
SVELTEKIT: 'sveltekit',
|
|
9
17
|
ASTRO: 'astro',
|
|
@@ -32,6 +40,7 @@ export const UiLibrary = {
|
|
|
32
40
|
const NEXT_CONFIGS = ['next.config.js', 'next.config.mjs', 'next.config.ts', 'next.config.cjs'];
|
|
33
41
|
const VITE_CONFIGS = ['vite.config.js', 'vite.config.ts', 'vite.config.mjs', 'vite.config.mts'];
|
|
34
42
|
const SVELTE_CONFIGS = ['svelte.config.js', 'svelte.config.ts', 'svelte.config.mjs'];
|
|
43
|
+
const NUXT_CONFIGS = ['nuxt.config.ts', 'nuxt.config.js', 'nuxt.config.mjs'];
|
|
35
44
|
const ASTRO_CONFIGS = [
|
|
36
45
|
'astro.config.mjs',
|
|
37
46
|
'astro.config.js',
|
|
@@ -98,6 +107,12 @@ function detectFramework(input) {
|
|
|
98
107
|
if (depVersion(pkg, 'next') !== undefined || hasAnyConfig(configFiles, NEXT_CONFIGS)) {
|
|
99
108
|
return Framework.NEXT;
|
|
100
109
|
}
|
|
110
|
+
// Nuxt before Vite: Nuxt apps can carry a `vite` dependency and even a vite.config, but the
|
|
111
|
+
// generic Vite path would wire a plugin into a config Nuxt does not read, and inject connect into
|
|
112
|
+
// an index.html Nuxt does not serve.
|
|
113
|
+
if (depVersion(pkg, 'nuxt') !== undefined || hasAnyConfig(configFiles, NUXT_CONFIGS)) {
|
|
114
|
+
return Framework.NUXT;
|
|
115
|
+
}
|
|
101
116
|
// SvelteKit is Vite-based but renders through app.html, so the Vite plugin's index.html injection
|
|
102
117
|
// never fires (verified) — it needs a manual client connect. Check BEFORE the generic Vite branch.
|
|
103
118
|
if (depVersion(pkg, '@sveltejs/kit') !== undefined || hasAnyConfig(configFiles, SVELTE_CONFIGS)) {
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telling a Python (or Ruby, or Go, or PHP) developer why Reticle cannot wire their app.
|
|
3
|
+
*
|
|
4
|
+
* `reticle init` answered "No package.json found here, and no app directory beneath it either",
|
|
5
|
+
* which reads as *you are in the wrong directory* — so an agent on a Streamlit app went looking for
|
|
6
|
+
* a directory that does not exist, then went looking for a standalone browser bundle to inject by
|
|
7
|
+
* hand, and only worked out the real answer several steps later.
|
|
8
|
+
*
|
|
9
|
+
* The real answer is not about directories at all: Reticle's SDK has to be imported by the app's own
|
|
10
|
+
* JS build, and a server-rendered Python app has no JS build to import it from. That is a property
|
|
11
|
+
* of the project, it is knowable from one look at the filesystem, and saying it plainly costs the
|
|
12
|
+
* reader nothing while the directory message costs them the whole investigation.
|
|
13
|
+
*
|
|
14
|
+
* Deliberately a hint, not a refusal: a Django or Rails app with a `frontend/` package.json is a
|
|
15
|
+
* perfectly ordinary Reticle install, and this only ever speaks when there is no package.json to be
|
|
16
|
+
* found anywhere.
|
|
17
|
+
*/
|
|
18
|
+
/** The ecosystem this directory looks like, or undefined when nothing says. */
|
|
19
|
+
export declare function detectNonJsEcosystem(exists: (file: string) => boolean): string | undefined;
|
|
20
|
+
/**
|
|
21
|
+
* What `init` should say when there is no package.json.
|
|
22
|
+
*
|
|
23
|
+
* Two genuinely different situations behind one old sentence: a JS developer standing in the wrong
|
|
24
|
+
* directory, and a developer whose project is not JavaScript at all. The first needs a path; the
|
|
25
|
+
* second needs to know that no path exists and why.
|
|
26
|
+
*/
|
|
27
|
+
export declare function noPackageJsonMessage(exists: (file: string) => boolean): string;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telling a Python (or Ruby, or Go, or PHP) developer why Reticle cannot wire their app.
|
|
3
|
+
*
|
|
4
|
+
* `reticle init` answered "No package.json found here, and no app directory beneath it either",
|
|
5
|
+
* which reads as *you are in the wrong directory* — so an agent on a Streamlit app went looking for
|
|
6
|
+
* a directory that does not exist, then went looking for a standalone browser bundle to inject by
|
|
7
|
+
* hand, and only worked out the real answer several steps later.
|
|
8
|
+
*
|
|
9
|
+
* The real answer is not about directories at all: Reticle's SDK has to be imported by the app's own
|
|
10
|
+
* JS build, and a server-rendered Python app has no JS build to import it from. That is a property
|
|
11
|
+
* of the project, it is knowable from one look at the filesystem, and saying it plainly costs the
|
|
12
|
+
* reader nothing while the directory message costs them the whole investigation.
|
|
13
|
+
*
|
|
14
|
+
* Deliberately a hint, not a refusal: a Django or Rails app with a `frontend/` package.json is a
|
|
15
|
+
* perfectly ordinary Reticle install, and this only ever speaks when there is no package.json to be
|
|
16
|
+
* found anywhere.
|
|
17
|
+
*/
|
|
18
|
+
/** Marker files that identify an ecosystem, and what to call it. */
|
|
19
|
+
const ECOSYSTEM_MARKERS = [
|
|
20
|
+
['requirements.txt', 'Python'],
|
|
21
|
+
['pyproject.toml', 'Python'],
|
|
22
|
+
['Pipfile', 'Python'],
|
|
23
|
+
['setup.py', 'Python'],
|
|
24
|
+
['manage.py', 'Python (Django)'],
|
|
25
|
+
['Gemfile', 'Ruby'],
|
|
26
|
+
['go.mod', 'Go'],
|
|
27
|
+
['composer.json', 'PHP'],
|
|
28
|
+
['Cargo.toml', 'Rust'],
|
|
29
|
+
['pom.xml', 'Java'],
|
|
30
|
+
['build.gradle', 'Java'],
|
|
31
|
+
['mix.exs', 'Elixir'],
|
|
32
|
+
// Not like the others. Every ecosystem above can grow a JS front end that Reticle instruments
|
|
33
|
+
// perfectly well, so their message ends with a lead. Flutter web paints into a canvas, and there
|
|
34
|
+
// is no DOM behind it at any path, so the honest answer is a named refusal.
|
|
35
|
+
['pubspec.yaml', 'Flutter'],
|
|
36
|
+
];
|
|
37
|
+
/** The one ecosystem where no directory, front end or flag could ever help. */
|
|
38
|
+
const FLUTTER = 'Flutter';
|
|
39
|
+
/** The ecosystem this directory looks like, or undefined when nothing says. */
|
|
40
|
+
export function detectNonJsEcosystem(exists) {
|
|
41
|
+
for (const [marker, name] of ECOSYSTEM_MARKERS) {
|
|
42
|
+
if (exists(marker))
|
|
43
|
+
return name;
|
|
44
|
+
}
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* What `init` should say when there is no package.json.
|
|
49
|
+
*
|
|
50
|
+
* Two genuinely different situations behind one old sentence: a JS developer standing in the wrong
|
|
51
|
+
* directory, and a developer whose project is not JavaScript at all. The first needs a path; the
|
|
52
|
+
* second needs to know that no path exists and why.
|
|
53
|
+
*/
|
|
54
|
+
export function noPackageJsonMessage(exists) {
|
|
55
|
+
const ecosystem = detectNonJsEcosystem(exists);
|
|
56
|
+
if (ecosystem === undefined) {
|
|
57
|
+
return ('No package.json found here, and no app directory beneath it either. Run `reticle init` from ' +
|
|
58
|
+
"your app's directory, or from a repo root that contains it.");
|
|
59
|
+
}
|
|
60
|
+
if (ecosystem === FLUTTER) {
|
|
61
|
+
// An agent on a Flutter workspace asked us to "support Flutter, or clearly identify it as
|
|
62
|
+
// unsupported during init". The second is the truthful one, and saying it by name in one line
|
|
63
|
+
// is the difference between an agent that stops and an agent that spends an hour looking for
|
|
64
|
+
// the package.json we told it was missing.
|
|
65
|
+
return ('This is a Flutter project. Reticle cannot instrument it, and no directory or flag will ' +
|
|
66
|
+
'change that: Reticle reads the DOM of a running page, and Flutter web paints its entire UI ' +
|
|
67
|
+
'into a single canvas element with no DOM behind it. Nothing here is missing or misconfigured. ' +
|
|
68
|
+
'If this repo also contains an ordinary web app with its own package.json, run `reticle init` ' +
|
|
69
|
+
'inside that directory instead.');
|
|
70
|
+
}
|
|
71
|
+
return (`This looks like a ${ecosystem} project, and there is no package.json here or in any app ` +
|
|
72
|
+
'directory beneath it. That is not a blocker: Reticle needs its SDK loaded by the page, not ' +
|
|
73
|
+
'built by npm, and a page with no build step can load it from a URL. ' +
|
|
74
|
+
// The old message ended at "there is no directory you could run this from that would change
|
|
75
|
+
// that", which read as a refusal and was acted on as one — agents went looking for a bundler,
|
|
76
|
+
// or for a standalone build to inject by hand, or gave up and used a different tool. The
|
|
77
|
+
// capability was there the whole time; only the sentence was missing. Proven end to end on a
|
|
78
|
+
// page served by `python3 -m http.server` before this line was written.
|
|
79
|
+
'Add the script-tag snippet below to a template you only serve in development, then reload the ' +
|
|
80
|
+
'page. ' +
|
|
81
|
+
'If this project ALSO has a JS front end (a `frontend/`, `client/` or `assets/` directory with ' +
|
|
82
|
+
'its own package.json), running `reticle init` there instead gives you source mapping and ' +
|
|
83
|
+
'framework state as well: point at it with `--app <dir>`.');
|
|
84
|
+
}
|
|
@@ -38,6 +38,15 @@ export declare function nextSteps(input: PlanInput): Step[];
|
|
|
38
38
|
* bundler never processes for modules. Reported from a real cra-redux-saga app.
|
|
39
39
|
*/
|
|
40
40
|
export declare function craSteps(input: PlanInput): Step[];
|
|
41
|
+
/**
|
|
42
|
+
* Nuxt: one manual step carrying the whole recipe, and no pretence of more.
|
|
43
|
+
*
|
|
44
|
+
* There is no Nuxt app in `apps/` and no CI gate for one, so an auto-written plugin would be a
|
|
45
|
+
* support claim nothing backs — the same reasoning SvelteKit already carries. What this DOES fix is
|
|
46
|
+
* everything that made the previous fall-through actively wrong: Nuxt was classified as `html`, so
|
|
47
|
+
* it was handed the React kit and a localhost-guarded snippet that cannot run in a Vue app.
|
|
48
|
+
*/
|
|
49
|
+
export declare function nuxtSteps(input: PlanInput): Step[];
|
|
41
50
|
export declare function svelteKitSteps(input: PlanInput): Step[];
|
|
42
51
|
/**
|
|
43
52
|
* Astro: the config define + build target, and the connect script in ONE layout.
|
|
@@ -8,7 +8,7 @@ import { patchNextConfig, patchRootLayout, patchPagesApp } from './next-patch.js
|
|
|
8
8
|
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
|
-
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, } from './snippets.js';
|
|
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
12
|
import { StepStatus } from './plan.js';
|
|
13
13
|
/** What adding `reticle()` to a Vite config buys, which differs by framework. */
|
|
14
14
|
export const VITE_PLUGIN_DETAIL = {
|
|
@@ -23,6 +23,36 @@ export const VITE_PLUGIN_DETAIL = {
|
|
|
23
23
|
SVELTEKIT: 'add reticle() to plugins (stamps data-reticle-source in .svelte components)',
|
|
24
24
|
};
|
|
25
25
|
const CAPABILITIES_TITLE = 'Capabilities + store';
|
|
26
|
+
/**
|
|
27
|
+
* The one step `init` genuinely cannot finish, addressed to the AGENT reading this report.
|
|
28
|
+
*
|
|
29
|
+
* It used to read "Capabilities are empty until you edit that file" and describe the consequence to
|
|
30
|
+
* a human — homework, in a report a human mostly does not read, naming no file to open and no thing
|
|
31
|
+
* to look for. The agent that ran `init` is right there, has the codebase, and can finish this in
|
|
32
|
+
* the same minute; what it needed was the specific instruction, not the rationale.
|
|
33
|
+
*
|
|
34
|
+
* What is left is exactly what could not be PROVEN from source: which atoms/actor/queryClient an
|
|
35
|
+
* adapter-wrapped store should be built from, and which elements matter enough to name. `init`
|
|
36
|
+
* emits nothing it cannot prove, because a wrong import here throws on every dev page load — worse
|
|
37
|
+
* than an empty file.
|
|
38
|
+
*/
|
|
39
|
+
const CAPABILITIES_TODO_TITLE = 'AGENT: finish the capabilities file';
|
|
40
|
+
function capabilitiesTodo(path, stores) {
|
|
41
|
+
const store = stores.length > 0
|
|
42
|
+
? `This app depends on a state library init cannot wire on its own (it needs an argument only ` +
|
|
43
|
+
`reading the code can supply). Uncomment the suggested line in that file and complete it: ` +
|
|
44
|
+
`${stores.join('; ')}.`
|
|
45
|
+
: `No state library was detected. If the app holds state somewhere init cannot see, register ` +
|
|
46
|
+
`it — see node_modules/@reticlehq/server/docs/usage.md.`;
|
|
47
|
+
return (`${path} was written but registers nothing, so \`hasCapabilities\` stays false and reticle_state ` +
|
|
48
|
+
`has nothing to read.\n` +
|
|
49
|
+
`Do this now, before driving anything:\n` +
|
|
50
|
+
` 1. ${store}\n` +
|
|
51
|
+
` 2. Open the app's main flow and add \`testids\` for the elements it touches (or add ` +
|
|
52
|
+
`data-testid attributes where there are none).\n` +
|
|
53
|
+
` 3. Prove it: drive one flow and check reticle_state returns your keys. An empty state read ` +
|
|
54
|
+
`means this file is still not doing anything, whatever else went green.`);
|
|
55
|
+
}
|
|
26
56
|
/**
|
|
27
57
|
* The dev module carrying `registerCapabilities` / `registerStore`.
|
|
28
58
|
*
|
|
@@ -37,23 +67,32 @@ function capabilitiesStep(input) {
|
|
|
37
67
|
title: CAPABILITIES_TITLE,
|
|
38
68
|
target: VITE_DEV_MODULE_PATH,
|
|
39
69
|
status: StepStatus.ALREADY,
|
|
40
|
-
detail: 'file exists
|
|
70
|
+
detail: 'file exists, left alone, it is yours to edit',
|
|
41
71
|
},
|
|
42
72
|
];
|
|
43
73
|
}
|
|
44
74
|
const testids = input.testids ?? [];
|
|
45
75
|
const stores = input.storeHints ?? [];
|
|
76
|
+
const wired = input.foundStores ?? [];
|
|
46
77
|
const found = testids.length > 0
|
|
47
78
|
? `${String(testids.length)} data-testid values`
|
|
48
79
|
: 'no data-testid values yet';
|
|
49
|
-
|
|
80
|
+
// A store we found and wired IS a registration, so the "registers nothing" notice must not fire.
|
|
81
|
+
const nothingToRegister = 0 === testids.length && 0 === stores.length && 0 === wired.length;
|
|
50
82
|
return [
|
|
51
83
|
{
|
|
52
84
|
title: CAPABILITIES_TITLE,
|
|
53
85
|
target: VITE_DEV_MODULE_PATH,
|
|
54
86
|
status: StepStatus.APPLY,
|
|
55
|
-
detail: `${found}; ${
|
|
56
|
-
|
|
87
|
+
detail: `${found}; ${wired.length > 0
|
|
88
|
+
? `registered ${wired.map((s) => `'${s.key}'`).join(', ')} from your source`
|
|
89
|
+
: stores.length > 0
|
|
90
|
+
? `store: uncomment the ${String(stores.length)} suggested line(s)`
|
|
91
|
+
: 'no state library detected'}`,
|
|
92
|
+
write: {
|
|
93
|
+
path: VITE_DEV_MODULE_PATH,
|
|
94
|
+
content: viteDevModuleFile(testids, stores, wired, input.detection.uiLibrary),
|
|
95
|
+
},
|
|
57
96
|
dependsOnInstall: true,
|
|
58
97
|
},
|
|
59
98
|
// The write is real; what it registers is not. `registerCapabilities({ testids: [], signals: [],
|
|
@@ -67,14 +106,10 @@ function capabilitiesStep(input) {
|
|
|
67
106
|
...(nothingToRegister
|
|
68
107
|
? [
|
|
69
108
|
{
|
|
70
|
-
title:
|
|
109
|
+
title: CAPABILITIES_TODO_TITLE,
|
|
71
110
|
target: VITE_DEV_MODULE_PATH,
|
|
72
111
|
status: StepStatus.NOTICE,
|
|
73
|
-
detail:
|
|
74
|
-
'no state library was detected, so `hasCapabilities` stays false and reticle_state ' +
|
|
75
|
-
'has nothing to read. Add data-testid to the elements your main flow touches, or ' +
|
|
76
|
-
'register a store in that file, and the agent gains what the app BELIEVES rather than ' +
|
|
77
|
-
'only what it rendered.',
|
|
112
|
+
detail: capabilitiesTodo(VITE_DEV_MODULE_PATH, stores),
|
|
78
113
|
},
|
|
79
114
|
]
|
|
80
115
|
: []),
|
|
@@ -94,7 +129,7 @@ function viteConfigSteps(input, detail) {
|
|
|
94
129
|
title: 'Vite plugin',
|
|
95
130
|
target: 'vite.config',
|
|
96
131
|
status: StepStatus.MANUAL,
|
|
97
|
-
detail: viteManual(port),
|
|
132
|
+
detail: viteManual(port, input.detection.uiLibrary),
|
|
98
133
|
},
|
|
99
134
|
];
|
|
100
135
|
}
|
|
@@ -115,7 +150,7 @@ function viteConfigSteps(input, detail) {
|
|
|
115
150
|
title: 'Vite plugin',
|
|
116
151
|
target: cfg.path,
|
|
117
152
|
status: StepStatus.MANUAL,
|
|
118
|
-
detail: `${patch.reason}\n\n${viteManual(port)}`,
|
|
153
|
+
detail: `${patch.reason}\n\n${viteManual(port, input.detection.uiLibrary)}`,
|
|
119
154
|
},
|
|
120
155
|
];
|
|
121
156
|
}
|
|
@@ -177,7 +212,10 @@ export function nextSteps(input) {
|
|
|
177
212
|
detail: 'create dev-only connect component',
|
|
178
213
|
write: {
|
|
179
214
|
path: devPath,
|
|
180
|
-
content: nextReticleDevFile(input.options.port, input.options.projectId, input.testids ?? [], input.storeHints ?? []
|
|
215
|
+
content: nextReticleDevFile(input.options.port, input.options.projectId, input.testids ?? [], input.storeHints ?? [],
|
|
216
|
+
// Next's dev module is not a sibling of `src/` — its imports are resolved from wherever
|
|
217
|
+
// the component actually lands (`app/`, or `src/app/` in a --src-dir app).
|
|
218
|
+
input.nextFoundStores ?? []),
|
|
181
219
|
},
|
|
182
220
|
dependsOnInstall: true,
|
|
183
221
|
};
|
|
@@ -291,6 +329,24 @@ export function craSteps(input) {
|
|
|
291
329
|
});
|
|
292
330
|
return steps;
|
|
293
331
|
}
|
|
332
|
+
/**
|
|
333
|
+
* Nuxt: one manual step carrying the whole recipe, and no pretence of more.
|
|
334
|
+
*
|
|
335
|
+
* There is no Nuxt app in `apps/` and no CI gate for one, so an auto-written plugin would be a
|
|
336
|
+
* support claim nothing backs — the same reasoning SvelteKit already carries. What this DOES fix is
|
|
337
|
+
* everything that made the previous fall-through actively wrong: Nuxt was classified as `html`, so
|
|
338
|
+
* it was handed the React kit and a localhost-guarded snippet that cannot run in a Vue app.
|
|
339
|
+
*/
|
|
340
|
+
export function nuxtSteps(input) {
|
|
341
|
+
return [
|
|
342
|
+
{
|
|
343
|
+
title: 'Connect snippet (Nuxt)',
|
|
344
|
+
target: NUXT_PLUGIN_PATH,
|
|
345
|
+
status: StepStatus.MANUAL,
|
|
346
|
+
detail: nuxtManual(input.options.port, input.options.projectId),
|
|
347
|
+
},
|
|
348
|
+
];
|
|
349
|
+
}
|
|
294
350
|
export function svelteKitSteps(input) {
|
|
295
351
|
const unverified = {
|
|
296
352
|
title: 'SvelteKit is UNVERIFIED',
|
|
@@ -320,7 +376,7 @@ export function svelteKitSteps(input) {
|
|
|
320
376
|
detail: 'create dev-only client connect (SvelteKit renders via app.html)',
|
|
321
377
|
write: {
|
|
322
378
|
path: SVELTEKIT_HOOKS_PATH,
|
|
323
|
-
content: svelteKitHooksFile(input.options.port, input.options.projectId),
|
|
379
|
+
content: svelteKitHooksFile(input.options.port, input.options.projectId, input.detection.uiLibrary),
|
|
324
380
|
},
|
|
325
381
|
dependsOnInstall: true,
|
|
326
382
|
},
|
|
@@ -357,7 +413,7 @@ export function astroSteps(input) {
|
|
|
357
413
|
// single recipe that does the whole job.
|
|
358
414
|
const manualWithLayout = astroManual(input.options.port, input.options.projectId, layout.path);
|
|
359
415
|
const configPatch = patchAstroConfig(config.source);
|
|
360
|
-
const layoutPatch = patchAstroLayout(layout.source, input.options.port, input.options.projectId);
|
|
416
|
+
const layoutPatch = patchAstroLayout(layout.source, input.options.port, input.options.projectId, input.detection.uiLibrary);
|
|
361
417
|
if (configPatch.kind === PatchKind.MANUAL || layoutPatch.kind === PatchKind.MANUAL) {
|
|
362
418
|
return [
|
|
363
419
|
{
|
package/dist/init/plan.d.ts
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
* contents, produce an ordered list of steps — each marked apply / manual / already / skip. The
|
|
4
4
|
* runner performs the `write` side-effects; this module decides *what* should happen.
|
|
5
5
|
*/
|
|
6
|
-
import { Framework, type Detection } from './detect.js';
|
|
6
|
+
import { Framework, UiLibrary, type Detection } from './detect.js';
|
|
7
|
+
import type { FoundStore } from './capabilities.js';
|
|
7
8
|
import { type McpClient } from './mcp-clients.js';
|
|
8
9
|
/**
|
|
9
10
|
* Pin the SDK to the CLI's own version.
|
|
@@ -15,8 +16,15 @@ import { type McpClient } from './mcp-clients.js';
|
|
|
15
16
|
* irrelevant, and a skewed pair impossible to install by accident.
|
|
16
17
|
*/
|
|
17
18
|
export declare function pinnedPackages(packages: readonly string[], version: string | undefined): readonly string[];
|
|
18
|
-
/**
|
|
19
|
-
|
|
19
|
+
/**
|
|
20
|
+
* The dev-dependencies `reticle init` installs — kit (or sensor) first, build plugin next.
|
|
21
|
+
*
|
|
22
|
+
* `uiLibrary` matters because the framework does not always name the renderer. The rule below was
|
|
23
|
+
* already written for Nuxt, correctly, and applied only there: a Vue app on plain Vite is
|
|
24
|
+
* `Framework.VITE` and a SvelteKit app renders Svelte, and both were being handed the React kit
|
|
25
|
+
* right after `init` had detected the real UI library and said so on screen.
|
|
26
|
+
*/
|
|
27
|
+
export declare function frameworkPackages(framework: Framework, uiLibrary?: UiLibrary): readonly string[];
|
|
20
28
|
/** Exported so the init telemetry can tell an MCP-registration failure from a dependency install. */
|
|
21
29
|
export declare const MCP_TARGET = "global (claude user scope)";
|
|
22
30
|
/** True when this step is what makes the app dial the daemon. */
|
|
@@ -77,6 +85,13 @@ export interface Step {
|
|
|
77
85
|
}
|
|
78
86
|
export interface Plan {
|
|
79
87
|
framework: Framework;
|
|
88
|
+
/**
|
|
89
|
+
* The renderer this app actually uses, carried so every consumer agrees on WHICH packages the
|
|
90
|
+
* plan installed. The retry guard checks node_modules for exactly those names; without this it
|
|
91
|
+
* looked for `@reticlehq/react` in a Vue app that had correctly been given the sensor, decided the
|
|
92
|
+
* install had failed, and skipped every wiring step on the retry.
|
|
93
|
+
*/
|
|
94
|
+
uiLibrary: UiLibrary;
|
|
80
95
|
steps: Step[];
|
|
81
96
|
}
|
|
82
97
|
export interface PlanInput {
|
|
@@ -144,6 +159,10 @@ export interface PlanInput {
|
|
|
144
159
|
testids?: readonly string[] | undefined;
|
|
145
160
|
/** Ready-to-uncomment `registerStore` lines for the state libraries the app actually depends on. */
|
|
146
161
|
storeHints?: readonly string[] | undefined;
|
|
162
|
+
/** Store instances found in the app's own source — imported and registered outright, not hinted. */
|
|
163
|
+
foundStores?: readonly FoundStore[] | undefined;
|
|
164
|
+
/** The same stores, with specifiers resolved from Next's dev-module directory instead of `src/`. */
|
|
165
|
+
nextFoundStores?: readonly FoundStore[] | undefined;
|
|
147
166
|
/** Whether src/reticle-dev.ts already exists — it is the one generated file users are meant to edit. */
|
|
148
167
|
viteDevModuleExists?: boolean | undefined;
|
|
149
168
|
/** Whether src/hooks.client.ts already exists (SvelteKit idempotency). */
|
|
@@ -159,10 +178,19 @@ export interface PlanInput {
|
|
|
159
178
|
pairingToken?: string;
|
|
160
179
|
/** Whether .reticle.json already exists in the project root (idempotency). */
|
|
161
180
|
reticleConfigExists?: boolean;
|
|
181
|
+
/**
|
|
182
|
+
* Its CONTENT, so an existing config can be checked instead of trusted.
|
|
183
|
+
*
|
|
184
|
+
* Existence alone said "a file is present" and was reported as "this is configured correctly" —
|
|
185
|
+
* see reticleConfigStep for the port that survived every re-run because of it.
|
|
186
|
+
*/
|
|
187
|
+
reticleConfigSource?: string | null | undefined;
|
|
162
188
|
/** Current project-root CLAUDE.md content (for the idempotent agent-rule merge), or null/undefined. */
|
|
163
189
|
claudeMdContent?: string | null | undefined;
|
|
164
|
-
/** Current project-root AGENTS.md content (cross-agent
|
|
190
|
+
/** Current project-root AGENTS.md content (the cross-agent rule), or null/undefined. */
|
|
165
191
|
agentsMdContent?: string | null | undefined;
|
|
192
|
+
/** Current project-root RETICLE.md content (the full rules), or null when absent. */
|
|
193
|
+
reticleMdContent?: string | null | undefined;
|
|
166
194
|
/**
|
|
167
195
|
* Current .cursor/rules/reticle.mdc content, or null when absent.
|
|
168
196
|
*
|
|
@@ -175,6 +203,14 @@ export interface PlanInput {
|
|
|
175
203
|
claudeCommandContent?: string | null | undefined;
|
|
176
204
|
/** Current .cursor/commands/reticle.md content, or null when absent. */
|
|
177
205
|
cursorCommandContent?: string | null | undefined;
|
|
206
|
+
/**
|
|
207
|
+
* Absolute directory the AGENT runs in, when that is not the app's directory.
|
|
208
|
+
*
|
|
209
|
+
* The rule and command files are read by the agent, not by the app, so a monorepo whose app is a
|
|
210
|
+
* directory down must still get its `/reticle` where the human's session actually stands. Absent
|
|
211
|
+
* (the single-package case) leaves every path project-relative exactly as before.
|
|
212
|
+
*/
|
|
213
|
+
agentFileRoot?: string | undefined;
|
|
178
214
|
options: {
|
|
179
215
|
port: number | undefined;
|
|
180
216
|
mcp: boolean;
|