@reticlehq/init 2.14.0 → 3.2.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 (125) hide show
  1. package/dist/{detect.d.ts → detect/detect.d.ts} +1 -1
  2. package/dist/{detect.js → detect/detect.js} +6 -2
  3. package/dist/{dev-server-ports.js → detect/dev-server-ports.js} +8 -0
  4. package/dist/{existing-config.d.ts → detect/existing-config.d.ts} +2 -4
  5. package/dist/{existing-config.js → detect/existing-config.js} +2 -4
  6. package/dist/{lockfiles.d.ts → detect/lockfiles.d.ts} +1 -1
  7. package/dist/{lockfiles.js → detect/lockfiles.js} +1 -2
  8. package/dist/{workspace-apps.d.ts → detect/workspace-apps.d.ts} +3 -5
  9. package/dist/{workspace-apps.js → detect/workspace-apps.js} +2 -4
  10. package/dist/{workspace-redirect.d.ts → detect/workspace-redirect.d.ts} +3 -5
  11. package/dist/{workspace-redirect.js → detect/workspace-redirect.js} +8 -12
  12. package/dist/{closing-hint.d.ts → diagnose/closing-hint.d.ts} +3 -4
  13. package/dist/{closing-hint.js → diagnose/closing-hint.js} +14 -19
  14. package/dist/diagnose/containerised-dev-server.d.ts +32 -0
  15. package/dist/diagnose/containerised-dev-server.js +56 -0
  16. package/dist/{csp-check.d.ts → diagnose/csp-check.d.ts} +2 -2
  17. package/dist/{csp-check.js → diagnose/csp-check.js} +2 -2
  18. package/dist/{desktop-doctor.js → diagnose/desktop-doctor.js} +12 -6
  19. package/dist/{init-failure.d.ts → diagnose/init-failure.d.ts} +1 -2
  20. package/dist/{init-failure.js → diagnose/init-failure.js} +1 -2
  21. package/dist/diagnose/install-hint.d.ts +8 -0
  22. package/dist/{install-hint.js → diagnose/install-hint.js} +3 -4
  23. package/dist/{install-retries.d.ts → diagnose/install-retries.d.ts} +3 -4
  24. package/dist/{install-retries.js → diagnose/install-retries.js} +6 -8
  25. package/dist/host.d.ts +14 -1
  26. package/dist/host.js +3 -0
  27. package/dist/index.d.ts +32 -12
  28. package/dist/index.js +36 -12
  29. package/dist/node-io.d.ts +12 -1
  30. package/dist/node-io.js +53 -25
  31. package/dist/{astro-patch.d.ts → patch/astro-patch.d.ts} +1 -1
  32. package/dist/{astro-patch.js → patch/astro-patch.js} +1 -1
  33. package/dist/{cra.js → patch/cra.js} +1 -1
  34. package/dist/{electron-patch.js → patch/electron-patch.js} +1 -1
  35. package/dist/{next-patch.d.ts → patch/next-patch.d.ts} +2 -2
  36. package/dist/{next-patch.js → patch/next-patch.js} +2 -2
  37. package/dist/{nuxt-patch.js → patch/nuxt-patch.js} +1 -1
  38. package/dist/{snippets.d.ts → patch/snippets.d.ts} +11 -21
  39. package/dist/{snippets.js → patch/snippets.js} +20 -29
  40. package/dist/{tanstack-start.d.ts → patch/tanstack-start.d.ts} +2 -3
  41. package/dist/{tanstack-start.js → patch/tanstack-start.js} +3 -4
  42. package/dist/{vite-config.js → patch/vite-config.js} +15 -13
  43. package/dist/{connect-steps.d.ts → plan/connect-steps.d.ts} +3 -3
  44. package/dist/{connect-steps.js → plan/connect-steps.js} +4 -4
  45. package/dist/{framework-adapter.d.ts → plan/framework-adapter.d.ts} +11 -2
  46. package/dist/{framework-adapter.js → plan/framework-adapter.js} +12 -1
  47. package/dist/plan/notices.d.ts +23 -0
  48. package/dist/plan/notices.js +94 -0
  49. package/dist/{plan-electron-vite.d.ts → plan/plan-electron-vite.d.ts} +3 -4
  50. package/dist/{plan-electron-vite.js → plan/plan-electron-vite.js} +7 -8
  51. package/dist/{plan-framework.d.ts → plan/plan-framework.d.ts} +4 -13
  52. package/dist/{plan-framework.js → plan/plan-framework.js} +14 -34
  53. package/dist/{plan.d.ts → plan/plan-types.d.ts} +14 -17
  54. package/dist/plan/plan-types.js +24 -0
  55. package/dist/plan/plan.d.ts +20 -0
  56. package/dist/{plan.js → plan/plan.js} +27 -88
  57. package/dist/{agent-rules.d.ts → project/agent-rules.d.ts} +2 -2
  58. package/dist/{agent-rules.js → project/agent-rules.js} +17 -21
  59. package/dist/project/install-source-config.d.ts +14 -0
  60. package/dist/{install-source-config.js → project/install-source-config.js} +3 -7
  61. package/dist/project/remember-project.js +74 -0
  62. package/dist/register/detect-clients.d.ts +23 -0
  63. package/dist/register/detect-clients.js +28 -0
  64. package/dist/{mcp-clients.js → register/mcp-clients.js} +4 -8
  65. package/dist/{mcp-registered.d.ts → register/mcp-registered.d.ts} +1 -4
  66. package/dist/{mcp-registered.js → register/mcp-registered.js} +1 -4
  67. package/dist/{mcp.d.ts → register/mcp.d.ts} +5 -8
  68. package/dist/{mcp.js → register/mcp.js} +6 -9
  69. package/dist/register/slash-command.d.ts +27 -0
  70. package/dist/{slash-command.js → register/slash-command.js} +10 -10
  71. package/dist/register/windows-quote.d.ts +17 -0
  72. package/dist/register/windows-quote.js +49 -0
  73. package/dist/run-types.d.ts +131 -0
  74. package/dist/run-types.js +8 -0
  75. package/dist/run.d.ts +3 -120
  76. package/dist/run.js +111 -56
  77. package/package.json +5 -5
  78. package/dist/install-hint.d.ts +0 -9
  79. package/dist/install-source-config.d.ts +0 -18
  80. package/dist/remember-project.js +0 -40
  81. package/dist/slash-command.d.ts +0 -27
  82. package/dist/windows-quote.d.ts +0 -38
  83. package/dist/windows-quote.js +0 -70
  84. /package/dist/{capabilities.d.ts → detect/capabilities.d.ts} +0 -0
  85. /package/dist/{capabilities.js → detect/capabilities.js} +0 -0
  86. /package/dist/{app-choice.d.ts → detect/declared/app-choice.d.ts} +0 -0
  87. /package/dist/{app-choice.js → detect/declared/app-choice.js} +0 -0
  88. /package/dist/{init-opt-out.d.ts → detect/declared/init-opt-out.d.ts} +0 -0
  89. /package/dist/{init-opt-out.js → detect/declared/init-opt-out.js} +0 -0
  90. /package/dist/{dev-script.d.ts → detect/dev-script.d.ts} +0 -0
  91. /package/dist/{dev-script.js → detect/dev-script.js} +0 -0
  92. /package/dist/{dev-server-ports.d.ts → detect/dev-server-ports.d.ts} +0 -0
  93. /package/dist/{non-js-project.d.ts → detect/non-js-project.d.ts} +0 -0
  94. /package/dist/{non-js-project.js → detect/non-js-project.js} +0 -0
  95. /package/dist/{platform.d.ts → detect/platform.d.ts} +0 -0
  96. /package/dist/{platform.js → detect/platform.js} +0 -0
  97. /package/dist/{csp-doctor.d.ts → diagnose/csp-doctor.d.ts} +0 -0
  98. /package/dist/{csp-doctor.js → diagnose/csp-doctor.js} +0 -0
  99. /package/dist/{desktop-doctor.d.ts → diagnose/desktop-doctor.d.ts} +0 -0
  100. /package/dist/{astro-host.d.ts → patch/astro-host.d.ts} +0 -0
  101. /package/dist/{astro-host.js → patch/astro-host.js} +0 -0
  102. /package/dist/{brace-scan.d.ts → patch/brace-scan.d.ts} +0 -0
  103. /package/dist/{brace-scan.js → patch/brace-scan.js} +0 -0
  104. /package/dist/{cra.d.ts → patch/cra.d.ts} +0 -0
  105. /package/dist/{electron-patch.d.ts → patch/electron-patch.d.ts} +0 -0
  106. /package/dist/{electron-vite-patch.d.ts → patch/electron-vite-patch.d.ts} +0 -0
  107. /package/dist/{electron-vite-patch.js → patch/electron-vite-patch.js} +0 -0
  108. /package/dist/{format-generated.d.ts → patch/format-generated.d.ts} +0 -0
  109. /package/dist/{format-generated.js → patch/format-generated.js} +0 -0
  110. /package/dist/{nuxt-patch.d.ts → patch/nuxt-patch.d.ts} +0 -0
  111. /package/dist/{patch-kind.d.ts → patch/patch-kind.d.ts} +0 -0
  112. /package/dist/{patch-kind.js → patch/patch-kind.js} +0 -0
  113. /package/dist/{vite-config.d.ts → patch/vite-config.d.ts} +0 -0
  114. /package/dist/{vite-owning-config.d.ts → patch/vite-owning-config.d.ts} +0 -0
  115. /package/dist/{vite-owning-config.js → patch/vite-owning-config.js} +0 -0
  116. /package/dist/{preflight.d.ts → plan/preflight.d.ts} +0 -0
  117. /package/dist/{preflight.js → plan/preflight.js} +0 -0
  118. /package/dist/{project-id.d.ts → project/project-id.d.ts} +0 -0
  119. /package/dist/{project-id.js → project/project-id.js} +0 -0
  120. /package/dist/{refresh-rules.d.ts → project/refresh-rules.d.ts} +0 -0
  121. /package/dist/{refresh-rules.js → project/refresh-rules.js} +0 -0
  122. /package/dist/{remember-project.d.ts → project/remember-project.d.ts} +0 -0
  123. /package/dist/{codex-toml.d.ts → register/codex-toml.d.ts} +0 -0
  124. /package/dist/{codex-toml.js → register/codex-toml.js} +0 -0
  125. /package/dist/{mcp-clients.d.ts → register/mcp-clients.d.ts} +0 -0
@@ -40,7 +40,7 @@ export declare const Framework: {
40
40
  * TanStack Start SSRs `<html>` from `src/routes/__root.tsx` and never sends Vite's index.html, so
41
41
  * the plugin's `transformIndexHtml` injection never fires. It used to fall through to
42
42
  * `Framework.VITE`, where `init` wired the plugin, reported every step green ("also injects
43
- * connect()"), and produced zero sessions — confirmed in the field as ~13 minutes of "still
43
+ * connect()"), and produced zero sessions — confirmed in the field as many minutes of "still
44
44
  * verifying" against a daemon showing none (#773).
45
45
  *
46
46
  * Keyed on `@tanstack/react-start` or the older `@tanstack/start`, never on
@@ -40,7 +40,7 @@ export const Framework = {
40
40
  * TanStack Start SSRs `<html>` from `src/routes/__root.tsx` and never sends Vite's index.html, so
41
41
  * the plugin's `transformIndexHtml` injection never fires. It used to fall through to
42
42
  * `Framework.VITE`, where `init` wired the plugin, reported every step green ("also injects
43
- * connect()"), and produced zero sessions — confirmed in the field as ~13 minutes of "still
43
+ * connect()"), and produced zero sessions — confirmed in the field as many minutes of "still
44
44
  * verifying" against a daemon showing none (#773).
45
45
  *
46
46
  * Keyed on `@tanstack/react-start` or the older `@tanstack/start`, never on
@@ -301,7 +301,11 @@ const INSTALL_ARGS = {
301
301
  * hard failure, which is the opposite of the problem being fixed.
302
302
  */
303
303
  const QUIET_INSTALL_ARGS = {
304
- [PackageManager.NPM]: ['--no-audit', '--no-fund'],
304
+ // `--no-update-notifier` joined the other two for the same reason and from the same measurement:
305
+ // a re-capture of a pristine Vite first run spent five of its lines on an `npm notice` block
306
+ // announcing a newer npm, its changelog url and the command to install it. Advice about npm,
307
+ // printed in the middle of somebody wiring up a different tool.
308
+ [PackageManager.NPM]: ['--no-audit', '--no-fund', '--no-update-notifier'],
305
309
  };
306
310
  /** Build a dev-dependency install command for one or more packages (e.g. the kit + its build plugin). */
307
311
  export function installCommandParts(pm, pkgs,
@@ -35,6 +35,14 @@ export const DEV_SERVER_PORTS = new Set([
35
35
  5175, // Vite, third instance
36
36
  7860, // Gradio
37
37
  8501, // Streamlit
38
+ // The same lesson again, from a run against Reticle's own bench-app: the scan reported "nothing is
39
+ // listening" while the app was serving on 4310. Absence is stated here as a fact, so a port left
40
+ // out is not a gap in the answer, it is a wrong answer delivered confidently.
41
+ 1234, // Parcel
42
+ 4000, // the common Node/GraphQL dev default
43
+ 4310, // Reticle's own bench-app, the fixture its agents drive
44
+ 6006, // Storybook
45
+ 19006, // Expo web
38
46
  ]);
39
47
  export function isLikelyDevServerPort(port) {
40
48
  return DEV_SERVER_PORTS.has(port);
@@ -3,10 +3,8 @@ export declare const RETICLE_CONFIG_FILE = ".reticle.json";
3
3
  /**
4
4
  * Reading a `.reticle.json` that is ALREADY on disk.
5
5
  *
6
- * Split out of plan.ts when it crossed the line cap. These two are one job -- looking at a config
7
- * somebody else wrote and deciding what it tells us -- and neither knows anything about steps,
8
- * plans or the shape of a report, which is what made them the cohesive piece to lift rather than
9
- * the nearest thousand-line boundary.
6
+ * These two are one job -- looking at a config somebody else wrote and deciding what it tells us --
7
+ * and neither knows anything about steps, plans or the shape of a report.
10
8
  */
11
9
  /**
12
10
  * What is wrong with the config that is already there, if anything.
@@ -4,10 +4,8 @@ export const RETICLE_CONFIG_FILE = '.reticle.json';
4
4
  /**
5
5
  * Reading a `.reticle.json` that is ALREADY on disk.
6
6
  *
7
- * Split out of plan.ts when it crossed the line cap. These two are one job -- looking at a config
8
- * somebody else wrote and deciding what it tells us -- and neither knows anything about steps,
9
- * plans or the shape of a report, which is what made them the cohesive piece to lift rather than
10
- * the nearest thousand-line boundary.
7
+ * These two are one job -- looking at a config somebody else wrote and deciding what it tells us --
8
+ * and neither knows anything about steps, plans or the shape of a report.
11
9
  */
12
10
  /**
13
11
  * What is wrong with the config that is already there, if anything.
@@ -1,4 +1,4 @@
1
- import type { InitIo } from './run.js';
1
+ import type { InitIo } from '../run-types.js';
2
2
  /**
3
3
  * Resolve the lockfiles set used to pick the package manager. A lockfile in the project root wins;
4
4
  * otherwise we walk UP the directory tree (monorepos keep the lockfile at the workspace root, not in
@@ -1,8 +1,7 @@
1
1
  /**
2
2
  * Which lockfile decides the package manager `init` will tell the user to run.
3
3
  *
4
- * Split out of `run.ts` when that file crossed the 1000-line cap: this is one self-contained
5
- * question with its own test block, and `run.ts` is the orchestration around it.
4
+ * One self-contained question with its own test block; `run.ts` is the orchestration around it.
6
5
  */
7
6
  import { dirname, join } from 'node:path';
8
7
  import { namesAPackageManager } from './detect.js';
@@ -1,4 +1,4 @@
1
- import type { InitIo } from './run.js';
1
+ import type { InitIo } from '../run-types.js';
2
2
  /**
3
3
  * The file names that say what a directory IS, shared by everything that has to ask.
4
4
  *
@@ -21,10 +21,8 @@ export declare function workspaceParents(sources: WorkspaceSources): string[];
21
21
  /**
22
22
  * Which directories under this workspace are runnable apps.
23
23
  *
24
- * Moved here from run.ts, which was over the 1000-line cap and had carried a note saying it was at
25
- * its cohesion limit. `library-path-boundary.test.ts` had already written down that this function
26
- * "squats in init/ for historical reasons" — it is a general-purpose question about a repository's
27
- * shape, next to `workspaceParents`, which answers the other half of it.
24
+ * A general-purpose question about a repository's shape, so it lives next to `workspaceParents`,
25
+ * which answers the other half of it.
28
26
  */
29
27
  /** Deps that mark a directory as a runnable web app even when it has no bundler config file. */
30
28
  export declare const APP_DEPS: readonly ["next", "vite", "electron-vite"];
@@ -96,10 +96,8 @@ export function workspaceParents(sources) {
96
96
  /**
97
97
  * Which directories under this workspace are runnable apps.
98
98
  *
99
- * Moved here from run.ts, which was over the 1000-line cap and had carried a note saying it was at
100
- * its cohesion limit. `library-path-boundary.test.ts` had already written down that this function
101
- * "squats in init/ for historical reasons" — it is a general-purpose question about a repository's
102
- * shape, next to `workspaceParents`, which answers the other half of it.
99
+ * A general-purpose question about a repository's shape, so it lives next to `workspaceParents`,
100
+ * which answers the other half of it.
103
101
  */
104
102
  /** Deps that mark a directory as a runnable web app even when it has no bundler config file. */
105
103
  export const APP_DEPS = ['next', 'vite', 'electron-vite'];
@@ -1,16 +1,14 @@
1
1
  /**
2
2
  * Workspace redirect: when `init` is run at a monorepo ROOT, wire the app rather than the root.
3
3
  *
4
- * Split out of `run.ts`, which sits one line under the 1000-line backstop — every init change that
5
- * touches the orchestration now trips the cap before it can be reviewed. This is the part of that
6
- * file that answers a single, separable question ("is the app somewhere else, and where?"), so it
7
- * is the seam that costs nothing to move.
4
+ * Its own module because it answers a single, separable question: is the app somewhere else, and
5
+ * where?
8
6
  *
9
7
  * `runInit` arrives as a parameter rather than an import: re-entering init is the whole point of a
10
8
  * redirect, and taking it as an argument keeps this module a leaf instead of forming a cycle with
11
9
  * the file it was cut from.
12
10
  */
13
- import type { InitIo, InitOptions, InitResult } from './run.js';
11
+ import type { InitIo, InitOptions, InitResult } from '../run-types.js';
14
12
  /** Re-enter `init`, scoped to one directory of the workspace. */
15
13
  export type RunInit = (options: InitOptions, io: InitIo) => InitResult;
16
14
  /**
@@ -1,10 +1,8 @@
1
1
  /**
2
2
  * Workspace redirect: when `init` is run at a monorepo ROOT, wire the app rather than the root.
3
3
  *
4
- * Split out of `run.ts`, which sits one line under the 1000-line backstop — every init change that
5
- * touches the orchestration now trips the cap before it can be reviewed. This is the part of that
6
- * file that answers a single, separable question ("is the app somewhere else, and where?"), so it
7
- * is the seam that costs nothing to move.
4
+ * Its own module because it answers a single, separable question: is the app somewhere else, and
5
+ * where?
8
6
  *
9
7
  * `runInit` arrives as a parameter rather than an import: re-entering init is the whole point of a
10
8
  * redirect, and taking it as an argument keeps this module a leaf instead of forming a cycle with
@@ -13,7 +11,7 @@
13
11
  import { join } from 'node:path';
14
12
  import { detect, Framework } from './detect.js';
15
13
  import { findWorkspaceApps, PACKAGE_JSON } from './workspace-apps.js';
16
- import { chooseWorkspaceApp, withoutTrailingSlashes } from './app-choice.js';
14
+ import { chooseWorkspaceApp, withoutTrailingSlashes } from './declared/app-choice.js';
17
15
  const AMBIGUOUS_HEADER = 'Several apps found in this workspace. Re-run `reticle init` inside the one you want:';
18
16
  /**
19
17
  * When the current directory is a workspace root with no app of its own, wire the app instead of the
@@ -33,13 +31,11 @@ export function redirectToWorkspaceApp(options, io, pkg, runInit) {
33
31
  // `--app` is an INSTRUCTION, and it is read before the guess below. The guess answers "where is
34
32
  // the app?" for somebody who did not say; when somebody said, there is nothing left to infer.
35
33
  //
36
- // It used to be read after, and the check underneath returns early for any directory that looks
37
- // like an app — which a JS monorepo ROOT does, because shared tooling puts `vite` in its
38
- // devDependencies. So on a real pnpm+turbo monorepo (measured on nuclear, a Tauri v2 app at
39
- // product scale) `reticle init --app packages/player` silently ignored the flag, installed the
40
- // SDK into the root's package.json, wrote `.reticle.json` and a whole `src/reticle-dev.ts` into a
41
- // repository root that has no `src/`, left `packages/player` untouched — and reported three ✓ and
42
- // one ⚠. The one flag documented for this shape wired the wrong directory and said it worked.
34
+ // Read BEFORE the check underneath, which returns early for any directory that looks like an app —
35
+ // which a JS monorepo ROOT does, because shared tooling puts `vite` in its devDependencies. Read
36
+ // after, `reticle init --app packages/player` silently ignored the flag on a pnpm+turbo monorepo:
37
+ // it installed the SDK into the root's package.json, wrote `.reticle.json` and `src/reticle-dev.ts`
38
+ // into a repository root that has no `src/`, left the named app untouched, and reported success.
43
39
  //
44
40
  // Existence is the test, not membership of the discovered list: discovery scans conventional
45
41
  // directories, and somebody who names a path knows their own layout better than the scan does.
@@ -1,12 +1,11 @@
1
1
  /**
2
2
  * The last thing `init` prints, and the only part of it a user is asked to act on.
3
3
  *
4
- * Split out of `run.ts` because that file sits exactly at the 1000-line cohesion cap, and because
5
- * this IS a separate concern: everything else in `run.ts` decides what to write, and this decides
6
- * what to say afterwards. The two failure modes are different too — a wrong write breaks an
4
+ * Its own file because it is its own concern: everything else in `run.ts` decides what to write,
5
+ * and this decides what to say afterwards. The failure modes differ too — a wrong write breaks an
7
6
  * install, a wrong sentence loses a user quietly.
8
7
  */
9
- import { StepStatus } from './plan.js';
8
+ import { StepStatus } from '../plan/plan.js';
10
9
  /**
11
10
  * The standing ask, printed at the end of every `init`.
12
11
  *
@@ -1,14 +1,13 @@
1
1
  /**
2
2
  * The last thing `init` prints, and the only part of it a user is asked to act on.
3
3
  *
4
- * Split out of `run.ts` because that file sits exactly at the 1000-line cohesion cap, and because
5
- * this IS a separate concern: everything else in `run.ts` decides what to write, and this decides
6
- * what to say afterwards. The two failure modes are different too — a wrong write breaks an
4
+ * Its own file because it is its own concern: everything else in `run.ts` decides what to write,
5
+ * and this decides what to say afterwards. The failure modes differ too — a wrong write breaks an
7
6
  * install, a wrong sentence loses a user quietly.
8
7
  */
9
- import { StepStatus } from './plan.js';
10
- import { wasMcpRegistered } from './mcp-registered.js';
11
- import { CLI } from './agent-rules.js';
8
+ import { StepStatus } from '../plan/plan.js';
9
+ import { wasMcpRegistered } from '../register/mcp-registered.js';
10
+ import { CLI } from '../project/agent-rules.js';
12
11
  /**
13
12
  * The standing ask, printed at the end of every `init`.
14
13
  *
@@ -59,14 +58,13 @@ devCommand) {
59
58
  // NAME THE COMMAND THAT PROVES IT, not one that merely asks.
60
59
  //
61
60
  // `init` writes files and stops; the install is not finished until an app carrying the SDK has
62
- // actually dialled the daemon, and nothing here confirmed that. The field shape is unambiguous:
63
- // people complete the agent half, never complete the app half, and keep a daemon running for
64
- // weeks with nothing to drive — so this is the last instruction most of them read.
61
+ // actually dialled the daemon, and nothing here confirmed that — so this is the last instruction
62
+ // a user reads before the half that can still fail.
65
63
  //
66
64
  // "Ask your agent: List Reticle sessions" asks a question whose failure is a dead end. `reticle
67
- // status` ANSWERS it: as of 2.7.0 it reports the session, or says why there is none — no app
68
- // running, an app running that never dialled us, a tab that closed — with the fix for each.
69
- // The one thing that works BEFORE the restart, so it goes before it.
65
+ // status` ANSWERS it: it reports the session, or says why there is none — no app running, an app
66
+ // running that never dialled us, a tab that closed — with the fix for each. It works BEFORE the
67
+ // restart, so it goes before it.
70
68
  //
71
69
  // What comes after the restart, named as a JOB rather than as a capability.
72
70
  //
@@ -89,13 +87,10 @@ devCommand) {
89
87
  return `${dev}\n${demo}\n${prove}`;
90
88
  // ALREADY: the tools are reachable RIGHT NOW, and this is the branch that used to lie.
91
89
  //
92
- // `wasMcpRegistered` is true for both APPLY and ALREADY, which is correct for the funnel field it
93
- // was written for and wrong here, because the two cases have opposite answers to "must anybody
94
- // restart". Every second project on a machine, and every Claude Code plugin install, took the
95
- // APPLY text: "restart your agent — the tools only appear after that". An agent reads this AFTER
96
- // whatever its skill file said, and it describes the output in front of it, so it wins. The agent
97
- // stops with the project wired and nothing driven, which is the exact failure the install path
98
- // exists to prevent, caused by the install path itself.
90
+ // `wasMcpRegistered` is true for both APPLY and ALREADY, which is wrong here: the two cases have
91
+ // opposite answers to "must anybody restart". ALREADY used to take the APPLY text — "restart your
92
+ // agent, the tools only appear after that" — and an agent believes the output in front of it over
93
+ // whatever its skill file said, so it stops with the project wired and nothing driven.
99
94
  if (mcpStatus === StepStatus.ALREADY) {
100
95
  return (`${dev}\n` +
101
96
  `${demo}\n` +
@@ -0,0 +1,32 @@
1
+ /**
2
+ * The install shape Reticle assumes, stated once, for the projects that do not have it.
3
+ *
4
+ * Everything in `init` assumes the CLI and the dev server share a filesystem: the daemon writes
5
+ * `~/.reticle/pairing-token`, the build plugin reads it at config time and inlines it into the page,
6
+ * and the bridge checks that they match. When the dev server runs in a container that assumption is
7
+ * silently false — `$HOME` in there is the image's throwaway root, so the plugin finds no token,
8
+ * mints its own, and every page it serves is refused with `authentication failed`.
9
+ *
10
+ * Measured in the field: six minutes to diagnose, ending in a grep through the plugin's shipped
11
+ * `dist/` inside the container for an environment variable that appeared in no documentation. The
12
+ * daemon now says all this when it happens (see no-session-next-action). This says it BEFORE it
13
+ * happens, which is the difference between a note and a debugging session.
14
+ *
15
+ * It also names the rebuild. A containerised dev server usually installs its own `node_modules` into
16
+ * an anonymous volume, so `init` adding two dependencies is not picked up by restarting the
17
+ * container — it needs an image rebuild and a fresh volume. The same field install lost two and a
18
+ * half minutes discovering that through a failed build.
19
+ *
20
+ * DETECTION IS DELIBERATELY CHEAP AND OVER-EAGER. A Dockerfile or a compose file near the app is not
21
+ * proof the dev server runs in one — plenty of repos containerise only production. But this step
22
+ * asks for nothing and changes nothing, so a false positive costs one paragraph the reader skips,
23
+ * while a false negative costs what it cost in the field.
24
+ */
25
+ /** Files whose presence means this repo builds a container image somewhere near the app. */
26
+ export declare const CONTAINER_MARKERS: readonly ["Dockerfile", "dockerfile", "docker-compose.yml", "docker-compose.yaml", "compose.yml", "compose.yaml", ".devcontainer/devcontainer.json"];
27
+ export declare const CONTAINERISED_TITLE = "Containerised dev server";
28
+ /**
29
+ * The note itself. Both halves are things that go wrong at the same moment and look identical from
30
+ * the page — the SDK loads, the socket opens, no session appears — so they are told together.
31
+ */
32
+ export declare function containerisedDevServerNote(marker: string): string;
@@ -0,0 +1,56 @@
1
+ /**
2
+ * The install shape Reticle assumes, stated once, for the projects that do not have it.
3
+ *
4
+ * Everything in `init` assumes the CLI and the dev server share a filesystem: the daemon writes
5
+ * `~/.reticle/pairing-token`, the build plugin reads it at config time and inlines it into the page,
6
+ * and the bridge checks that they match. When the dev server runs in a container that assumption is
7
+ * silently false — `$HOME` in there is the image's throwaway root, so the plugin finds no token,
8
+ * mints its own, and every page it serves is refused with `authentication failed`.
9
+ *
10
+ * Measured in the field: six minutes to diagnose, ending in a grep through the plugin's shipped
11
+ * `dist/` inside the container for an environment variable that appeared in no documentation. The
12
+ * daemon now says all this when it happens (see no-session-next-action). This says it BEFORE it
13
+ * happens, which is the difference between a note and a debugging session.
14
+ *
15
+ * It also names the rebuild. A containerised dev server usually installs its own `node_modules` into
16
+ * an anonymous volume, so `init` adding two dependencies is not picked up by restarting the
17
+ * container — it needs an image rebuild and a fresh volume. The same field install lost two and a
18
+ * half minutes discovering that through a failed build.
19
+ *
20
+ * DETECTION IS DELIBERATELY CHEAP AND OVER-EAGER. A Dockerfile or a compose file near the app is not
21
+ * proof the dev server runs in one — plenty of repos containerise only production. But this step
22
+ * asks for nothing and changes nothing, so a false positive costs one paragraph the reader skips,
23
+ * while a false negative costs what it cost in the field.
24
+ */
25
+ import { ReticleEnv } from '@reticlehq/core';
26
+ /** Files whose presence means this repo builds a container image somewhere near the app. */
27
+ export const CONTAINER_MARKERS = [
28
+ 'Dockerfile',
29
+ 'dockerfile',
30
+ 'docker-compose.yml',
31
+ 'docker-compose.yaml',
32
+ 'compose.yml',
33
+ 'compose.yaml',
34
+ '.devcontainer/devcontainer.json',
35
+ ];
36
+ export const CONTAINERISED_TITLE = 'Containerised dev server';
37
+ /**
38
+ * The note itself. Both halves are things that go wrong at the same moment and look identical from
39
+ * the page — the SDK loads, the socket opens, no session appears — so they are told together.
40
+ */
41
+ export function containerisedDevServerNote(marker) {
42
+ return (`this project has a \`${marker}\`, so IF your dev server runs in a container two things about ` +
43
+ 'this install are different, and both of them end in "no session" with nothing looking broken.\n' +
44
+ ' 1. DEPENDENCIES. A container that installs its own node_modules will not see the two packages ' +
45
+ 'added here by restarting — rebuild the image and renew the volume ' +
46
+ '(`docker compose build <service> && docker compose up -d --renew-anon-volumes <service>`).\n' +
47
+ ' 2. THE PAIRING TOKEN. The daemon writes `~/.reticle/pairing-token` on the HOST; the build ' +
48
+ 'plugin reads it from `$HOME` INSIDE the container, does not find it, and mints a different one — ' +
49
+ 'which the bridge then refuses. Mount the host file read-only and point the plugin at it:\n' +
50
+ ' environment:\n' +
51
+ ` ${ReticleEnv.PAIRING_TOKEN_DIR}: /reticle-state\n` +
52
+ ' volumes:\n' +
53
+ ' - ~/.reticle/pairing-token:/reticle-state/pairing-token:ro\n' +
54
+ ' The token is inlined when the dev server resolves its config, so restart it afterwards. ' +
55
+ 'Run the Reticle daemon at least once first, or Docker creates a DIRECTORY at that host path.');
56
+ }
@@ -5,8 +5,8 @@
5
5
  * the dial URL was correct, and the app never connected because a strict `connect-src` excluded
6
6
  * `ws://localhost:<port>`. The browser blocks the WebSocket and says so in its own console, which
7
7
  * nothing on the Reticle side reads — so `status` and `doctor` reported perfect health at an app
8
- * that could not connect at all, forever. A setup step that exits 0 while leaving the user broken is
9
- * the worst failure mode this repo has.
8
+ * that could not connect at all. A setup step that exits 0 while leaving the user broken is the
9
+ * failure mode this file exists to prevent.
10
10
  *
11
11
  * A TEXT SCAN, deliberately. `headers()` is a function; `init` cannot execute a user's Next config
12
12
  * without importing their whole app, and the thing worth finding is the policy string a developer
@@ -5,8 +5,8 @@
5
5
  * the dial URL was correct, and the app never connected because a strict `connect-src` excluded
6
6
  * `ws://localhost:<port>`. The browser blocks the WebSocket and says so in its own console, which
7
7
  * nothing on the Reticle side reads — so `status` and `doctor` reported perfect health at an app
8
- * that could not connect at all, forever. A setup step that exits 0 while leaving the user broken is
9
- * the worst failure mode this repo has.
8
+ * that could not connect at all. A setup step that exits 0 while leaving the user broken is the
9
+ * failure mode this file exists to prevent.
10
10
  *
11
11
  * A TEXT SCAN, deliberately. `headers()` is a function; `init` cannot execute a user's Next config
12
12
  * without importing their whole app, and the thing worth finding is the policy string a developer
@@ -1,4 +1,4 @@
1
- import { RETICLE_IPC_GLOBAL } from '@reticlehq/core';
1
+ import { RETICLE_IPC_GLOBAL, realmOf, realmOfProject } from '@reticlehq/core';
2
2
  /**
3
3
  * Setup RCA for desktop apps.
4
4
  *
@@ -177,13 +177,19 @@ function diagnoseElectron(read, main) {
177
177
  }
178
178
  /** Is this project a desktop shell at all? Used to print a positive result instead of silence. */
179
179
  export function isDesktopProject(read) {
180
- if (read(TAURI_CONF) !== undefined)
181
- return true;
180
+ // Asked of the realm table rather than answered here. Which file or dependency marks a realm is a
181
+ // fact about that realm, and the copy of it that lived in this function was the second one -- so a
182
+ // realm added to the table but not to this line would have been reported as an ordinary web app,
183
+ // which is the answer least likely to look wrong.
184
+ const found = realmOfProject((path) => read(path) !== undefined, (name) => dependenciesOf(read)[name] !== undefined);
185
+ return found !== undefined && realmOf(found).isDesktopShell;
186
+ }
187
+ /** Every dependency a project declares, both kinds, in one record. */
188
+ function dependenciesOf(read) {
182
189
  const pkg = parseJson(read('package.json'));
183
190
  if (pkg === undefined)
184
- return false;
185
- const deps = { ...record(pkg['dependencies']), ...record(pkg['devDependencies']) };
186
- return deps['electron'] !== undefined;
191
+ return {};
192
+ return { ...record(pkg['dependencies']), ...record(pkg['devDependencies']) };
187
193
  }
188
194
  /** Which desktop shell (if any) this project is, and what is missing from its Reticle wiring. */
189
195
  export function diagnoseDesktop(read, port) {
@@ -2,8 +2,7 @@
2
2
  * Classified `reticle init` failures — our own vocabulary, never a raw error or a path.
3
3
  *
4
4
  * Lives here rather than beside the telemetry client because `run.ts` is what classifies a failed
5
- * run, and `@reticlehq/init` must not depend on the daemon. `@reticlehq/server`'s
6
- * `telemetry/init-telemetry.ts` re-exports it, so every existing consumer is unchanged.
5
+ * run, and `@reticlehq/init` must not depend on the daemon.
7
6
  */
8
7
  export declare const InitFailure: {
9
8
  /** Run outside a project root. The single most common first-run mistake. */
@@ -2,8 +2,7 @@
2
2
  * Classified `reticle init` failures — our own vocabulary, never a raw error or a path.
3
3
  *
4
4
  * Lives here rather than beside the telemetry client because `run.ts` is what classifies a failed
5
- * run, and `@reticlehq/init` must not depend on the daemon. `@reticlehq/server`'s
6
- * `telemetry/init-telemetry.ts` re-exports it, so every existing consumer is unchanged.
5
+ * run, and `@reticlehq/init` must not depend on the daemon.
7
6
  */
8
7
  export const InitFailure = {
9
8
  /** Run outside a project root. The single most common first-run mistake. */
@@ -0,0 +1,8 @@
1
+ /**
2
+ * What to say when the dependency install fails.
3
+ *
4
+ * A cohesive unit on its own: prose about one step's failure modes, and the only thing it needs is
5
+ * which package manager was used.
6
+ */
7
+ import { PackageManager } from '../detect/detect.js';
8
+ export declare function installFailureHint(pm: PackageManager): string;
@@ -1,11 +1,10 @@
1
1
  /**
2
2
  * What to say when the dependency install fails.
3
3
  *
4
- * Split out of plan.ts, which was at the 1000-line backstop. A cohesive unit rather than an
5
- * arbitrary cut: it is prose about one step's failure modes, and the only thing it needs is which
6
- * package manager was used.
4
+ * A cohesive unit on its own: prose about one step's failure modes, and the only thing it needs is
5
+ * which package manager was used.
7
6
  */
8
- import { PackageManager } from './detect.js';
7
+ import { PackageManager } from '../detect/detect.js';
9
8
  /**
10
9
  * Every package manager fetches, so every one of them can fail for a reason that is nothing to do
11
10
  * with the project: offline, a proxy that blocks npmjs, a corporate mirror that is down.
@@ -2,11 +2,10 @@
2
2
  * The weaker install attempts, in the order they are tried.
3
3
  *
4
4
  * Ordered by how much each one gives up, cheapest concession first, because the first that succeeds
5
- * is the one that stands. Split out of plan.ts: the ladder is its own subject — two unrelated
6
- * install failures, each with a different cost and a different sentence to say afterwards — and
7
- * plan.ts is at its file cap.
5
+ * is the one that stands. The ladder is its own subject: two unrelated install failures, each with
6
+ * a different cost and a different sentence to say afterwards.
8
7
  */
9
- import { PackageManager } from './detect.js';
8
+ import { PackageManager } from '../detect/detect.js';
10
9
  export interface InstallRetry {
11
10
  command: string;
12
11
  args: string[];
@@ -2,11 +2,10 @@
2
2
  * The weaker install attempts, in the order they are tried.
3
3
  *
4
4
  * Ordered by how much each one gives up, cheapest concession first, because the first that succeeds
5
- * is the one that stands. Split out of plan.ts: the ladder is its own subject — two unrelated
6
- * install failures, each with a different cost and a different sentence to say afterwards — and
7
- * plan.ts is at its file cap.
5
+ * is the one that stands. The ladder is its own subject: two unrelated install failures, each with
6
+ * a different cost and a different sentence to say afterwards.
8
7
  */
9
- import { PackageManager, installCommandParts } from './detect.js';
8
+ import { PackageManager, installCommandParts } from '../detect/detect.js';
10
9
  const LEGACY_PEER_DEPS = '--legacy-peer-deps';
11
10
  /**
12
11
  * What the peer-conflict retry gives up, said out loud.
@@ -23,10 +22,9 @@ const LEGACY_PEER_NOTE = `the pinned install failed and succeeded with ${LEGACY_
23
22
  /**
24
23
  * Said out loud when the exact-version install failed and the unpinned one worked.
25
24
  *
26
- * It used to assert a cause it cannot know. Every one of nine fixture apps got the same sentence —
27
- * "the registry refused 2.5.0 (pnpm's minimumReleaseAge holds new releases back)" — when the actual
28
- * cause on that run was that the version did not exist yet, and the remedy offered was a `pnpm
29
- * config` command handed to a yarn 1 project that will never read it.
25
+ * It used to assert a cause it cannot know: every fixture app got the same sentence, blaming pnpm's
26
+ * `minimumReleaseAge`, when the actual cause was that the version did not exist yet — and the remedy
27
+ * offered was a `pnpm config` command handed to a yarn 1 project that will never read it.
30
28
  *
31
29
  * This note is built at PLAN time, before anything runs, and `io.exec` returns a bare boolean, so
32
30
  * the apply layer has no failure text to hand back either. The honest move is therefore to report
package/dist/host.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { InitOutcome } from '@reticlehq/core/telemetry';
1
+ import type { InitOutcome, OnboardingStep } from '@reticlehq/core/telemetry';
2
2
  /**
3
3
  * Everything the scaffolder needs that only the daemon knows.
4
4
  *
@@ -20,6 +20,19 @@ export interface InitHost {
20
20
  span<T>(name: string, fields: Record<string, unknown>, fn: () => T): T;
21
21
  /** Report how the run went. Best-effort — setup must never fail because a metric did. */
22
22
  reportOutcome(outcome: InitOutcome): void;
23
+ /**
24
+ * Report ONE step of the setup funnel, as it happens.
25
+ *
26
+ * `reportOutcome` fires once, at the end, and says whether the whole run succeeded. That cannot
27
+ * answer the question the funnel exists for — WHERE people stop — because a run that dies at the
28
+ * MCP registration and one that dies before it ever looked for a package.json are the same single
29
+ * row. A step reported as it happens is the difference between "init failed" and "init failed at
30
+ * this step, on this stack, after this long".
31
+ *
32
+ * REQUIRED for the same reason `reportOutcome` is: an optional reporter is one somebody forgets to
33
+ * pass and nobody notices is missing, because telemetry fails silently.
34
+ */
35
+ reportStep(step: OnboardingStep): void;
23
36
  /**
24
37
  * The daemon's pairing token, minted if nothing has written one yet.
25
38
  *
package/dist/host.js CHANGED
@@ -12,6 +12,9 @@ export const SILENT_HOST = {
12
12
  reportOutcome() {
13
13
  /* nothing to report to */
14
14
  },
15
+ reportStep() {
16
+ /* nothing to report to */
17
+ },
15
18
  pairingToken() {
16
19
  return '';
17
20
  },
package/dist/index.d.ts CHANGED
@@ -9,17 +9,37 @@
9
9
  * ends up unable to move a file.
10
10
  */
11
11
  export { runInit, resolveLockfiles, type InitIo, type InitOptions, type InitResult, } from './run.js';
12
- export { buildNodeIo } from './node-io.js';
12
+ /**
13
+ * Exported for the ONE other place in this repo that spawns a package manager: `reticle update`.
14
+ *
15
+ * It is a pure string rule with its own tests, and it was private while a second caller needed it
16
+ * -- which is exactly the shape that let `reticle setup mcp` reimplement the spawn as a bare
17
+ * `execFileSync` and break on Windows. Two copies of a quoting rule is the version of this bug that
18
+ * is hardest to see, so there is one.
19
+ */
20
+ export { windowsShellArg } from './register/windows-quote.js';
21
+ export { buildNodeIo, probeCli } from './node-io.js';
13
22
  export { type InitHost, SILENT_HOST } from './host.js';
14
23
  export { RETICLE_VERSION, RETICLE_NPM_PACKAGE } from './version.js';
15
- export { InitFailure } from './init-failure.js';
16
- export { NodePlatform } from './platform.js';
17
- export { DEV_SERVER_PORTS, isLikelyDevServerPort, devServerPortWarning, } from './dev-server-ports.js';
18
- export { configWithInstallSource } from './install-source-config.js';
19
- export { projectIdOf, rememberProjectOnDisk, type RegistryIo } from './remember-project.js';
20
- export { detectPackageManager, installCommandParts, parseMajor, PackageManager } from './detect.js';
21
- export { findWorkspaceApps } from './workspace-apps.js';
22
- export { refreshAgentRules } from './refresh-rules.js';
23
- export { diagnoseDesktop, isDesktopProject } from './desktop-doctor.js';
24
- export { diagnoseWebCsp } from './csp-doctor.js';
25
- export { reticleConfigContent } from './snippets.js';
24
+ export { InitFailure } from './diagnose/init-failure.js';
25
+ export { NodePlatform } from './detect/platform.js';
26
+ export { DEV_SERVER_PORTS, isLikelyDevServerPort, devServerPortWarning, } from './detect/dev-server-ports.js';
27
+ export { configWithInstallSource } from './project/install-source-config.js';
28
+ export { projectIdOf, rememberProjectOnDisk, type RegistryIo } from './project/remember-project.js';
29
+ export { detectPackageManager, installCommandParts, parseMajor, PackageManager, } from './detect/detect.js';
30
+ export { findWorkspaceApps } from './detect/workspace-apps.js';
31
+ export { refreshAgentRules } from './project/refresh-rules.js';
32
+ export { diagnoseDesktop, isDesktopProject } from './diagnose/desktop-doctor.js';
33
+ export { diagnoseWebCsp } from './diagnose/csp-doctor.js';
34
+ export { reticleConfigContent } from './patch/snippets.js';
35
+ /**
36
+ * MCP registration, for a caller that has no project.
37
+ *
38
+ * `reticle setup mcp` registers the server for the USER across every agent on the machine, which is
39
+ * what the one-line installer runs before any project exists. It needs the same client table and
40
+ * the same detection `init` uses — a second copy would be a second answer to "which agents are
41
+ * here", and the two would disagree the first time a client was added to one of them.
42
+ */
43
+ export { McpClient, ConfigScope, MCP_CLIENTS, ClientMergeStatus, clientSpec, mergeClientConfig, } from './register/mcp-clients.js';
44
+ export { claudeAddCommand, claudeAvailableProbe, claudeExistsProbe, } from './register/mcp.js';
45
+ export { detectMcpClients, type DetectedClient } from './register/detect-clients.js';