@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/run.js CHANGED
@@ -4,21 +4,23 @@
4
4
  * access goes through `InitIo` so the orchestration is unit-testable with an in-memory IO.
5
5
  */
6
6
  import { dirname, join } from 'node:path';
7
+ import { CSP_FILES } from './csp-doctor.js';
8
+ import { preflightRefusal } from './preflight.js';
7
9
  import { noPackageJsonMessage } from './non-js-project.js';
8
10
  import { devCommandFrom } from './dev-script.js';
9
11
  import { restartHint, FEEDBACK_HINT } from './closing-hint.js';
10
12
  import { spanSync } from '../trace.js';
11
- import { readFileSync } from 'node:fs';
12
- import { homedir } from 'node:os';
13
13
  import { projectIdOf, rememberProjectOnDisk } from '../project/remember-project.js';
14
14
  import { detect, Framework, namesAPackageManager, UiLibrary } from './detect.js';
15
15
  import { wasMcpRegistered } from './mcp-registered.js';
16
16
  import { pickAstroHost } from './astro-host.js';
17
- import { workspaceParents } from './workspace-apps.js';
17
+ import { findWorkspaceApps, NEXT_CONFIG_CANDIDATES, PACKAGE_JSON, VITE_CONFIG_CANDIDATES, } from './workspace-apps.js';
18
18
  import { chooseWorkspaceApp } from './app-choice.js';
19
19
  import { isConnectStep } from './connect-steps.js';
20
20
  import { CURSOR_RULE_PATH, RETICLE_MD_PATH } from './agent-rules.js';
21
21
  import { CRA_ENV_PATH } from './cra.js';
22
+ import { defaultPairingTokenDir, readOrCreatePairingTokenSync } from '../bridge/pairing-token.js';
23
+ import { formatGeneratedSource } from './format-generated.js';
22
24
  /** CRA's bundled entry, in the order create-react-app itself generates them. */
23
25
  const CRA_ENTRY_CANDIDATES = ['src/index.tsx', 'src/index.jsx', 'src/index.ts', 'src/index.js'];
24
26
  function craEntryOf(io) {
@@ -30,26 +32,20 @@ function craEntryOf(io) {
30
32
  return null;
31
33
  }
32
34
  /**
33
- * The daemon's pairing token, for the one stack that cannot read it at build time.
35
+ * The daemon's pairing token, minted here if nothing has written it yet.
34
36
  *
35
- * Every other framework's snippet reads this file in Node when the dev server starts, so the token
36
- * is never written into the project. CRA bundles browser code and inlines only REACT_APP_*, so it
37
- * has to be materialised into .env.development.local — which CRA's own template gitignores.
37
+ * `init` used to READ the file and return empty when the daemon had never started. The CDN snippet
38
+ * inlined that empty value permanently, and regenerating the token made the pasted literal stale.
39
+ * Same mint as the daemon (`readOrCreatePairingToken`), honours `RETICLE_PAIRING_TOKEN_DIR`.
38
40
  */
39
41
  function readPairingToken() {
40
- try {
41
- return readFileSync(join(homedir(), '.reticle', 'pairing-token'), 'utf8').trim();
42
- }
43
- catch {
44
- return '';
45
- }
42
+ return readOrCreatePairingTokenSync(defaultPairingTokenDir()) ?? '';
46
43
  }
47
44
  import { DEPS_TARGET, RETICLE_CONFIG_FILE, frameworkPackages, MCP_TARGET, buildPlan, StepStatus, } from './plan.js';
48
45
  import { claudeAvailableProbe, claudeExistsProbe } from './mcp.js';
49
46
  import { reticleDevLocation } from './next-patch.js';
50
47
  import { scanTestids, storeHints, scanStores } from './capabilities.js';
51
- import { CURSOR_DIR_RELPATH, CURSOR_MCP_RELPATH } from './cursor.js';
52
- import { fileBackedClients, clientMarkerRelPath, ConfigScope, McpClient } from './mcp-clients.js';
48
+ import { fileBackedClients, clientMarkerRelPath, ConfigScope, McpClient, CURSOR_PROJECT_MARKER, } from './mcp-clients.js';
53
49
  import { deriveProjectId, packageName } from './project-id.js';
54
50
  import { VITE_DEV_MODULE_PATH, connectArgWithToken, staticPageSnippet } from './snippets.js';
55
51
  import { CLAUDE_COMMAND_PATH, CURSOR_COMMAND_PATH } from './slash-command.js';
@@ -96,7 +92,6 @@ export function resolveLockfiles(rootFiles, cwd, io, nodeModulesMarkers = new Se
96
92
  }
97
93
  return set;
98
94
  }
99
- const PACKAGE_JSON = 'package.json';
100
95
  const NODE_MODULES_DIR = 'node_modules';
101
96
  /**
102
97
  * Root-layout candidates, App Router only. `--src-dir` apps keep theirs under `src/app`, and the
@@ -200,12 +195,6 @@ function dependencyNames(pkg) {
200
195
  ...Object.keys(p['devDependencies'] ?? {}),
201
196
  ]);
202
197
  }
203
- const VITE_CONFIG_CANDIDATES = [
204
- 'vite.config.ts',
205
- 'vite.config.js',
206
- 'vite.config.mjs',
207
- 'vite.config.mts',
208
- ];
209
198
  const ASTRO_CONFIG_CANDIDATES = [
210
199
  'astro.config.mjs',
211
200
  'astro.config.js',
@@ -216,12 +205,6 @@ const ASTRO_CONFIG_CANDIDATES = [
216
205
  const ASTRO_LAYOUTS_DIR = 'src/layouts';
217
206
  /** Also searched: an app with no layouts directory renders the document straight from a page. */
218
207
  const ASTRO_PAGES_DIR = 'src/pages';
219
- const NEXT_CONFIG_CANDIDATES = [
220
- 'next.config.mjs',
221
- 'next.config.js',
222
- 'next.config.ts',
223
- 'next.config.cjs',
224
- ];
225
208
  /**
226
209
  * `⚠` means WORK LEFT TO DO and nothing else — it is what an agent (and the release gate) counts to
227
210
  * decide whether the install finished. A notice gets its own mark so "steps remaining" can reach zero
@@ -266,8 +249,7 @@ function agentRootOf(options) {
266
249
  const root = options.agentRoot;
267
250
  return root === undefined || root === options.cwd ? undefined : root;
268
251
  }
269
- function gatherPlanInput(options, io, pkgRaw) {
270
- const pkg = JSON.parse(pkgRaw);
252
+ function gatherPlanInput(options, io, pkg) {
271
253
  // Stable identity derived from the app's package.json name + root, so it survives port changes.
272
254
  const projectId = deriveProjectId(packageName(pkg), options.cwd);
273
255
  const rootFiles = new Set(io.rootFiles());
@@ -291,32 +273,25 @@ function gatherPlanInput(options, io, pkgRaw) {
291
273
  const claudeCli = options.mcp ? io.probe(availableProbe.command, availableProbe.args) : false;
292
274
  const existsProbe = claudeExistsProbe();
293
275
  const mcpExists = claudeCli ? io.probe(existsProbe.command, existsProbe.args) : false;
294
- // join(), not a literal '/': this path is checked and WRITTEN on the user's machine, and a
295
- // backslash platform silently misses the config it is meant to find.
296
- const cursorDir = join(io.homeDir(), CURSOR_DIR_RELPATH);
297
- const cursorConfigPath = join(io.homeDir(), CURSOR_MCP_RELPATH);
298
- // Cursor is "present" if its global config directory exists OR the project carries a `.cursor/`.
299
- // The directory alone missed a real case: Cursor installed with a FRESH profile has not written
300
- // ~/.cursor yet, so a user who had it open got no Cursor step and no message about one. A
301
- // project-level .cursor/ is unambiguous evidence somebody uses it here.
302
- const cursorPresent = options.mcp && (io.exists(cursorDir) || io.exists(CURSOR_DIR_RELPATH));
303
- const cursorConfig = cursorPresent ? io.readFile(cursorConfigPath) : null;
304
- // Every OTHER MCP client this machine shows evidence of. Conservative and one-directional: we
305
- // write into a config a client ALREADY has, and never create ~/.gemini or ~/.codeium for somebody
306
- // who does not use them. Cursor and Claude are handled above by their own steps.
276
+ // Every MCP client this machine shows evidence of. Conservative and one-directional: we write
277
+ // into a config a client ALREADY has, and never create ~/.gemini or ~/.codeium for somebody who
278
+ // does not use them.
307
279
  const detectedClients = options.mcp
308
280
  ? fileBackedClients()
309
- .filter((spec) => spec.id !== McpClient.CURSOR)
310
281
  .map((spec) => {
311
282
  const marker = clientMarkerRelPath(spec);
312
283
  const absolute = spec.scope === ConfigScope.HOME ? join(io.homeDir(), spec.relPath) : spec.relPath;
313
284
  const markerPath = spec.scope === ConfigScope.HOME ? join(io.homeDir(), marker) : marker;
314
- if (!io.exists(markerPath))
285
+ // A fresh Cursor profile has not written ~/.cursor yet; the project-level .cursor/ is the
286
+ // fallback that kept that real case working. Same signal, project scope.
287
+ const projectFallback = spec.id === McpClient.CURSOR && !io.exists(markerPath)
288
+ ? io.exists(CURSOR_PROJECT_MARKER)
289
+ : false;
290
+ if (!io.exists(markerPath) && !projectFallback)
315
291
  return null;
316
292
  return {
317
293
  id: spec.id,
318
294
  configPath: absolute,
319
- // io.readFile passes absolute paths through unchanged, so one call covers both scopes.
320
295
  existing: io.readFile(absolute),
321
296
  };
322
297
  })
@@ -349,21 +324,27 @@ function gatherPlanInput(options, io, pkgRaw) {
349
324
  const sourceFiles = readSourceFiles(io);
350
325
  const agentRoot = agentRootOf(options);
351
326
  const agentFile = (relPath) => agentRoot === undefined ? relPath : join(agentRoot, relPath);
327
+ const cspSources = {};
328
+ for (const file of CSP_FILES) {
329
+ const source = io.readFile(file);
330
+ if (null !== source)
331
+ cspSources[file] = source;
332
+ }
352
333
  return {
353
334
  detection,
335
+ captureBodies: options.captureBodies,
336
+ cspSources,
354
337
  claudeCli,
355
338
  mcpExists,
356
- cursorPresent,
357
339
  platform: process.platform,
358
340
  detectedClients,
359
- cursorProjectPresent: io.exists(CURSOR_DIR_RELPATH),
360
- cursorConfig,
361
- cursorConfigPath,
341
+ cursorProjectPresent: io.exists(CURSOR_PROJECT_MARKER),
362
342
  viteConfig,
363
343
  astroConfig: astroPath !== null && astroSource !== null ? { path: astroPath, source: astroSource } : null,
364
344
  astroLayout: layoutRelPath !== null && astroLayoutSource !== null
365
345
  ? { path: layoutRelPath, source: astroLayoutSource }
366
346
  : null,
347
+ astroEnvDts: io.readFile('src/env.d.ts'),
367
348
  nextConfigFile,
368
349
  nextConfigSource: null === nextConfigFile ? null : io.readFile(nextConfigFile),
369
350
  nextLayout: layoutPath !== null && layoutSource !== null
@@ -414,69 +395,19 @@ function gatherPlanInput(options, io, pkgRaw) {
414
395
  }
415
396
  /** Where workspace tooling conventionally puts packages. */
416
397
  /** pnpm's workspace declaration, read when present — it is authoritative about where packages live. */
417
- const PNPM_WORKSPACE = 'pnpm-workspace.yaml';
418
- /** Deps that mark a directory as a runnable web app even when it has no bundler config file. */
419
- const APP_DEPS = ['next', 'vite'];
420
- function looksLikeApp(dir, io) {
421
- const pkgRaw = io.readFile(`${dir}/${PACKAGE_JSON}`);
422
- if (null === pkgRaw)
423
- return false;
424
- const configs = [...VITE_CONFIG_CANDIDATES, ...NEXT_CONFIG_CANDIDATES];
425
- if (configs.some((c) => io.exists(`${dir}/${c}`)))
426
- return true;
427
- // `next.config` is optional in Next, so the dependency list is the other half of the signal.
428
- return APP_DEPS.some((d) => pkgRaw.includes(`"${d}"`));
429
- }
430
- /**
431
- * App directories under a workspace root.
432
- *
433
- * Running `reticle init` at the repo root is what people actually do, and in a monorepo the app is a
434
- * directory down — so init detected "no framework", printed a wall of manual HTML instructions, and
435
- * would have installed the SDK into the ROOT package.json. It already walks UP for the lockfile, so
436
- * it knows it is in a workspace; this is the matching walk DOWN.
437
- */
438
- export function findWorkspaceApps(io) {
439
- const found = [];
440
- // A workspace DECLARES its packages; `['apps','packages']` was a guess that missed a real repo
441
- // with three Next apps at web/, admin/ and space/ — and missing them meant init ran against the
442
- // root and reported ✓ for a file Next never compiles. See workspace-apps.
443
- const pkgRaw = io.readFile(PACKAGE_JSON);
444
- let pkgWorkspaces;
445
- try {
446
- const parsed = null === pkgRaw ? undefined : JSON.parse(pkgRaw);
447
- pkgWorkspaces =
448
- 'object' === typeof parsed && parsed !== null
449
- ? parsed.workspaces
450
- : undefined;
451
- }
452
- catch {
453
- pkgWorkspaces = undefined;
454
- }
455
- const parents = workspaceParents({
456
- ...(null === io.readFile(PNPM_WORKSPACE)
457
- ? {}
458
- : { pnpmWorkspace: io.readFile(PNPM_WORKSPACE) ?? '' }),
459
- ...(pkgWorkspaces === undefined ? {} : { pkgWorkspaces }),
460
- topLevelDirs: io.listDirs('.'),
461
- });
462
- // A declared parent can itself BE the app (`workspaces: ["web"]`), so check both the directory and
463
- // its children rather than assuming one level of nesting.
464
- for (const parent of parents) {
465
- if (looksLikeApp(parent, io))
466
- found.push(parent);
467
- for (const name of io.listDirs(parent)) {
468
- const dir = `${parent}/${name}`;
469
- if (looksLikeApp(dir, io))
470
- found.push(dir);
471
- }
472
- }
473
- return [...new Set(found)];
474
- }
475
398
  const SKIPPED_DETAIL = 'skipped — the dependency install above failed, and wiring the app to a package that is not ' +
476
399
  'installed stops it booting. Run that install, then re-run `reticle init`.';
477
400
  function report(plan, dryRun, failed, skipped, degraded, io, projectDir, agentRoot,
478
401
  /** The project's own dev command, so the closing line names what a human would type. */
479
- devCommand) {
402
+ devCommand,
403
+ /**
404
+ * Whether this run continues into the phases that boot the app and drive it.
405
+ *
406
+ * The closing hint tells the reader to restart their dev server and then drive a flow. When those
407
+ * are the very next things this command does, printing them is worse than noise: it is an
408
+ * instruction to do by hand what is about to happen automatically, three lines before it happens.
409
+ */
410
+ continuesToRuntime = false) {
480
411
  io.print(dryRun ? 'reticle init (dry run, no files written)' : 'reticle init');
481
412
  // Every path below is printed RELATIVE, and until now nothing said what to. Reported from the
482
413
  // field as "[✓] Reticle config → .reticle.json" followed by the file not being there: the app was
@@ -536,7 +467,9 @@ devCommand) {
536
467
  '"no browser session connected" until then. Everything else is already in place.');
537
468
  io.print('');
538
469
  }
539
- io.print(restartHint(plan.framework, resolvedStatus(plan, MCP_TARGET, failed, skipped), devCommand));
470
+ if (!continuesToRuntime) {
471
+ io.print(restartHint(plan.framework, resolvedStatus(plan, MCP_TARGET, failed, skipped), devCommand));
472
+ }
540
473
  return { ok: !connectPending, applied, manual };
541
474
  }
542
475
  /**
@@ -588,7 +521,10 @@ function applyEffects(plan, io) {
588
521
  // is decoration, and this one is the first thing a new user reads. Same shape as #139.
589
522
  const wrote = spanSync('init.write', { target: s.target, path: write.path }, () => {
590
523
  try {
591
- io.writeFile(write.path, write.content);
524
+ // Format connect modules with the project's Prettier when present (#684) — a clean
525
+ // install must not fail the project's own lint on a file we just wrote.
526
+ const content = formatGeneratedSource(write.content, write.path, io.cwd());
527
+ io.writeFile(write.path, content);
592
528
  }
593
529
  catch {
594
530
  return false; // a throw is the loud version of the same failure
@@ -663,16 +599,36 @@ const AMBIGUOUS_HEADER = 'Several apps found in this workspace. Re-run `reticle
663
599
  * guessing which app someone meant is worse than one line of output.
664
600
  * Returns null when there is nothing to redirect to — the caller then proceeds here as before.
665
601
  */
666
- function redirectToWorkspaceApp(options, io, pkgRaw) {
602
+ function redirectToWorkspaceApp(options, io, pkg) {
667
603
  if (true === options.redirected)
668
604
  return null;
669
- const pkg = JSON.parse(pkgRaw);
670
605
  const rootFiles = new Set(io.rootFiles());
671
606
  const here = detect({
672
607
  pkg: 'object' === typeof pkg && pkg !== null ? pkg : {},
673
608
  configFiles: rootFiles,
674
609
  lockfiles: new Set(),
675
610
  });
611
+ // `--app` is an INSTRUCTION, and it is read before the guess below. The guess answers "where is
612
+ // the app?" for somebody who did not say; when somebody said, there is nothing left to infer.
613
+ //
614
+ // It used to be read after, and the check underneath returns early for any directory that looks
615
+ // like an app — which a JS monorepo ROOT does, because shared tooling puts `vite` in its
616
+ // devDependencies. So on a real pnpm+turbo monorepo (measured on nuclear, a Tauri v2 app at
617
+ // product scale) `reticle init --app packages/player` silently ignored the flag, installed the
618
+ // SDK into the root's package.json, wrote `.reticle.json` and a whole `src/reticle-dev.ts` into a
619
+ // repository root that has no `src/`, left `packages/player` untouched — and reported three ✓ and
620
+ // one ⚠. The one flag documented for this shape wired the wrong directory and said it worked.
621
+ //
622
+ // Existence is the test, not membership of the discovered list: discovery scans conventional
623
+ // directories, and somebody who names a path knows their own layout better than the scan does.
624
+ const named = options.app === undefined || '' === options.app ? undefined : options.app;
625
+ if (named !== undefined) {
626
+ const wanted = named.replace(/\/+$/, '');
627
+ if (io.exists(`${wanted}/${PACKAGE_JSON}`))
628
+ return enterApp(options, io, wanted, 'Wiring');
629
+ io.print(`--app ${named} does not name a directory with a package.json in it.`);
630
+ return { ok: false, applied: 0, manual: 1 };
631
+ }
676
632
  if (here.framework !== Framework.HTML)
677
633
  return null; // this directory IS the app
678
634
  const apps = findWorkspaceApps(io);
@@ -695,7 +651,18 @@ function redirectToWorkspaceApp(options, io, pkgRaw) {
695
651
  io.print(`Or name one without changing directory: reticle init --app ${apps[0] ?? '<dir>'}`);
696
652
  return { ok: false, applied: 0, manual: apps.length };
697
653
  }
698
- io.print(`No app in this directory — wiring ${target} instead.`);
654
+ return enterApp(options, io, target, 'No app in this directory — wiring');
655
+ }
656
+ /**
657
+ * Re-enter `init` scoped to one directory of a workspace.
658
+ *
659
+ * One implementation for both routes in — the app somebody NAMED and the app discovery found when
660
+ * nobody did. They differ only in the sentence printed; scoping the io, moving the cwd and keeping
661
+ * the agent root has to be identical for both, and was worth having twice for exactly as long as it
662
+ * took to get one of them wrong.
663
+ */
664
+ function enterApp(options, io, target, lead) {
665
+ io.print(`${lead} ${target}.`);
699
666
  io.print('');
700
667
  return runInit({
701
668
  ...options,
@@ -721,8 +688,42 @@ export function runInit(options, io) {
721
688
  }
722
689
  return result;
723
690
  }
691
+ /**
692
+ * The manifest, parsed once, or the reason it could not be.
693
+ *
694
+ * It used to be parsed in three places from the same raw string, and two of them were unguarded —
695
+ * so `reticle init` on a `package.json` with a trailing comma died with a raw `SyntaxError` and a
696
+ * stack through `redirectToWorkspaceApp`. A stack trace in front of a user is a bug whatever caused
697
+ * it, and this one lands on the very first thing the command does, before it has said anything.
698
+ *
699
+ * `setup/reticle.mjs` has always got this right ("… is not valid JSON (…). Fix it and re-run"). The
700
+ * shipped CLI did not, which is the shape of every divergence between the two: the prototype refuses
701
+ * politely, `init` throws. Parsing once at the single point the file enters means no later caller
702
+ * CAN reintroduce it — a guard per call site would have been three guards and a fourth one waiting.
703
+ */
704
+ function readManifest(io) {
705
+ const raw = io.readFile(PACKAGE_JSON);
706
+ if (null === raw)
707
+ return { pkg: null };
708
+ try {
709
+ return { pkg: JSON.parse(raw) };
710
+ }
711
+ catch (err) {
712
+ // First line only: JSON.parse's message carries the offending position, and the rest is noise.
713
+ const detail = String(err instanceof Error ? err.message : err).split('\n')[0] ?? 'unparseable';
714
+ return { error: detail };
715
+ }
716
+ }
724
717
  function runInitSteps(options, io) {
725
- const pkgRaw = io.readFile(PACKAGE_JSON);
718
+ const manifest = readManifest(io);
719
+ if ('error' in manifest) {
720
+ io.print(`${PACKAGE_JSON} is not valid JSON (${manifest.error}). Fix it and re-run — init reads the ` +
721
+ 'framework, the dev script and the package manager from it, and will not guess at any of ' +
722
+ 'them from a file it cannot read.');
723
+ reportInitOutcome({ ok: false, reason: InitFailure.MALFORMED_PACKAGE_JSON });
724
+ return { ok: false, applied: 0, manual: 0 };
725
+ }
726
+ const pkgRaw = manifest.pkg;
726
727
  // Look for the app BEFORE concluding there isn't one.
727
728
  //
728
729
  // A root with no package.json is not a dead end — it is the ordinary shape of a repo whose app
@@ -735,7 +736,7 @@ function runInitSteps(options, io) {
735
736
  //
736
737
  // `'{}'` because the redirect only needs the manifest to ask "is THIS directory the app", and a
737
738
  // directory with no package.json is definitively not.
738
- const redirectedEarly = redirectToWorkspaceApp(options, io, pkgRaw ?? '{}');
739
+ const redirectedEarly = redirectToWorkspaceApp(options, io, pkgRaw ?? {});
739
740
  if (redirectedEarly !== null)
740
741
  return redirectedEarly;
741
742
  if (null === pkgRaw) {
@@ -759,6 +760,20 @@ function runInitSteps(options, io) {
759
760
  // 1–6s per app with no explanation of the spread. These three spans split that number into detect
760
761
  // (filesystem probing), plan (pure), and apply (writes + package-manager and CLI subprocesses).
761
762
  const planInput = gatherPlanInput(options, io, pkgRaw);
763
+ // Before anything is written, and AFTER detection — the package manager to check is the one init
764
+ // RESOLVED, not a raw lockfile read. An inherited pnpm-lock.yaml at a monorepo root does not mean
765
+ // the app in frontend/ uses pnpm, and checking the file refused a scaffold the install gate proves
766
+ // must succeed. Both conditions make every later phase fail, and each arrives far from its cause
767
+ // when it is not checked here. See preflight.ts.
768
+ const refusal = preflightRefusal({
769
+ cwd: () => io.cwd(),
770
+ canWrite: () => io.canWrite(),
771
+ probe: (command, args) => io.probe(command, args),
772
+ }, planInput.detection.packageManager);
773
+ if (refusal !== undefined) {
774
+ io.print(refusal);
775
+ return { ok: false, applied: 0, manual: 1 };
776
+ }
762
777
  const plan = spanSync('init.plan', {}, () => buildPlan(planInput));
763
778
  const effects = options.dryRun
764
779
  ? { failed: new Set(), skipped: new Set(), degraded: new Map() }
@@ -766,7 +781,7 @@ function runInitSteps(options, io) {
766
781
  const { failed, skipped, degraded } = effects;
767
782
  // The project's own dev command, so the closing line names what a human would actually type.
768
783
  const devCommand = devCommandFrom(pkgRaw, planInput.detection.packageManager);
769
- const result = report(plan, options.dryRun, failed, skipped, degraded, io, options.cwd, agentRootOf(options), devCommand);
784
+ const result = report(plan, options.dryRun, failed, skipped, degraded, io, options.cwd, agentRootOf(options), devCommand, true === options.continuesToRuntime);
770
785
  // A dry run is a preview, not an outcome — reporting it would inflate both success and failure.
771
786
  if (options.dryRun)
772
787
  return result;
@@ -777,8 +792,15 @@ function runInitSteps(options, io) {
777
792
  // The step's REAL final status, not the absence of a failure — see mcp-registered.
778
793
  mcpRegistered: wasMcpRegistered(resolvedStatus(plan, MCP_TARGET, failed, skipped)),
779
794
  };
795
+ const context = {
796
+ appDir: options.cwd,
797
+ framework: plan.framework,
798
+ packageManager: planInput.detection.packageManager,
799
+ ...(undefined === devCommand ? {} : { devCommand }),
800
+ ...(true === options.redirected ? { redirectedTo: options.cwd } : {}),
801
+ };
780
802
  if (true === options.deferOutcome)
781
- return { ...result, outcome };
803
+ return { ...result, context, outcome };
782
804
  reportInitOutcome(outcome);
783
- return result;
805
+ return { ...result, context };
784
806
  }
@@ -24,4 +24,4 @@ export declare const CURSOR_COMMAND_PATH = ".cursor/commands/reticle.md";
24
24
  * are told apart.
25
25
  */
26
26
  export declare const SLASH_COMMAND_SIGNATURE = "description: Verify this app in the browser with Reticle";
27
- export declare const SLASH_COMMAND_BODY = "---\ndescription: Verify this app in the browser with Reticle \u2014 drive one real flow and report what happened.\n---\n\nVerify this running app with Reticle. Drive it; do not read the code and guess.\n\n## Step 0 \u2014 is anything connected?\n\nCall `reticle_sessions` first.\n\n**A session is listed?** Go to \"Pick ONE flow\".\n\n**No session?** Setup is not finished, and stopping to ask the user to run something is not an\nanswer. Every step below is yours to do without them. Work these in order, and say which one you are\non, one line each:\n\n0. **Is Reticle even wired into this project?** `cat .reticle.json`. If it is not there, this\n project was never set up: run `npx @reticlehq/server@latest init`,\n fix every `\u26A0` and `\u2139` it prints, and carry on. Do not ask first, and do not ask which\n framework or port \u2014 both come out of the repo you are sitting in.\n\n1. **Is anything serving the app at all?** If a dev server is already listening, use it and go to\n step 2 \u2014 the cause is then almost always that the SDK never loaded in the page, not a missing\n server, so do not tell the user to start one they are already running. If nothing is listening:\n\n**If no dev server is listening, start one yourself.** Read the project's own dev script out of `package.json` (`dev`, `start`, whatever this project calls it), run it in the BACKGROUND, tell the user in one line that it is running and how to stop it, then carry on. Stopping to ask is how a verification turn ends with nothing verified.\n\nFive guards, none optional:\n\n1. **Never start a second one.** If something is already listening on the app's port, use it.\n2. **Never guess the command.** It comes from `package.json` scripts. If there is no recognisable dev script, say so and stop rather than inventing one.\n3. **Never kill anything.** Not a dev server, not a daemon, not a port holder \u2014 including one you started.\n4. **Background it, and say so.** The user must know a server is running and how to stop it. A dev server the human does not know about is the same failure one step later.\n5. **The permission prompt belongs to your host.** Never try to bypass, suppress or auto-approve it, and take a refusal as the answer.\n\n2. **Is a page open on it?** Do not ask the user to open a browser \u2014 open it:\n\n ```bash\n npx @reticlehq/server open <the url the dev server is serving>\n ```\n\n That reuses an already-connected tab or opens a new one, and waits for the page to register. On a\n machine with no browser to open, take a tab Reticle owns instead:\n `reticle_run({ tool: \"reticle_lease\", args: { action: \"acquire\", url: \"<the same url>\" } })`.\n\nThen, once something is serving the app and a page is on it:\n\n3. **Is the SDK actually in the app?** Read the app's entry file (`src/main.tsx`, `app/layout.tsx`,\n `src/app.vue`, whatever this project uses). You are looking for an `import` from\n `@reticlehq/react` or `@reticlehq/browser` and a `connect()` call, or the Reticle plugin in\n `vite.config.*`, or `withReticle` in `next.config.*`. **If it is not there, that is the bug.**\n Wire it in \u2014 see the framework snippets at https://docs.reticle.sh/instrumentation \u2014 then restart\n the dev server and re-check.\n4. **Is the connect guarded on `hostname === 'localhost'`?** If so, remove that guard. It is false on\n any non-localhost dev host, and `window` does not exist during SSR, so the guard silently prevents\n the connect and logs nothing. Use the framework's dev flag instead (`import.meta.env.DEV`,\n `process.env.NODE_ENV !== 'production'`, `import.meta.dev`).\n5. **Is the dev server serving that entry?** Look in `reticle_console`, or in the page you opened in\n step 2, for a line starting `[Reticle]`. That line, or its absence, tells you which side is at\n fault. Reticle itself never starts a dev server \u2014 starting it is your job, under the guards above.\n6. **Do both sides agree on the port?** Run `npx @reticlehq/server doctor` and compare the daemon\n port against the port the page dials.\n\nIf you are still stuck after all of those, file it: `reticle_feedback` with kind `gap`, saying what you\nchecked and what you saw. Then tell the user exactly which step is blocked. Do not report the install\nas finished.\n\n## Pick ONE flow\n\nNot the whole app. Pick the single most important flow you can complete in a handful of steps \u2014 the\none a user would do first (sign in, search, add to cart, submit the form). If the user named a flow or\njust changed some code, use that one instead.\n\nState which flow you picked in one line before you start.\n\n## Drive it where the human can see\n\n1. `reticle_snapshot` \u2014 read the accessibility tree and locate the elements the flow needs. Elements\n are addressable by role and name; you do **not** need `data-testid` to drive them.\n2. Walk the flow with `reticle_act_and_wait`, one step at a time. Narrate each step first \u2014 the human\n is watching the page, and the HUD shows what you say.\n3. After each step, check the effect with `reticle_assert` \u2014 not just that the click dispatched, but\n that the thing it was supposed to do actually happened.\n4. Finish with `reticle_console` and `reticle_network` to catch errors the DOM does not show.\n\n**Only `reticle_act_and_wait` and `reticle_assert` produce a verdict.** A run that ends on a\nsnapshot has proved nothing, however many tools it used.\n\n## Report\n\n- What you drove, step by step, and what each step actually produced.\n- Anything broken, with the `file:line` Reticle gave you.\n- If a step could not be verified, say so plainly. \"Unknown\" is a real answer; a green verdict you\n cannot back is not.\n\nDo not weaken or skip an assertion to make the run pass \u2014 that is a finding, not a fix.\n\n## If the user handed you a license key\n\nAppend `RETICLE_LICENSE_KEY=<key>` to the project's `.env` (create it if absent), make sure `.env`\nis in `.gitignore`, and confirm with `npx @reticlehq/server license`. The CLI folds a project-local\n`.env` in on every invocation, so nothing else changes. Never echo the key back, and never put it in\na commit, a comment, or a feedback report.\n\n## Two things afterwards\n\n- **Anything about Reticle itself that was wrong, missing, confusing, or expensive?** Send it with\n `reticle_feedback` now, before your context is gone. That report is the only signal that decides\n what gets fixed. Bugs in the app under test are Reticle working; those go in your answer to the user.\n- **If the verification actually succeeded**, add one line for the human: Reticle is open source at\n https://github.com/reticlehq/reticle, and a star helps other people find it. One line, once. Skip it\n entirely if the run did not produce a verdict.\n";
27
+ export declare const SLASH_COMMAND_BODY = "---\ndescription: Verify this app in the browser with Reticle \u2014 drive one real flow and report what happened.\n---\n\nVerify this running app with Reticle. Drive it; do not read the code and guess.\n\n## Step 0 \u2014 is anything connected?\n\nCall `reticle_sessions` first.\n\n**A session is listed?** Go to \"Pick ONE flow\".\n\n**No session?** Setup is not finished, and stopping to ask the user to run something is not an\nanswer. Every step below is yours to do without them. Work these in order, and say which one you are\non, one line each:\n\n0. **Is Reticle even wired into this project?** `cat .reticle.json`. If it is not there, this\n project was never set up: run `npx @reticlehq/server@latest init`,\n fix every `\u26A0` and `\u2139` it prints, and carry on. Do not ask first, and do not ask which\n framework or port \u2014 both come out of the repo you are sitting in.\n\n1. **Is anything serving the app at all?** If a dev server is already listening, use it and go to\n step 2 \u2014 the cause is then almost always that the SDK never loaded in the page, not a missing\n server, so do not tell the user to start one they are already running. If nothing is listening:\n\n**A dev server already running when `reticle init` ran does not have Reticle in its bundle.** It read the build config at boot; `init` edited it afterwards. It serves the old bundle and no session appears. In order:\n\n1. **A dev server was already running?** Restart it, then hard-reload the tab. \"Something is listening\" does not mean the right bundle is served.\n2. **Nothing was running?** Start it in the BACKGROUND and say so in one line. `reticle_sessions` gives you this project's own dev command in `next_action`; use that, never compose one. Started after `init`, it needs no restart.\n\nStopping to ask is how a verification turn ends with nothing verified.\n\nFour guards, none optional:\n\n1. **Never run two at once.** One dev server on the app's port. Restarting a stale one means stopping it first, not starting a second alongside it.\n2. **Never guess the command.** It comes from `package.json` scripts. No recognisable dev script means say so and stop, not invent one.\n3. **Never kill anything you did not start**, and never a daemon or a port holder. The one exception is the restart above, and say in one line that you did it.\n4. **The permission prompt belongs to your host.** Never bypass, suppress or auto-approve it, and take a refusal as the answer.\n\n2. **Is a page open on it?** Do not ask the user to open a browser \u2014 open it:\n\n ```bash\n npx @reticlehq/server open <the url the dev server is serving>\n ```\n\n That reuses an already-connected tab or opens a new one, and waits for the page to register. On a\n machine with no browser to open, take a tab Reticle owns instead:\n `reticle_run({ tool: \"reticle_lease\", args: { action: \"acquire\", url: \"<the same url>\" } })`.\n\nThen, once something is serving the app and a page is on it:\n\n3. **Is the SDK actually in the app?** Read the app's entry file (`src/main.tsx`, `app/layout.tsx`,\n `src/app.vue`, whatever this project uses). You are looking for an `import` from\n `@reticlehq/react` or `@reticlehq/browser` and a `connect()` call, or the Reticle plugin in\n `vite.config.*`, or `withReticle` in `next.config.*`. **If it is not there, that is the bug.**\n Wire it in \u2014 see the framework snippets at https://docs.reticle.sh/instrumentation \u2014 then restart\n the dev server and re-check.\n4. **Is the connect guarded on `hostname === 'localhost'`?** If so, remove that guard. It is false on\n any non-localhost dev host, and `window` does not exist during SSR, so the guard silently prevents\n the connect and logs nothing. Use the framework's dev flag instead (`import.meta.env.DEV`,\n `process.env.NODE_ENV !== 'production'`, `import.meta.dev`).\n5. **Is the dev server serving that entry?** Look in `reticle_console`, or in the page you opened in\n step 2, for a line starting `[Reticle]`. That line, or its absence, tells you which side is at\n fault. Reticle itself never starts a dev server \u2014 starting it is your job, under the guards above.\n6. **Do both sides agree on the port?** Run `npx @reticlehq/server doctor` and compare the daemon\n port against the port the page dials.\n\nIf you are still stuck after all of those, file it: `reticle_feedback` with kind `gap`, saying what you\nchecked and what you saw. Then tell the user exactly which step is blocked. Do not report the install\nas finished.\n\n## Pick ONE flow\n\nNot the whole app. Pick the single most important flow you can complete in a handful of steps \u2014 the\none a user would do first (sign in, search, add to cart, submit the form). If the user named a flow or\njust changed some code, use that one instead.\n\nState which flow you picked in one line before you start.\n\n## Drive it where the human can see\n\n1. `reticle_snapshot` \u2014 read the accessibility tree and locate the elements the flow needs. Elements\n are addressable by role and name; you do **not** need `data-testid` to drive them.\n2. Walk the flow with `reticle_act_and_wait`, one step at a time. Narrate each step first \u2014 the human\n is watching the page, and the HUD shows what you say.\n3. After each step, check the effect with `reticle_assert` \u2014 not just that the click dispatched, but\n that the thing it was supposed to do actually happened.\n4. Finish with `reticle_console` and `reticle_network` to catch errors the DOM does not show.\n\n**Only `reticle_act_and_wait` and `reticle_assert` produce a verdict.** A run that ends on a\nsnapshot has proved nothing, however many tools it used.\n\n## Report\n\n- What you drove, step by step, and what each step actually produced.\n- Anything broken, with the `file:line` Reticle gave you.\n- If a step could not be verified, say so plainly. \"Unknown\" is a real answer; a green verdict you\n cannot back is not.\n\nDo not weaken or skip an assertion to make the run pass \u2014 that is a finding, not a fix.\n\n## If the user handed you a license key\n\nAppend `RETICLE_LICENSE_KEY=<key>` to the project's `.env` (create it if absent), make sure `.env`\nis in `.gitignore`, and confirm with `npx @reticlehq/server license`. The CLI folds a project-local\n`.env` in on every invocation, so nothing else changes. Never echo the key back, and never put it in\na commit, a comment, or a feedback report.\n\n## Two things afterwards\n\n- **Anything about Reticle itself that was wrong, missing, confusing, or expensive?** Send it with\n `reticle_feedback` now, before your context is gone. That report is the only signal that decides\n what gets fixed. Bugs in the app under test are Reticle working; those go in your answer to the user.\n- **If the verification actually succeeded**, add one line for the human: Reticle is open source at\n https://github.com/reticlehq/reticle, and a star helps other people find it. One line, once. Skip it\n entirely if the run did not produce a verdict.\n";
@@ -132,41 +132,51 @@ export function nextReticleDevFile(port, projectId, testids = [], stores = [], f
132
132
  // Same rule as the Vite module: a store we found is registered outright, and the commented hint
133
133
  // survives only for the libraries we can name but not wire.
134
134
  const storeImports = found.map((s) => `import { ${s.ident} } from '${s.importPath}';`).join('\n');
135
+ // Eight spaces: nested inside useEffect → .then callback (see multiline shape below for #684).
135
136
  const storeBlock = found.length > 0
136
- ? found.map((s) => ` registerStore('${s.key}', ${s.ident});`).join('\n')
137
+ ? found.map((s) => ` registerStore('${s.key}', ${s.ident});`).join('\n')
137
138
  : 0 === stores.length
138
- ? ' // No state library detected. If you add one, register it here — see node_modules/@reticlehq/server/docs/usage.md.'
139
- : stores.map((h) => ` // import your store, then: ${h}`).join('\n');
139
+ ? ' // No state library detected. If you add one, register it here — see node_modules/@reticlehq/server/docs/usage.md.'
140
+ : stores.map((h) => ` // import your store, then: ${h}`).join('\n');
141
+ const registerNames = found.length > 0 ? ', registerStore' : '';
142
+ // Multiline connect + single blank after imports (#684): the one-line connect and the double blank
143
+ // both fail Prettier on a clean Next install.
140
144
  return `'use client';
141
145
  import { useEffect } from 'react';
142
- ${storeImports.length > 0 ? storeImports : ''}
143
-
146
+ ${storeImports.length > 0 ? `${storeImports}\n` : ''}
144
147
  /** Dev-only: connect Reticle + install the React adapter, after hydration. */
145
148
  export function ReticleDev() {
146
149
  useEffect(() => {
147
150
  if (process.env.NODE_ENV !== 'development') return;
148
- void import('@reticlehq/react').then(({ reticle, install, registerCapabilities${found.length > 0 ? ', registerStore' : ''} }) => {
149
- install();
150
- // Both provided by withReticle() in next.config. The bridge rejects a connect with no token;
151
- // the root makes source paths repo-relative instead of absolute.
152
- const token = process.env.NEXT_PUBLIC_RETICLE_TOKEN;
153
- const root = process.env.NEXT_PUBLIC_RETICLE_ROOT;
154
- // withReticle() finds the daemon serving this project on every dev-server start. It wins over
155
- // any port written into this file at install time, so moving the daemon needs no edit here.
156
- const url = process.env.NEXT_PUBLIC_RETICLE_URL;
157
- reticle.connect({ ${fields}...(url ? { url } : {}), ...(token ? { token } : {}), ...(root ? { root } : {}) });
151
+ void import('@reticlehq/react').then(
152
+ ({ reticle, install, registerCapabilities${registerNames} }) => {
153
+ install();
154
+ // Both provided by withReticle() in next.config. The bridge rejects a connect with no token;
155
+ // the root makes source paths repo-relative instead of absolute.
156
+ const token = process.env.NEXT_PUBLIC_RETICLE_TOKEN;
157
+ const root = process.env.NEXT_PUBLIC_RETICLE_ROOT;
158
+ // withReticle() finds the daemon serving this project on every dev-server start. It wins over
159
+ // any port written into this file at install time, so moving the daemon needs no edit here.
160
+ const url = process.env.NEXT_PUBLIC_RETICLE_URL;
161
+ reticle.connect({
162
+ ${fields}...(url ? { url } : {}),
163
+ ...(token ? { token } : {}),
164
+ ...(root ? { root } : {}),
165
+ });
158
166
 
159
- // ── Start with ONE flow. ──────────────────────────────────────────────────────────────────
160
- // Registering a store is the highest-value line here: it lets the agent check what the app
161
- // BELIEVES, not just what it rendered. Pass the STORE, not \`() => store.getState()\` — the store
162
- // form wires \`subscribe\` too, so every mutation emits a diff; the getter form is read-only.
167
+ // ── Start with ONE flow. ────────────────────────────────────────────────────────────────
168
+ // Registering a store is the highest-value line here: it lets the agent check what the app
169
+ // BELIEVES, not just what it rendered. Pass the STORE, not \`() => store.getState()\` — the
170
+ // store form wires \`subscribe\` too, so every mutation emits a diff; the getter form is
171
+ // read-only.
163
172
  ${storeBlock}
164
- registerCapabilities({
165
- testids: [${ids}],${0 === testids.length ? ' // none found; add data-testid to your key elements' : ''}
166
- signals: [], // names you pass to reticle.signal()
167
- stores: [${found.map((s) => `'${s.key}'`).join(', ')}], // the keys you registered above
168
- });
169
- });
173
+ registerCapabilities({
174
+ testids: [${ids}],${0 === testids.length ? ' // none found; add data-testid to your key elements' : ''}
175
+ signals: [], // names you pass to reticle.signal()
176
+ stores: [${found.map((s) => `'${s.key}'`).join(', ')}], // the keys you registered above
177
+ });
178
+ },
179
+ );
170
180
  }, []);
171
181
  return null;
172
182
  }
@@ -289,6 +299,12 @@ ${layoutHost(layoutPath)}
289
299
  }
290
300
  </script>
291
301
 
302
+ 3. In src/env.d.ts — declare the Vite define names so \`astro check\` can see them (create-astro's
303
+ default build runs check first, and without this it fails with Cannot find name '__RETICLE_TOKEN__'):
304
+
305
+ declare const __RETICLE_TOKEN__: string | undefined;
306
+ declare const __RETICLE_ROOT__: string | undefined;
307
+
292
308
  Start the daemon BEFORE \`astro dev\`, so the token file exists when the config is read. Until it does the token is empty and the page reloads once the daemon is up.`;
293
309
  }
294
310
  /**
@@ -14,5 +14,5 @@ export declare const VitePatchKind: {
14
14
  };
15
15
  export type VitePatchKind = PatchKind;
16
16
  type VitePatch = SourcePatch;
17
- export declare function patchViteConfig(source: string, port?: number): VitePatch;
17
+ export declare function patchViteConfig(source: string, port?: number, captureBodies?: boolean): VitePatch;
18
18
  export {};