@reticlehq/init 3.1.0 → 3.3.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.
Files changed (39) hide show
  1. package/dist/detect/detect.d.ts +13 -2
  2. package/dist/detect/detect.js +46 -3
  3. package/dist/detect/workspace-redirect.d.ts +2 -1
  4. package/dist/diagnose/csp-doctor.d.ts +20 -1
  5. package/dist/diagnose/csp-doctor.js +101 -4
  6. package/dist/diagnose/desktop-doctor.d.ts +1 -1
  7. package/dist/diagnose/install-retries.d.ts +2 -1
  8. package/dist/host.d.ts +6 -0
  9. package/dist/host.js +3 -0
  10. package/dist/index.d.ts +11 -1
  11. package/dist/index.js +11 -1
  12. package/dist/patch/astro-patch.d.ts +47 -12
  13. package/dist/patch/astro-patch.js +149 -41
  14. package/dist/patch/nuxt-snippets.d.ts +44 -0
  15. package/dist/patch/nuxt-snippets.js +135 -0
  16. package/dist/patch/snippets.d.ts +1 -48
  17. package/dist/patch/snippets.js +68 -178
  18. package/dist/patch/vite-env-types.d.ts +9 -0
  19. package/dist/patch/vite-env-types.js +19 -0
  20. package/dist/patch/vite-owning-config.d.ts +13 -0
  21. package/dist/patch/vite-owning-config.js +43 -16
  22. package/dist/plan/connect-steps.d.ts +3 -2
  23. package/dist/plan/connect-steps.js +4 -2
  24. package/dist/plan/framework-adapter.d.ts +2 -5
  25. package/dist/plan/framework-adapter.js +6 -5
  26. package/dist/plan/plan-framework.d.ts +1 -1
  27. package/dist/plan/plan-framework.js +33 -11
  28. package/dist/plan/plan-types.d.ts +18 -2
  29. package/dist/plan/plan.js +117 -35
  30. package/dist/plan/stop-hook-step.d.ts +4 -0
  31. package/dist/plan/stop-hook-step.js +49 -0
  32. package/dist/project/agent-rules.js +2 -0
  33. package/dist/register/mcp-clients.d.ts +13 -0
  34. package/dist/register/mcp-clients.js +22 -0
  35. package/dist/register/mcp.d.ts +8 -0
  36. package/dist/register/mcp.js +16 -0
  37. package/dist/run-types.d.ts +12 -0
  38. package/dist/run.js +42 -6
  39. package/package.json +2 -2
@@ -1,66 +1,170 @@
1
1
  /**
2
- * Pure, conservative patchers for an Astro app: the config gets the token/root `define` and the
3
- * raised build target, one layout gets the dev-only connect `<script>`.
2
+ * Pure, conservative patchers for an Astro app: the config gets the raised build target and the
3
+ * watcher ignore, one layout gets the pairing token via frontmatter `<meta>` tags and a processed
4
+ * `<script>` that statically imports a local connect module.
4
5
  *
5
6
  * Astro was the last gated framework where `init` printed a correct recipe and applied none of it —
6
7
  * the only ⚠ left on a supported stack, and the user's first session was two hand-copied snippets
7
8
  * away. Astro is Vite-based but renders its own HTML, so the Vite plugin's injection never fires and
8
- * there is no entry module to wire: the connect has to live in a page or layout `<script>`, and the
9
- * token has to be inlined by the config because nothing else is in the page's path to inject it.
9
+ * there is no entry module to wire: the connect has to live in a page or layout.
10
+ *
11
+ * The token used to be inlined by `vite.define`. On Astro 7.2+ that substitution no longer reaches
12
+ * the client pipeline (#1008), so the identifier stays literal, `connect()` omits `token`, and the
13
+ * bridge refuses with "no pairing token on the page". The channel that does work is a `<meta>`: read
14
+ * the file in frontmatter and let a local module query `meta[name="reticle-pairing-token"]`. Reading
15
+ * per request also means a token written after the dev server started needs no restart. The SDK import
16
+ * lives in `src/components/ReticleDev.ts` so the page only ever statically imports a project module.
10
17
  *
11
18
  * Both patchers bail to `manual` (the printed recipe) on any shape they do not fully recognise.
12
19
  * Half-editing a build config is worse than a documented manual step.
13
20
  */
21
+ import { posix } from 'node:path';
14
22
  import { RETICLE_DEFAULT_PORT, bridgeWsUrl } from '@reticlehq/core';
15
23
  import { PatchKind } from './patch-kind.js';
16
24
  import { UiLibrary } from '../detect/detect.js';
17
25
  import { registerCapabilitiesCall, sdkImport } from './snippets.js';
18
26
  import { patchViteOwningConfig } from './vite-owning-config.js';
19
- /** Present in a patched layout and in the printed recipe's script. */
27
+ /**
28
+ * Local module `init` writes for Astro. A processed page `<script>` statically imports this file
29
+ * so Vite owns the SDK the same way it owns any other project module.
30
+ *
31
+ * `.ts`, not `.tsx`: the install-gate scaffold is `create-astro --template minimal`, which has no
32
+ * React integration. The example app uses the same static import in a processed page `<script>`.
33
+ */
34
+ export const ASTRO_RETICLE_DEV_PATH = 'src/components/ReticleDev.ts';
35
+ /**
36
+ * Dev-only Astro connect module. Static SDK import, token from frontmatter `<meta>` tags.
37
+ */
38
+ export function astroReticleDevFile(port, projectId, uiLibrary = UiLibrary.REACT, testids = []) {
39
+ const sdk = sdkImport(uiLibrary);
40
+ const extra = port !== undefined && port !== RETICLE_DEFAULT_PORT ? `\n url: '${bridgeWsUrl(port)}',` : '';
41
+ const id = projectId !== undefined && projectId.length > 0 ? `\n projectId: '${projectId}',` : '';
42
+ return `import { reticle${sdk.usesInstall ? ', install' : ''}, registerCapabilities } from '${sdk.specifier}';
43
+
44
+ /** Dev-only: connect Reticle. Imported from a processed page script so Vite owns the SDK graph. */
45
+ export default function connectReticle() {
46
+ const token =
47
+ document.querySelector('meta[name="reticle-pairing-token"]')?.getAttribute('content') ?? '';
48
+ const root =
49
+ document.querySelector('meta[name="reticle-pairing-root"]')?.getAttribute('content') ?? '';
50
+ const url =
51
+ document.querySelector('meta[name="reticle-pairing-url"]')?.getAttribute('content') ?? '';
52
+ if (0 === token.length) {
53
+ console.warn(${JSON.stringify(ASTRO_MISSING_TOKEN_WARNING)});
54
+ }
55
+ ${sdk.usesInstall ? 'install();' : '// The sensor has no install(); that is the React adapter.'}
56
+ reticle.connect({${id}${extra}
57
+ ...(0 < token.length ? { token } : {}),
58
+ ...(0 < root.length ? { root } : {}),
59
+ ...(0 < url.length ? { url } : {}),
60
+ });
61
+ ${registerCapabilitiesCall(testids, ' ')}
62
+ }
63
+ `;
64
+ }
65
+ /** Present in a patched layout (and in an already-wired one that still has the old script). */
20
66
  const LAYOUT_MARKER = 'reticle.connect';
67
+ const LAYOUT_MODULE_MARKER = 'connectReticle';
21
68
  const BODY_CLOSE = '</body>';
69
+ const FRONTMATTER_FENCE = '---';
70
+ /**
71
+ * Written into the Astro `vite:` block so a second init reports ALREADY. The Nuxt path uses
72
+ * `__RETICLE_TOKEN__` in `define` as its marker; Astro no longer writes that name into the config.
73
+ */
74
+ export const ASTRO_VITE_OWNING_MARKER = 'reticle-vite-owning';
75
+ /**
76
+ * The one runtime condition that guarantees a refused connect, said out loud on the page.
77
+ *
78
+ * Matching the Vite plugin's missing-token wording, because Astro never loads that plugin. The
79
+ * token is read at render time now, not at config load, so a reload is enough — the user does not
80
+ * have to restart the dev server.
81
+ */
82
+ export const ASTRO_MISSING_TOKEN_WARNING = '[reticle] no pairing token was available when this page rendered, so the app will connect ' +
83
+ 'and be refused — you will see NO SESSION even though the SDK loads and the socket opens. ' +
84
+ 'The token is written by the Reticle daemon: start it (`reticle serve`, or let your agent start ' +
85
+ 'it) and reload this page.';
22
86
  /**
23
87
  * Astro's half of the Vite-owning config patch: the call to extend, and the SDK to pre-bundle.
24
88
  *
25
89
  * `@reticlehq/react` and not the detected UI library: Astro hosts islands from any framework, but
26
90
  * `frameworkPackages` gives every Astro app the React kit (see FRAMEWORK_ADAPTERS), so that is the
27
91
  * package the connect script imports and the one Vite must pre-bundle.
92
+ *
93
+ * `defineToken: false` is the #1008 fix. `build.target`, `optimizeDeps.include` and the `.reticle/`
94
+ * watcher ignore stay — each has a measured reason and none of them is the defect.
28
95
  */
29
96
  const ASTRO_VITE_CONFIG = {
30
97
  defineCall: /defineConfig\s*\(\s*\{/,
31
98
  sdkSpecifier: sdkImport(UiLibrary.REACT).specifier,
99
+ defineToken: false,
100
+ alreadyMarker: ASTRO_VITE_OWNING_MARKER,
32
101
  };
33
102
  export function patchAstroConfig(source) {
34
103
  return patchViteOwningConfig(source, ASTRO_VITE_CONFIG);
35
104
  }
36
- /** The dev-only connect that goes inside the layout's `<body>`. */
37
- function astroConnectScript(port, projectId, uiLibrary, testids) {
38
- // Must match what `frameworkPackages` installed. Astro hosts islands from any framework, so an
39
- // Astro app whose islands are Vue or Svelte gets the sensor, and a script importing the React
40
- // adapter would load a package that is not in node_modules. See sdkImport.
41
- const sdk = sdkImport(uiLibrary);
42
- const url = port !== undefined && port !== RETICLE_DEFAULT_PORT
43
- ? `\n url: '${bridgeWsUrl(port)}',`
44
- : '';
45
- const id = projectId !== undefined && projectId.length > 0 ? `\n projectId: '${projectId}',` : '';
46
- return ` <script>
105
+ /**
106
+ * Aliased imports so a layout that already pulled `readFileSync` from `node:fs` does not get a
107
+ * duplicate binding. The names are the already-marker for the frontmatter half.
108
+ */
109
+ const FRONTMATTER_ADDITIONS = `import { readFileSync as reticleReadFileSync } from 'node:fs';
110
+ import { homedir as reticleHomedir } from 'node:os';
111
+ import { join as reticleJoin } from 'node:path';
112
+ const pairingToken = (() => {
113
+ if (!import.meta.env.DEV) return '';
114
+ const dir = process.env['RETICLE_PAIRING_TOKEN_DIR'] || reticleJoin(reticleHomedir(), '.reticle');
115
+ try { return reticleReadFileSync(reticleJoin(dir, 'pairing-token'), 'utf8').trim(); } catch { return ''; }
116
+ })();
117
+ const pairingRoot = import.meta.env.DEV ? process.cwd() : '';
118
+ `;
119
+ /**
120
+ * Import path from a layout/page to the generated connect module, always relative and posix.
121
+ *
122
+ * `src/layouts/Layout.astro` and `src/pages/index.astro` both resolve to `../components/ReticleDev`.
123
+ */
124
+ export function astroReticleDevImport(layoutPath) {
125
+ const fromDir = posix.dirname(layoutPath.replaceAll('\\', '/'));
126
+ const to = ASTRO_RETICLE_DEV_PATH.replace(/\.ts$/, '');
127
+ let rel = posix.relative(fromDir, to);
128
+ if (!rel.startsWith('.'))
129
+ rel = `./${rel}`;
130
+ return rel;
131
+ }
132
+ /** Meta tags plus a processed script that statically imports the local connect module. */
133
+ function astroConnectMarkup(moduleSpecifier) {
134
+ return ` <meta name="reticle-pairing-token" content={pairingToken} />
135
+ <meta name="reticle-pairing-root" content={pairingRoot} />
136
+ <script>
137
+ import connectReticle from '${moduleSpecifier}';
47
138
  if (import.meta.env.DEV) {
48
- const token = typeof __RETICLE_TOKEN__ !== 'undefined' ? __RETICLE_TOKEN__ : '';
49
- const root = typeof __RETICLE_ROOT__ !== 'undefined' ? __RETICLE_ROOT__ : '';
50
- const { reticle${sdk.usesInstall ? ', install' : ''}, registerCapabilities } = await import('${sdk.specifier}');
51
- ${sdk.usesInstall ? 'install();' : '// The sensor has no install(); that is the React adapter.'}
52
- reticle.connect({${id}${url}
53
- ...(token.length > 0 ? { token } : {}),
54
- ...(root.length > 0 ? { root } : {}),
55
- });
56
- ${registerCapabilitiesCall(testids, ' ')}
139
+ connectReticle();
57
140
  }
58
141
  </script>
59
142
  `;
60
143
  }
61
- export function patchAstroLayout(source, port, projectId, uiLibrary = UiLibrary.REACT, testids = []) {
62
- if (source.includes(LAYOUT_MARKER))
144
+ /**
145
+ * Marker for the frontmatter half. It must be a name ONLY the frontmatter writes: this ran against
146
+ * `pairingToken`, which the meta markup inserted one step earlier already contains, so the guard
147
+ * always tripped and the token read was never added — leaving every scaffolded page throwing
148
+ * `ReferenceError: pairingToken is not defined`.
149
+ */
150
+ const FRONTMATTER_MARKER = 'reticleReadFileSync';
151
+ function withFrontmatter(source) {
152
+ if (source.includes(FRONTMATTER_MARKER))
153
+ return source;
154
+ if (source.startsWith(FRONTMATTER_FENCE)) {
155
+ const closeAt = source.indexOf(`\n${FRONTMATTER_FENCE}`, FRONTMATTER_FENCE.length);
156
+ if (closeAt < 0)
157
+ return null;
158
+ const inner = source.slice(FRONTMATTER_FENCE.length + 1, closeAt);
159
+ const padded = inner.endsWith('\n') ? inner : `${inner}\n`;
160
+ return `${FRONTMATTER_FENCE}\n${padded}${FRONTMATTER_ADDITIONS}${source.slice(closeAt + 1)}`;
161
+ }
162
+ return `${FRONTMATTER_FENCE}\n${FRONTMATTER_ADDITIONS}${FRONTMATTER_FENCE}\n${source}`;
163
+ }
164
+ export function patchAstroLayout(source, layoutPath = 'src/layouts/Layout.astro') {
165
+ if (source.includes(LAYOUT_MARKER) || source.includes(LAYOUT_MODULE_MARKER)) {
63
166
  return { kind: PatchKind.ALREADY };
167
+ }
64
168
  const at = source.lastIndexOf(BODY_CLOSE);
65
169
  if (at < 0) {
66
170
  return {
@@ -68,28 +172,32 @@ export function patchAstroLayout(source, port, projectId, uiLibrary = UiLibrary.
68
172
  reason: "couldn't find a `</body>` to place the connect script before",
69
173
  };
70
174
  }
71
- return {
72
- kind: PatchKind.APPLY,
73
- code: `${source.slice(0, at)}${astroConnectScript(port, projectId, uiLibrary, testids)}${source.slice(at)}`,
74
- };
175
+ const withScripts = `${source.slice(0, at)}${astroConnectMarkup(astroReticleDevImport(layoutPath))}${source.slice(at)}`;
176
+ const withFence = withFrontmatter(withScripts);
177
+ if (null === withFence) {
178
+ return {
179
+ kind: PatchKind.MANUAL,
180
+ reason: "couldn't find a closing frontmatter fence to place the pairing-token read in",
181
+ };
182
+ }
183
+ return { kind: PatchKind.APPLY, code: withFence };
75
184
  }
76
185
  /** Where Astro keeps ambient types — `create-astro` ships this file. */
77
186
  export const ASTRO_ENV_DTS_PATH = 'src/env.d.ts';
78
187
  /**
79
- * The declarations `astro check` needs for the Vite `define` names (#677).
80
- *
81
- * Matching SvelteKit's `hooks.client.ts` typing: `string | undefined`, because the define can be
82
- * absent when the pairing token file is missing.
188
+ * The declarations `astro check` needs for the window names the inline script writes (#677, #1008).
83
189
  */
84
- export const ASTRO_ENV_DTS_DECLARES = 'declare const __RETICLE_TOKEN__: string | undefined;\n' +
85
- 'declare const __RETICLE_ROOT__: string | undefined;\n';
86
- /** Present once either declare is in the file — both are written together. */
190
+ export const ASTRO_ENV_DTS_DECLARES = 'interface Window {\n' +
191
+ ' __RETICLE_TOKEN__?: string;\n' +
192
+ ' __RETICLE_ROOT__?: string;\n' +
193
+ '}\n';
194
+ /** Present once either name is in the file — both are written together. */
87
195
  const ENV_DTS_MARKER = '__RETICLE_TOKEN__';
88
196
  /**
89
- * Append the Vite-define ambient declarations to `src/env.d.ts`, or create the file.
197
+ * Append the window ambient declarations to `src/env.d.ts`, or create the file.
90
198
  *
91
- * Without this, `create-astro`'s default `"astro check && astro build"` fails with four
92
- * `ts(2304) Cannot find name '__RETICLE_TOKEN__'` errors after a clean init (#677).
199
+ * Without this, `create-astro`'s default `"astro check && astro build"` fails with
200
+ * `ts(2339) Property '__RETICLE_TOKEN__' does not exist on type 'Window'` after a clean init.
93
201
  */
94
202
  export function patchAstroEnvDts(existing) {
95
203
  if (null !== existing && existing.includes(ENV_DTS_MARKER)) {
@@ -0,0 +1,44 @@
1
+ /**
2
+ * The plugin directory Nuxt actually scans, which is not the same on 3 and 4.
3
+ *
4
+ * Nuxt 4's default `srcDir` is `app/`, so plugins live in `app/plugins/`. Nuxt 3's is the project
5
+ * root, so they live in `plugins/`. Writing to the wrong one is the silent failure this whole change
6
+ * exists to remove: the file is on disk, `init` reports it green, Nuxt never scans that directory,
7
+ * and no plugin is ever registered. Keyed on whether the app HAS an `app/` directory, which is the
8
+ * same signal Nuxt itself uses to pick its srcDir.
9
+ */
10
+ export declare function nuxtPluginPath(hasAppDir: boolean): string;
11
+ /**
12
+ * The dev-only Nuxt client plugin `init` writes.
13
+ *
14
+ * Every trap in it is one somebody actually hit. `import.meta.dev` rather than a hostname check: the
15
+ * reported failure was a snippet guarded on `window.location.hostname === 'localhost'`, which is
16
+ * false on any hosts-file alias or LAN address and throws during SSR, so the connect never ran with
17
+ * no error and no log line. `.client.ts` rather than an SSR guard, because that suffix is what keeps
18
+ * this out of the server bundle. `@reticlehq/browser` rather than the React kit, because installing
19
+ * a package named `@reticlehq/react` into a Vue codebase is the single thing most likely to make
20
+ * somebody abandon the setup.
21
+ *
22
+ * The token is inlined by `nuxt.config`'s `vite.define` (see nuxt-patch.ts). The bridge requires it
23
+ * even on localhost, and nothing in a browser can read the file it lives in.
24
+ */
25
+ export declare function nuxtPluginFile(port: number | undefined, projectId?: string, testids?: readonly string[]): string;
26
+ /**
27
+ * Said as its own NOTICE beside the write, not inside it: a running dev server does not pick up a
28
+ * new plugin, and off localhost the connect needs two more arguments.
29
+ *
30
+ * A ✓ line is one SKILL.md tells the reader to skip, so a caveat carried on the ✓ is a caveat nobody
31
+ * reads — and both of these end with an app that boots, looks correct and never pairs.
32
+ */
33
+ export declare const NUXT_PLUGIN_NOTICE: string;
34
+ /**
35
+ * The Nuxt recipe, written out in full because each trap in it is one a Nuxt 4 app actually hit:
36
+ * a Vue codebase classified as `html` and handed `@reticlehq/react`, a connect guarded on
37
+ * `window.location.hostname === 'localhost'` (false on any hosts-file alias, and `window` does not
38
+ * exist during SSR), and a running dev server that never picked up the new plugin.
39
+ *
40
+ * So: `import.meta.dev` instead of a hostname check, `.client.ts` instead of an SSR guard, the
41
+ * framework-neutral sensor instead of the React kit, the non-localhost flag named up front, and the
42
+ * restart said out loud.
43
+ */
44
+ export declare function nuxtManual(port: number | undefined, projectId?: string): string;
@@ -0,0 +1,135 @@
1
+ /**
2
+ * The Nuxt connect plugin `reticle init` writes, split out of snippets.ts (which crossed the
3
+ * 1000-line cap): its path, the plugin file itself, the notice, and the manual fallback.
4
+ */
5
+ import { ReticleDir } from '@reticlehq/core';
6
+ import { connectArg, registerCapabilitiesCall } from './snippets.js';
7
+ /** Where a Nuxt dev-only client plugin belongs. `.client` keeps it out of SSR; Nuxt auto-registers it. */
8
+ const NUXT_PLUGIN_PATH = 'app/plugins/reticle.client.ts';
9
+ /**
10
+ * The plugin directory Nuxt actually scans, which is not the same on 3 and 4.
11
+ *
12
+ * Nuxt 4's default `srcDir` is `app/`, so plugins live in `app/plugins/`. Nuxt 3's is the project
13
+ * root, so they live in `plugins/`. Writing to the wrong one is the silent failure this whole change
14
+ * exists to remove: the file is on disk, `init` reports it green, Nuxt never scans that directory,
15
+ * and no plugin is ever registered. Keyed on whether the app HAS an `app/` directory, which is the
16
+ * same signal Nuxt itself uses to pick its srcDir.
17
+ */
18
+ export function nuxtPluginPath(hasAppDir) {
19
+ return hasAppDir ? NUXT_PLUGIN_PATH : 'plugins/reticle.client.ts';
20
+ }
21
+ /**
22
+ * The dev-only Nuxt client plugin `init` writes.
23
+ *
24
+ * Every trap in it is one somebody actually hit. `import.meta.dev` rather than a hostname check: the
25
+ * reported failure was a snippet guarded on `window.location.hostname === 'localhost'`, which is
26
+ * false on any hosts-file alias or LAN address and throws during SSR, so the connect never ran with
27
+ * no error and no log line. `.client.ts` rather than an SSR guard, because that suffix is what keeps
28
+ * this out of the server bundle. `@reticlehq/browser` rather than the React kit, because installing
29
+ * a package named `@reticlehq/react` into a Vue codebase is the single thing most likely to make
30
+ * somebody abandon the setup.
31
+ *
32
+ * The token is inlined by `nuxt.config`'s `vite.define` (see nuxt-patch.ts). The bridge requires it
33
+ * even on localhost, and nothing in a browser can read the file it lives in.
34
+ */
35
+ export function nuxtPluginFile(port, projectId, testids = []) {
36
+ const base = connectArg(port, projectId);
37
+ const fields = '' === base ? '' : `${base.slice(1, -1).trim()}, `;
38
+ return `// Dev-only: connect Reticle from a Nuxt client plugin. Nuxt owns its own Vite instance and
39
+ // renders its own HTML, so the Vite plugin's index.html injection never fires — a .client plugin is
40
+ // the path that can register a session at all.
41
+ export default defineNuxtPlugin(() => {
42
+ // import.meta.dev is resolved at BUILD time, so it does not care what hostname you develop on.
43
+ // Do NOT guard on window.location.hostname === 'localhost': that is false on any hosts-file alias
44
+ // or LAN address, and window does not exist during SSR.
45
+ if (!import.meta.dev) return;
46
+ void import('@reticlehq/browser').then(({ reticle, registerCapabilities }) => {
47
+ // Both inlined by nuxt.config's vite.define, which \`reticle init\` wrote. The bridge refuses a
48
+ // connect with no token even on localhost; the root makes source paths repo-relative.
49
+ const token = typeof __RETICLE_TOKEN__ !== 'undefined' ? __RETICLE_TOKEN__ : '';
50
+ const root = typeof __RETICLE_ROOT__ !== 'undefined' ? __RETICLE_ROOT__ : '';
51
+ reticle.connect({
52
+ ${fields}...(token.length > 0 ? { token } : {}),
53
+ ...(root.length > 0 ? { root } : {}),
54
+ });
55
+
56
+ // What the agent can drive without guessing. Add a store here too — registerStore('cart',
57
+ // piniaStore(useCartStore())) — and the agent can check what the app BELIEVES, not just what it
58
+ // rendered. See node_modules/@reticlehq/server/docs/usage.md.
59
+ ${registerCapabilitiesCall(testids, ' ')}
60
+ });
61
+ });
62
+
63
+ declare const __RETICLE_TOKEN__: string | undefined;
64
+ declare const __RETICLE_ROOT__: string | undefined;
65
+ `;
66
+ }
67
+ /**
68
+ * Said as its own NOTICE beside the write, not inside it: a running dev server does not pick up a
69
+ * new plugin, and off localhost the connect needs two more arguments.
70
+ *
71
+ * A ✓ line is one SKILL.md tells the reader to skip, so a caveat carried on the ✓ is a caveat nobody
72
+ * reads — and both of these end with an app that boots, looks correct and never pairs.
73
+ */
74
+ export const NUXT_PLUGIN_NOTICE = 'Restart the dev server: one that is already running does not pick up a new plugin — it will not ' +
75
+ 'appear in .nuxt/plugins/client.mjs and the app comes up with no SDK at all. And if your dev host ' +
76
+ 'is anything other than localhost (a hosts-file alias, a LAN IP, a tunnel), that connect call ' +
77
+ 'needs TWO additions, not one: allowNonLocalhost: true AND the token from ~/.reticle/pairing-token. ' +
78
+ 'The flag alone is NOT sufficient off localhost.';
79
+ /**
80
+ * The Nuxt recipe, written out in full because each trap in it is one a Nuxt 4 app actually hit:
81
+ * a Vue codebase classified as `html` and handed `@reticlehq/react`, a connect guarded on
82
+ * `window.location.hostname === 'localhost'` (false on any hosts-file alias, and `window` does not
83
+ * exist during SSR), and a running dev server that never picked up the new plugin.
84
+ *
85
+ * So: `import.meta.dev` instead of a hostname check, `.client.ts` instead of an SSR guard, the
86
+ * framework-neutral sensor instead of the React kit, the non-localhost flag named up front, and the
87
+ * restart said out loud.
88
+ */
89
+ export function nuxtManual(port, projectId) {
90
+ const base = connectArg(port, projectId);
91
+ const fields = '' === base ? '' : base.slice(1, -1).trim();
92
+ const connect = '' === fields ? 'reticle.connect()' : `reticle.connect({ ${fields} })`;
93
+ return `Nuxt owns its own Vite instance and renders its own HTML, so there is no vite.config to patch
94
+ and no index.html to inject into. Wire it with a dev-only CLIENT plugin, which is Nuxt's own idiom:
95
+
96
+ 1. Create ${NUXT_PLUGIN_PATH}:
97
+
98
+ export default defineNuxtPlugin(() => {
99
+ // import.meta.dev is the correct guard: it is resolved at build time, so it does not care
100
+ // what hostname you develop on. Do NOT guard on window.location.hostname === 'localhost' —
101
+ // that is false on any hosts-file alias or LAN address, and window does not exist in SSR.
102
+ if (!import.meta.dev) return
103
+ void import('@reticlehq/browser').then(({ reticle }) => {
104
+ ${connect}
105
+ })
106
+ })
107
+
108
+ The .client.ts suffix is load-bearing: it is what keeps this out of the server bundle.
109
+
110
+ 2. Restart the dev server. A dev server that is already running does not pick up a new plugin —
111
+ it will not appear in .nuxt/plugins/client.mjs, and the app will come up with no SDK at all.
112
+
113
+ 3. If your dev host is anything other than localhost (a hosts-file alias, a LAN IP, a tunnel), that
114
+ connect call needs TWO additions, not one: allowNonLocalhost: true AND a pairing token passed as
115
+ token. The flag alone is NOT sufficient off localhost. The token is the one in
116
+ ~/.reticle/pairing-token. Without both, the SDK loads and then refuses, and the only sign is one
117
+ line in the browser console.
118
+
119
+ 4. Add this to nuxt.config, so the dev server does not watch Reticle's own journal:
120
+
121
+ vite: { server: { watch: { ignored: [/(^|[\\\\/])\\.${ReticleDir.ROOT.slice(1)}([\\\\/]|$)/] } } }
122
+
123
+ Reticle journals every session into ${ReticleDir.ROOT}/ in your project root, rewriting one file
124
+ in it continuously while a session is live. Nuxt's dev server watches that root, so without this
125
+ it sees each write as a project file changing and full-reloads the page — which reconnects the
126
+ SDK, which produces the next write. The loop runs several times a second and looks like anything
127
+ except what it is: refs go stale, actions die mid-flight, and the session appears to flap.
128
+ It is a RegExp rather than a glob on purpose — chokidar dropped glob support in v4, so a
129
+ double-star pattern here is accepted and matches nothing.
130
+
131
+ The package is @reticlehq/browser — the framework-neutral sensor. DOM, network, console, routing and
132
+ source file:line all work in Vue. What you do not get is React component identity, which is the only
133
+ thing the React adapter adds. There is no Nuxt app in this project's CI, so this path is UNVERIFIED:
134
+ if something does not work, please open an issue.`;
135
+ }
@@ -181,8 +181,6 @@ export declare const WEBPACK4_REACT_SCRIPTS_MAJOR = 5;
181
181
  export declare function webpack4TranspileNote(reactScriptsMajor: number): string;
182
182
  /** React Router's client-entry override point, in framework mode. */
183
183
  export declare const REACT_ROUTER_ENTRY_PATH = "app/entry.client.tsx";
184
- /** The one line that puts Reticle in a React Router client entry. */
185
- export declare const REACT_ROUTER_CONNECT_LINE = "if (import.meta.env.DEV) void import('/@reticle-connect');";
186
184
  /**
187
185
  * The client entry `init` writes when React Router framework mode has none.
188
186
  *
@@ -204,52 +202,7 @@ export declare function reactRouterEntryFile(): string;
204
202
  * still be evaluated first, and this is a side-effect import.
205
203
  */
206
204
  export declare function reactRouterEntryPatch(source: string): string | null;
207
- /** Where a Nuxt dev-only client plugin belongs. `.client` keeps it out of SSR; Nuxt auto-registers it. */
208
- export declare const NUXT_PLUGIN_PATH = "app/plugins/reticle.client.ts";
209
- /**
210
- * The plugin directory Nuxt actually scans, which is not the same on 3 and 4.
211
- *
212
- * Nuxt 4's default `srcDir` is `app/`, so plugins live in `app/plugins/`. Nuxt 3's is the project
213
- * root, so they live in `plugins/`. Writing to the wrong one is the silent failure this whole change
214
- * exists to remove: the file is on disk, `init` reports it green, Nuxt never scans that directory,
215
- * and no plugin is ever registered. Keyed on whether the app HAS an `app/` directory, which is the
216
- * same signal Nuxt itself uses to pick its srcDir.
217
- */
218
- export declare function nuxtPluginPath(hasAppDir: boolean): string;
219
- /**
220
- * The dev-only Nuxt client plugin `init` writes.
221
- *
222
- * Every trap in it is one somebody actually hit. `import.meta.dev` rather than a hostname check: the
223
- * reported failure was a snippet guarded on `window.location.hostname === 'localhost'`, which is
224
- * false on any hosts-file alias or LAN address and throws during SSR, so the connect never ran with
225
- * no error and no log line. `.client.ts` rather than an SSR guard, because that suffix is what keeps
226
- * this out of the server bundle. `@reticlehq/browser` rather than the React kit, because installing
227
- * a package named `@reticlehq/react` into a Vue codebase is the single thing most likely to make
228
- * somebody abandon the setup.
229
- *
230
- * The token is inlined by `nuxt.config`'s `vite.define` (see nuxt-patch.ts). The bridge requires it
231
- * even on localhost, and nothing in a browser can read the file it lives in.
232
- */
233
- export declare function nuxtPluginFile(port: number | undefined, projectId?: string, testids?: readonly string[]): string;
234
- /**
235
- * Said as its own NOTICE beside the write, not inside it: a running dev server does not pick up a
236
- * new plugin, and off localhost the connect needs two more arguments.
237
- *
238
- * A ✓ line is one SKILL.md tells the reader to skip, so a caveat carried on the ✓ is a caveat nobody
239
- * reads — and both of these end with an app that boots, looks correct and never pairs.
240
- */
241
- export declare const NUXT_PLUGIN_NOTICE: string;
242
- /**
243
- * The Nuxt recipe, written out in full because each trap in it is one a Nuxt 4 app actually hit:
244
- * a Vue codebase classified as `html` and handed `@reticlehq/react`, a connect guarded on
245
- * `window.location.hostname === 'localhost'` (false on any hosts-file alias, and `window` does not
246
- * exist during SSR), and a running dev server that never picked up the new plugin.
247
- *
248
- * So: `import.meta.dev` instead of a hostname check, `.client.ts` instead of an SSR guard, the
249
- * framework-neutral sensor instead of the React kit, the non-localhost flag named up front, and the
250
- * restart said out loud.
251
- */
252
- export declare function nuxtManual(port: number | undefined, projectId?: string): string;
205
+ export { nuxtPluginPath, nuxtPluginFile, NUXT_PLUGIN_NOTICE, nuxtManual } from './nuxt-snippets.js';
253
206
  /**
254
207
  * Root-level project config for Reticle. Written by `reticle init`; read by `reticle mcp` for the port
255
208
  * and by tooling for the stable projectId (the app's identity across port changes).