@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.
- package/dist/detect/detect.d.ts +13 -2
- package/dist/detect/detect.js +46 -3
- 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 +11 -1
- package/dist/index.js +11 -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 +68 -178
- 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 +12 -0
- package/dist/run.js +42 -6
- package/package.json +2 -2
package/dist/patch/snippets.js
CHANGED
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
* Generated file contents and copy-paste snippets for `reticle init`. Kept as named constants/builders
|
|
3
3
|
* so the runner never inlines free strings.
|
|
4
4
|
*/
|
|
5
|
-
import { RETICLE_DEFAULT_PORT,
|
|
5
|
+
import { RETICLE_DEFAULT_PORT, bridgeWsUrl, RETICLE_CLIENT_HOST, RETICLE_WS_PATH, } from '@reticlehq/core';
|
|
6
6
|
import { UiLibrary } from '../detect/detect.js';
|
|
7
7
|
import { RETICLE_VERSION } from '../version.js';
|
|
8
|
+
import { VITE_ENV_DEV_DECLARATION } from './vite-env-types.js';
|
|
8
9
|
/**
|
|
9
10
|
* The SDK as one import a plain page can actually resolve.
|
|
10
11
|
*
|
|
@@ -113,11 +114,13 @@ pointers, not the app.`;
|
|
|
113
114
|
import { reticle } from '@reticlehq/vite-plugin';
|
|
114
115
|
|
|
115
116
|
export default defineConfig({
|
|
116
|
-
plugins: [${
|
|
117
|
+
plugins: [${call}, ${frameworkPluginExample(uiLibrary)}],
|
|
117
118
|
});
|
|
118
119
|
|
|
119
|
-
|
|
120
|
-
|
|
120
|
+
The position in the array does not matter: the plugin declares \`enforce: 'pre'\`, so Vite runs it
|
|
121
|
+
before every normal plugin wherever you put it. Shown first only because that is where \`init\`
|
|
122
|
+
writes it when it patches the config for you. It applies during \`vite\` (dev) only — it is dropped
|
|
123
|
+
from \`vite build\`.${note}`;
|
|
121
124
|
}
|
|
122
125
|
/**
|
|
123
126
|
* The electron-vite recipe: the plugin belongs in `renderer`, never in `main` or `preload`.
|
|
@@ -202,10 +205,13 @@ export function ReticleDev() {
|
|
|
202
205
|
// withReticle() finds the daemon serving this project on every dev-server start. It wins over
|
|
203
206
|
// any port written into this file at install time, so moving the daemon needs no edit here.
|
|
204
207
|
const url = process.env.NEXT_PUBLIC_RETICLE_URL;
|
|
208
|
+
// So a version-skewed pair names the SDK's version instead of "an unknown older wire".
|
|
209
|
+
const sdkVersion = process.env.NEXT_PUBLIC_RETICLE_SDK_VERSION;
|
|
205
210
|
reticle.connect({
|
|
206
211
|
${fields}...(url ? { url } : {}),
|
|
207
212
|
...(token ? { token } : {}),
|
|
208
213
|
...(root ? { root } : {}),
|
|
214
|
+
...(sdkVersion ? { sdkVersion } : {}),
|
|
209
215
|
});
|
|
210
216
|
|
|
211
217
|
// ── Start with ONE flow. ────────────────────────────────────────────────────────────────
|
|
@@ -261,10 +267,9 @@ layoutPath) {
|
|
|
261
267
|
? `\n url: '${bridgeWsUrl(port)}',`
|
|
262
268
|
: '';
|
|
263
269
|
const id = projectId !== undefined && projectId.length > 0 ? `\n projectId: '${projectId}',` : '';
|
|
264
|
-
// Astro owns its Vite instance, so
|
|
265
|
-
//
|
|
266
|
-
//
|
|
267
|
-
// moment the daemon moves; this does not.
|
|
270
|
+
// Astro owns its Vite instance, so the layout can resolve the daemon URL in frontmatter on every
|
|
271
|
+
// `astro dev` and put it on a <meta> the client script reads. The port above is written once at
|
|
272
|
+
// install time and goes stale the moment the daemon moves; this does not.
|
|
268
273
|
//
|
|
269
274
|
// Needs the projectId to know WHICH daemon is ours. Without one there is nothing to match on, and
|
|
270
275
|
// adopting a daemon serving another project would report its state as this app's, so the whole
|
|
@@ -274,10 +279,8 @@ layoutPath) {
|
|
|
274
279
|
// `bridgeWsUrl` builds, and a generator that spells it out by hand is exactly how the four call
|
|
275
280
|
// sites that constant exists to unify drifted apart in the first place.
|
|
276
281
|
const RETICLE_CLIENT_HOST_LITERAL = `ws://${RETICLE_CLIENT_HOST}:`;
|
|
277
|
-
const
|
|
282
|
+
const urlFrontmatter = canDiscover
|
|
278
283
|
? `
|
|
279
|
-
// The live daemon serving THIS project, re-read on every \`astro dev\`. Same rule as the Vite and
|
|
280
|
-
// Next plugins: skip dead daemons, match on projectId, lowest port wins, '' when none matches.
|
|
281
284
|
function reticleUrl() {
|
|
282
285
|
const dir = process.env['RETICLE_PAIRING_TOKEN_DIR'] || join(homedir(), '.reticle');
|
|
283
286
|
let best;
|
|
@@ -293,63 +296,77 @@ layoutPath) {
|
|
|
293
296
|
} catch { return ''; }
|
|
294
297
|
return best === undefined ? '' : '${RETICLE_CLIENT_HOST_LITERAL}' + best + '${RETICLE_WS_PATH}';
|
|
295
298
|
}
|
|
299
|
+
const pairingUrl = import.meta.env.DEV ? reticleUrl() : '';
|
|
296
300
|
`
|
|
297
301
|
: '';
|
|
298
|
-
const
|
|
302
|
+
const urlMeta = canDiscover ? `\n <meta name="reticle-pairing-url" content={pairingUrl} />` : '';
|
|
299
303
|
const urlRead = canDiscover
|
|
300
|
-
? `\n
|
|
304
|
+
? `\n const url = document.querySelector('meta[name="reticle-pairing-url"]')?.getAttribute('content') ?? '';`
|
|
301
305
|
: '';
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
return `Astro renders its own HTML, so the connect goes in a page <script> and the pairing token is inlined by the config.
|
|
306
|
+
const urlSpread = canDiscover ? `\n ...(url.length > 0 ? { url } : {}),` : '';
|
|
307
|
+
return `Astro renders its own HTML, so the connect goes in a local module the page statically imports. Do not put the pairing token through \`vite.define\`: on Astro 7.2+ that substitution never reaches the client, the identifier stays literal, and the bridge refuses the dial. Read the token in frontmatter and put it on a <meta> the module can query. Reading it per request also means a token written after the dev server started is picked up on the next load instead of needing a restart.
|
|
305
308
|
|
|
306
|
-
1. In astro.config.mjs
|
|
309
|
+
1. In astro.config.mjs, raise the build target. The token does NOT belong here:
|
|
307
310
|
|
|
308
|
-
import { readFileSync, readdirSync } from 'node:fs';
|
|
309
|
-
import { homedir } from 'node:os';
|
|
310
|
-
import { join } from 'node:path';
|
|
311
|
-
|
|
312
|
-
function reticleToken() {
|
|
313
|
-
const dir = process.env['RETICLE_PAIRING_TOKEN_DIR'] || join(homedir(), '.reticle');
|
|
314
|
-
try { return readFileSync(join(dir, 'pairing-token'), 'utf8').trim(); } catch { return ''; }
|
|
315
|
-
}
|
|
316
|
-
${urlHelper}
|
|
317
311
|
export default defineConfig({
|
|
318
312
|
vite: {
|
|
319
313
|
// Astro's default target down-levels the modern SDK bundle and fails on a destructuring transform.
|
|
320
314
|
build: { target: 'es2022' },
|
|
321
|
-
optimizeDeps: {
|
|
322
|
-
define: {
|
|
323
|
-
__RETICLE_TOKEN__: JSON.stringify(reticleToken()),${urlDefine}
|
|
324
|
-
// Without this, source pointers come back as absolute paths from YOUR machine — useless in a
|
|
325
|
-
// report. Every other framework gets it from its build plugin; Astro owns its Vite instance.
|
|
326
|
-
__RETICLE_ROOT__: JSON.stringify(process.cwd()),
|
|
327
|
-
},
|
|
315
|
+
optimizeDeps: { include: ['@reticlehq/react'] },
|
|
328
316
|
},
|
|
329
317
|
});
|
|
330
318
|
|
|
331
319
|
${layoutHost(layoutPath)}
|
|
332
320
|
|
|
321
|
+
---
|
|
322
|
+
import { readFileSync${canDiscover ? ', readdirSync' : ''} } from 'node:fs';
|
|
323
|
+
import { homedir } from 'node:os';
|
|
324
|
+
import { join } from 'node:path';
|
|
325
|
+
const pairingToken = (() => {
|
|
326
|
+
if (!import.meta.env.DEV) return '';
|
|
327
|
+
const dir = process.env['RETICLE_PAIRING_TOKEN_DIR'] || join(homedir(), '.reticle');
|
|
328
|
+
try { return readFileSync(join(dir, 'pairing-token'), 'utf8').trim(); } catch { return ''; }
|
|
329
|
+
})();
|
|
330
|
+
const pairingRoot = import.meta.env.DEV ? process.cwd() : '';${urlFrontmatter} ---
|
|
331
|
+
<meta name="reticle-pairing-token" content={pairingToken} />
|
|
332
|
+
<meta name="reticle-pairing-root" content={pairingRoot} />${urlMeta}
|
|
333
333
|
<script>
|
|
334
|
+
import connectReticle from '../components/ReticleDev';
|
|
334
335
|
if (import.meta.env.DEV) {
|
|
335
|
-
|
|
336
|
-
const root = typeof __RETICLE_ROOT__ !== 'undefined' ? __RETICLE_ROOT__ : '';${urlRead}
|
|
337
|
-
const { reticle, install } = await import('@reticlehq/react');
|
|
338
|
-
install();
|
|
339
|
-
reticle.connect({${id}${extra}${urlSpread}
|
|
340
|
-
...(token.length > 0 ? { token } : {}),
|
|
341
|
-
...(root.length > 0 ? { root } : {}),
|
|
342
|
-
});
|
|
336
|
+
connectReticle();
|
|
343
337
|
}
|
|
344
338
|
</script>
|
|
345
339
|
|
|
346
|
-
|
|
347
|
-
|
|
340
|
+
The <script> tag must not sit inside a conditional. Astro hoists them statically, so
|
|
341
|
+
\`{isDev && (<script />)}\` is never hoisted and the module 404s.
|
|
342
|
+
The dev gate belongs in the imported module.
|
|
348
343
|
|
|
349
|
-
|
|
350
|
-
declare const __RETICLE_ROOT__: string | undefined;
|
|
344
|
+
3. Create src/components/ReticleDev.ts — Vite owns this file, so the SDK is a static import:
|
|
351
345
|
|
|
352
|
-
|
|
346
|
+
import { reticle, install } from '@reticlehq/react';
|
|
347
|
+
export default function connectReticle() {
|
|
348
|
+
const token = document.querySelector('meta[name="reticle-pairing-token"]')?.getAttribute('content') ?? '';
|
|
349
|
+
const root = document.querySelector('meta[name="reticle-pairing-root"]')?.getAttribute('content') ?? '';${urlRead}
|
|
350
|
+
if (token.length === 0) {
|
|
351
|
+
console.warn('[reticle] no pairing token was available when this page rendered, so the app will connect and be refused — you will see NO SESSION even though the SDK loads and the socket opens. The token is written by the Reticle daemon: start it and reload this page.');
|
|
352
|
+
}
|
|
353
|
+
install();
|
|
354
|
+
reticle.connect({${id}${extra}${urlSpread}
|
|
355
|
+
...(token.length > 0 ? { token } : {}),
|
|
356
|
+
...(root.length > 0 ? { root } : {}),
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
4. In src/env.d.ts — declare the window names so \`astro check\` can see them (create-astro's
|
|
361
|
+
default build runs check first):
|
|
362
|
+
|
|
363
|
+
interface Window {
|
|
364
|
+
__RETICLE_TOKEN__?: string;
|
|
365
|
+
__RETICLE_ROOT__?: string;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
Start the daemon, then load the page. The token is read when the page renders, so a reload is enough
|
|
369
|
+
once the token file exists — you do not have to restart \`astro dev\`.`;
|
|
353
370
|
}
|
|
354
371
|
/**
|
|
355
372
|
* The `registerCapabilities` call every generated connect carries.
|
|
@@ -422,7 +439,8 @@ ${storeBlock}
|
|
|
422
439
|
stores: [${found.map((s) => `'${s.key}'`).join(', ')}], // the keys you registered above
|
|
423
440
|
});
|
|
424
441
|
}
|
|
425
|
-
|
|
442
|
+
|
|
443
|
+
${VITE_ENV_DEV_DECLARATION}`;
|
|
426
444
|
}
|
|
427
445
|
/** Where that module goes. Matches @reticlehq/vite-plugin's convention list. */
|
|
428
446
|
export const VITE_DEV_MODULE_PATH = 'src/reticle-dev.ts';
|
|
@@ -502,7 +520,7 @@ st.html(
|
|
|
502
520
|
* `<head>`, so the SDK sees a parsed document.
|
|
503
521
|
*/
|
|
504
522
|
export function djangoMiddlewareSnippet(connectArgLiteral) {
|
|
505
|
-
return `# reticle_dev.py
|
|
523
|
+
return `# reticle_dev.py: add to MIDDLEWARE while DEBUG is on:
|
|
506
524
|
# MIDDLEWARE = [..., "reticle_dev.ReticleDevMiddleware"]
|
|
507
525
|
from django.conf import settings
|
|
508
526
|
|
|
@@ -682,7 +700,7 @@ export const REACT_ROUTER_ENTRY_PATH = 'app/entry.client.tsx';
|
|
|
682
700
|
/** The dev-only module @reticlehq/vite-plugin serves: connect() with the token already in it. */
|
|
683
701
|
const RETICLE_CONNECT_MODULE = '/@reticle-connect';
|
|
684
702
|
/** The one line that puts Reticle in a React Router client entry. */
|
|
685
|
-
|
|
703
|
+
const REACT_ROUTER_CONNECT_LINE = `if (import.meta.env.DEV) void import('/@reticle-connect');`;
|
|
686
704
|
/**
|
|
687
705
|
* The client entry `init` writes when React Router framework mode has none.
|
|
688
706
|
*
|
|
@@ -735,135 +753,7 @@ export function reactRouterEntryPatch(source) {
|
|
|
735
753
|
lines.splice(lastImport + 1, 0, REACT_ROUTER_CONNECT_LINE);
|
|
736
754
|
return lines.join('\n');
|
|
737
755
|
}
|
|
738
|
-
|
|
739
|
-
export const NUXT_PLUGIN_PATH = 'app/plugins/reticle.client.ts';
|
|
740
|
-
/**
|
|
741
|
-
* The plugin directory Nuxt actually scans, which is not the same on 3 and 4.
|
|
742
|
-
*
|
|
743
|
-
* Nuxt 4's default `srcDir` is `app/`, so plugins live in `app/plugins/`. Nuxt 3's is the project
|
|
744
|
-
* root, so they live in `plugins/`. Writing to the wrong one is the silent failure this whole change
|
|
745
|
-
* exists to remove: the file is on disk, `init` reports it green, Nuxt never scans that directory,
|
|
746
|
-
* and no plugin is ever registered. Keyed on whether the app HAS an `app/` directory, which is the
|
|
747
|
-
* same signal Nuxt itself uses to pick its srcDir.
|
|
748
|
-
*/
|
|
749
|
-
export function nuxtPluginPath(hasAppDir) {
|
|
750
|
-
return hasAppDir ? NUXT_PLUGIN_PATH : 'plugins/reticle.client.ts';
|
|
751
|
-
}
|
|
752
|
-
/**
|
|
753
|
-
* The dev-only Nuxt client plugin `init` writes.
|
|
754
|
-
*
|
|
755
|
-
* Every trap in it is one somebody actually hit. `import.meta.dev` rather than a hostname check: the
|
|
756
|
-
* reported failure was a snippet guarded on `window.location.hostname === 'localhost'`, which is
|
|
757
|
-
* false on any hosts-file alias or LAN address and throws during SSR, so the connect never ran with
|
|
758
|
-
* no error and no log line. `.client.ts` rather than an SSR guard, because that suffix is what keeps
|
|
759
|
-
* this out of the server bundle. `@reticlehq/browser` rather than the React kit, because installing
|
|
760
|
-
* a package named `@reticlehq/react` into a Vue codebase is the single thing most likely to make
|
|
761
|
-
* somebody abandon the setup.
|
|
762
|
-
*
|
|
763
|
-
* The token is inlined by `nuxt.config`'s `vite.define` (see nuxt-patch.ts). The bridge requires it
|
|
764
|
-
* even on localhost, and nothing in a browser can read the file it lives in.
|
|
765
|
-
*/
|
|
766
|
-
export function nuxtPluginFile(port, projectId, testids = []) {
|
|
767
|
-
const base = connectArg(port, projectId);
|
|
768
|
-
const fields = '' === base ? '' : `${base.slice(1, -1).trim()}, `;
|
|
769
|
-
return `// Dev-only: connect Reticle from a Nuxt client plugin. Nuxt owns its own Vite instance and
|
|
770
|
-
// renders its own HTML, so the Vite plugin's index.html injection never fires — a .client plugin is
|
|
771
|
-
// the path that can register a session at all.
|
|
772
|
-
export default defineNuxtPlugin(() => {
|
|
773
|
-
// import.meta.dev is resolved at BUILD time, so it does not care what hostname you develop on.
|
|
774
|
-
// Do NOT guard on window.location.hostname === 'localhost': that is false on any hosts-file alias
|
|
775
|
-
// or LAN address, and window does not exist during SSR.
|
|
776
|
-
if (!import.meta.dev) return;
|
|
777
|
-
void import('@reticlehq/browser').then(({ reticle, registerCapabilities }) => {
|
|
778
|
-
// Both inlined by nuxt.config's vite.define, which \`reticle init\` wrote. The bridge refuses a
|
|
779
|
-
// connect with no token even on localhost; the root makes source paths repo-relative.
|
|
780
|
-
const token = typeof __RETICLE_TOKEN__ !== 'undefined' ? __RETICLE_TOKEN__ : '';
|
|
781
|
-
const root = typeof __RETICLE_ROOT__ !== 'undefined' ? __RETICLE_ROOT__ : '';
|
|
782
|
-
reticle.connect({
|
|
783
|
-
${fields}...(token.length > 0 ? { token } : {}),
|
|
784
|
-
...(root.length > 0 ? { root } : {}),
|
|
785
|
-
});
|
|
786
|
-
|
|
787
|
-
// What the agent can drive without guessing. Add a store here too — registerStore('cart',
|
|
788
|
-
// piniaStore(useCartStore())) — and the agent can check what the app BELIEVES, not just what it
|
|
789
|
-
// rendered. See node_modules/@reticlehq/server/docs/usage.md.
|
|
790
|
-
${registerCapabilitiesCall(testids, ' ')}
|
|
791
|
-
});
|
|
792
|
-
});
|
|
793
|
-
|
|
794
|
-
declare const __RETICLE_TOKEN__: string | undefined;
|
|
795
|
-
declare const __RETICLE_ROOT__: string | undefined;
|
|
796
|
-
`;
|
|
797
|
-
}
|
|
798
|
-
/**
|
|
799
|
-
* Said as its own NOTICE beside the write, not inside it: a running dev server does not pick up a
|
|
800
|
-
* new plugin, and off localhost the connect needs two more arguments.
|
|
801
|
-
*
|
|
802
|
-
* A ✓ line is one SKILL.md tells the reader to skip, so a caveat carried on the ✓ is a caveat nobody
|
|
803
|
-
* reads — and both of these end with an app that boots, looks correct and never pairs.
|
|
804
|
-
*/
|
|
805
|
-
export const NUXT_PLUGIN_NOTICE = 'Restart the dev server: one that is already running does not pick up a new plugin — it will not ' +
|
|
806
|
-
'appear in .nuxt/plugins/client.mjs and the app comes up with no SDK at all. And if your dev host ' +
|
|
807
|
-
'is anything other than localhost (a hosts-file alias, a LAN IP, a tunnel), that connect call ' +
|
|
808
|
-
'needs TWO additions, not one: allowNonLocalhost: true AND the token from ~/.reticle/pairing-token. ' +
|
|
809
|
-
'The flag alone is NOT sufficient off localhost.';
|
|
810
|
-
/**
|
|
811
|
-
* The Nuxt recipe, written out in full because each trap in it is one a Nuxt 4 app actually hit:
|
|
812
|
-
* a Vue codebase classified as `html` and handed `@reticlehq/react`, a connect guarded on
|
|
813
|
-
* `window.location.hostname === 'localhost'` (false on any hosts-file alias, and `window` does not
|
|
814
|
-
* exist during SSR), and a running dev server that never picked up the new plugin.
|
|
815
|
-
*
|
|
816
|
-
* So: `import.meta.dev` instead of a hostname check, `.client.ts` instead of an SSR guard, the
|
|
817
|
-
* framework-neutral sensor instead of the React kit, the non-localhost flag named up front, and the
|
|
818
|
-
* restart said out loud.
|
|
819
|
-
*/
|
|
820
|
-
export function nuxtManual(port, projectId) {
|
|
821
|
-
const base = connectArg(port, projectId);
|
|
822
|
-
const fields = '' === base ? '' : base.slice(1, -1).trim();
|
|
823
|
-
const connect = '' === fields ? 'reticle.connect()' : `reticle.connect({ ${fields} })`;
|
|
824
|
-
return `Nuxt owns its own Vite instance and renders its own HTML, so there is no vite.config to patch
|
|
825
|
-
and no index.html to inject into. Wire it with a dev-only CLIENT plugin, which is Nuxt's own idiom:
|
|
826
|
-
|
|
827
|
-
1. Create ${NUXT_PLUGIN_PATH}:
|
|
828
|
-
|
|
829
|
-
export default defineNuxtPlugin(() => {
|
|
830
|
-
// import.meta.dev is the correct guard: it is resolved at build time, so it does not care
|
|
831
|
-
// what hostname you develop on. Do NOT guard on window.location.hostname === 'localhost' —
|
|
832
|
-
// that is false on any hosts-file alias or LAN address, and window does not exist in SSR.
|
|
833
|
-
if (!import.meta.dev) return
|
|
834
|
-
void import('@reticlehq/browser').then(({ reticle }) => {
|
|
835
|
-
${connect}
|
|
836
|
-
})
|
|
837
|
-
})
|
|
838
|
-
|
|
839
|
-
The .client.ts suffix is load-bearing: it is what keeps this out of the server bundle.
|
|
840
|
-
|
|
841
|
-
2. Restart the dev server. A dev server that is already running does not pick up a new plugin —
|
|
842
|
-
it will not appear in .nuxt/plugins/client.mjs, and the app will come up with no SDK at all.
|
|
843
|
-
|
|
844
|
-
3. If your dev host is anything other than localhost (a hosts-file alias, a LAN IP, a tunnel), that
|
|
845
|
-
connect call needs TWO additions, not one: allowNonLocalhost: true AND a pairing token passed as
|
|
846
|
-
token. The flag alone is NOT sufficient off localhost. The token is the one in
|
|
847
|
-
~/.reticle/pairing-token. Without both, the SDK loads and then refuses, and the only sign is one
|
|
848
|
-
line in the browser console.
|
|
849
|
-
|
|
850
|
-
4. Add this to nuxt.config, so the dev server does not watch Reticle's own journal:
|
|
851
|
-
|
|
852
|
-
vite: { server: { watch: { ignored: [/(^|[\\\\/])\\.${ReticleDir.ROOT.slice(1)}([\\\\/]|$)/] } } }
|
|
853
|
-
|
|
854
|
-
Reticle journals every session into ${ReticleDir.ROOT}/ in your project root, rewriting one file
|
|
855
|
-
in it continuously while a session is live. Nuxt's dev server watches that root, so without this
|
|
856
|
-
it sees each write as a project file changing and full-reloads the page — which reconnects the
|
|
857
|
-
SDK, which produces the next write. The loop runs several times a second and looks like anything
|
|
858
|
-
except what it is: refs go stale, actions die mid-flight, and the session appears to flap.
|
|
859
|
-
It is a RegExp rather than a glob on purpose — chokidar dropped glob support in v4, so a
|
|
860
|
-
double-star pattern here is accepted and matches nothing.
|
|
861
|
-
|
|
862
|
-
The package is @reticlehq/browser — the framework-neutral sensor. DOM, network, console, routing and
|
|
863
|
-
source file:line all work in Vue. What you do not get is React component identity, which is the only
|
|
864
|
-
thing the React adapter adds. There is no Nuxt app in this project's CI, so this path is UNVERIFIED:
|
|
865
|
-
if something does not work, please open an issue.`;
|
|
866
|
-
}
|
|
756
|
+
export { nuxtPluginPath, nuxtPluginFile, NUXT_PLUGIN_NOTICE, nuxtManual } from './nuxt-snippets.js';
|
|
867
757
|
/**
|
|
868
758
|
* Root-level project config for Reticle. Written by `reticle init`; read by `reticle mcp` for the port
|
|
869
759
|
* and by tooling for the stable projectId (the app's identity across port changes).
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The generated dev module must compile even when the host excludes `vite/client` ambient types.
|
|
3
|
+
* Keep the guard itself as `import.meta.env.DEV` so Vite can remove dev-only code from production.
|
|
4
|
+
*
|
|
5
|
+
* These declarations must merge with Vite's existing interfaces: DEV is mutable there, while env
|
|
6
|
+
* is readonly and refers to the named ImportMetaEnv interface. A triple-slash package reference
|
|
7
|
+
* would also require vite to be directly resolvable, which electron-vite-only projects need not have.
|
|
8
|
+
*/
|
|
9
|
+
export declare const VITE_ENV_DEV_DECLARATION = "// Supply the dev flag type when the host does not include vite/client.\n// These interfaces merge with vite/client when it is already present.\ndeclare global {\n interface ImportMetaEnv {\n DEV: boolean;\n }\n interface ImportMeta {\n readonly env: ImportMetaEnv;\n }\n}\n";
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The generated dev module must compile even when the host excludes `vite/client` ambient types.
|
|
3
|
+
* Keep the guard itself as `import.meta.env.DEV` so Vite can remove dev-only code from production.
|
|
4
|
+
*
|
|
5
|
+
* These declarations must merge with Vite's existing interfaces: DEV is mutable there, while env
|
|
6
|
+
* is readonly and refers to the named ImportMetaEnv interface. A triple-slash package reference
|
|
7
|
+
* would also require vite to be directly resolvable, which electron-vite-only projects need not have.
|
|
8
|
+
*/
|
|
9
|
+
export const VITE_ENV_DEV_DECLARATION = `// Supply the dev flag type when the host does not include vite/client.
|
|
10
|
+
// These interfaces merge with vite/client when it is already present.
|
|
11
|
+
declare global {
|
|
12
|
+
interface ImportMetaEnv {
|
|
13
|
+
DEV: boolean;
|
|
14
|
+
}
|
|
15
|
+
interface ImportMeta {
|
|
16
|
+
readonly env: ImportMetaEnv;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
`;
|
|
@@ -21,5 +21,18 @@ export interface ViteOwningConfig {
|
|
|
21
21
|
readonly defineCall: RegExp;
|
|
22
22
|
/** The SDK package this framework's connect imports, declared so Vite pre-bundles it. */
|
|
23
23
|
readonly sdkSpecifier: string;
|
|
24
|
+
/**
|
|
25
|
+
* When true (default), inline the pairing token and project root via `vite.define`.
|
|
26
|
+
*
|
|
27
|
+
* Nuxt still needs this: its client plugin reads `__RETICLE_TOKEN__` from the define. Astro 7.2+
|
|
28
|
+
* no longer substitutes `define` into the client pipeline (#1008), so Astro sets this false and
|
|
29
|
+
* puts the token on a frontmatter `<meta>` instead.
|
|
30
|
+
*/
|
|
31
|
+
readonly defineToken?: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Present once this patch has applied. Defaults to `__RETICLE_TOKEN__`, which is what the define
|
|
34
|
+
* path writes. Astro's path never writes that name into the config, so it supplies its own.
|
|
35
|
+
*/
|
|
36
|
+
readonly alreadyMarker?: string;
|
|
24
37
|
}
|
|
25
38
|
export declare function patchViteOwningConfig(source: string, config: ViteOwningConfig): SourcePatch;
|
|
@@ -18,7 +18,13 @@ import { ReticleDir } from '@reticlehq/core';
|
|
|
18
18
|
import { blockAfter } from './brace-scan.js';
|
|
19
19
|
import { PatchKind } from './patch-kind.js';
|
|
20
20
|
/** Present in a patched config AND in a hand-followed recipe — so both count as already wired. */
|
|
21
|
-
const
|
|
21
|
+
const DEFINE_TOKEN_MARKER = '__RETICLE_TOKEN__';
|
|
22
|
+
function usesDefineToken(config) {
|
|
23
|
+
return config.defineToken !== false;
|
|
24
|
+
}
|
|
25
|
+
function alreadyMarker(config) {
|
|
26
|
+
return config.alreadyMarker ?? DEFINE_TOKEN_MARKER;
|
|
27
|
+
}
|
|
22
28
|
/**
|
|
23
29
|
* The SDK, declared so Vite pre-bundles it BEFORE the first page load.
|
|
24
30
|
*
|
|
@@ -82,30 +88,36 @@ import { join } from 'node:path';
|
|
|
82
88
|
* cosmetic: Astro's default down-levels the modern SDK bundle and dies on a destructuring transform.
|
|
83
89
|
*/
|
|
84
90
|
function viteKeys(config) {
|
|
85
|
-
|
|
91
|
+
const keys = [
|
|
86
92
|
{ key: 'build', inner: `\n target: 'es2022',` },
|
|
87
93
|
{
|
|
88
94
|
key: 'optimizeDeps',
|
|
89
95
|
inner: `\n include: [${sdkInclude(config)}],\n esbuildOptions: { target: 'es2022' },`,
|
|
90
96
|
},
|
|
91
|
-
{
|
|
92
|
-
key: 'define',
|
|
93
|
-
inner: `\n __RETICLE_TOKEN__: JSON.stringify(reticleToken()),\n __RETICLE_ROOT__: JSON.stringify(process.cwd()),`,
|
|
94
|
-
},
|
|
95
97
|
// Merged in first, so a `server: { port }` the app already set keeps its port. An app that
|
|
96
98
|
// already sets `server.watch` itself is the one shape this loses to — the inner `watch` key would
|
|
97
99
|
// be duplicated and the app's would win — which is the same nested-merge ceiling `build` has.
|
|
98
100
|
{ key: 'server', inner: `\n watch: { ignored: [${WATCH_IGNORE_LITERAL}] },` },
|
|
99
101
|
];
|
|
102
|
+
if (usesDefineToken(config)) {
|
|
103
|
+
keys.splice(2, 0, {
|
|
104
|
+
key: 'define',
|
|
105
|
+
inner: `\n __RETICLE_TOKEN__: JSON.stringify(reticleToken()),\n __RETICLE_ROOT__: JSON.stringify(process.cwd()),`,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
return keys;
|
|
100
109
|
}
|
|
101
110
|
/** Whole-key form, for a `vite:` block that does not have the key at all. */
|
|
102
111
|
function wholeKeys(config) {
|
|
103
|
-
|
|
112
|
+
const keys = {
|
|
104
113
|
build: `\n build: { target: 'es2022' },`,
|
|
105
114
|
optimizeDeps: `\n optimizeDeps: { include: [${sdkInclude(config)}], esbuildOptions: { target: 'es2022' } },`,
|
|
106
|
-
define: `\n define: {\n __RETICLE_TOKEN__: JSON.stringify(reticleToken()),\n __RETICLE_ROOT__: JSON.stringify(process.cwd()),\n },`,
|
|
107
115
|
server: `\n server: { watch: { ignored: [${WATCH_IGNORE_LITERAL}] } },`,
|
|
108
116
|
};
|
|
117
|
+
if (usesDefineToken(config)) {
|
|
118
|
+
keys.define = `\n define: {\n __RETICLE_TOKEN__: JSON.stringify(reticleToken()),\n __RETICLE_ROOT__: JSON.stringify(process.cwd()),\n },`;
|
|
119
|
+
}
|
|
120
|
+
return keys;
|
|
109
121
|
}
|
|
110
122
|
/**
|
|
111
123
|
* Add our keys to an existing `vite: { ... }` block, merging into any the app already set.
|
|
@@ -160,19 +172,34 @@ function withoutInclude(inner, config) {
|
|
|
160
172
|
* that ran `init`.
|
|
161
173
|
*/
|
|
162
174
|
function viteBlock(config) {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
define: {
|
|
175
|
+
const marker = usesDefineToken(config) ? '' : ` /* ${alreadyMarker(config)} */\n`;
|
|
176
|
+
const define = usesDefineToken(config)
|
|
177
|
+
? ` define: {
|
|
167
178
|
__RETICLE_TOKEN__: JSON.stringify(reticleToken()),
|
|
168
179
|
__RETICLE_ROOT__: JSON.stringify(process.cwd()),
|
|
169
180
|
},
|
|
170
|
-
|
|
181
|
+
`
|
|
182
|
+
: '';
|
|
183
|
+
return ` vite: {
|
|
184
|
+
${marker} build: { target: 'es2022' },
|
|
185
|
+
optimizeDeps: { include: [${sdkInclude(config)}], esbuildOptions: { target: 'es2022' } },
|
|
186
|
+
${define} server: { watch: { ignored: [${WATCH_IGNORE_LITERAL}] } },
|
|
171
187
|
},
|
|
172
188
|
`;
|
|
173
189
|
}
|
|
190
|
+
function withHelper(source, config) {
|
|
191
|
+
if (!usesDefineToken(config)) {
|
|
192
|
+
return source.endsWith('\n') ? source : `${source}\n`;
|
|
193
|
+
}
|
|
194
|
+
return `${HELPER_IMPORTS}${source.trimStart()}\n${HELPER}`.trimEnd() + '\n';
|
|
195
|
+
}
|
|
196
|
+
function withOwningMarker(source, config, braceAt) {
|
|
197
|
+
if (usesDefineToken(config) || source.includes(alreadyMarker(config)))
|
|
198
|
+
return source;
|
|
199
|
+
return insertAt(source, braceAt, `\n /* ${alreadyMarker(config)} */`);
|
|
200
|
+
}
|
|
174
201
|
export function patchViteOwningConfig(source, config) {
|
|
175
|
-
if (source.includes(
|
|
202
|
+
if (source.includes(alreadyMarker(config)))
|
|
176
203
|
return { kind: PatchKind.ALREADY };
|
|
177
204
|
// A `vite: { ... }` block is an object literal, so our keys can go straight after the brace and
|
|
178
205
|
// whatever is already in there is untouched. Refusing outright was the only genuine install defect
|
|
@@ -186,7 +213,7 @@ export function patchViteOwningConfig(source, config) {
|
|
|
186
213
|
if (merged !== null) {
|
|
187
214
|
return {
|
|
188
215
|
kind: PatchKind.APPLY,
|
|
189
|
-
code:
|
|
216
|
+
code: withHelper(withOwningMarker(merged, config, at), config),
|
|
190
217
|
};
|
|
191
218
|
}
|
|
192
219
|
return {
|
|
@@ -214,6 +241,6 @@ export function patchViteOwningConfig(source, config) {
|
|
|
214
241
|
const withBlock = `${source.slice(0, at)}\n${viteBlock(config)}${source.slice(at)}`;
|
|
215
242
|
return {
|
|
216
243
|
kind: PatchKind.APPLY,
|
|
217
|
-
code:
|
|
244
|
+
code: withHelper(withBlock, config),
|
|
218
245
|
};
|
|
219
246
|
}
|
|
@@ -34,8 +34,9 @@ export declare const StepTitle: {
|
|
|
34
34
|
readonly SVELTEKIT_UNVERIFIED: "SvelteKit is UNVERIFIED";
|
|
35
35
|
readonly CLIENT_HOOK: "Reticle client hook";
|
|
36
36
|
readonly CONNECT_SNIPPET_ASTRO: "Connect snippet (Astro)";
|
|
37
|
-
readonly
|
|
38
|
-
readonly
|
|
37
|
+
readonly ASTRO_RETICLE_DEV: "Astro ReticleDev module";
|
|
38
|
+
readonly ASTRO_CONFIG: "Astro config (build target)";
|
|
39
|
+
readonly ASTRO_ENV_DTS: "Astro env types (window token)";
|
|
39
40
|
readonly CSP: "Content-Security-Policy";
|
|
40
41
|
readonly CONNECT_SNIPPET: "Connect snippet";
|
|
41
42
|
};
|
|
@@ -35,8 +35,9 @@ export const StepTitle = {
|
|
|
35
35
|
SVELTEKIT_UNVERIFIED: 'SvelteKit is UNVERIFIED',
|
|
36
36
|
CLIENT_HOOK: 'Reticle client hook',
|
|
37
37
|
CONNECT_SNIPPET_ASTRO: 'Connect snippet (Astro)',
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
ASTRO_RETICLE_DEV: 'Astro ReticleDev module',
|
|
39
|
+
ASTRO_CONFIG: 'Astro config (build target)',
|
|
40
|
+
ASTRO_ENV_DTS: 'Astro env types (window token)',
|
|
40
41
|
CSP: CSP_STEP_TITLE,
|
|
41
42
|
CONNECT_SNIPPET: 'Connect snippet',
|
|
42
43
|
};
|
|
@@ -54,6 +55,7 @@ export const CONNECT_STEP_TITLES = new Set([
|
|
|
54
55
|
StepTitle.CONNECT_SNIPPET,
|
|
55
56
|
StepTitle.CONNECT_SNIPPET_CRA,
|
|
56
57
|
StepTitle.CONNECT_SNIPPET_ASTRO,
|
|
58
|
+
StepTitle.ASTRO_RETICLE_DEV,
|
|
57
59
|
StepTitle.CONNECT_SNIPPET_NUXT,
|
|
58
60
|
// The config is the other half of the Nuxt connect, not a nicety: it is the only thing in a Nuxt
|
|
59
61
|
// app that can inline the pairing token, and the bridge refuses a connect without one even on
|
|
@@ -24,11 +24,7 @@ import type { PlanInput, Step } from './plan-types.js';
|
|
|
24
24
|
export declare const RETICLE_REACT_KIT = "@reticlehq/react";
|
|
25
25
|
/** The framework-neutral sensor, for stacks the React adapter has nothing to attach to. */
|
|
26
26
|
export declare const RETICLE_BROWSER_SDK = "@reticlehq/browser";
|
|
27
|
-
|
|
28
|
-
export declare const RETICLE_NEXT_PLUGIN = "@reticlehq/next";
|
|
29
|
-
/** The Electron main/preload helper — what makes IPC and screenshots exist at all. */
|
|
30
|
-
export declare const RETICLE_ELECTRON = "@reticlehq/electron";
|
|
31
|
-
export interface FrameworkAdapter {
|
|
27
|
+
interface FrameworkAdapter {
|
|
32
28
|
/**
|
|
33
29
|
* The dev dependencies `init` installs, given the kit the UI-library check chose
|
|
34
30
|
* (`@reticlehq/react` or the neutral sensor). A framework that knows its own renderer ignores the
|
|
@@ -67,3 +63,4 @@ export declare const FRAMEWORK_ADAPTERS: Record<Framework, FrameworkAdapter>;
|
|
|
67
63
|
* for the packages, the connect-step titles and the unverified-note flag in the same edit.
|
|
68
64
|
*/
|
|
69
65
|
export declare function frameworkSteps(input: PlanInput): Step[];
|
|
66
|
+
export {};
|
|
@@ -29,10 +29,10 @@ import { electronViteSteps } from './plan-electron-vite.js';
|
|
|
29
29
|
export const RETICLE_REACT_KIT = '@reticlehq/react';
|
|
30
30
|
/** The framework-neutral sensor, for stacks the React adapter has nothing to attach to. */
|
|
31
31
|
export const RETICLE_BROWSER_SDK = '@reticlehq/browser';
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
const RETICLE_VITE_PLUGIN = '@reticlehq/vite-plugin';
|
|
33
|
+
const RETICLE_NEXT_PLUGIN = '@reticlehq/next';
|
|
34
34
|
/** The Electron main/preload helper — what makes IPC and screenshots exist at all. */
|
|
35
|
-
|
|
35
|
+
const RETICLE_ELECTRON = '@reticlehq/electron';
|
|
36
36
|
export const FRAMEWORK_ADAPTERS = {
|
|
37
37
|
[Framework.NEXT]: {
|
|
38
38
|
// Next is React by construction, so the detection cannot disagree in a way worth honouring.
|
|
@@ -92,10 +92,11 @@ export const FRAMEWORK_ADAPTERS = {
|
|
|
92
92
|
},
|
|
93
93
|
[Framework.ASTRO]: {
|
|
94
94
|
// Astro owns its own Vite instance and renders its own HTML, so there is no config for the
|
|
95
|
-
// plugin to attach to — the kit alone, connected from a
|
|
95
|
+
// plugin to attach to — the kit alone, connected from a local module the page imports
|
|
96
|
+
// (see astroManual).
|
|
96
97
|
packages: (kit) => [kit],
|
|
97
98
|
steps: astroSteps,
|
|
98
|
-
connectStepTitles: [StepTitle.CONNECT_SNIPPET_ASTRO],
|
|
99
|
+
connectStepTitles: [StepTitle.CONNECT_SNIPPET_ASTRO, StepTitle.ASTRO_RETICLE_DEV],
|
|
99
100
|
carriesOwnUnverifiedNote: false,
|
|
100
101
|
},
|
|
101
102
|
[Framework.ELECTRON_VITE]: {
|
|
@@ -85,7 +85,7 @@ export declare function reactRouterSteps(input: PlanInput): Step[];
|
|
|
85
85
|
export declare function tanstackStartSteps(input: PlanInput): Step[];
|
|
86
86
|
export declare function svelteKitSteps(input: PlanInput): Step[];
|
|
87
87
|
/**
|
|
88
|
-
* Astro: the config
|
|
88
|
+
* Astro: the config build target, and the connect script (with the pairing token) in ONE layout.
|
|
89
89
|
*
|
|
90
90
|
* Astro was the last gated stack left printing a recipe it did not apply. It still falls back to the
|
|
91
91
|
* printed one whenever the choice is not obvious — no config, no single layout, or a shape the
|