@reticlehq/init 3.2.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.
- package/dist/detect/detect.d.ts +13 -2
- package/dist/detect/detect.js +41 -2
- package/dist/detect/workspace-redirect.d.ts +2 -1
- package/dist/diagnose/csp-doctor.d.ts +20 -1
- package/dist/diagnose/csp-doctor.js +101 -4
- package/dist/diagnose/desktop-doctor.d.ts +1 -1
- package/dist/diagnose/install-retries.d.ts +2 -1
- package/dist/host.d.ts +6 -0
- package/dist/host.js +3 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/patch/astro-patch.d.ts +47 -12
- package/dist/patch/astro-patch.js +149 -41
- package/dist/patch/nuxt-snippets.d.ts +44 -0
- package/dist/patch/nuxt-snippets.js +135 -0
- package/dist/patch/snippets.d.ts +1 -48
- package/dist/patch/snippets.js +63 -175
- package/dist/patch/vite-env-types.d.ts +9 -0
- package/dist/patch/vite-env-types.js +19 -0
- package/dist/patch/vite-owning-config.d.ts +13 -0
- package/dist/patch/vite-owning-config.js +43 -16
- package/dist/plan/connect-steps.d.ts +3 -2
- package/dist/plan/connect-steps.js +4 -2
- package/dist/plan/framework-adapter.d.ts +2 -5
- package/dist/plan/framework-adapter.js +6 -5
- package/dist/plan/plan-framework.d.ts +1 -1
- package/dist/plan/plan-framework.js +33 -11
- package/dist/plan/plan-types.d.ts +18 -2
- package/dist/plan/plan.js +117 -35
- package/dist/plan/stop-hook-step.d.ts +4 -0
- package/dist/plan/stop-hook-step.js +49 -0
- package/dist/project/agent-rules.js +2 -0
- package/dist/register/mcp-clients.d.ts +13 -0
- package/dist/register/mcp-clients.js +22 -0
- package/dist/register/mcp.d.ts +8 -0
- package/dist/register/mcp.js +16 -0
- package/dist/run-types.d.ts +2 -0
- package/dist/run.js +22 -1
- package/package.json +2 -2
|
@@ -1,66 +1,170 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Pure, conservative patchers for an Astro app: the config gets the
|
|
3
|
-
*
|
|
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
|
|
9
|
-
*
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
|
|
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
|
-
|
|
62
|
-
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
|
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 = '
|
|
85
|
-
'
|
|
86
|
-
|
|
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
|
|
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
|
|
92
|
-
* `ts(
|
|
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
|
+
}
|
package/dist/patch/snippets.d.ts
CHANGED
|
@@ -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
|
-
|
|
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).
|