@reticlehq/server 2.12.0 → 2.13.1

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 (386) hide show
  1. package/SKILL.md +67 -109
  2. package/dist/bridge/auth-failure-reason.d.ts +6 -2
  3. package/dist/bridge/auth-failure-reason.js +15 -8
  4. package/dist/bridge/bridge.d.ts +18 -1
  5. package/dist/bridge/bridge.js +247 -164
  6. package/dist/bridge/pairing-token.d.ts +5 -0
  7. package/dist/bridge/pairing-token.js +28 -0
  8. package/dist/bridge/protocol-skew.d.ts +37 -0
  9. package/dist/bridge/protocol-skew.js +45 -0
  10. package/dist/capsule/capsule-store.d.ts +6 -6
  11. package/dist/capsule/causal-summary.js +7 -2
  12. package/dist/cli/cli-doctor.js +31 -1
  13. package/dist/cli/cli-launch.d.ts +11 -1
  14. package/dist/cli/cli-launch.js +59 -10
  15. package/dist/cli/cli-parse.d.ts +15 -1
  16. package/dist/cli/cli-parse.js +212 -8
  17. package/dist/cli/cli-verify.d.ts +1 -0
  18. package/dist/cli/cli-verify.js +3 -2
  19. package/dist/cli/cloud-cli.js +428 -259
  20. package/dist/cli/cloud-keystore.d.ts +37 -0
  21. package/dist/cli/cloud-keystore.js +97 -0
  22. package/dist/cli/cloud-kit.d.ts +37 -0
  23. package/dist/cli/cloud-kit.js +121 -0
  24. package/dist/cli/cloud-login.d.ts +17 -0
  25. package/dist/cli/cloud-login.js +183 -0
  26. package/dist/cli/cloud-session.d.ts +48 -0
  27. package/dist/cli/cloud-session.js +53 -0
  28. package/dist/cli/config-discovery.js +1 -0
  29. package/dist/cli/daemon-start-options.d.ts +32 -0
  30. package/dist/cli/daemon-start-options.js +38 -0
  31. package/dist/cli/device-flow.d.ts +76 -0
  32. package/dist/cli/device-flow.js +56 -0
  33. package/dist/cli/doctor-rows.d.ts +9 -0
  34. package/dist/cli/doctor-rows.js +9 -0
  35. package/dist/cli/drive-attach.d.ts +13 -0
  36. package/dist/cli/drive-attach.js +18 -0
  37. package/dist/cli/drive-command.js +7 -3
  38. package/dist/cli/mcp-command.js +51 -20
  39. package/dist/cli/project-name.d.ts +30 -0
  40. package/dist/cli/project-name.js +44 -0
  41. package/dist/cli/status-next-action.d.ts +14 -0
  42. package/dist/cli/status-next-action.js +4 -4
  43. package/dist/cli.d.ts +1 -0
  44. package/dist/cli.js +134 -80
  45. package/dist/cloud/cloud-config.d.ts +22 -1
  46. package/dist/cloud/cloud-config.js +64 -5
  47. package/dist/cloud/cloud-sync.d.ts +5 -5
  48. package/dist/cloud/cloud-sync.js +33 -2
  49. package/dist/cloud/sync-cycle.d.ts +110 -0
  50. package/dist/cloud/sync-cycle.js +270 -0
  51. package/dist/cloud/sync-daemon.d.ts +69 -0
  52. package/dist/cloud/sync-daemon.js +260 -0
  53. package/dist/cloud/sync-disk.d.ts +9 -0
  54. package/dist/cloud/sync-disk.js +191 -0
  55. package/dist/cloud/sync-hash.d.ts +2 -0
  56. package/dist/cloud/sync-hash.js +35 -0
  57. package/dist/crawl/crawl-tools.js +5 -3
  58. package/dist/crawl/crawl.d.ts +3 -0
  59. package/dist/crawl/crawl.js +5 -0
  60. package/dist/daemon/daemon-resolve.d.ts +43 -0
  61. package/dist/daemon/daemon-resolve.js +69 -0
  62. package/dist/daemon/dev-servers.d.ts +15 -0
  63. package/dist/daemon/dev-servers.js +68 -0
  64. package/dist/daemon/wake-decision.d.ts +32 -0
  65. package/dist/daemon/wake-decision.js +38 -0
  66. package/dist/domain/domain-tools.js +2 -2
  67. package/dist/events/contradictions.d.ts +18 -0
  68. package/dist/events/contradictions.js +134 -21
  69. package/dist/events/declared.d.ts +7 -4
  70. package/dist/events/declared.js +15 -6
  71. package/dist/events/echo-mismatch.js +64 -14
  72. package/dist/events/event-filters.d.ts +8 -0
  73. package/dist/events/event-filters.js +19 -2
  74. package/dist/events/predicate-eval.js +53 -5
  75. package/dist/events/predicate-precheck.d.ts +8 -0
  76. package/dist/events/predicate-precheck.js +41 -0
  77. package/dist/events/predicate-route.d.ts +14 -0
  78. package/dist/events/predicate-route.js +36 -8
  79. package/dist/events/predicate.d.ts +5 -0
  80. package/dist/events/predicate.js +37 -5
  81. package/dist/events/split-text-miss.d.ts +30 -0
  82. package/dist/events/split-text-miss.js +44 -0
  83. package/dist/flows/flake.d.ts +6 -6
  84. package/dist/flows/flow-memory-consult.d.ts +55 -0
  85. package/dist/flows/flow-memory-consult.js +38 -0
  86. package/dist/flows/flow-replay-run.d.ts +18 -7
  87. package/dist/flows/flow-replay-run.js +239 -22
  88. package/dist/flows/flow-replay.js +11 -2
  89. package/dist/flows/flow-step-runners.d.ts +8 -0
  90. package/dist/flows/flow-step-runners.js +8 -2
  91. package/dist/flows/flow-store-for-session.d.ts +31 -0
  92. package/dist/flows/flow-store-for-session.js +33 -0
  93. package/dist/flows/flow-tools.js +58 -32
  94. package/dist/flows/flows.d.ts +8 -0
  95. package/dist/flows/flows.js +468 -2
  96. package/dist/flows/heal-run.js +5 -2
  97. package/dist/flows/predicate-to-expect.d.ts +17 -12
  98. package/dist/flows/predicate-to-expect.js +30 -12
  99. package/dist/flows/recordings.d.ts +13 -1
  100. package/dist/flows/recordings.js +17 -2
  101. package/dist/flows/replay.d.ts +22 -2
  102. package/dist/flows/replay.js +28 -2
  103. package/dist/flows/verify-change-tools.js +5 -2
  104. package/dist/honesty/blind-spots.d.ts +55 -2
  105. package/dist/honesty/blind-spots.js +63 -3
  106. package/dist/honesty/honesty.d.ts +24 -0
  107. package/dist/honesty/honesty.js +25 -1
  108. package/dist/honesty/instrumentation-gaps.d.ts +9 -0
  109. package/dist/honesty/instrumentation-gaps.js +29 -2
  110. package/dist/honesty/verified.d.ts +2 -1
  111. package/dist/honesty/verified.js +16 -1
  112. package/dist/http-server.js +12 -0
  113. package/dist/impact/impact-recorder.d.ts +31 -8
  114. package/dist/impact/impact-recorder.js +104 -21
  115. package/dist/impact/impact-store.d.ts +15 -1
  116. package/dist/impact/impact-store.js +43 -2
  117. package/dist/index.js +113 -7
  118. package/dist/init/agent-rules.d.ts +13 -6
  119. package/dist/init/agent-rules.js +23 -12
  120. package/dist/init/astro-patch.d.ts +16 -0
  121. package/dist/init/astro-patch.js +28 -0
  122. package/dist/init/capabilities.d.ts +4 -1
  123. package/dist/init/capabilities.js +22 -2
  124. package/dist/init/confirm.d.ts +31 -7
  125. package/dist/init/confirm.js +48 -8
  126. package/dist/init/cra.d.ts +20 -4
  127. package/dist/init/cra.js +57 -17
  128. package/dist/init/csp-check.js +17 -2
  129. package/dist/init/csp-doctor.d.ts +8 -1
  130. package/dist/init/csp-doctor.js +8 -3
  131. package/dist/init/dev-script.d.ts +3 -1
  132. package/dist/init/dev-script.js +8 -5
  133. package/dist/init/format-generated.d.ts +18 -0
  134. package/dist/init/format-generated.js +39 -0
  135. package/dist/init/install-hint.d.ts +9 -0
  136. package/dist/init/install-hint.js +29 -0
  137. package/dist/init/mcp-clients.d.ts +7 -0
  138. package/dist/init/mcp-clients.js +7 -0
  139. package/dist/init/mcp.d.ts +1 -1
  140. package/dist/init/mcp.js +1 -1
  141. package/dist/init/node-io.js +11 -1
  142. package/dist/init/plan-framework.js +81 -45
  143. package/dist/init/plan.d.ts +20 -7
  144. package/dist/init/plan.js +20 -45
  145. package/dist/init/preflight.d.ts +28 -0
  146. package/dist/init/preflight.js +38 -0
  147. package/dist/init/run.d.ts +32 -9
  148. package/dist/init/run.js +141 -119
  149. package/dist/init/slash-command.d.ts +1 -1
  150. package/dist/init/snippets.js +41 -25
  151. package/dist/init/vite-config.d.ts +1 -1
  152. package/dist/init/vite-config.js +39 -25
  153. package/dist/init/workspace-apps.d.ts +17 -10
  154. package/dist/init/workspace-apps.js +110 -0
  155. package/dist/input/network-mock-tools.js +3 -6
  156. package/dist/input/real-input.js +25 -5
  157. package/dist/input/scroll-tools.js +4 -6
  158. package/dist/input/viewport-tools.js +5 -4
  159. package/dist/intent/inline-intent.d.ts +10 -19
  160. package/dist/intent/inline-intent.js +91 -1
  161. package/dist/intent/intent-shard-store.d.ts +72 -0
  162. package/dist/intent/intent-shard-store.js +203 -0
  163. package/dist/intent/intent-shard.d.ts +411 -0
  164. package/dist/intent/intent-shard.js +176 -0
  165. package/dist/intent/intent-store.d.ts +12 -0
  166. package/dist/intent/intent-store.js +21 -0
  167. package/dist/intent/intent-subject.d.ts +45 -0
  168. package/dist/intent/intent-subject.js +177 -0
  169. package/dist/intent/intent-tools.js +65 -2
  170. package/dist/intent/open-intents.d.ts +21 -0
  171. package/dist/intent/open-intents.js +15 -0
  172. package/dist/license/license-env.d.ts +29 -0
  173. package/dist/license/license-env.js +148 -0
  174. package/dist/loopback-agent.d.ts +31 -0
  175. package/dist/loopback-agent.js +57 -0
  176. package/dist/mcp/attach-memory.d.ts +18 -2
  177. package/dist/mcp/attach-memory.js +50 -7
  178. package/dist/mcp/client-identity.d.ts +31 -0
  179. package/dist/mcp/client-identity.js +51 -0
  180. package/dist/mcp/mcp-post-transport.d.ts +26 -0
  181. package/dist/mcp/mcp-post-transport.js +109 -0
  182. package/dist/mcp/mcp-proxy.d.ts +2 -1
  183. package/dist/mcp/mcp-proxy.js +52 -62
  184. package/dist/mcp/mcp.js +7 -1
  185. package/dist/mcp/proxy-backoff.d.ts +11 -0
  186. package/dist/mcp/proxy-backoff.js +13 -0
  187. package/dist/mcp/proxy-daemon-probe.d.ts +1 -0
  188. package/dist/mcp/proxy-daemon-probe.js +7 -1
  189. package/dist/mcp/proxy-handshake.d.ts +23 -0
  190. package/dist/mcp/proxy-handshake.js +31 -0
  191. package/dist/mcp/tool-catalog-cache.d.ts +8 -0
  192. package/dist/mcp/tool-catalog-cache.js +12 -0
  193. package/dist/memory/memory-tools.d.ts +2 -0
  194. package/dist/memory/memory-tools.js +82 -0
  195. package/dist/memory/project-memory.d.ts +34 -0
  196. package/dist/memory/project-memory.js +96 -0
  197. package/dist/oracles/propose-consequences.js +12 -3
  198. package/dist/pool/browser-pool.d.ts +63 -4
  199. package/dist/pool/browser-pool.js +113 -4
  200. package/dist/pool/playwright-launcher.js +9 -0
  201. package/dist/project/project-tools.js +3 -3
  202. package/dist/project/reticle-dir.d.ts +15 -2
  203. package/dist/project/reticle-dir.js +21 -4
  204. package/dist/runs/run-store.d.ts +10 -0
  205. package/dist/runs/run-store.js +10 -0
  206. package/dist/runs/verification-sync.d.ts +0 -11
  207. package/dist/runs/verification-sync.js +22 -1
  208. package/dist/runs/verify-server.d.ts +8 -1
  209. package/dist/runs/verify-server.js +14 -2
  210. package/dist/session/command-timeout.d.ts +5 -2
  211. package/dist/session/command-timeout.js +5 -4
  212. package/dist/session/lease-visibility.d.ts +67 -0
  213. package/dist/session/lease-visibility.js +76 -0
  214. package/dist/session/no-session-diagnosis.d.ts +23 -0
  215. package/dist/session/no-session-diagnosis.js +58 -17
  216. package/dist/session/no-session-next-action.d.ts +7 -0
  217. package/dist/session/no-session-next-action.js +42 -3
  218. package/dist/session/no-session-watch.d.ts +10 -6
  219. package/dist/session/no-session-watch.js +51 -19
  220. package/dist/session/pending-commands.d.ts +11 -1
  221. package/dist/session/pending-commands.js +12 -2
  222. package/dist/session/resolve-within.d.ts +34 -0
  223. package/dist/session/resolve-within.js +53 -0
  224. package/dist/session/session-info.d.ts +18 -0
  225. package/dist/session/session-info.js +11 -0
  226. package/dist/session/session-manager.d.ts +14 -0
  227. package/dist/session/session-manager.js +61 -3
  228. package/dist/session/session-mirror.d.ts +16 -0
  229. package/dist/session/session-mirror.js +22 -0
  230. package/dist/session/session-recommendation.d.ts +19 -5
  231. package/dist/session/session-recommendation.js +25 -9
  232. package/dist/session/session-successor.js +36 -1
  233. package/dist/session/session-tools.js +2 -2
  234. package/dist/session/session.d.ts +22 -2
  235. package/dist/session/session.js +87 -7
  236. package/dist/session/wall-clock.d.ts +12 -0
  237. package/dist/session/wall-clock.js +14 -0
  238. package/dist/setup/agent-configs.d.ts +96 -0
  239. package/dist/setup/agent-configs.js +333 -0
  240. package/dist/setup/agent-io.d.ts +9 -0
  241. package/dist/setup/agent-io.js +16 -0
  242. package/dist/setup/agent-writer.d.ts +51 -0
  243. package/dist/setup/agent-writer.js +115 -0
  244. package/dist/setup/approval-migration.d.ts +47 -0
  245. package/dist/setup/approval-migration.js +68 -0
  246. package/dist/setup/auto-approve.d.ts +85 -0
  247. package/dist/setup/auto-approve.js +150 -0
  248. package/dist/setup/bridge-port.d.ts +15 -0
  249. package/dist/setup/bridge-port.js +24 -0
  250. package/dist/setup/desktop-shape.d.ts +57 -0
  251. package/dist/setup/desktop-shape.js +63 -0
  252. package/dist/setup/dev-server-wait.d.ts +68 -0
  253. package/dist/setup/dev-server-wait.js +67 -0
  254. package/dist/setup/drive-agent.d.ts +71 -0
  255. package/dist/setup/drive-agent.js +171 -0
  256. package/dist/setup/drive-plan.d.ts +68 -0
  257. package/dist/setup/drive-plan.js +130 -0
  258. package/dist/setup/ensure-daemon.d.ts +29 -0
  259. package/dist/setup/ensure-daemon.js +58 -0
  260. package/dist/setup/init-runtime.d.ts +47 -0
  261. package/dist/setup/init-runtime.js +160 -0
  262. package/dist/setup/interrupt.d.ts +25 -0
  263. package/dist/setup/interrupt.js +40 -0
  264. package/dist/setup/license-key.d.ts +39 -0
  265. package/dist/setup/license-key.js +72 -0
  266. package/dist/setup/listeners.d.ts +37 -0
  267. package/dist/setup/listeners.js +85 -0
  268. package/dist/setup/node-effects.d.ts +60 -0
  269. package/dist/setup/node-effects.js +210 -0
  270. package/dist/setup/page-probe.d.ts +39 -0
  271. package/dist/setup/page-probe.js +49 -0
  272. package/dist/setup/relaunch.d.ts +36 -0
  273. package/dist/setup/relaunch.js +57 -0
  274. package/dist/setup/remaining-steps.d.ts +19 -0
  275. package/dist/setup/remaining-steps.js +31 -0
  276. package/dist/setup/run-setup.d.ts +99 -0
  277. package/dist/setup/run-setup.js +203 -0
  278. package/dist/setup/session-pick.d.ts +47 -0
  279. package/dist/setup/session-pick.js +51 -0
  280. package/dist/setup/setup-command.d.ts +45 -0
  281. package/dist/setup/setup-command.js +199 -0
  282. package/dist/setup/setup-options.d.ts +74 -0
  283. package/dist/setup/setup-options.js +67 -0
  284. package/dist/setup/transcripts.d.ts +16 -0
  285. package/dist/setup/transcripts.js +43 -0
  286. package/dist/status-payload.d.ts +14 -1
  287. package/dist/status-payload.js +23 -1
  288. package/dist/telemetry/daemon-telemetry.d.ts +8 -1
  289. package/dist/telemetry/daemon-telemetry.js +52 -5
  290. package/dist/telemetry/error-class.js +1 -1
  291. package/dist/telemetry/feedback-context.d.ts +9 -1
  292. package/dist/telemetry/feedback-context.js +60 -29
  293. package/dist/telemetry/feedback-local.d.ts +15 -0
  294. package/dist/telemetry/feedback-local.js +56 -0
  295. package/dist/telemetry/feedback.d.ts +11 -0
  296. package/dist/telemetry/feedback.js +19 -6
  297. package/dist/telemetry/init-telemetry.d.ts +2 -0
  298. package/dist/telemetry/init-telemetry.js +2 -0
  299. package/dist/telemetry/install-source.d.ts +14 -0
  300. package/dist/telemetry/install-source.js +30 -0
  301. package/dist/telemetry/license-activation.d.ts +7 -1
  302. package/dist/telemetry/license-activation.js +9 -5
  303. package/dist/telemetry/mcp-connection.js +7 -0
  304. package/dist/telemetry/project-profile.d.ts +12 -1
  305. package/dist/telemetry/project-profile.js +11 -6
  306. package/dist/telemetry/proxy-telemetry.d.ts +3 -0
  307. package/dist/telemetry/proxy-telemetry.js +21 -0
  308. package/dist/telemetry/session-metrics.d.ts +12 -0
  309. package/dist/telemetry/session-metrics.js +35 -1
  310. package/dist/telemetry/telemetry.js +17 -2
  311. package/dist/telemetry/tool-refused.d.ts +3 -1
  312. package/dist/telemetry/tool-refused.js +34 -2
  313. package/dist/tools/act-capsule.d.ts +10 -0
  314. package/dist/tools/act-capsule.js +8 -2
  315. package/dist/tools/act-danger.d.ts +2 -1
  316. package/dist/tools/act-danger.js +11 -3
  317. package/dist/tools/act-observation.d.ts +41 -0
  318. package/dist/tools/act-observation.js +36 -0
  319. package/dist/tools/act-preflight.d.ts +20 -0
  320. package/dist/tools/act-preflight.js +98 -0
  321. package/dist/tools/act-sequence-retry.d.ts +45 -0
  322. package/dist/tools/act-sequence-retry.js +58 -0
  323. package/dist/tools/act-sequence-tool.d.ts +13 -0
  324. package/dist/tools/act-sequence-tool.js +135 -0
  325. package/dist/tools/act-target.d.ts +14 -0
  326. package/dist/tools/act-target.js +47 -0
  327. package/dist/tools/act-tools.d.ts +21 -1
  328. package/dist/tools/act-tools.js +100 -213
  329. package/dist/tools/assert-verdict.js +22 -16
  330. package/dist/tools/browser-tools.js +6 -2
  331. package/dist/tools/dynamic-tools.js +24 -5
  332. package/dist/tools/error-recovery.d.ts +1 -0
  333. package/dist/tools/error-recovery.js +8 -0
  334. package/dist/tools/invoke-tool.js +59 -9
  335. package/dist/tools/lease-tools.d.ts +30 -4
  336. package/dist/tools/lease-tools.js +169 -5
  337. package/dist/tools/merged-name-redirect.d.ts +13 -0
  338. package/dist/tools/merged-name-redirect.js +22 -0
  339. package/dist/tools/numeric-bounds.d.ts +100 -0
  340. package/dist/tools/numeric-bounds.js +125 -0
  341. package/dist/tools/observe-tools.js +47 -40
  342. package/dist/tools/read-tools.js +28 -7
  343. package/dist/tools/real-input-attempt.d.ts +10 -0
  344. package/dist/tools/real-input-attempt.js +59 -1
  345. package/dist/tools/reconcile-tools.js +4 -4
  346. package/dist/tools/tool-kit.d.ts +12 -0
  347. package/dist/tools/tool-kit.js +6 -1
  348. package/dist/tools/tool-names.d.ts +2 -0
  349. package/dist/tools/tool-names.js +2 -0
  350. package/dist/tools/tools-helpers.d.ts +19 -0
  351. package/dist/tools/tools-helpers.js +46 -0
  352. package/dist/tools/tools.d.ts +2 -0
  353. package/dist/tools/tools.js +56 -8
  354. package/dist/update/reticle-deps.d.ts +9 -0
  355. package/dist/update/reticle-deps.js +27 -0
  356. package/dist/update/sdk-sync.d.ts +1 -2
  357. package/dist/update/sdk-sync.js +1 -21
  358. package/dist/update/update-nudge.d.ts +18 -0
  359. package/dist/update/update-nudge.js +20 -0
  360. package/dist/version/peer-announce.js +4 -2
  361. package/dist/version/sdk-fix.d.ts +47 -0
  362. package/dist/version/sdk-fix.js +127 -0
  363. package/dist/version/version-nudge.d.ts +14 -0
  364. package/dist/version/version-nudge.js +21 -0
  365. package/dist/version/version-skew.d.ts +38 -3
  366. package/dist/version/version-skew.js +83 -7
  367. package/dist/visual/visual-store.d.ts +6 -6
  368. package/dist/visual/visual-store.js +18 -16
  369. package/dist/visual/visual-tools.js +57 -12
  370. package/docs/cli/doctor.mdx +1 -0
  371. package/docs/cli/serve.mdx +4 -2
  372. package/docs/cli.mdx +7 -3
  373. package/docs/desktop.mdx +1 -1
  374. package/docs/docs.json +1 -1
  375. package/docs/getting-started.md +19 -3
  376. package/docs/index.mdx +9 -6
  377. package/docs/install-agentic.mdx +38 -12
  378. package/docs/install-manual.mdx +1 -1
  379. package/docs/packages.mdx +1 -1
  380. package/docs/skill-file.mdx +3 -3
  381. package/docs/telemetry-contract.md +108 -4
  382. package/docs/tools-act-sequence.mdx +16 -0
  383. package/docs/usage.md +1 -1
  384. package/package.json +4 -3
  385. package/dist/init/cursor.d.ts +0 -24
  386. package/dist/init/cursor.js +0 -80
package/dist/init/mcp.js CHANGED
@@ -55,7 +55,7 @@ const WINDOWS_SHELL_C = '/c';
55
55
  *
56
56
  * So it stays unpinned, and the skew is handled where it actually shows up: the contract fingerprint
57
57
  * makes a real mismatch loud on the next tool result (see version-skew), and a stale entry of our own
58
- * shape is now repaired on re-run rather than reported "already registered" (see cursor.ts).
58
+ * shape is now repaired on re-run rather than reported "already registered" (see mcp-clients.ts).
59
59
  */
60
60
  export function npxServerArgs() {
61
61
  return [RETICLE_PACKAGE, MCP_SUBCOMMAND];
@@ -3,7 +3,7 @@
3
3
  * `runInit` stays testable with an in-memory IO. Prints to stdout — `init` is a one-shot CLI
4
4
  * command, not the MCP stdio transport.
5
5
  */
6
- import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, statSync } from 'node:fs';
6
+ import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, statSync, accessSync, constants, } from 'node:fs';
7
7
  import { NodePlatform } from '../platform.js';
8
8
  import { join, dirname, isAbsolute } from 'node:path';
9
9
  import { homedir } from 'node:os';
@@ -95,6 +95,16 @@ export function buildNodeIo(cwd) {
95
95
  const result = spawnSync(command, shellSafe(args), { cwd, stdio: 'inherit', ...shellOpt() });
96
96
  return 0 === result.status;
97
97
  },
98
+ /** One access check, rather than discovering it as an EACCES stack four phases later. */
99
+ canWrite() {
100
+ try {
101
+ accessSync(cwd, constants.W_OK);
102
+ return true;
103
+ }
104
+ catch {
105
+ return false;
106
+ }
107
+ },
98
108
  probe(command, args) {
99
109
  // Quiet yes/no check (CLI availability, existing registration). Never throws.
100
110
  const result = spawnSync(command, shellSafe(args), { cwd, stdio: 'ignore', ...shellOpt() });
@@ -6,13 +6,14 @@
6
6
  import { bridgeWsUrl } from '@reticlehq/core';
7
7
  import { patchViteConfig, VitePatchKind } from './vite-config.js';
8
8
  import { patchNextConfig, patchRootLayout, patchPagesApp } from './next-patch.js';
9
- import { patchAstroConfig, patchAstroLayout } from './astro-patch.js';
10
- import { CRA_DEV_MODULE_IMPORT, CRA_DEV_MODULE_PATH, CRA_ENV_PATH, CRA_TOKEN_PER_MACHINE_NOTICE, TOKEN_VAR, craDevModuleFile, craEnvPatch, craImportPatch, } from './cra.js';
9
+ import { ASTRO_ENV_DTS_PATH, patchAstroConfig, patchAstroEnvDts, patchAstroLayout, } from './astro-patch.js';
10
+ import { CRA_DEV_MODULE_IMPORT, CRA_ENV_PATH, CRA_TOKEN_PER_MACHINE_NOTICE, TOKEN_VAR, craDevModuleFile, craDevModulePath, craEnvPatch, craImportPatch, } from './cra.js';
11
11
  import { PatchKind } from './patch-kind.js';
12
12
  import { viteManual, NEXT_LAYOUT_MANUAL, NEXT_LAYOUT_PATH, viteDevModuleFile, VITE_DEV_MODULE_PATH, nextReticleDevFile, NEXT_RETICLE_DEV_PATH, nextConfigManual, svelteKitHooksFile, SVELTEKIT_HOOKS_PATH, UNVERIFIED_FRAMEWORK_NOTE, astroManual, nuxtManual, NUXT_PLUGIN_PATH, } from './snippets.js';
13
- import { RETICLE_CONFIG_FILE, StepStatus } from './plan.js';
13
+ import { StepStatus } from './plan.js';
14
14
  import { RETICLE_DEFAULT_PORT } from '@reticlehq/core';
15
- import { CSP_STEP_TITLE, cspPlanProblem } from './csp-check.js';
15
+ import { CSP_STEP_TITLE } from './csp-check.js';
16
+ import { diagnoseWebCsp } from './csp-doctor.js';
16
17
  /** What adding `reticle()` to a Vite config buys, which differs by framework. */
17
18
  export const VITE_PLUGIN_DETAIL = {
18
19
  /** A plain Vite app gets both halves from the plugin. */
@@ -41,20 +42,13 @@ const CAPABILITIES_TITLE = 'Capabilities + store';
41
42
  */
42
43
  const CAPABILITIES_TODO_TITLE = 'AGENT: finish the capabilities file';
43
44
  function capabilitiesTodo(path, stores) {
44
- const store = stores.length > 0
45
- ? `This app depends on a state library init cannot wire on its own (it needs an argument only ` +
46
- `reading the code can supply). Uncomment the suggested line in that file and complete it: ` +
47
- `${stores.join('; ')}.`
48
- : `No state library was detected. If the app holds state somewhere init cannot see, register ` +
49
- `it — see node_modules/@reticlehq/server/docs/usage.md.`;
50
- return (`${path} was written but registers nothing, so \`hasCapabilities\` stays false and reticle_state ` +
51
- `has nothing to read.\n` +
45
+ return (`This app's state lives in a library nothing in the running app points at, so it is the one ` +
46
+ `thing Reticle cannot find for itself — reticle_state will not see it until you say where it ` +
47
+ `is.\n` +
52
48
  `Do this now, before driving anything:\n` +
53
- ` 1. ${store}\n` +
54
- ` 2. Open the app's main flow and add \`testids\` for the elements it touches (or add ` +
55
- `data-testid attributes where there are none).\n` +
56
- ` 3. Prove it: drive one flow and check reticle_state returns your keys. An empty state read ` +
57
- `means this file is still not doing anything, whatever else went green.`);
49
+ ` 1. In ${path}, uncomment the suggested line and complete it: ${stores.join('; ')}.\n` +
50
+ ` 2. Prove it: drive one flow and check reticle_state returns your key. An empty state read ` +
51
+ `means the line is still not doing anything, whatever else went green.`);
58
52
  }
59
53
  /**
60
54
  * The dev module carrying `registerCapabilities` / `registerStore`.
@@ -64,20 +58,26 @@ function capabilitiesTodo(path, stores) {
64
58
  * only when absent, because it is the one generated file a user is expected to EDIT.
65
59
  */
66
60
  /**
67
- * Does the generated dev module actually REGISTER anything?
61
+ * Is there state left that ONLY the app can hand over?
68
62
  *
69
- * Shared by the Vite and Next paths because it is one question about one generated call, and the
70
- * two answered it differently for as long as only Vite asked: `capabilitiesStep` was called from
71
- * `viteSteps` and nowhere else, so a Next app whose store can only be SUGGESTED wrote
72
- * `registerCapabilities({ testids: [], stores: [] })`, registered nothing, and said nothing.
73
- * Measured on a real Next + mobx app: five steps, every one a checkmark, no capabilities line.
63
+ * This used to ask a much bigger question — "does the generated file register anything at all?" —
64
+ * and fired on almost every install, because almost every install generated a file whose testids
65
+ * and stores were both empty. The answer was homework: go read the source, uncomment a line, add
66
+ * data-testid attributes, then drive to prove it. Several turns, on every onboarding.
67
+ *
68
+ * Two of those three are no longer anyone's homework. Testids are read from the live DOM, and a
69
+ * store passed through a React context provider (Redux, TanStack Query) is discovered and
70
+ * registered on the first commit. What is left is the genuinely unreachable case: a module-scope
71
+ * store, or one needing an argument only the source supplies — Zustand, Jotai atoms, an XState
72
+ * actor. Nothing in the running app points at those, so the notice still has to fire, and it now
73
+ * fires ONLY there.
74
74
  *
75
75
  * `wired` are stores init resolved and wrote a live `registerStore` call for. Hints are not
76
76
  * registrations: they land in the file as a commented line, and counting one as a registration
77
77
  * silences the notice whose whole purpose is to say "act on the hint".
78
78
  */
79
- function registersNothing(testids, wired) {
80
- return 0 === testids.length && 0 === wired.length;
79
+ function needsManualStore(hints, wired) {
80
+ return hints.length > 0 && 0 === wired.length;
81
81
  }
82
82
  function capabilitiesStep(input) {
83
83
  if (true === input.viteDevModuleExists) {
@@ -93,13 +93,15 @@ function capabilitiesStep(input) {
93
93
  const testids = input.testids ?? [];
94
94
  const stores = input.storeHints ?? [];
95
95
  const wired = input.foundStores ?? [];
96
- const found = testids.length > 0
97
- ? `${String(testids.length)} data-testid values`
98
- : 'no data-testid values yet';
99
- // A store we found and WIRED is a registration, so the "registers nothing" notice must not fire.
100
- // A store HINT is not: `stores` holds suggestions, written into the file as a commented line of
101
- // the form `// import your store, then: registerStore(...)`. Counting a suggestion as a
102
- // registration let the hint silence the notice whose entire job is to say "act on the hint".
96
+ // Testids no longer need counting here. They are read from the live DOM at announce time, so a
97
+ // number printed at install time would be a stale claim about a codebase that is about to change —
98
+ // which is exactly what "no data-testid values yet" used to be on an app whose testids arrive with
99
+ // a lazy route.
100
+ const found = 'testids read from the live DOM';
101
+ // A store we found and WIRED is a registration, so the notice must not fire. A store HINT is not:
102
+ // `stores` holds suggestions, written into the file as a commented line of the form
103
+ // `// import your store, then: registerStore(...)`. Counting a suggestion as a registration let
104
+ // the hint silence the notice whose entire job is to say "act on the hint".
103
105
  //
104
106
  // Measured against a real product UI (rowy — 70+ deps, jotai, a whole src/atoms tree): init
105
107
  // detected jotai, offered one commented line, emitted NO notice, and wrote
@@ -107,10 +109,10 @@ function capabilitiesStep(input) {
107
109
  // false, `reticle_state` was empty forever, and the install gate reported "connected: 1,
108
110
  // manual ⚠: none". Every check green, state observability zero.
109
111
  //
110
- // This is the common case rather than an edge one: a library whose registration needs an argument
111
- // only reading the source supplies — jotai atoms, an XState actor, a TanStack queryClient — is
112
- // exactly what init leaves commented.
113
- const nothingToRegister = registersNothing(testids, wired);
112
+ // jotai is still exactly that case. A Redux or TanStack app is not, any more: its store rides a
113
+ // context provider and the React adapter registers it on the first commit, so `storeHints` no
114
+ // longer names either one and this notice no longer fires for them.
115
+ const nothingToRegister = needsManualStore(stores, wired);
114
116
  return [
115
117
  {
116
118
  title: CAPABILITIES_TITLE,
@@ -165,7 +167,7 @@ function viteConfigSteps(input, detail) {
165
167
  },
166
168
  ];
167
169
  }
168
- const patch = patchViteConfig(cfg.source, port);
170
+ const patch = patchViteConfig(cfg.source, port, true === input.captureBodies);
169
171
  if (patch.kind === VitePatchKind.ALREADY) {
170
172
  return [
171
173
  {
@@ -294,7 +296,7 @@ export function nextSteps(input) {
294
296
  // The same question the Vite path asks. Skipped when the module already exists: its contents are
295
297
  // the user's, and re-nagging about a file we did not write is noise.
296
298
  const nextTodo = true !== input.nextReticleDevExists &&
297
- registersNothing(input.testids ?? [], input.nextFoundStores ?? [])
299
+ needsManualStore(input.storeHints ?? [], input.nextFoundStores ?? [])
298
300
  ? [
299
301
  {
300
302
  title: CAPABILITIES_TODO_TITLE,
@@ -327,15 +329,19 @@ export function nextSteps(input) {
327
329
  */
328
330
  export function craSteps(input) {
329
331
  const entry = input.craEntry ?? null;
332
+ // Match the project language the same way Next does (#675): a JS CRA app cannot resolve `.ts`.
333
+ const modulePath = craDevModulePath(input.detection.typescript);
330
334
  const steps = [
331
335
  {
332
336
  title: 'Reticle connect module',
333
- target: CRA_DEV_MODULE_PATH,
337
+ target: modulePath,
334
338
  status: StepStatus.APPLY,
335
339
  detail: 'create the dev-only connect (CRA cannot inject through public/index.html)',
336
340
  write: {
337
- path: CRA_DEV_MODULE_PATH,
338
- content: craDevModuleFile(input.options.port, input.options.projectId),
341
+ path: modulePath,
342
+ content: craDevModuleFile(input.options.port, input.options.projectId, {
343
+ typescript: input.detection.typescript,
344
+ }),
339
345
  },
340
346
  dependsOnInstall: true,
341
347
  },
@@ -500,9 +506,14 @@ export function astroSteps(input) {
500
506
  },
501
507
  ];
502
508
  }
509
+ const envPatch = patchAstroEnvDts(input.astroEnvDts ?? null);
503
510
  return [
504
511
  patchStep('Astro config (token + build target)', config.path, configPatch, 'inline the pairing token and raise build.target to es2022', manualWithLayout),
505
512
  patchStep('Connect snippet (Astro)', layout.path, layoutPatch, 'add the dev-only connect <script> before </body>', manualWithLayout),
513
+ // Declares the Vite define names so `astro check` (create-astro's default build) can see them
514
+ // (#677). Independent of the two halves above: even an ALREADY config/layout still needs this
515
+ // when the env file was never written.
516
+ patchStep('Astro env types (Vite defines)', ASTRO_ENV_DTS_PATH, envPatch, 'declare __RETICLE_TOKEN__ / __RETICLE_ROOT__ for astro check', manualWithLayout),
506
517
  ];
507
518
  }
508
519
  /**
@@ -515,15 +526,40 @@ export function astroSteps(input) {
515
526
  * warning and a fix.
516
527
  */
517
528
  export function cspStep(input) {
518
- const problem = cspPlanProblem([input.nextConfigSource, input.nextLayout?.source], input.options.port ?? RETICLE_DEFAULT_PORT);
519
- if (problem === undefined)
529
+ // Reads the SAME list `reticle doctor` reads. It used to read a hand-written pair —
530
+ // `[nextConfigSource, nextLayout?.source]` — while csp-doctor.ts already carried the full set
531
+ // including `index.html`, which is where every Vite and Electron app declares its policy. So the
532
+ // command you run BEFORE anything works checked less than the one you run after it has failed.
533
+ //
534
+ // Measured on MarkText, a production Electron editor: its renderer sets `default-src 'self'` with
535
+ // no `connect-src`, the browser blocked the bridge WebSocket, and `init` printed a clean plan. The
536
+ // daemon cannot see a dial that never left the page, so nothing anywhere said why — and the check
537
+ // that would have said it was sitting one import away.
538
+ // The pre-read Next sources are folded in ON TOP of the shared list, not replaced by it: init
539
+ // resolves `nextConfigFile` across more spellings than CSP_FILES names (`.mts`, for one), and a
540
+ // layout is found by search rather than by a fixed path. Two sources of the same truth is the
541
+ // problem being fixed here — one of them being a SUPERSET is not.
542
+ const extra = {
543
+ ...(input.nextConfigFile !== null && input.nextConfigFile !== undefined
544
+ ? { [input.nextConfigFile]: input.nextConfigSource ?? undefined }
545
+ : {}),
546
+ ...(input.nextLayout ? { [input.nextLayout.path]: input.nextLayout.source } : {}),
547
+ };
548
+ const read = (file) => extra[file] ?? input.cspSources?.[file];
549
+ const findings = diagnoseWebCsp(read, input.options.port ?? RETICLE_DEFAULT_PORT, [
550
+ ...Object.keys(extra),
551
+ ]);
552
+ const first = findings[0];
553
+ if (first === undefined)
520
554
  return [];
521
555
  return [
522
556
  {
523
557
  title: CSP_STEP_TITLE,
524
- target: input.nextConfigFile ?? RETICLE_CONFIG_FILE,
558
+ target: first.file,
525
559
  status: StepStatus.NOTICE,
526
- detail: problem,
560
+ // `problem` already ends with the text to paste; `fix` is the same sentence for callers that
561
+ // want it on its own (doctor renders them separately). Printing both said it twice.
562
+ detail: first.problem,
527
563
  },
528
564
  ];
529
565
  }
@@ -5,7 +5,7 @@
5
5
  */
6
6
  import { Framework, UiLibrary, type Detection } from './detect.js';
7
7
  import type { FoundStore } from './capabilities.js';
8
- import { type McpClient } from './mcp-clients.js';
8
+ import { McpClient } from './mcp-clients.js';
9
9
  /**
10
10
  * The dev-dependencies `reticle init` installs — kit (or sensor) first, build plugin next.
11
11
  *
@@ -85,20 +85,28 @@ export interface Plan {
85
85
  }
86
86
  export interface PlanInput {
87
87
  detection: Detection;
88
+ /**
89
+ * Write `captureNetworkBodies: true` into the app's config. Off unless the caller asked (#705).
90
+ *
91
+ * Optional so every existing caller and test keeps the safe default without naming it — the one
92
+ * direction a default about somebody else's data should be wrong in.
93
+ */
94
+ captureBodies?: boolean | undefined;
95
+ /**
96
+ * The CSP-bearing files this project actually has, keyed by path — read once in `gatherPlanInput`.
97
+ *
98
+ * Pre-read rather than given a reader, because `PlanInput` is the pure input to a pure planner and
99
+ * handing it an io would let any later step reach the disk from inside `buildPlan`.
100
+ */
101
+ cspSources?: Readonly<Record<string, string | undefined>> | undefined;
88
102
  /** Whether the `claude` CLI is installed (so we can register the MCP server globally). */
89
103
  claudeCli: boolean;
90
104
  /** Whether an `reticle` MCP server is already registered with Claude (any scope) — idempotency. */
91
105
  mcpExists: boolean;
92
- /** Whether Cursor is installed for this user (its global config dir exists). */
93
- cursorPresent: boolean;
94
106
  /** `process.platform`. Injected so this module stays pure. Windows is the only branch. */
95
107
  platform?: string;
96
108
  /** Whether THIS project has a .cursor/ directory — the signal that Cursor works on this repo. */
97
109
  cursorProjectPresent?: boolean | undefined;
98
- /** Current ~/.cursor/mcp.json content, or null if absent. */
99
- cursorConfig: string | null;
100
- /** Absolute path of ~/.cursor/mcp.json (the write target). */
101
- cursorConfigPath: string;
102
110
  /**
103
111
  * Every OTHER MCP client detected on this machine, with its config path and current content.
104
112
  *
@@ -131,6 +139,11 @@ export interface PlanInput {
131
139
  path: string;
132
140
  source: string;
133
141
  } | null | undefined;
142
+ /**
143
+ * Existing `src/env.d.ts` content, when present — where the Vite-define ambient declarations go
144
+ * so `astro check` can see `__RETICLE_TOKEN__` / `__RETICLE_ROOT__` (#677).
145
+ */
146
+ astroEnvDts?: string | null | undefined;
134
147
  /** Discovered Next config filename (e.g. 'next.config.mjs'), or null. */
135
148
  nextConfigFile: string | null;
136
149
  /** Source of that Next config, so the export can be wrapped in withReticle. */
package/dist/init/plan.js CHANGED
@@ -4,16 +4,16 @@
4
4
  * runner performs the `write` side-effects; this module decides *what* should happen.
5
5
  */
6
6
  import { Framework, PackageManager, UiLibrary, installCommand, installCommandParts, } from './detect.js';
7
+ import { installFailureHint } from './install-hint.js';
7
8
  import { claudeAddCommand, mcpManual, mcpWindowsNote } from './mcp.js';
8
9
  import { NodePlatform } from '../platform.js';
9
- import { mergeCursorConfig, CursorMergeStatus, cursorServerEntry } from './cursor.js';
10
- import { mergeClientConfig, ClientMergeStatus, clientSnippet, clientSpec, } from './mcp-clients.js';
10
+ import { mergeClientConfig, ClientMergeStatus, clientSnippet, clientSpec, McpClient, } from './mcp-clients.js';
11
11
  import { CLAUDE_COMMAND_PATH, CURSOR_COMMAND_PATH, SLASH_COMMAND_BODY, SLASH_COMMAND_SIGNATURE, } from './slash-command.js';
12
12
  import { mergeMarkedInstruction, reticleMdFile, cursorRuleFile, AgentRuleStatus, CLAUDE_MD_PATH, AGENTS_MD_PATH, RETICLE_MD_PATH, CURSOR_RULE_PATH, } from './agent-rules.js';
13
13
  import { viteSteps, nextSteps, craSteps, svelteKitSteps, nuxtSteps, astroSteps, cspStep, VITE_PLUGIN_DETAIL, } from './plan-framework.js';
14
14
  import { join } from 'node:path';
15
15
  import { htmlManual, reticleConfigContent, unverifiedUiLibraryNote } from './snippets.js';
16
- import { declaredInstallSource } from '../telemetry/install-source.js';
16
+ import { configWithInstallSource, declaredInstallSource } from '../telemetry/install-source.js';
17
17
  import { devServerPortWarning, isLikelyDevServerPort } from '../cli/cli-port.js';
18
18
  // An app dev installs exactly the audience-scoped browser-side dependencies — never the retired
19
19
  // `@reticlehq/core` umbrella (which dragged the Node MCP server + ws into every app). The kit is the
@@ -122,7 +122,6 @@ function agentFile(input, relPath) {
122
122
  return root === undefined || 0 === root.length ? relPath : join(root, relPath);
123
123
  }
124
124
  const CLAUDE_MCP_TITLE = 'MCP server (Claude, global)';
125
- const CURSOR_MCP_TITLE = 'MCP server (Cursor, global)';
126
125
  function claudeMcpStep(input) {
127
126
  if (!input.claudeCli)
128
127
  return null;
@@ -143,34 +142,6 @@ function claudeMcpStep(input) {
143
142
  exec: { command: cmd.command, args: cmd.args, fallback: cmd.display },
144
143
  };
145
144
  }
146
- function cursorMcpStep(input) {
147
- if (!input.cursorPresent)
148
- return null;
149
- const r = mergeCursorConfig(input.cursorConfig);
150
- if (r.status === CursorMergeStatus.ALREADY) {
151
- return {
152
- title: CURSOR_MCP_TITLE,
153
- target: input.cursorConfigPath,
154
- status: StepStatus.ALREADY,
155
- detail: 'reticle already in Cursor global config',
156
- };
157
- }
158
- if (r.status === CursorMergeStatus.MANUAL) {
159
- return {
160
- title: CURSOR_MCP_TITLE,
161
- target: input.cursorConfigPath,
162
- status: StepStatus.MANUAL,
163
- detail: `couldn't parse ${input.cursorConfigPath} — add this server by hand:\n "reticle": ${JSON.stringify(cursorServerEntry())}`,
164
- };
165
- }
166
- return {
167
- title: CURSOR_MCP_TITLE,
168
- target: input.cursorConfigPath,
169
- status: StepStatus.APPLY,
170
- detail: 'register reticle in Cursor global config',
171
- write: { path: input.cursorConfigPath, content: r.content },
172
- };
173
- }
174
145
  /**
175
146
  * One step per OTHER detected client.
176
147
  *
@@ -304,7 +275,8 @@ function claudeCommandStep(input) {
304
275
  return commandStepFor(agentFile(input, CLAUDE_COMMAND_PATH), input.claudeCli, input.claudeCommandContent);
305
276
  }
306
277
  function cursorCommandStep(input) {
307
- const present = true === input.cursorProjectPresent || (input.cursorPresent && !input.claudeCli);
278
+ const cursorGlobal = (input.detectedClients ?? []).some((c) => c.id === McpClient.CURSOR);
279
+ const present = true === input.cursorProjectPresent || (cursorGlobal && !input.claudeCli);
308
280
  return commandStepFor(agentFile(input, CURSOR_COMMAND_PATH), present, input.cursorCommandContent);
309
281
  }
310
282
  const AGENT_RULE_TITLE = 'Agent verification rule';
@@ -339,7 +311,8 @@ function claudeRuleStep(input) {
339
311
  * committed into their repo. (Global MCP registration is different: it is global, and stays.)
340
312
  */
341
313
  function cursorRuleStep(input) {
342
- if (!input.cursorPresent)
314
+ const cursorGlobal = (input.detectedClients ?? []).some((c) => c.id === McpClient.CURSOR);
315
+ if (!cursorGlobal && true !== input.cursorProjectPresent)
343
316
  return null;
344
317
  if (input.cursorProjectPresent !== true && input.claudeCli)
345
318
  return null;
@@ -389,14 +362,13 @@ const AGENT_INTEGRATIONS = [
389
362
  },
390
363
  {
391
364
  id: AgentId.CURSOR,
392
- mcpStep: cursorMcpStep,
393
365
  ruleStep: cursorRuleStep,
394
366
  commandStep: cursorCommandStep,
395
367
  },
396
368
  ];
397
369
  /** The steps one surface contributes across every known agent, in registry order. */
398
370
  function stepsForAgents(input, surface) {
399
- return AGENT_INTEGRATIONS.map((a) => surface(a)(input)).filter((s) => s !== null);
371
+ return AGENT_INTEGRATIONS.map((a) => surface(a)?.(input) ?? null).filter((s) => s !== null);
400
372
  }
401
373
  /** The `/reticle` command file for every agent that has a command surface and is present here. */
402
374
  function slashCommandSteps(input) {
@@ -503,15 +475,6 @@ function unpinnedRetryNote(version, pm) {
503
475
  `installed instead. That may not match the daemon — if the agent reports protocol errors, check ` +
504
476
  `\`versionSkew\` in reticle_sessions.${remedy}`);
505
477
  }
506
- function installFailureHint(pm) {
507
- if (pm !== PackageManager.PNPM)
508
- return 'If the version was refused, install the SDK yourself.';
509
- return ('If pnpm reported ERR_PNPM_NO_MATURE_MATCHING_VERSION, its minimumReleaseAge setting is holding ' +
510
- 'this release back. Either wait out the window, or allow these packages explicitly:\n' +
511
- ' pnpm config set minimumReleaseAgeExclude "@reticlehq/*"\n' +
512
- 'Do NOT drop the version pin — unpinned, pnpm installs an older SDK against a newer daemon, and ' +
513
- 'that mismatch surfaces as a -32000 with nothing naming a version.');
514
- }
515
478
  function installStep(input) {
516
479
  const pm = input.detection.packageManager;
517
480
  const packages = pinnedPackages(frameworkPackages(input.detection.framework, input.detection.uiLibrary), input.options.sdkVersion);
@@ -629,6 +592,18 @@ function reticleConfigStep(input, content) {
629
592
  detail: problem,
630
593
  };
631
594
  }
595
+ // The one thing a re-run can still learn: which channel the user actually arrived through.
596
+ // See configWithInstallSource — it only ever ADDS a field that is absent.
597
+ const backfilled = configWithInstallSource(input.reticleConfigSource, declaredInstallSource());
598
+ if (backfilled !== undefined) {
599
+ return {
600
+ title: RETICLE_CONFIG_TITLE,
601
+ target: RETICLE_CONFIG_FILE,
602
+ status: StepStatus.APPLY,
603
+ detail: 'record which install route this project came through',
604
+ write: { path: RETICLE_CONFIG_FILE, content: backfilled },
605
+ };
606
+ }
632
607
  return {
633
608
  title: RETICLE_CONFIG_TITLE,
634
609
  target: RETICLE_CONFIG_FILE,
@@ -0,0 +1,28 @@
1
+ /**
2
+ * The two conditions that make every later phase fail, checked before anything is written.
3
+ *
4
+ * Both lived in setup/reticle.mjs and neither survived the port into `init`. What they buy is the
5
+ * distance between a failure and its cause: without them, an unwritable checkout arrives as EACCES
6
+ * in a stack trace at phase four, and a missing package manager arrives as `spawn pnpm ENOENT`
7
+ * inside "the dev server exited" — which sends the reader into their own dev script hunting a bug
8
+ * that is not there.
9
+ */
10
+ /** The parts of the environment preflight reads, so the rules are testable without a filesystem. */
11
+ export interface PreflightIo {
12
+ /** Absolute path of the directory init is running in. */
13
+ cwd(): string;
14
+ /** Can this process write into the project root. */
15
+ canWrite(): boolean;
16
+ /** Runs a command quietly for a yes/no check; true on exit code 0. */
17
+ probe(command: string, args: readonly string[]): boolean;
18
+ }
19
+ /**
20
+ * The refusal to print, or undefined when this machine can run the install.
21
+ *
22
+ * `packageManager` is the one init RESOLVED, never a raw lockfile check. An inherited
23
+ * `pnpm-lock.yaml` at a monorepo root does not mean the app in `frontend/` uses pnpm — that app's own
24
+ * installed tree outranks an ancestor lockfile, and detect.ts already works this out. Re-deriving it
25
+ * here from `exists('pnpm-lock.yaml')` refused an npm app sitting under a pnpm monorepo on a machine
26
+ * with no pnpm, which the install gate proves must succeed.
27
+ */
28
+ export declare function preflightRefusal(io: PreflightIo, packageManager: string): string | undefined;
@@ -0,0 +1,38 @@
1
+ /**
2
+ * The two conditions that make every later phase fail, checked before anything is written.
3
+ *
4
+ * Both lived in setup/reticle.mjs and neither survived the port into `init`. What they buy is the
5
+ * distance between a failure and its cause: without them, an unwritable checkout arrives as EACCES
6
+ * in a stack trace at phase four, and a missing package manager arrives as `spawn pnpm ENOENT`
7
+ * inside "the dev server exited" — which sends the reader into their own dev script hunting a bug
8
+ * that is not there.
9
+ */
10
+ /**
11
+ * npm ships with node, so refusing for its absence would refuse on a machine that is fine.
12
+ */
13
+ const ALWAYS_PRESENT = 'npm';
14
+ /**
15
+ * The refusal to print, or undefined when this machine can run the install.
16
+ *
17
+ * `packageManager` is the one init RESOLVED, never a raw lockfile check. An inherited
18
+ * `pnpm-lock.yaml` at a monorepo root does not mean the app in `frontend/` uses pnpm — that app's own
19
+ * installed tree outranks an ancestor lockfile, and detect.ts already works this out. Re-deriving it
20
+ * here from `exists('pnpm-lock.yaml')` refused an npm app sitting under a pnpm monorepo on a machine
21
+ * with no pnpm, which the install gate proves must succeed.
22
+ */
23
+ export function preflightRefusal(io, packageManager) {
24
+ // First: on a read-only checkout nothing else matters, and one access check is cheaper and
25
+ // quieter than spawning a subprocess to discover the same thing.
26
+ if (!io.canWrite()) {
27
+ return (`${io.cwd()} is not writable, and init has to write into it (.reticle.json, the build config, ` +
28
+ 'a capabilities file). Fix the permissions, or run init from a checkout you own.');
29
+ }
30
+ // What the project resolves to says nothing about what the machine HAS, and a project committed to
31
+ // pnpm on an npm-only box is an ordinary Monday.
32
+ if (ALWAYS_PRESENT !== packageManager && !io.probe(packageManager, ['--version'])) {
33
+ return (`this project uses ${packageManager} (its lockfile says so) and ${packageManager} is not ` +
34
+ `installed on this machine. Install it (npm i -g ${packageManager}, or corepack enable), or ` +
35
+ 'pass --url with the address the app already serves.');
36
+ }
37
+ return undefined;
38
+ }
@@ -18,6 +18,8 @@ import type { InitOutcome } from '@reticlehq/core';
18
18
  */
19
19
  export declare function resolveLockfiles(rootFiles: ReadonlySet<string>, cwd: string, io: Pick<InitIo, 'exists'>, nodeModulesMarkers?: ReadonlySet<string>): Set<string>;
20
20
  export interface InitOptions {
21
+ /** `--capture-bodies`: write `captureNetworkBodies: true` into the app's config. Off by default (#705). */
22
+ captureBodies?: boolean | undefined;
21
23
  cwd: string;
22
24
  port: number | undefined;
23
25
  mcp: boolean;
@@ -47,6 +49,13 @@ export interface InitOptions {
47
49
  * today's fire-and-forget behaviour, so no path loses its event by forgetting to report.
48
50
  */
49
51
  deferOutcome?: boolean;
52
+ /**
53
+ * Whether the caller carries on into booting the app and driving it.
54
+ *
55
+ * Only affects the closing hint, which otherwise tells the reader to restart their dev server and
56
+ * drive a flow by hand — three lines before this command does both.
57
+ */
58
+ continuesToRuntime?: boolean;
50
59
  }
51
60
  export interface InitIo {
52
61
  /** Returns file content or null if it does not exist. Path is project-relative or absolute. */
@@ -70,25 +79,39 @@ export interface InitIo {
70
79
  exec(command: string, args: readonly string[]): boolean;
71
80
  /** Runs a subprocess quietly (no stdio) for a yes/no check; returns true on exit code 0. */
72
81
  probe(command: string, args: readonly string[]): boolean;
82
+ /** Can this process write into the project root — see preflight.ts. */
83
+ canWrite(): boolean;
73
84
  print(line: string): void;
74
85
  }
86
+ /**
87
+ * What a run established, for a caller that means to continue where init stopped.
88
+ *
89
+ * Everything here was already computed and then thrown away. That was fine while init only wrote
90
+ * files: nobody downstream existed. A caller that goes on to boot the app needs the same answers,
91
+ * and re-deriving them is how two parts of one command end up disagreeing about which directory
92
+ * they are in — which is not hypothetical, because a monorepo redirect re-enters `runInit` with a
93
+ * different cwd and the outer caller never learns that it happened.
94
+ */
95
+ export interface InitContext {
96
+ /** The directory actually wired, AFTER any monorepo redirect. */
97
+ readonly appDir: string;
98
+ readonly framework: string;
99
+ readonly packageManager: string;
100
+ /** The project's own dev command, when its scripts name one. Never composed. */
101
+ readonly devCommand?: string | undefined;
102
+ /** Set when init redirected into a workspace app, naming the one it chose. */
103
+ readonly redirectedTo?: string | undefined;
104
+ }
75
105
  export interface InitResult {
76
106
  ok: boolean;
77
107
  applied: number;
78
108
  manual: number;
109
+ /** What this run established. Absent only where init exits before establishing anything. */
110
+ context?: InitContext;
79
111
  /**
80
112
  * The event body this run would report, handed to the caller instead of emitted, when
81
113
  * `deferOutcome` is set. Absent on a dry run and on the exits that report for themselves.
82
114
  */
83
115
  outcome?: InitOutcome;
84
116
  }
85
- /**
86
- * App directories under a workspace root.
87
- *
88
- * Running `reticle init` at the repo root is what people actually do, and in a monorepo the app is a
89
- * directory down — so init detected "no framework", printed a wall of manual HTML instructions, and
90
- * would have installed the SDK into the ROOT package.json. It already walks UP for the lockfile, so
91
- * it knows it is in a workspace; this is the matching walk DOWN.
92
- */
93
- export declare function findWorkspaceApps(io: Pick<InitIo, 'exists' | 'readFile' | 'listDirs'>): string[];
94
117
  export declare function runInit(options: InitOptions, io: InitIo): InitResult;