@reticlehq/init 3.6.0 → 3.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/dist/detect/detect.d.ts +9 -0
  2. package/dist/detect/detect.js +5 -1
  3. package/dist/detect/dev-script.d.ts +17 -0
  4. package/dist/detect/dev-script.js +128 -0
  5. package/dist/detect/dev-server-ports.d.ts +2 -0
  6. package/dist/detect/dev-server-ports.js +20 -0
  7. package/dist/detect/prettier-config.d.ts +21 -0
  8. package/dist/detect/prettier-config.js +38 -0
  9. package/dist/detect/workspace-apps.d.ts +11 -0
  10. package/dist/detect/workspace-apps.js +63 -0
  11. package/dist/detect/workspace-redirect.js +69 -10
  12. package/dist/diagnose/closing-hint.d.ts +5 -0
  13. package/dist/diagnose/closing-hint.js +18 -11
  14. package/dist/diagnose/init-failure.d.ts +2 -0
  15. package/dist/diagnose/init-failure.js +2 -0
  16. package/dist/gather-plan-input.d.ts +11 -0
  17. package/dist/gather-plan-input.js +468 -0
  18. package/dist/host.d.ts +6 -0
  19. package/dist/index.d.ts +11 -4
  20. package/dist/index.js +14 -4
  21. package/dist/no-package-json.js +62 -14
  22. package/dist/node-io.d.ts +1 -1
  23. package/dist/node-io.js +11 -2
  24. package/dist/patch/next-patch.d.ts +45 -9
  25. package/dist/patch/next-patch.js +93 -0
  26. package/dist/patch/nuxt-snippets.js +1 -1
  27. package/dist/patch/prettier-ignore.d.ts +34 -0
  28. package/dist/patch/prettier-ignore.js +49 -0
  29. package/dist/patch/python-snippets.d.ts +22 -0
  30. package/dist/patch/python-snippets.js +113 -0
  31. package/dist/patch/snippets.d.ts +10 -31
  32. package/dist/patch/snippets.js +42 -105
  33. package/dist/patch/static-page.d.ts +1 -0
  34. package/dist/patch/static-page.js +6 -8
  35. package/dist/patch/vite-config.js +11 -0
  36. package/dist/plan/connect-steps.d.ts +2 -0
  37. package/dist/plan/connect-steps.js +6 -0
  38. package/dist/plan/framework-adapter.d.ts +10 -0
  39. package/dist/plan/framework-adapter.js +122 -3
  40. package/dist/plan/notices.js +1 -1
  41. package/dist/plan/plan-angular.js +1 -1
  42. package/dist/plan/plan-framework.d.ts +1 -1
  43. package/dist/plan/plan-framework.js +140 -37
  44. package/dist/plan/plan-types.d.ts +50 -2
  45. package/dist/plan/plan-vite.js +15 -0
  46. package/dist/plan/plan.d.ts +1 -10
  47. package/dist/plan/plan.js +181 -126
  48. package/dist/plan/stop-hook-step.d.ts +30 -0
  49. package/dist/plan/stop-hook-step.js +154 -5
  50. package/dist/project/agent-rules.d.ts +1 -1
  51. package/dist/project/agent-rules.js +22 -17
  52. package/dist/project/refresh-rules.js +2 -1
  53. package/dist/register/codex-toml.d.ts +12 -0
  54. package/dist/register/codex-toml.js +44 -0
  55. package/dist/register/mcp.d.ts +15 -2
  56. package/dist/register/mcp.js +16 -0
  57. package/dist/register/prompt-hooks.d.ts +123 -0
  58. package/dist/register/prompt-hooks.js +449 -0
  59. package/dist/register/slash-command.d.ts +1 -1
  60. package/dist/register/slash-command.js +5 -4
  61. package/dist/run-types.d.ts +10 -0
  62. package/dist/run.d.ts +1 -1
  63. package/dist/run.js +15 -390
  64. package/package.json +2 -2
@@ -9,14 +9,39 @@ import { patchNextConfig, patchRootLayout, patchPagesApp } from '../patch/next-p
9
9
  import { ASTRO_ENV_DTS_PATH, ASTRO_RETICLE_DEV_PATH, astroReticleDevFile, patchAstroConfig, patchAstroEnvDts, patchAstroLayout, } from '../patch/astro-patch.js';
10
10
  import { CRA_DEV_MODULE_IMPORT, CRA_ENV_PATH, CRA_TOKEN_PER_MACHINE_NOTICE, TOKEN_VAR, craDevModuleFile, craDevModulePath, craEnvPatch, craImportPatch, } from '../patch/cra.js';
11
11
  import { PatchKind } from '../patch/patch-kind.js';
12
- import { NEXT_LAYOUT_MANUAL, NEXT_LAYOUT_PATH, nextReticleDevFile, NEXT_RETICLE_DEV_PATH, nextConfigManual, svelteKitHooksFile, SVELTEKIT_HOOKS_PATH, SVELTEKIT_SETUP_GATED_NOTE, astroManual, nuxtManual, nuxtPluginFile, nuxtPluginPath, NUXT_PLUGIN_NOTICE, webpack4TranspileNote, WEBPACK4_REACT_SCRIPTS_MAJOR, reactRouterEntryFile, reactRouterEntryPatch, REACT_ROUTER_ENTRY_PATH, UNVERIFIED_TANSTACK_START_NOTE, htmlManual, } from '../patch/snippets.js';
12
+ import { NEXT_LAYOUT_MANUAL, NEXT_LAYOUT_PATH, nextReticleDevFile, NEXT_RETICLE_DEV_PATH, nextConfigManual, svelteKitHooksFile, SVELTEKIT_HOOKS_PATH, SVELTEKIT_SETUP_GATED_NOTE, astroManual, nuxtManual, nuxtPluginFile, nuxtPluginPath, NUXT_PLUGIN_NOTICE, webpack4TranspileNote, WEBPACK4_REACT_SCRIPTS_MAJOR, reactRouterEntryFile, reactRouterEntryPatch, REACT_ROUTER_ENTRY_PATH, UNVERIFIED_TANSTACK_START_NOTE, htmlManual, connectArg, } from '../patch/snippets.js';
13
13
  import { StepStatus } from './plan-types.js';
14
14
  import { StepTitle } from './connect-steps.js';
15
15
  import { patchTanstackStartRoot, tanstackStartConnectFile, tanstackStartConnectPath, tanstackStartManual, TANSTACK_START_ROOT_PATH, } from '../patch/tanstack-start.js';
16
16
  import { patchNuxtConfig } from '../patch/nuxt-patch.js';
17
17
  import { alreadyOrMovedPort } from './port-steps.js';
18
18
  import { capabilitiesTodo, needsManualStore } from './plan-vite.js';
19
- import { HTML_INDEX_PATH, hasStaticSnippet } from '../patch/static-page.js';
19
+ import { HTML_INDEX_PATH, STATIC_GITIGNORE_PATH, STATIC_SNIPPET_MARKER, STATIC_TOKEN_MODULE, staticTokenFiles, withStaticSnippet, } from '../patch/static-page.js';
20
+ /*
21
+ * Threat model for the static page's token module. `reticle.local.js` sits beside `index.html`, so
22
+ * whatever serves the page serves it too, to any client that can reach that server. That is the same
23
+ * exposure every other dev path already has: the Vite plugin bakes the token into the connect module
24
+ * it serves, and the no-package.json path writes this very file. The dev server's bind address is
25
+ * the boundary; the module is gitignored so it never reaches a commit or a published build.
26
+ */
27
+ const STATIC_TOKEN_DETAIL = `write this machine’s pairing token to ${STATIC_TOKEN_MODULE} beside the page`;
28
+ const STATIC_IGNORE_DETAIL = `add ${STATIC_TOKEN_MODULE} to ${STATIC_GITIGNORE_PATH} — it holds this machine’s pairing token`;
29
+ const STATIC_CONNECT_DETAIL = 'write the dev-only connect snippet (loopback hosts only)';
30
+ const STATIC_CONNECT_ALREADY_DETAIL = `${HTML_INDEX_PATH} already loads the Reticle SDK`;
31
+ /** The package whose presence makes a static page an Electron renderer. */
32
+ const ELECTRON_PACKAGE = 'electron';
33
+ /**
34
+ * An Electron app often serves its renderer from a local HTTP server, so the packaged app runs on
35
+ * `localhost` on every user's machine. The localhost-only guard then lets the snippet run in
36
+ * production, and a packager glob such as `**\/*.js` can ship the token module.
37
+ */
38
+ const ELECTRON_STATIC_DETAIL = `This is an Electron app. Its packaged renderer can also run on localhost, so init did not ` +
39
+ `write the localhost-only snippet into ${HTML_INDEX_PATH} or the ${STATIC_TOKEN_MODULE} token ` +
40
+ 'module. Connect only in development: for example, send `!app.isPackaged` from the main ' +
41
+ 'process to the renderer, and call `reticle.connect()` only when it is true. Use ' +
42
+ `@reticlehq/electron for IPC and capture. If ${STATIC_TOKEN_MODULE} is in the project, keep ` +
43
+ `it out of the package: electron-builder \`files: ["!${STATIC_TOKEN_MODULE}"]\`, or Electron ` +
44
+ `Forge \`packagerConfig.ignore: [/${STATIC_TOKEN_MODULE.replaceAll('.', '\\.')}$/]\`.`;
20
45
  /**
21
46
  * Turn a conservative source patch into a step: applied when it patched, already when the wiring is
22
47
  * there, and the hand-edit instructions when the file shape wasn't one we recognise.
@@ -49,15 +74,21 @@ export function patchStep(title, path, patch, applyDetail, manualDetail) {
49
74
  */
50
75
  /** The env var withReticle sets from the discovered daemon, and the component must read. */
51
76
  const NEXT_DAEMON_URL_ENV = 'NEXT_PUBLIC_RETICLE_URL';
77
+ const NEXT_SDK_VERSION_ENV = 'NEXT_PUBLIC_RETICLE_SDK_VERSION';
52
78
  /**
53
79
  * The two-line edit that unfreezes an existing install's port.
54
80
  *
55
81
  * Stated as the edit rather than as a problem, because the reader is an agent that can apply it in
56
82
  * one write and is otherwise going to ask what to do.
57
83
  */
58
- const NEXT_DEV_FILE_STALE_DETAIL = 'predates daemon discovery, so it dials the port init saw when it ran. In this file add ' +
84
+ const NEXT_DEV_FILE_STALE_DETAIL_SOLO = 'predates daemon discovery, so it dials the port init saw when it ran. In this file add ' +
59
85
  '`const url = process.env.NEXT_PUBLIC_RETICLE_URL;` and spread `...(url ? { url } : {})` into ' +
60
86
  'reticle.connect(), after any url already there. Nothing else needs to change.';
87
+ const NEXT_DEV_FILE_STALE_DETAIL_DUAL = 'predates daemon discovery, so it dials the port init saw when it ran. In this file add ' +
88
+ '`const url = process.env.NEXT_PUBLIC_RETICLE_URL;` and spread `...(url ? { url } : {})` into ' +
89
+ 'reticle.connect(), after any url already there.';
90
+ const NEXT_SDK_VERSION_DETAIL = 'lacks the SDK version. In this file add `const sdkVersion = process.env.NEXT_PUBLIC_RETICLE_SDK_VERSION;` ' +
91
+ 'and spread `...(sdkVersion ? { sdkVersion } : {})` into the reticle.connect() options.';
61
92
  export function nextSteps(input) {
62
93
  const configFile = input.nextConfigFile ?? 'next.config.mjs';
63
94
  const devPath = input.nextReticleDevPath ?? NEXT_RETICLE_DEV_PATH;
@@ -71,34 +102,52 @@ export function nextSteps(input) {
71
102
  const devStale = true === input.nextReticleDevExists &&
72
103
  'string' === typeof input.nextReticleDevSource &&
73
104
  !input.nextReticleDevSource.includes(NEXT_DAEMON_URL_ENV);
74
- const devFile = input.nextReticleDevExists
75
- ? devStale
76
- ? {
105
+ const sdkVersionMissing = true === input.nextReticleDevExists &&
106
+ 'string' === typeof input.nextReticleDevSource &&
107
+ !input.nextReticleDevSource.includes(NEXT_SDK_VERSION_ENV);
108
+ const baseDevStep = devStale
109
+ ? {
110
+ title: StepTitle.RETICLE_DEV_COMPONENT,
111
+ target: devPath,
112
+ status: StepStatus.MANUAL,
113
+ detail: sdkVersionMissing
114
+ ? `${NEXT_DEV_FILE_STALE_DETAIL_DUAL} Also ${NEXT_SDK_VERSION_DETAIL}`
115
+ : NEXT_DEV_FILE_STALE_DETAIL_SOLO,
116
+ }
117
+ : alreadyOrMovedPort({
118
+ title: StepTitle.RETICLE_DEV_COMPONENT,
119
+ target: devPath,
120
+ status: StepStatus.ALREADY,
121
+ detail: 'file exists',
122
+ }, input.nextReticleDevSource, input.options.port);
123
+ const devNoticeSteps = !devStale && sdkVersionMissing
124
+ ? [
125
+ {
77
126
  title: StepTitle.RETICLE_DEV_COMPONENT,
78
127
  target: devPath,
79
- status: StepStatus.MANUAL,
80
- detail: NEXT_DEV_FILE_STALE_DETAIL,
81
- }
82
- : alreadyOrMovedPort({
128
+ status: StepStatus.NOTICE,
129
+ detail: NEXT_SDK_VERSION_DETAIL,
130
+ },
131
+ ]
132
+ : [];
133
+ const devFileSteps = input.nextReticleDevExists
134
+ ? [baseDevStep, ...devNoticeSteps]
135
+ : [
136
+ {
83
137
  title: StepTitle.RETICLE_DEV_COMPONENT,
84
138
  target: devPath,
85
- status: StepStatus.ALREADY,
86
- detail: 'file exists',
87
- }, input.nextReticleDevSource, input.options.port)
88
- : {
89
- title: StepTitle.RETICLE_DEV_COMPONENT,
90
- target: devPath,
91
- status: StepStatus.APPLY,
92
- detail: 'create dev-only connect component',
93
- write: {
94
- path: devPath,
95
- content: nextReticleDevFile(input.options.port, input.options.projectId, input.testids ?? [], input.storeHints ?? [],
96
- // Next's dev module is not a sibling of `src/` — its imports are resolved from wherever
97
- // the component actually lands (`app/`, or `src/app/` in a --src-dir app).
98
- input.nextFoundStores ?? []),
139
+ status: StepStatus.APPLY,
140
+ detail: 'create dev-only connect component',
141
+ write: {
142
+ path: devPath,
143
+ content: nextReticleDevFile(input.options.port, input.options.projectId, input.testids ?? [], input.storeHints ?? [],
144
+ // Next's dev module is not a sibling of `src/` — its imports are resolved from wherever
145
+ // the component actually lands (`app/`, or `src/app/` in a --src-dir app).
146
+ input.nextFoundStores ?? []),
147
+ },
148
+ dependsOnInstall: true,
99
149
  },
100
- dependsOnInstall: true,
101
- };
150
+ ];
102
151
  const configPatch = null === input.nextConfigSource || input.nextConfigSource === undefined
103
152
  ? { kind: PatchKind.MANUAL, reason: `no ${configFile} found` }
104
153
  : patchNextConfig(input.nextConfigSource, true !== input.detection?.customReconciler);
@@ -106,8 +155,14 @@ export function nextSteps(input) {
106
155
  // Pages Router mounts through pages/_app, App Router through the root layout — different edits,
107
156
  // and picking by path is what stops a Pages app being handed the layout patch that cannot apply.
108
157
  const isPagesRouter = layout !== null && /(^|\/)pages\/_app\.[jt]sx?$/.test(layout.path);
158
+ const candidates = input.nextLayoutCandidates ?? [];
109
159
  const layoutPatch = null === layout
110
- ? { kind: PatchKind.MANUAL, reason: 'no root layout (app/layout.tsx) or pages/_app found' }
160
+ ? {
161
+ kind: PatchKind.MANUAL,
162
+ reason: 0 === candidates.length
163
+ ? 'no root layout (app/layout.tsx) or pages/_app found'
164
+ : `no app/layout.tsx; route-group root layouts ${candidates.join(', ')}, and none of them alone renders /`,
165
+ }
111
166
  : isPagesRouter
112
167
  ? patchPagesApp(layout.source, input.nextReticleDevImport)
113
168
  : patchRootLayout(layout.source);
@@ -124,11 +179,26 @@ export function nextSteps(input) {
124
179
  },
125
180
  ]
126
181
  : [];
182
+ // Each route-group root layout renders only its own routes, so one mount covers one group: the
183
+ // routes under the other root layouts render without ReticleDev, and saying nothing about them
184
+ // would read as an app that is fully wired.
185
+ const unmounted = input.nextUnmountedLayouts ?? [];
186
+ const otherRootLayouts = null !== layout && 0 < unmounted.length
187
+ ? [
188
+ {
189
+ title: StepTitle.ROUTE_GROUPS_WITHOUT_RETICLE_DEV,
190
+ target: unmounted.join(', '),
191
+ status: StepStatus.NOTICE,
192
+ detail: `<ReticleDev /> is mounted in ${layout.path} only; ${unmounted.join(', ')} ${1 === unmounted.length ? 'is a root layout' : 'are root layouts'} of ${1 === unmounted.length ? 'its' : 'their'} own, so the routes under ${1 === unmounted.length ? 'it' : 'them'} render without it and Reticle sees nothing there. Mount it there too (dev-only) to cover them.`,
193
+ },
194
+ ]
195
+ : [];
127
196
  return [
128
- devFile,
197
+ ...devFileSteps,
129
198
  ...nextTodo,
130
199
  patchStep(StepTitle.NEXT_CONFIG, configFile, configPatch, 'wrap the export in withReticle (source mapping, dev-only)', nextConfigManual(configFile, true !== input.detection?.customReconciler)),
131
200
  patchStep(StepTitle.MOUNT_RETICLE_DEV, layout?.path ?? NEXT_LAYOUT_PATH, layoutPatch, 'mount <ReticleDev /> in the root layout (dev-only)', NEXT_LAYOUT_MANUAL),
201
+ ...otherRootLayouts,
132
202
  ];
133
203
  }
134
204
  /**
@@ -512,28 +582,61 @@ export function astroSteps(input) {
512
582
  patchStep(StepTitle.ASTRO_ENV_DTS, ASTRO_ENV_DTS_PATH, envPatch, 'declare window.__RETICLE_TOKEN__ / __RETICLE_ROOT__ for astro check', manualWithLayout),
513
583
  ];
514
584
  }
585
+ /** Write the gitignored module that holds this machine's pairing token, and its ignore line. */
586
+ function staticTokenSteps(input) {
587
+ return Object.entries(staticTokenFiles(input.pairingToken, (path) => input.htmlLocalSources?.[path] ?? null)).map(([path, content]) => ({
588
+ title: StepTitle.STATIC_PAIRING_TOKEN,
589
+ target: path,
590
+ status: StepStatus.APPLY,
591
+ detail: STATIC_GITIGNORE_PATH === path ? STATIC_IGNORE_DETAIL : STATIC_TOKEN_DETAIL,
592
+ write: { path, content },
593
+ }));
594
+ }
515
595
  /**
516
- * The plain-HTML path: no bundler to hook, so the connect snippet is printed for a hand edit.
596
+ * The plain-HTML path: write the static connect when the page exists, otherwise print the recipe.
517
597
  *
518
598
  * A function of its own, and NOT a fallthrough. Every framework is sent here on purpose or not at
519
599
  * all — a member of `Framework` with no adapter entry is a compile error in `FRAMEWORK_ADAPTERS`,
520
600
  * where the if/else chain this replaced used to hand it these instructions silently.
521
601
  */
522
602
  export function htmlSteps(input) {
523
- // Read the page before calling the step undone. It used to be MANUAL unconditionally, so a re-run
524
- // over a page that already connects said "This app will NOT connect until the ⚠ step".
525
- const index = input.htmlIndexSource ?? null;
526
- if (null !== index && hasStaticSnippet(index)) {
603
+ if (input.detection.dependencies?.[ELECTRON_PACKAGE] !== undefined) {
527
604
  return [
528
- alreadyOrMovedPort({
605
+ {
529
606
  title: StepTitle.CONNECT_SNIPPET,
530
607
  target: HTML_INDEX_PATH,
531
- status: StepStatus.ALREADY,
532
- detail: 'index.html already loads the Reticle SDK',
533
- }, index, input.options.port),
608
+ status: StepStatus.MANUAL,
609
+ detail: ELECTRON_STATIC_DETAIL,
610
+ },
611
+ ];
612
+ }
613
+ const index = input.htmlIndexSource ?? null;
614
+ if (null !== index) {
615
+ // The page is publishable; the pairing token belongs in its gitignored sibling module instead.
616
+ // Refresh that module even when the connect block itself is already in place.
617
+ const steps = staticTokenSteps(input);
618
+ const written = withStaticSnippet(index, connectArg(input.options.port, input.options.projectId));
619
+ return [
620
+ ...steps,
621
+ null !== written
622
+ ? {
623
+ title: StepTitle.CONNECT_SNIPPET,
624
+ target: HTML_INDEX_PATH,
625
+ status: StepStatus.APPLY,
626
+ detail: STATIC_CONNECT_DETAIL,
627
+ write: { path: HTML_INDEX_PATH, content: written, expect: [STATIC_SNIPPET_MARKER] },
628
+ }
629
+ : alreadyOrMovedPort({
630
+ title: StepTitle.CONNECT_SNIPPET,
631
+ target: HTML_INDEX_PATH,
632
+ status: StepStatus.ALREADY,
633
+ detail: STATIC_CONNECT_ALREADY_DETAIL,
634
+ }, index, input.options.port),
534
635
  ];
535
636
  }
536
637
  return [
638
+ // The manual snippet imports the token from this module rather than printing it.
639
+ ...staticTokenSteps(input),
537
640
  {
538
641
  title: StepTitle.CONNECT_SNIPPET,
539
642
  target: HTML_INDEX_PATH,
@@ -8,6 +8,7 @@
8
8
  import type { Detection, Framework, UiLibrary } from '../detect/detect.js';
9
9
  import type { FoundStore } from '../detect/capabilities.js';
10
10
  import type { McpClient } from '../register/mcp-clients.js';
11
+ import type { PromptHookTarget } from '../register/prompt-hooks.js';
11
12
  /** Exported so the init telemetry can tell an MCP-registration failure from a dependency install. */
12
13
  export declare const MCP_TARGET = "global (claude user scope)";
13
14
  /** The step that runs the package manager — the other thing that commonly fails on a user's machine. */
@@ -96,6 +97,11 @@ export interface Plan {
96
97
  }
97
98
  export interface PlanInput {
98
99
  detection: Detection;
100
+ /**
101
+ * `node_modules` is a link to another tree (a workspace's, a shared cache). A package manager run
102
+ * here replaces the link with a fresh tree, so the install is skipped and named instead.
103
+ */
104
+ nodeModulesLinked?: boolean | undefined;
99
105
  /**
100
106
  * Write `captureNetworkBodies: true` into the app's config. Off unless the caller asked (#705).
101
107
  *
@@ -110,6 +116,11 @@ export interface PlanInput {
110
116
  * handing it an io would let any later step reach the disk from inside `buildPlan`.
111
117
  */
112
118
  cspSources?: Readonly<Record<string, string | undefined>> | undefined;
119
+ /**
120
+ * The project's `.prettierignore` content, pre-read so the prettier-ignore step stays pure.
121
+ * Null when the file does not exist — `init` creates it only when the project uses Prettier.
122
+ */
123
+ prettierIgnoreSource?: string | null | undefined;
113
124
  /** Whether the `claude` CLI is installed (so we can register the MCP server globally). */
114
125
  claudeCli: boolean;
115
126
  /** Whether an `reticle` MCP server is already registered with Claude (any scope) — idempotency. */
@@ -145,11 +156,18 @@ export interface PlanInput {
145
156
  configPath: string;
146
157
  existing: string | null;
147
158
  }[] | undefined;
159
+ /**
160
+ * Whether the `codex` CLI is on PATH. Only probed when Codex was detected, and absent otherwise.
161
+ * With it, Codex registers through `codex mcp add` instead of a block to paste.
162
+ */
163
+ codexCli?: boolean | undefined;
148
164
  /** Discovered Vite config: its path + source, or null if none found. */
149
165
  viteConfig: {
150
166
  path: string;
151
167
  source: string;
152
168
  } | null;
169
+ /** Config paths found but not safely attributable to the selected dev command. */
170
+ viteConfigCandidates?: readonly string[] | undefined;
153
171
  /** Discovered electron-vite config: its path + source, or null if none found. */
154
172
  electronViteConfig?: {
155
173
  path: string;
@@ -200,6 +218,16 @@ export interface PlanInput {
200
218
  path: string;
201
219
  source: string;
202
220
  } | null | undefined;
221
+ /**
222
+ * Route-group root layouts `init` could not choose between (`app/(a)/layout.tsx`, ...), set only
223
+ * when there is no top-level layout and no single group renders `/` (#1538).
224
+ */
225
+ nextLayoutCandidates?: readonly string[] | undefined;
226
+ /**
227
+ * The other route-group root layouts when one was chosen to mount into, that do not mount
228
+ * `<ReticleDev />` themselves: the routes under them render without it.
229
+ */
230
+ nextUnmountedLayouts?: readonly string[] | undefined;
203
231
  /** Where the dev-only connect component goes — never inside `pages/`, which routes on presence. */
204
232
  nextReticleDevPath?: string | undefined;
205
233
  /** What the mount file should import — a sibling for App Router, `../components/…` for Pages. */
@@ -277,10 +305,12 @@ export interface PlanInput {
277
305
  /** The generated `reticle.proxy.mjs`, when a previous run left one (idempotency). */
278
306
  angularProxySource?: string | null | undefined;
279
307
  /**
280
- * The plain-HTML app's `index.html`, when it has one — so a re-run over a page that already
281
- * carries the connect snippet reports it as done instead of "will NOT connect".
308
+ * The plain-HTML app's `index.html`, when it has one — patched on the first run, recognised on
309
+ * later runs, and left manual only when there is no page to write into.
282
310
  */
283
311
  htmlIndexSource?: string | null | undefined;
312
+ /** Current token module and .gitignore beside the HTML page, for idempotent token-file writes. */
313
+ htmlLocalSources?: Record<string, string | null> | undefined;
284
314
  /** CRA's bundled entry (src/index.tsx or .js) — where the connect import has to go. */
285
315
  craEntry?: {
286
316
  path: string;
@@ -306,6 +336,14 @@ export interface PlanInput {
306
336
  * see reticleConfigStep for the port that survived every re-run because of it.
307
337
  */
308
338
  reticleConfigSource?: string | null | undefined;
339
+ /**
340
+ * `--new-id`: the app's source files that quote the id being replaced. A hand-written
341
+ * `connect({ projectId: '…' })` still naming it sends this checkout's evidence to the original.
342
+ */
343
+ copiedIdSources?: readonly {
344
+ path: string;
345
+ source: string;
346
+ }[] | undefined;
309
347
  /** Current project-root CLAUDE.md content (for the idempotent agent-rule merge), or null/undefined. */
310
348
  claudeMdContent?: string | null | undefined;
311
349
  /** Current project-root AGENTS.md content (the cross-agent rule), or null/undefined. */
@@ -328,6 +366,12 @@ export interface PlanInput {
328
366
  hooks?: boolean | undefined;
329
367
  /** Current .claude/settings.json content, or null when absent (the hook is MERGED into it). */
330
368
  claudeSettingsContent?: string | null | undefined;
369
+ /** Install the prompt-capture hook into `.claude/settings.json`: Claude Code is used here. */
370
+ claudePromptHook?: boolean | undefined;
371
+ /** Every OTHER agent found here with a prompt hook, and its config as it is now. */
372
+ promptHooks?: readonly PromptHookTarget[] | undefined;
373
+ /** See `InitHost.hookBin`. */
374
+ hookBin?: string | undefined;
331
375
  /**
332
376
  * Absolute directory the AGENT runs in, when that is not the app's directory.
333
377
  *
@@ -342,12 +386,16 @@ export interface PlanInput {
342
386
  * that disagrees with the app's is worse than no root config, and only reading it can tell.
343
387
  */
344
388
  agentRootConfigSource?: string | null | undefined;
389
+ /** The agent root holds several apps, so its config would name one and mislead about the rest. */
390
+ agentRootSeveralApps?: boolean | undefined;
345
391
  options: {
346
392
  port: number | undefined;
347
393
  mcp: boolean;
348
394
  install: boolean;
349
395
  /** Stable project identity derived at init (package.json name + root). Baked into snippets/.reticle.json. */
350
396
  projectId?: string;
397
+ /** `--new-id`: the config on disk takes `projectId` above in place of the id it was copied with. */
398
+ newId?: boolean;
351
399
  /** The CLI's own version, pinned onto the SDK install so a stale registry cache cannot skew it. */
352
400
  sdkVersion?: string;
353
401
  };
@@ -194,6 +194,21 @@ function viteConfigSteps(input, detail, inject = true) {
194
194
  // plugin, and a file carrying ours alone would not boot the app.
195
195
  const plainVite = Framework.VITE === input.detection.framework;
196
196
  if (null === cfg) {
197
+ const candidates = input.viteConfigCandidates ?? [];
198
+ if (candidates.length > 0) {
199
+ const named = candidates.map((path) => `\`${path}\``).join(', ');
200
+ return [
201
+ {
202
+ title: StepTitle.VITE_PLUGIN,
203
+ target: candidates[0] ?? 'vite.config',
204
+ status: StepStatus.MANUAL,
205
+ detail: `Reticle could not safely patch the Vite config used by the dev server. ` +
206
+ `Candidate config(s): ${named}. ` +
207
+ `Add the Reticle plugin to the config passed to createServer().\n\n` +
208
+ viteManual(port, input.detection.uiLibrary, inject, stampSource),
209
+ },
210
+ ];
211
+ }
197
212
  if (!plainVite) {
198
213
  return [
199
214
  {
@@ -3,18 +3,9 @@
3
3
  * contents, produce an ordered list of steps — each marked apply / manual / already / skip. The
4
4
  * runner performs the `write` side-effects; this module decides *what* should happen.
5
5
  */
6
- import { Framework, UiLibrary } from '../detect/detect.js';
7
6
  import { type Plan, type PlanInput } from './plan-types.js';
8
7
  export { DEPS_TARGET, MCP_TARGET, StepStatus, type Step, type Plan, type PlanInput, } from './plan-types.js';
9
8
  import { RETICLE_CONFIG_FILE } from '../detect/existing-config.js';
10
9
  export { RETICLE_CONFIG_FILE };
11
- /**
12
- * The dev-dependencies `reticle init` installs — kit (or sensor) first, build plugin next.
13
- *
14
- * `uiLibrary` matters because the framework does not always name the renderer. The rule below was
15
- * already written for Nuxt, correctly, and applied only there: a Vue app on plain Vite is
16
- * `Framework.VITE` and a SvelteKit app renders Svelte, and both were being handed the React kit
17
- * right after `init` had detected the real UI library and said so on screen.
18
- */
19
- export declare function frameworkPackages(framework: Framework, uiLibrary?: UiLibrary): readonly string[];
10
+ export { frameworkPackages } from './framework-adapter.js';
20
11
  export declare function buildPlan(input: PlanInput): Plan;