@reticlehq/server 3.4.0 → 3.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (350) hide show
  1. package/README.md +1 -1
  2. package/SKILL.md +16 -8
  3. package/dist/chromium-launch-options.d.ts +3 -1
  4. package/dist/chromium-launch-options.js +2 -1
  5. package/dist/command/cli/adhoc-verdict.d.ts +1 -0
  6. package/dist/command/cli/adhoc-verdict.js +6 -3
  7. package/dist/command/cli/answers/open-note.d.ts +2 -0
  8. package/dist/command/cli/answers/open-note.js +22 -0
  9. package/dist/command/cli/auth/cloud-session.d.ts +4 -4
  10. package/dist/command/cli/auth/device-flow.d.ts +2 -2
  11. package/dist/command/cli/cli-flow-commands.js +4 -8
  12. package/dist/command/cli/cli-parse.d.ts +1 -1
  13. package/dist/command/cli/cli-parse.js +13 -11
  14. package/dist/command/cli/cli-verify.d.ts +1 -1
  15. package/dist/command/cli/cli-verify.js +35 -25
  16. package/dist/command/cli/cloud-cli.js +110 -12
  17. package/dist/command/cli/cloud-login.d.ts +9 -1
  18. package/dist/command/cli/cloud-login.js +43 -8
  19. package/dist/command/cli/demo/demo-page.js +11 -3
  20. package/dist/command/cli/doctor/browser/chromium-hint.d.ts +46 -0
  21. package/dist/command/cli/doctor/browser/chromium-hint.js +99 -1
  22. package/dist/command/cli/drive/drive-attach.d.ts +27 -2
  23. package/dist/command/cli/drive/drive-attach.js +71 -4
  24. package/dist/command/cli/drive/drive-command.d.ts +2 -1
  25. package/dist/command/cli/drive/drive-command.js +13 -7
  26. package/dist/command/cli/lifecycle/daemon-lifecycle.js +4 -1
  27. package/dist/command/cli/ports/port-holder.d.ts +10 -0
  28. package/dist/command/cli/ports/port-holder.js +24 -0
  29. package/dist/command/cli/setup-mcp-cli.js +15 -5
  30. package/dist/command/cli/tutorial.d.ts +2 -2
  31. package/dist/command/cli/tutorial.js +6 -9
  32. package/dist/command/cli.d.ts +6 -0
  33. package/dist/command/cli.js +45 -23
  34. package/dist/command/daemon/binding/daemon-status-probe.d.ts +2 -0
  35. package/dist/command/daemon/binding/daemon-status-probe.js +1 -0
  36. package/dist/command/daemon/daemon-resolve.d.ts +14 -0
  37. package/dist/command/daemon/daemon-resolve.js +33 -2
  38. package/dist/command/setup/agent-writer.d.ts +1 -1
  39. package/dist/command/setup/agent-writer.js +3 -2
  40. package/dist/command/setup/bringup/connect-lease.d.ts +1 -0
  41. package/dist/command/setup/bringup/connect-lease.js +1 -0
  42. package/dist/command/setup/bringup/dev-server-wait.d.ts +10 -0
  43. package/dist/command/setup/bringup/dev-server-wait.js +20 -1
  44. package/dist/command/setup/init/init-port.d.ts +4 -0
  45. package/dist/command/setup/init/init-port.js +10 -0
  46. package/dist/command/setup/init/init-runtime.d.ts +10 -0
  47. package/dist/command/setup/init/init-runtime.js +71 -9
  48. package/dist/command/setup/node-effects.js +8 -2
  49. package/dist/command/setup/remaining-steps.js +2 -1
  50. package/dist/command/setup/run-setup.d.ts +7 -0
  51. package/dist/command/setup/run-setup.js +38 -25
  52. package/dist/command/setup/setup-command.d.ts +2 -1
  53. package/dist/command/setup/setup-command.js +2 -0
  54. package/dist/command/setup/setup-mcp.d.ts +2 -0
  55. package/dist/command/setup/setup-mcp.js +14 -5
  56. package/dist/command/version/peer-announce.d.ts +6 -2
  57. package/dist/command/version/peer-announce.js +8 -6
  58. package/dist/command/version/version-nudge.d.ts +17 -3
  59. package/dist/command/version/version-nudge.js +25 -4
  60. package/dist/command/version/version-skew.js +11 -2
  61. package/dist/features/crawl/crawl-tools.js +4 -0
  62. package/dist/features/crawl/crawl.d.ts +6 -0
  63. package/dist/features/crawl/crawl.js +9 -1
  64. package/dist/features/exhaust/ledger.d.ts +7 -0
  65. package/dist/features/exhaust/ledger.js +35 -9
  66. package/dist/features/harness/app-findings.d.ts +2 -0
  67. package/dist/features/harness/app-findings.js +52 -0
  68. package/dist/features/harness/drive-report.d.ts +27 -1
  69. package/dist/features/harness/drive-report.js +162 -11
  70. package/dist/features/harness/drivers.d.ts +10 -14
  71. package/dist/features/harness/drivers.js +10 -14
  72. package/dist/features/harness/fill-values.d.ts +3 -69
  73. package/dist/features/harness/fill-values.js +3 -133
  74. package/dist/features/harness/goals.d.ts +21 -0
  75. package/dist/features/harness/goals.js +77 -0
  76. package/dist/features/harness/harness.d.ts +21 -0
  77. package/dist/features/harness/harness.js +69 -2
  78. package/dist/features/harness/platform/drive-target.d.ts +30 -0
  79. package/dist/features/harness/platform/drive-target.js +81 -0
  80. package/dist/features/harness/platform/local-apps.d.ts +94 -0
  81. package/dist/features/harness/platform/local-apps.js +204 -0
  82. package/dist/features/harness/platform/remote-drive.d.ts +180 -0
  83. package/dist/features/harness/platform/remote-drive.js +365 -0
  84. package/dist/features/harness/platform/script.d.ts +48 -0
  85. package/dist/features/harness/platform/script.js +128 -0
  86. package/dist/features/harness/platform/server-driver.d.ts +41 -0
  87. package/dist/features/harness/platform/server-driver.js +179 -0
  88. package/dist/features/harness/platform-config.d.ts +7 -0
  89. package/dist/features/harness/platform-config.js +14 -2
  90. package/dist/features/harness/script/app-controls.d.ts +5 -0
  91. package/dist/features/harness/script/app-controls.js +40 -0
  92. package/dist/features/harness/script/lane-ids.d.ts +14 -0
  93. package/dist/features/harness/script/lane-ids.js +16 -0
  94. package/dist/features/harness/script/script-run.d.ts +41 -0
  95. package/dist/features/harness/script/script-run.js +299 -0
  96. package/dist/features/license/license.d.ts +2 -2
  97. package/dist/features/visual/visual-tools.d.ts +10 -1
  98. package/dist/features/visual/visual-tools.js +40 -3
  99. package/dist/hooks/driven-by.d.ts +4 -0
  100. package/dist/hooks/driven-by.js +16 -0
  101. package/dist/hooks/hook-commands.js +15 -4
  102. package/dist/hooks/tool-log.d.ts +9 -0
  103. package/dist/hooks/tool-log.js +29 -0
  104. package/dist/index.d.ts +1 -0
  105. package/dist/index.js +91 -89
  106. package/dist/judgement/capsule/capsule-store.d.ts +4 -4
  107. package/dist/judgement/runs/artifact/build-verification-run.js +6 -0
  108. package/dist/judgement/runs/artifact/replay-mapping.d.ts +1 -1
  109. package/dist/judgement/runs/artifact/replay-mapping.js +16 -6
  110. package/dist/judgement/runs/artifact/run-store.d.ts +1 -1
  111. package/dist/judgement/runs/artifact/run-store.js +26 -1
  112. package/dist/judgement/runs/drive-run.d.ts +22 -0
  113. package/dist/judgement/runs/drive-run.js +99 -1
  114. package/dist/judgement/runs/repair-prompt.js +1 -1
  115. package/dist/judgement/runs/reticle-runner.js +8 -1
  116. package/dist/judgement/runs/verification-sync.d.ts +3 -2
  117. package/dist/judgement/runs/verification-sync.js +4 -3
  118. package/dist/language/flows/change/gate.d.ts +10 -0
  119. package/dist/language/flows/change/gate.js +22 -0
  120. package/dist/language/flows/decision.js +34 -0
  121. package/dist/language/flows/flow-anchor.d.ts +54 -8
  122. package/dist/language/flows/flow-anchor.js +88 -3
  123. package/dist/language/flows/flow-cross-step.d.ts +15 -0
  124. package/dist/language/flows/flow-cross-step.js +21 -0
  125. package/dist/language/flows/flow-learning.js +8 -11
  126. package/dist/language/flows/flow-mutate-tools.js +5 -3
  127. package/dist/language/flows/flow-replay-run.d.ts +23 -9
  128. package/dist/language/flows/flow-replay-run.js +101 -94
  129. package/dist/language/flows/flow-replay.d.ts +23 -1
  130. package/dist/language/flows/flow-replay.js +92 -19
  131. package/dist/language/flows/flow-scope.js +5 -1
  132. package/dist/language/flows/flow-step-runners.d.ts +20 -2
  133. package/dist/language/flows/flow-step-runners.js +236 -42
  134. package/dist/language/flows/flow-store-for-session.d.ts +2 -2
  135. package/dist/language/flows/flow-store-for-session.js +2 -2
  136. package/dist/language/flows/flow-tools.js +58 -34
  137. package/dist/language/flows/flows.js +5 -2
  138. package/dist/language/flows/heal-run.js +6 -4
  139. package/dist/language/flows/recording/flake.d.ts +2 -2
  140. package/dist/language/flows/recording/tape/recordings.d.ts +30 -4
  141. package/dist/language/flows/recording/tape/recordings.js +40 -8
  142. package/dist/language/flows/replay-from-hud.d.ts +20 -0
  143. package/dist/language/flows/replay-from-hud.js +51 -0
  144. package/dist/language/flows/replay.d.ts +9 -5
  145. package/dist/language/flows/replay.js +27 -9
  146. package/dist/language/flows/suite/suite-flakes.js +2 -0
  147. package/dist/language/flows/suite-selection.d.ts +2 -15
  148. package/dist/language/flows/suite-selection.js +4 -3
  149. package/dist/language/flows/verify-change-tools.js +8 -4
  150. package/dist/launch-chromium.d.ts +27 -5
  151. package/dist/launch-chromium.js +38 -8
  152. package/dist/memory/cloud/cloud-config.d.ts +12 -0
  153. package/dist/memory/cloud/cloud-config.js +15 -0
  154. package/dist/memory/cloud/cloud-sync.d.ts +17 -14
  155. package/dist/memory/cloud/cloud-sync.js +12 -1
  156. package/dist/memory/cloud/harness-config.d.ts +26 -1
  157. package/dist/memory/cloud/harness-config.js +99 -4
  158. package/dist/memory/cloud/hud-notices-source.d.ts +21 -0
  159. package/dist/memory/cloud/hud-notices-source.js +128 -0
  160. package/dist/memory/cloud/memory-scope.d.ts +144 -0
  161. package/dist/memory/cloud/memory-scope.js +205 -0
  162. package/dist/memory/cloud/sync-cycle.d.ts +21 -3
  163. package/dist/memory/cloud/sync-cycle.js +45 -5
  164. package/dist/memory/cloud/sync-daemon.js +7 -5
  165. package/dist/memory/cloud/sync-disk.d.ts +1 -1
  166. package/dist/memory/cloud/sync-disk.js +5 -8
  167. package/dist/memory/impact/account-watch.d.ts +19 -0
  168. package/dist/memory/impact/account-watch.js +34 -0
  169. package/dist/memory/impact/impact-recorder.d.ts +8 -0
  170. package/dist/memory/impact/impact-recorder.js +12 -2
  171. package/dist/memory/impact/impact-store.d.ts +16 -0
  172. package/dist/memory/impact/impact-store.js +142 -26
  173. package/dist/memory/intent/intent-shard.d.ts +2 -2
  174. package/dist/memory/intent/intent-tools.js +88 -5
  175. package/dist/memory/journal/envelope.d.ts +16 -16
  176. package/dist/memory/journal/journal-query.d.ts +14 -0
  177. package/dist/memory/journal/journal-query.js +15 -0
  178. package/dist/memory/journal/journal-recorder.js +7 -1
  179. package/dist/memory/journal/on-disk/retention.d.ts +3 -0
  180. package/dist/memory/journal/on-disk/retention.js +5 -0
  181. package/dist/memory/journal/on-disk/startup-maintenance.js +3 -1
  182. package/dist/memory/journal/on-disk/workspace-tiers.js +7 -4
  183. package/dist/memory/journal/session-end.js +6 -4
  184. package/dist/memory/project/artifact-root-resolver.d.ts +36 -1
  185. package/dist/memory/project/artifact-root-resolver.js +134 -26
  186. package/dist/memory/project/artifact-root.d.ts +42 -5
  187. package/dist/memory/project/artifact-root.js +87 -8
  188. package/dist/memory/project/dir/reticle-dir.d.ts +4 -2
  189. package/dist/memory/project/dir/reticle-dir.js +3 -1
  190. package/dist/memory/project/project-tools.js +22 -3
  191. package/dist/memory/project/session-root.d.ts +24 -1
  192. package/dist/memory/project/session-root.js +41 -5
  193. package/dist/memory/project/sync-status.d.ts +39 -0
  194. package/dist/memory/project/sync-status.js +90 -0
  195. package/dist/memory/recall/memory-tools.js +2 -0
  196. package/dist/memory/recall/project-memory.d.ts +19 -0
  197. package/dist/memory/recall/project-memory.js +45 -6
  198. package/dist/portal/bridge/bridge.d.ts +7 -4
  199. package/dist/portal/bridge/bridge.js +25 -3
  200. package/dist/portal/bridge/pairing-token.d.ts +9 -0
  201. package/dist/portal/bridge/pairing-token.js +17 -0
  202. package/dist/portal/input/real-input.d.ts +81 -10
  203. package/dist/portal/input/real-input.js +207 -22
  204. package/dist/portal/input/viewport-tools.js +19 -9
  205. package/dist/portal/pool/browser-pool.d.ts +22 -9
  206. package/dist/portal/pool/browser-pool.js +70 -35
  207. package/dist/portal/pool/playwright-launcher.d.ts +13 -0
  208. package/dist/portal/pool/playwright-launcher.js +17 -1
  209. package/dist/portal/pool/pool-contract.d.ts +7 -4
  210. package/dist/portal/session/command-payload.d.ts +8 -0
  211. package/dist/portal/session/command-payload.js +19 -0
  212. package/dist/portal/session/dev-server/dev-command.d.ts +12 -0
  213. package/dist/portal/session/dev-server/dev-command.js +61 -12
  214. package/dist/portal/session/facts/departure-note.d.ts +22 -0
  215. package/dist/portal/session/facts/departure-note.js +47 -0
  216. package/dist/portal/session/facts/session-replaced.d.ts +5 -0
  217. package/dist/portal/session/facts/session-replaced.js +5 -0
  218. package/dist/portal/session/first-run-wiring.d.ts +42 -0
  219. package/dist/portal/session/first-run-wiring.js +132 -0
  220. package/dist/portal/session/human/notes-ledger.d.ts +20 -0
  221. package/dist/portal/session/human/notes-ledger.js +55 -0
  222. package/dist/portal/session/human/review-store.d.ts +12 -0
  223. package/dist/portal/session/human/review-store.js +18 -10
  224. package/dist/portal/session/live-control-tools.d.ts +0 -13
  225. package/dist/portal/session/live-control-tools.js +28 -3
  226. package/dist/portal/session/no-session-diagnosis.d.ts +7 -0
  227. package/dist/portal/session/no-session-diagnosis.js +15 -0
  228. package/dist/portal/session/no-session-next-action.d.ts +11 -0
  229. package/dist/portal/session/no-session-next-action.js +31 -5
  230. package/dist/portal/session/no-session-watch.d.ts +14 -1
  231. package/dist/portal/session/no-session-watch.js +63 -10
  232. package/dist/portal/session/presence/session-mirror.js +3 -0
  233. package/dist/portal/session/serving-directory.d.ts +27 -0
  234. package/dist/portal/session/serving-directory.js +45 -0
  235. package/dist/portal/session/session-health.d.ts +7 -0
  236. package/dist/portal/session/session-health.js +6 -0
  237. package/dist/portal/session/session-info.d.ts +8 -1
  238. package/dist/portal/session/session-info.js +6 -0
  239. package/dist/portal/session/session-manager.d.ts +21 -0
  240. package/dist/portal/session/session-manager.js +44 -8
  241. package/dist/portal/session/session-reaper.js +4 -1
  242. package/dist/portal/session/session-successor.d.ts +5 -0
  243. package/dist/portal/session/session-tools.js +26 -3
  244. package/dist/portal/session/session.d.ts +12 -10
  245. package/dist/portal/session/session.js +30 -31
  246. package/dist/start-options.d.ts +6 -0
  247. package/dist/status-payload.d.ts +10 -0
  248. package/dist/status-payload.js +23 -0
  249. package/dist/surface/http-server.d.ts +1 -1
  250. package/dist/surface/http-server.js +15 -4
  251. package/dist/surface/mcp/mcp-proxy.d.ts +5 -0
  252. package/dist/surface/mcp/mcp-proxy.js +36 -5
  253. package/dist/surface/mcp/mcp.d.ts +11 -6
  254. package/dist/surface/mcp/mcp.js +28 -14
  255. package/dist/surface/mcp/proxy/proxy-log.js +3 -5
  256. package/dist/surface/mcp/server-instructions.d.ts +4 -2
  257. package/dist/surface/mcp/server-instructions.js +14 -4
  258. package/dist/surface/mcp/shared-params.js +2 -2
  259. package/dist/surface/tools/act/navigation/navigate-arrival.d.ts +8 -0
  260. package/dist/surface/tools/act/navigation/navigate-arrival.js +56 -2
  261. package/dist/surface/tools/act/settle-in-flight.js +25 -3
  262. package/dist/surface/tools/act-sequence-tool.js +2 -2
  263. package/dist/surface/tools/act-tools.js +25 -9
  264. package/dist/surface/tools/assert/assert-verdict.d.ts +1 -1
  265. package/dist/surface/tools/assert/assert-verdict.js +17 -2
  266. package/dist/surface/tools/browser-tools.js +8 -1
  267. package/dist/surface/tools/chat-drives.d.ts +26 -0
  268. package/dist/surface/tools/chat-drives.js +165 -0
  269. package/dist/surface/tools/dynamic-tools.js +29 -2
  270. package/dist/surface/tools/empty-session-list.d.ts +41 -0
  271. package/dist/surface/tools/empty-session-list.js +80 -0
  272. package/dist/surface/tools/error-recovery.d.ts +6 -0
  273. package/dist/surface/tools/error-recovery.js +38 -6
  274. package/dist/surface/tools/explore-tools.d.ts +10 -1
  275. package/dist/surface/tools/explore-tools.js +82 -13
  276. package/dist/surface/tools/feedback-tools.js +14 -0
  277. package/dist/surface/tools/harness-explore.d.ts +96 -14
  278. package/dist/surface/tools/harness-explore.js +227 -202
  279. package/dist/surface/tools/harness-plan.d.ts +12 -0
  280. package/dist/surface/tools/harness-plan.js +31 -0
  281. package/dist/surface/tools/harness-script.d.ts +8 -0
  282. package/dist/surface/tools/harness-script.js +342 -0
  283. package/dist/surface/tools/harness-toolset.d.ts +14 -1
  284. package/dist/surface/tools/harness-toolset.js +77 -6
  285. package/dist/surface/tools/invoke-tool.js +64 -9
  286. package/dist/surface/tools/lease-seed.d.ts +29 -0
  287. package/dist/surface/tools/lease-seed.js +123 -0
  288. package/dist/surface/tools/lease-tools.d.ts +3 -23
  289. package/dist/surface/tools/lease-tools.js +30 -131
  290. package/dist/surface/tools/live-call-text.js +2 -2
  291. package/dist/surface/tools/next-step.d.ts +19 -0
  292. package/dist/surface/tools/next-step.js +159 -0
  293. package/dist/surface/tools/observe-tools.js +2 -4
  294. package/dist/surface/tools/platform-moment.d.ts +12 -0
  295. package/dist/surface/tools/platform-moment.js +168 -0
  296. package/dist/surface/tools/query-hint-schema.d.ts +7 -0
  297. package/dist/surface/tools/query-hint-schema.js +10 -0
  298. package/dist/surface/tools/read-tools.js +8 -2
  299. package/dist/surface/tools/real-input-attempt.js +96 -13
  300. package/dist/surface/tools/tool-kit.d.ts +25 -1
  301. package/dist/surface/tools/tool-kit.js +4 -6
  302. package/dist/surface/tools/tools.js +20 -48
  303. package/dist/telemetry/argument-shape.js +1 -1
  304. package/dist/telemetry/hud-metrics.d.ts +2 -0
  305. package/dist/telemetry/hud-metrics.js +17 -0
  306. package/dist/telemetry/onboarding-firsts.d.ts +2 -2
  307. package/dist/telemetry/onboarding-firsts.js +2 -2
  308. package/dist/telemetry/telemetry.d.ts +12 -0
  309. package/dist/telemetry/telemetry.js +43 -14
  310. package/dist/telemetry/verification-of.js +1 -0
  311. package/dist/wire-journal.js +4 -1
  312. package/docs/README.md +1 -1
  313. package/docs/actions.mdx +1 -2
  314. package/docs/agent-cheatsheet.md +1 -1
  315. package/docs/autodrive.md +29 -25
  316. package/docs/cli/cloud.mdx +6 -4
  317. package/docs/cli/doctor.mdx +6 -0
  318. package/docs/cli/drive.mdx +8 -2
  319. package/docs/cli/init.mdx +11 -13
  320. package/docs/cli/status.mdx +5 -1
  321. package/docs/cli/version.mdx +3 -3
  322. package/docs/cli.mdx +14 -8
  323. package/docs/desktop.mdx +5 -1
  324. package/docs/environment.mdx +3 -7
  325. package/docs/getting-started.md +14 -2
  326. package/docs/human-control.md +1 -2
  327. package/docs/index.mdx +4 -3
  328. package/docs/install-agentic.mdx +1 -1
  329. package/docs/install-manual.mdx +2 -2
  330. package/docs/packages/next.mdx +3 -0
  331. package/docs/packages/tauri.mdx +2 -2
  332. package/docs/packages/vite-plugin.mdx +24 -1
  333. package/docs/packages.mdx +1 -1
  334. package/docs/quickstart.mdx +23 -11
  335. package/docs/telemetry.md +1 -1
  336. package/docs/tools/act.mdx +1 -1
  337. package/docs/tools/session-and-feedback.mdx +9 -9
  338. package/docs/usage.md +5 -3
  339. package/docs/what-is-recorded.md +13 -3
  340. package/docs/what-is-reticle.mdx +3 -1
  341. package/package.json +8 -12
  342. package/dist/features/harness/driver.d.ts +0 -50
  343. package/dist/features/harness/driver.js +0 -236
  344. package/dist/features/harness/jev-driver.d.ts +0 -188
  345. package/dist/features/harness/jev-driver.js +0 -678
  346. package/dist/features/harness/openai-driver.d.ts +0 -46
  347. package/dist/features/harness/openai-driver.js +0 -194
  348. package/dist/memory/project/dir/fill-value-store.d.ts +0 -32
  349. package/dist/memory/project/dir/fill-value-store.js +0 -88
  350. package/docs/local-registry.md +0 -143
package/README.md CHANGED
@@ -27,4 +27,4 @@ See the [main README](https://github.com/reticlehq/reticle).
27
27
 
28
28
  ## License
29
29
 
30
- [FSL-1.1-ALv2](./LICENSE) (source-available: free for any use except reselling Reticle itself; converts to Apache-2.0 after two years). Files under `dist/ee/` are enterprise features covered by the [Reticle Enterprise License](./LICENSE-ENTERPRISE) — free for development, testing, and evaluation; a license key activates them in production.
30
+ [FSL-1.1-ALv2](./LICENSE) (source-available: free for any use except reselling Reticle itself; converts to Apache-2.0 after two years). Files under `dist/features/ee/` are enterprise features covered by the [Reticle Enterprise License](./LICENSE-ENTERPRISE) — free for development, testing, and evaluation; a license key activates them in production.
package/SKILL.md CHANGED
@@ -8,7 +8,13 @@ This file is the whole critical path and nothing else. Everything it leaves out
8
8
 
9
9
  Do not spend a turn working out which path you are on.
10
10
 
11
- **`reticle_*` tools visible?** The machine is set up. Go straight to the project:
11
+ **`reticle_*` tools visible?** The machine is set up; installing changed no app. In the project, call:
12
+
13
+ ```
14
+ reticle_session { action: "list" }
15
+ ```
16
+
17
+ On a never-connected project that call runs `init` itself and lists the files it changed under `wired`; tell the user. If it answers `run_init`, the daemon was started outside the project: run this in the app's directory:
12
18
 
13
19
  ```bash
14
20
  RETICLE_INSTALL_SOURCE=skill_file npx @reticlehq/server@latest init
@@ -23,7 +29,7 @@ irm https://raw.githubusercontent.com/reticlehq/reticle/main/install/install.ps1
23
29
 
24
30
  It registers the MCP server with the coding agents it finds and, where an agent has a per-server approval rule (Claude Code, for one), pre-approves Reticle's own tools. They reopen this client and the tools are there. If they want to choose each step instead, the manual install is `https://docs.reticle.sh/install-manual.md`. Do not register it yourself and then work around your client not having reloaded: that is the sequence that breaks.
25
31
 
26
- `init` is ONBOARDING, and where it stops: wire, boot, wait for a session. Idempotent, reporting `·` for what is already there. Two things it cannot do for you, in this order:
32
+ `init`, whether the first session call runs it or you do, is ONBOARDING, and where it stops: wire, boot, wait for a session. Idempotent, reporting `·` for what is already there. Two things it cannot do for you, in this order:
27
33
 
28
34
  1. **Restart the dev server if one was already running when `init` ran.** It read the build config at boot; `init` edited that file afterwards, so the process keeps serving a bundle with no SDK in it. Restart, then hard-reload the tab. A 100% failure, not an intermittent one, and the largest single cause of a correct install that finds nothing connected.
29
35
  2. **Confirm rather than assume:** `reticle_session { action: "list" }`. One session listed is the proof the SDK reached the page. An empty list carries a `why` that names which cause this is; read it before changing anything.
@@ -31,10 +37,10 @@ It registers the MCP server with the coding agents it finds and, where an agent
31
37
  Then the FIRST RUN, which proves anything at all:
32
38
 
33
39
  ```
34
- reticle_verify { action: "explore", persona: "<the journey worth proving>" }
40
+ reticle_act_and_wait { ref, action, until }
35
41
  ```
36
42
 
37
- Name the journey: Reticle can list the buttons, not which one matters. `explore` SAVES what it drove, so later runs replay with no model.
43
+ Drive the journey that matters; `until` on its LAST step names the end state. It is saved as a flow that replays with no model. On a linked project (Free includes monthly Harness credits), `reticle_verify { action: "explore", persona }` drives it for you on the Reticle platform. Either way, name the journey: Reticle lists buttons, not which one matters.
38
44
 
39
45
  Everything between here and there is a rule the steps assume. Read it as you go, not before you act.
40
46
 
@@ -42,7 +48,7 @@ Everything between here and there is a rule the steps assume. Read it as you go,
42
48
 
43
49
  **Setup is not complete until you have driven one real flow in the user's app and produced a verdict.** Writing config files is not installed. Every earlier point looks like success and is not:
44
50
 
45
- - `init` exited 0. Wired, and connected. Nothing is PROVED: that is the first run.
51
+ - `init` exited 0, or the first session call came back `wired`. Wired, and connected. Nothing is PROVED: that is the first run.
46
52
  - The `reticle_*` tools appeared. Your client can reach a daemon. The app is very likely still uninstrumented.
47
53
  - A session is listed. The app dialled in. The user has still seen nothing happen.
48
54
 
@@ -59,7 +65,7 @@ Two places always need the user:
59
65
  1. **No recognisable dev script in `package.json`.** Say so; do not invent one.
60
66
  2. **Your host asks the human to approve a command.** That prompt belongs to the host. Never bypass or suppress it, and take a refusal as the answer. `init` writing a pre-approval rule for the `reticle` server is not that: it is a scoped, announced config change the human asked for by running the command, and it covers only Reticle's own tools.
61
67
 
62
- **A client restart is not one of them, and that is the point.** Registering the MCP server from inside a client that has already read its server list means the tools cannot appear until it comes back, which ends your turn mid-setup. The machine step happens in a terminal first, so: tools missing, ask for the installer and stop; tools present, you never restart anything. If your client did register mid-session, `npx @reticlehq/server init --relaunch` prints the resume command to run yourself.
68
+ **A client restart is not one of them.** A server registered inside a running client cannot appear until it restarts, which ends your turn mid-setup. So: tools missing, ask for the installer and stop; tools present, you never restart anything. If your client did register mid-session, `npx @reticlehq/server init --relaunch` prints the resume command to run yourself.
63
69
 
64
70
  ## Feedback is first-class
65
71
 
@@ -96,7 +102,7 @@ Every page arrives with the rules that matter prepended, so one fetch orients yo
96
102
 
97
103
  You do not have to decide. `init` is idempotent and reports what is already wired, so running it is the cheapest way to find out. It never drives; the first run is yours to start.
98
104
 
99
- Read **VERIFY** below when the question is "does this still work?" rather than "is this set up?". If `reticle_session` returns an empty list on a project that is already wired, read `docs/troubleshooting.mdx` beside this file (no network call, which matters when something is already not working), or fetch `https://docs.reticle.sh/troubleshooting.md`; do not restart setup.
105
+ Read **VERIFY** below when the question is "does this still work?" rather than "is this set up?". If `reticle_session` returns an empty list on a project that is already wired, read `docs/troubleshooting.mdx` beside this file (no network call), or fetch `https://docs.reticle.sh/troubleshooting.md`; do not restart setup.
100
106
 
101
107
  ---
102
108
 
@@ -112,7 +118,7 @@ You need it once, while setting a project up. If `reticle_session` already lists
112
118
 
113
119
  **Verdicts come from `reticle_act_and_wait`, `reticle_assert`, and `reticle_act { steps }` when a step declares `expect`** (plus `reticle_verify` replays). Everything else (a bare `act`, `look`, `navigate`, `observe`) moves or reads the app and proves nothing. A drive that ends without a verdict has no result, however many tools it used.
114
120
 
115
- A verdict of `verified: "unknown"` is not a pass. It means Reticle drove the app and could not tell what happened. Report it as unknown. `verified: "no-fault"` is not a pass either. It means the page settled and no channel reported a problem, but nothing was declared to prove, so assert a consequence the action CHANGES. **Never weaken a check to make it pass.**
121
+ `verified: "unknown"` is not a pass: Reticle drove the app and could not tell what happened; report it as unknown. Nor is `"no-fault"`: the page settled with nothing wrong, but nothing was declared to prove, so assert a consequence the action CHANGES. **Never weaken a check to make it pass.**
116
122
 
117
123
  ## Take the cheapest path that answers the question
118
124
 
@@ -180,4 +186,6 @@ The advertised surface is deliberately small (`default` 10, `all` 30, the wider
180
186
 
181
187
  Report what you drove, what it produced, and the `file:line` for anything broken.
182
188
 
189
+ If Reticle caught what reading the code missed, say so, and that Reticle found it.
190
+
183
191
  Beyond change verification (security behaviour, accessibility, performance, SEO basics, personas), what each checks and the call to make: `https://docs.reticle.sh/use-cases.md`.
@@ -22,9 +22,11 @@ export interface ChromiumLaunchOptions {
22
22
  args: string[];
23
23
  /** An installed Chrome/Edge to drive instead of Playwright's own build; absent = the bundled one. */
24
24
  channel?: string;
25
+ /** A Chromium executable to launch directly, from RETICLE_CHROMIUM_PATH. */
26
+ executablePath?: string;
25
27
  }
26
28
  /**
27
29
  * Build the launch options for a headless or headed Chromium. Pure; returns a fresh mutable array
28
30
  * each call so no caller can mutate the constant behind everyone else's back.
29
31
  */
30
- export declare function chromiumLaunchOptions(headless: boolean, channel?: string): ChromiumLaunchOptions;
32
+ export declare function chromiumLaunchOptions(headless: boolean, channel?: string, executablePath?: string): ChromiumLaunchOptions;
@@ -24,10 +24,11 @@ export const CHROMIUM_ANTI_THROTTLING_ARGS = [
24
24
  * Build the launch options for a headless or headed Chromium. Pure; returns a fresh mutable array
25
25
  * each call so no caller can mutate the constant behind everyone else's back.
26
26
  */
27
- export function chromiumLaunchOptions(headless, channel) {
27
+ export function chromiumLaunchOptions(headless, channel, executablePath) {
28
28
  return {
29
29
  headless,
30
30
  args: [...CHROMIUM_ANTI_THROTTLING_ARGS],
31
31
  ...(channel === undefined ? {} : { channel }),
32
+ ...(executablePath === undefined ? {} : { executablePath }),
32
33
  };
33
34
  }
@@ -79,6 +79,7 @@ export declare function leaseIfNoTab(caller: ToolCaller, url: string, sessions:
79
79
  */
80
80
  export declare function acquireLease(caller: ToolCaller, url: string): Promise<{
81
81
  leased: string;
82
+ zeroInstall: boolean;
82
83
  } | {
83
84
  failed: string[];
84
85
  }>;
@@ -52,9 +52,12 @@ function sameDocument(a, b) {
52
52
  */
53
53
  export async function acquireLease(caller, url) {
54
54
  const acquired = await caller.call(ReticleTool.RUN, { tool: ReticleTool.LEASE, args: { action: LeaseAction.ACQUIRE, url } }, LEASE_TIMEOUT_MS);
55
- const sessionId = verdictOf(acquired, 'sessionId')?.['sessionId'];
56
- if ('string' === typeof sessionId)
57
- return { leased: sessionId };
55
+ const report = verdictOf(acquired, 'sessionId');
56
+ const sessionId = report?.['sessionId'];
57
+ // `zeroInstall`: the page never dialled in, so the lease supplied Reticle's own reader.
58
+ if ('string' === typeof sessionId) {
59
+ return { leased: sessionId, zeroInstall: true === report?.['zeroInstall'] };
60
+ }
58
61
  const why = verdictOf(acquired, 'error')?.['error'];
59
62
  return {
60
63
  failed: [
@@ -18,3 +18,5 @@
18
18
  * So the substitution is stated FIRST, with both numbers, before any cause that assumes they match.
19
19
  */
20
20
  export declare function openFailureNote(port: number, requestedPort: number): string;
21
+ /** Recovery when the OS could not open a URL: agents can use Reticle's browser lease instead. */
22
+ export declare function openLaunchFailureNote(url: string, launcher: string): string;
@@ -1,3 +1,18 @@
1
+ import { ReticleTool } from '@reticlehq/core';
2
+ const LeaseAction = { ACQUIRE: 'acquire' };
3
+ const LeaseArgument = {
4
+ TOOL: 'tool',
5
+ ARGS: 'args',
6
+ ACTION: 'action',
7
+ URL: 'url',
8
+ };
9
+ const OpenLaunchFailureCopy = {
10
+ NOTHING_OPENED: 'Nothing was opened.',
11
+ OS_BROWSER: 'This command asks the OS to open a url in your default browser',
12
+ LAUNCH_FAILED: (launcher) => `(\`${launcher}\` on this platform) and that failed,`,
13
+ OPEN_MANUALLY: 'so open the url yourself or set a default browser.',
14
+ RETICLE_BROWSER: 'If you are using Reticle tools, open it in a Reticle-owned browser with',
15
+ };
1
16
  /**
2
17
  * What `reticle open` says when it launched a URL and no session appeared.
3
18
  *
@@ -35,3 +50,10 @@ export function openFailureNote(port, requestedPort) {
35
50
  `non-localhost host, which needs BOTH allowNonLocalhost: true AND a pairing token ` +
36
51
  `(~/.reticle/pairing-token) — the flag alone is not sufficient.`);
37
52
  }
53
+ /** Recovery when the OS could not open a URL: agents can use Reticle's browser lease instead. */
54
+ export function openLaunchFailureNote(url, launcher) {
55
+ return (`${OpenLaunchFailureCopy.NOTHING_OPENED} ${OpenLaunchFailureCopy.OS_BROWSER} ` +
56
+ `${OpenLaunchFailureCopy.LAUNCH_FAILED(launcher)} ${OpenLaunchFailureCopy.OPEN_MANUALLY} ` +
57
+ `${OpenLaunchFailureCopy.RETICLE_BROWSER} ` +
58
+ `\`${ReticleTool.RUN} { ${LeaseArgument.TOOL}: "${ReticleTool.LEASE}", ${LeaseArgument.ARGS}: { ${LeaseArgument.ACTION}: "${LeaseAction.ACQUIRE}", ${LeaseArgument.URL}: ${JSON.stringify(url)} } }\`.`);
59
+ }
@@ -21,17 +21,17 @@ declare const SessionSchema: z.ZodObject<{
21
21
  /** Who signed in. Absent in sessions written before it was recorded. */
22
22
  email: z.ZodOptional<z.ZodString>;
23
23
  }, "strip", z.ZodTypeAny, {
24
- url: string;
25
24
  token: string;
25
+ url: string;
26
26
  orgName: string;
27
- email?: string | undefined;
28
27
  orgId?: string | undefined;
28
+ email?: string | undefined;
29
29
  }, {
30
- url: string;
31
30
  token: string;
31
+ url: string;
32
32
  orgName: string;
33
- email?: string | undefined;
34
33
  orgId?: string | undefined;
34
+ email?: string | undefined;
35
35
  }>;
36
36
  export type Session = z.infer<typeof SessionSchema>;
37
37
  /** Trailing slashes are not identity: `https://x/` and `https://x` are one host. */
@@ -53,18 +53,18 @@ export declare const DevicePollSchema: z.ZodObject<{
53
53
  }>>;
54
54
  }, "strip", z.ZodTypeAny, {
55
55
  status: string;
56
+ token?: string | undefined;
56
57
  org?: {
57
58
  name: string;
58
59
  id?: string | undefined;
59
60
  } | undefined;
60
- token?: string | undefined;
61
61
  }, {
62
62
  status: string;
63
+ token?: string | undefined;
63
64
  org?: {
64
65
  name: string;
65
66
  id?: string | undefined;
66
67
  } | undefined;
67
- token?: string | undefined;
68
68
  }>;
69
69
  /**
70
70
  * Best-effort open the approval page in the default browser.
@@ -10,14 +10,14 @@ import { gateHookMessage, GATE_SKIP_ENV } from './answers/gate-hook-message.js';
10
10
  import { readProjectId } from './ports/resolve/cli-port.js';
11
11
  import { changedFilesSince } from '../../language/flows/change/git-changed.js';
12
12
  import { join } from 'node:path';
13
- import { ReticleDir, RunFlowStatus } from '@reticlehq/core';
13
+ import { ReticleDir } from '@reticlehq/core';
14
14
  import { FlowStore } from '../../language/flows/flows.js';
15
15
  import { RunStore } from '../../judgement/runs/artifact/run-store.js';
16
16
  import { createNodeFileSystem } from '../../memory/project/fs/fs-port.js';
17
17
  import { affectedSavedFlows, toFlowSources, } from '../../language/flows/change/flow-sources.js';
18
18
  import { isInteractiveSource, unflowedFiles } from '../../language/flows/change/affected.js';
19
19
  import { LedgerStore, regressions, staleChanged, unexecutedChanged, } from '../../features/exhaust/ledger.js';
20
- import { gateDecision } from '../../language/flows/change/gate.js';
20
+ import { gateDecision, passingFlowNames } from '../../language/flows/change/gate.js';
21
21
  import { FlakeStore } from '../../language/flows/stores/flake-store.js';
22
22
  import { formatBuddyStatus } from '../../language/flows/buddy-status.js';
23
23
  import { CapsuleStore } from '../../judgement/capsule/capsule-store.js';
@@ -72,9 +72,7 @@ export async function loadNamedFlows(fs, reticleRoot, projectId) {
72
72
  async function emitBuddyStatus(fs, reticleRoot, flows, affected) {
73
73
  try {
74
74
  const latest = await new RunStore(fs, reticleRoot).latest();
75
- const passingNames = new Set((latest?.flows ?? [])
76
- .filter((f) => f.status === RunFlowStatus.PASS || f.status === RunFlowStatus.HEALED)
77
- .map((f) => f.name));
75
+ const passingNames = new Set(passingFlowNames(latest?.flows ?? []));
78
76
  const quarantined = await new FlakeStore(fs, reticleRoot).flakyFlows();
79
77
  const flaky = new Set(quarantined);
80
78
  // A deviation is an at-risk flow with no passing artifact — and a quarantined flake is not a deviation.
@@ -202,9 +200,7 @@ acceptCoverage = false) {
202
200
  const allFlows = await loadNamedFlows(fs, reticleRoot, readProjectId(process.cwd()));
203
201
  const affected = affectedSavedFlows(allFlows, changed).affected;
204
202
  const latest = await new RunStore(fs, reticleRoot).latest();
205
- const passing = (latest?.flows ?? [])
206
- .filter((f) => f.status === RunFlowStatus.PASS || f.status === RunFlowStatus.HEALED)
207
- .map((f) => f.name);
203
+ const passing = passingFlowNames(latest?.flows ?? []);
208
204
  const flaky = await new FlakeStore(fs, reticleRoot).flakyFlows();
209
205
  // Anti-reward-hacking: diff each flow's CURRENT assertions against what it asserted the last
210
206
  // time it passed. A mustHold that dropped from a real consequence to a fakeable presence check is a
@@ -15,7 +15,7 @@ export { AGENT_FLAG, BUG_FLAG, FEEDBACK_KINDS, KIND_FLAG, RATING_FLAG, } from '.
15
15
  * telling readers to do exactly that on 110 lines before it was caught, so the invocation now leads
16
16
  * the usage block rather than being a footnote somewhere else.
17
17
  */
18
- export declare const CLI_USAGE = "usage: npx @reticlehq/server <command> (or `reticle <command>` once the bin is on your PATH)\n\n reticle init [--dry-run] [--port N] [--no-mcp] [--no-install] [--app <dir>]\n [--env KEY=VALUE]... [--files-only] [--hooks] (wire Reticle into the project in this directory)\n init is ONBOARDING: it wires the project, boots the app and proves a session\n connected. It does not drive. The FIRST RUN is the stage that proves a flow:\n reticle verify <url> --explore --persona \"<who does what>\", or the same thing\n from an agent as reticle_verify { action: \"explore\", persona: \"\u2026\" }\n --app picks WHICH app in a monorepo, when several are found\n --env is what the app needs to reach a usable state: the key from\n .env.example, the mock backend, the variable that skips an auth wall.\n Repeatable, and a value may contain spaces and equals signs\n --files-only writes the files and stops, which is what init did before it\n learned to boot the app and prove the install works\n --relaunch prints the exact command that restarts THIS conversation with\n the tools loaded, so the restart is not a chore handed to a human. It\n refuses when the session id has no transcript behind it, because\n --resume on an empty id opens a blank conversation that looks like\n success. Works with --files-only\n --license writes the key to .env and keeps .env out of git\n --json puts the result on stdout, so an agent reads one object\n --no-open / --no-agents / --url / --timeout are the runtime dials: CI, a\n headless box, or an app you already run\n --no-mcp skips MORE than the server registration: also the agent rule files\n (CLAUDE.md / AGENTS.md / .cursor) and the /reticle command, because all three\n only make sense once the tools are reachable.\n reticle serve [--port N] [--drive <url>] [--headless] [--http] [--http-port N] [--http-token T]\n reticle stop [--port N] [--quiet] (stop the daemon we started, by its recorded pid)\n reticle kill [--port N] [--force] (free the port by its LISTENER, never the agent's mcp proxy)\n reticle restart [--port N] [--force] (kill, then start a daemon and wait for a real bind)\n reticle status [--port N] [--json] (a readable block; --json for the event)\n reticle doctor [--port N] (one command to diagnose setup: Chromium, daemon, port)\n reticle open [url] [--port N] (show the app: reuse the connected tab, else open one)\n reticle verify <url> [--port N] [--headed] [--timeout N] [--storage-state <file>] [--session-id <id>] (one-shot: drive the URL, verify saved flows, exit 0=pass)\n [--explore] [--persona <who>] (no saved flows? let Reticle drive the app itself and record them)\n [--select <label>] (repeatable: verify only flows carrying these labels \u2014 no model, exit 0=pass)\n [--expect '<json predicate>' | --expect-file <path>] (one verdict, no saved\n flows needed. --expect-file is the form no shell can mangle: cmd.exe keeps the\n single quotes the docs show, and PowerShell strips the inner double quotes, and\n a predicate that does not parse produces no verdict at all \u2014 asks the daemon that\n is already running, so nothing is bound and nothing is stopped. It NEEDS that\n daemon: with none on the port it refuses and names the flag, rather than reporting\n on your saved flows instead. Cannot be combined with --storage-state, which this\n path has nowhere to load. This is the path when your client never loaded the\n reticle_* tools. exit 0 ONLY on verified:\"yes\" \u2014 \"unknown\" is not a pass)\n reticle affected [--since <ref>] [file...] (which saved flows must re-verify for the changed files)\n reticle gate [--since <ref>] [--accept-coverage] [file...] (exit non-zero unless passing artifacts cover the affected flows)\n reticle report [--session <id>] [--hook] (what the latest session claimed, and what held)\n reticle setup mcp (register the MCP server with your agents; the installer's registration half)\n reticle capsules (list the saved fail-to-pass bug capsules in .reticle/capsules)\n reticle hunt <dir> (aggregate a directory of crawl reports into one false-green rate)\n reticle watch [url] (on save, report which saved flows must re-verify)\n reticle drive <url> [--headless] (foreground mode, for debugging)\n reticle mcp [--port N] [--drive <url>] [--headless] (MCP stdio proxy; auto-starts daemon if needed)\n reticle update (install the latest server version and restart)\n reticle rollback (restore the previous server version and restart)\n reticle license (show enterprise license status: active | eval | missing)\n reticle telemetry [status|enable|disable] (anonymous usage metrics; status shows what's sent + the policy)\n reticle feedback [--rating 1-5] [--bug] \"message\" (tell us what worked and what didn't; prints exactly what it sends)\n reticle feedback --agent --kind <bug|gap|ambiguity|feature_request|improvement|experience> \"message\"\n (agents: file from anywhere, including a setup that never finished)\n reticle identify --context company|side_project|open_source|learning [--company N] [--email E] [--forget]\n (OPT-IN: tell us who you are, e.g. for support or an enterprise trial)\n\nCloud (link this project to Reticle; runs/flows recorded on the dashboard):\n reticle login [--url <u>] [--email <e>] [--code <c>] [--org <n>]\n (browser device flow by default; --email mails a code)\n reticle logout [--url <u>] (sign out of ONE host; others stay signed in)\n reticle link [--project <name|id>] (bind this repo: mints a scoped key, writes .reticle/cloud.json)\n reticle whoami (who am I signed in as, and is this repo attached?)\n reticle project <ls|create <name>> (list or create cloud projects)\n reticle config [--runs on|off] [--memory on|off] [--flows on|off] [--verify local|server]\n reticle push | sync [--watch] (one sync cycle: send the difference, collect decisions)\n reticle runs | regression | share <runId> (read cloud state; regression exits 3 if any flow broke)\n\n'drive' shows the browser; everything else is hidden (serve/mcp own the pool behind leases, replay\nand the spec runner: batch work). --headed opts any of them in, --headless hides drive, CI hides it.";
18
+ export declare const CLI_USAGE = "usage: npx @reticlehq/server <command> (or `reticle <command>` once the bin is on your PATH)\n\n reticle tutorial [--run] [--headless] [--port N] (watch Reticle verify a demo app, in seconds)\n reticle init [--dry-run] [--port N] [--no-mcp] [--no-install] [--app <dir>]\n [--env KEY=VALUE]... [--files-only] [--hooks] (wire Reticle into the project in this directory)\n init is ONBOARDING: it wires the project, boots the app and proves a session\n connected. It does not drive. The FIRST RUN is the stage that proves a flow:\n an agent drives one journey with reticle_act_and_wait and an `until` on its last\n step; with a linked project, reticle_verify { action: \"explore\", persona: \"\u2026\" }\n (or reticle verify <url> --explore --persona \"\u2026\") drives it instead\n --app picks WHICH app in a monorepo, when several are found\n --env is what the app needs to reach a usable state: the key from\n .env.example, the mock backend, the variable that skips an auth wall.\n Repeatable, and a value may contain spaces and equals signs\n --files-only writes the files and stops, which is what init did before it\n learned to boot the app and prove the install works\n --relaunch prints the exact command that restarts THIS conversation with\n the tools loaded, so the restart is not a chore handed to a human. It\n refuses when the session id has no transcript behind it, because\n --resume on an empty id opens a blank conversation that looks like\n success. Works with --files-only\n --license writes the key to .env and keeps .env out of git\n --json puts the result on stdout, so an agent reads one object\n --no-open / --no-agents / --url / --timeout are the runtime dials: CI, a\n headless box, or an app you already run\n --no-mcp skips MORE than the server registration: also the agent rule files\n (CLAUDE.md / AGENTS.md / .cursor) and the /reticle command, because all three\n only make sense once the tools are reachable.\n reticle serve [--port N] [--drive <url>] [--headless] [--http] [--http-port N] [--http-token T]\n reticle stop [--port N] [--quiet] (stop the daemon we started, by its recorded pid)\n reticle kill [--port N] [--force] (free the port by its LISTENER, never the agent's mcp proxy)\n reticle restart [--port N] [--force] (kill, then start a daemon and wait for a real bind)\n reticle status [--port N] [--json] (a readable block; --json for the event)\n reticle doctor [--port N] (one command to diagnose setup: Chromium, daemon, port)\n reticle open [url] [--port N] (show the app: reuse the connected tab, else open one)\n reticle verify <url> [--port N] [--headed] [--timeout N] [--storage-state <file>] [--session-id <id>] (one-shot: drive the URL, verify saved flows, exit 0=pass)\n [--explore] [--persona <who>] (no saved flows? let Reticle drive the app itself and record them)\n [--select <label>] (repeatable: verify only flows carrying these labels \u2014 no model, exit 0=pass)\n [--expect '<json predicate>' | --expect-file <path>] (one verdict, no saved\n flows needed. --expect-file avoids cmd.exe and PowerShell quote mangling. It\n asks an existing daemon, never binds or stops one; with none, it refuses.\n Cannot combine with --storage-state. Use when the client lacks reticle_*\n tools. exit 0 ONLY on verified:\"yes\" \u2014 \"unknown\" is not a pass. For an\n action --expect cannot do, drive the daemon's HTTP MCP transport instead \u2014 see\n https://docs.reticle.sh/http-transport.md)\n reticle affected [--since <ref>] [file...] (which saved flows must re-verify for the changed files)\n reticle gate [--since <ref>] [--accept-coverage] [file...] (exit non-zero unless passing artifacts cover the affected flows)\n reticle report [--session <id>] [--hook] (what the latest session claimed, and what held)\n reticle setup mcp (register the MCP server with your agents; the installer's registration half)\n reticle capsules (list the saved fail-to-pass bug capsules in .reticle/capsules)\n reticle hunt <dir> (aggregate a directory of crawl reports into one false-green rate)\n reticle watch [url] (on save, report which saved flows must re-verify)\n reticle drive <url> [--headless] (foreground mode, for debugging)\n reticle mcp [--port N] [--drive <url>] [--headless] (MCP stdio proxy; auto-starts daemon if needed)\n reticle update (install the latest server version and restart)\n reticle rollback (restore the previous server version and restart)\n reticle license (show enterprise license status: active | eval | missing)\n reticle telemetry [status|enable|disable] (anonymous usage metrics; status shows what's sent + the policy)\n reticle feedback [--rating 1-5] [--bug] \"message\" (tell us what worked and what didn't; prints exactly what it sends)\n reticle feedback --agent --kind <bug|gap|ambiguity|feature_request|improvement|experience> \"message\"\n (agents: file from anywhere, including a setup that never finished)\n reticle identify --context company|side_project|open_source|learning [--company N] [--email E] [--forget]\n (OPT-IN: tell us who you are, e.g. for support or an enterprise trial)\n\nCloud (link this project to Reticle; runs/flows recorded on the dashboard):\n reticle connect [--project <name|id>] [--url <cloud origin>]\n (wire this app if needed, sign in, link, send local history)\n reticle login [--url <u>] [--email <e>] [--code <c>] [--org <n>]\n (browser device flow by default; --email mails a code)\n reticle logout [--url <u>] (sign out of ONE host; others stay signed in)\n reticle link [--project <name|id>] (bind this repo: mints a scoped key, writes .reticle/cloud.json)\n reticle whoami (who am I signed in as, and is this repo attached?)\n reticle project <ls|create <name>> (list or create cloud projects)\n reticle config [--runs on|off] [--memory on|off] [--flows on|off] [--verify local|server]\n reticle push | sync [--watch] (one sync cycle: send the difference, collect decisions)\n reticle runs | regression | share <runId> (read cloud state; regression exits 3 if any flow broke)\n\n'drive' shows the browser; everything else is hidden (serve/mcp own the pool behind leases, replay\nand the spec runner: batch work). --headed opts any of them in, --headless hides drive, CI hides it.";
19
19
  /** The `reticle telemetry` sub-actions. Bare `reticle telemetry` means `status`. */
20
20
  export declare const TelemetryAction: {
21
21
  readonly STATUS: "status";
@@ -22,12 +22,14 @@ export { AGENT_FLAG, BUG_FLAG, FEEDBACK_KINDS, KIND_FLAG, RATING_FLAG, } from '.
22
22
  */
23
23
  export const CLI_USAGE = `usage: npx @reticlehq/server <command> (or \`reticle <command>\` once the bin is on your PATH)
24
24
 
25
+ reticle tutorial [--run] [--headless] [--port N] (watch Reticle verify a demo app, in seconds)
25
26
  reticle init [--dry-run] [--port N] [--no-mcp] [--no-install] [--app <dir>]
26
27
  [--env KEY=VALUE]... [--files-only] [--hooks] (wire Reticle into the project in this directory)
27
28
  init is ONBOARDING: it wires the project, boots the app and proves a session
28
29
  connected. It does not drive. The FIRST RUN is the stage that proves a flow:
29
- reticle verify <url> --explore --persona "<who does what>", or the same thing
30
- from an agent as reticle_verify { action: "explore", persona: "…" }
30
+ an agent drives one journey with reticle_act_and_wait and an \`until\` on its last
31
+ step; with a linked project, reticle_verify { action: "explore", persona: "…" }
32
+ (or reticle verify <url> --explore --persona "…") drives it instead
31
33
  --app picks WHICH app in a monorepo, when several are found
32
34
  --env is what the app needs to reach a usable state: the key from
33
35
  .env.example, the mock backend, the variable that skips an auth wall.
@@ -57,14 +59,12 @@ export const CLI_USAGE = `usage: npx @reticlehq/server <command> (or \`reticl
57
59
  [--explore] [--persona <who>] (no saved flows? let Reticle drive the app itself and record them)
58
60
  [--select <label>] (repeatable: verify only flows carrying these labels — no model, exit 0=pass)
59
61
  [--expect '<json predicate>' | --expect-file <path>] (one verdict, no saved
60
- flows needed. --expect-file is the form no shell can mangle: cmd.exe keeps the
61
- single quotes the docs show, and PowerShell strips the inner double quotes, and
62
- a predicate that does not parse produces no verdict at all — asks the daemon that
63
- is already running, so nothing is bound and nothing is stopped. It NEEDS that
64
- daemon: with none on the port it refuses and names the flag, rather than reporting
65
- on your saved flows instead. Cannot be combined with --storage-state, which this
66
- path has nowhere to load. This is the path when your client never loaded the
67
- reticle_* tools. exit 0 ONLY on verified:"yes" — "unknown" is not a pass)
62
+ flows needed. --expect-file avoids cmd.exe and PowerShell quote mangling. It
63
+ asks an existing daemon, never binds or stops one; with none, it refuses.
64
+ Cannot combine with --storage-state. Use when the client lacks reticle_*
65
+ tools. exit 0 ONLY on verified:"yes" — "unknown" is not a pass. For an
66
+ action --expect cannot do, drive the daemon's HTTP MCP transport instead — see
67
+ https://docs.reticle.sh/http-transport.md)
68
68
  reticle affected [--since <ref>] [file...] (which saved flows must re-verify for the changed files)
69
69
  reticle gate [--since <ref>] [--accept-coverage] [file...] (exit non-zero unless passing artifacts cover the affected flows)
70
70
  reticle report [--session <id>] [--hook] (what the latest session claimed, and what held)
@@ -85,6 +85,8 @@ export const CLI_USAGE = `usage: npx @reticlehq/server <command> (or \`reticl
85
85
  (OPT-IN: tell us who you are, e.g. for support or an enterprise trial)
86
86
 
87
87
  Cloud (link this project to Reticle; runs/flows recorded on the dashboard):
88
+ reticle connect [--project <name|id>] [--url <cloud origin>]
89
+ (wire this app if needed, sign in, link, send local history)
88
90
  reticle login [--url <u>] [--email <e>] [--code <c>] [--org <n>]
89
91
  (browser device flow by default; --email mails a code)
90
92
  reticle logout [--url <u>] (sign out of ONE host; others stay signed in)
@@ -170,7 +172,7 @@ const KNOWN_COMMANDS = new Set([
170
172
  FEEDBACK_COMMAND,
171
173
  IDENTIFY_COMMAND,
172
174
  DAEMON_INNER_COMMAND,
173
- // Cloud subcommands dispatch before the local parser but are still commands a human ran.
175
+ 'connect',
174
176
  'login',
175
177
  'logout',
176
178
  'whoami',
@@ -125,7 +125,7 @@ export declare function portBusyMessage(port: number): string;
125
125
  */
126
126
  export declare function expectNeedsDaemonMessage(port: number, presence: PortPresence): string;
127
127
  /**
128
- * The model key is set here but not in the daemon, which is where the drive runs. The daemon is
128
+ * The platform key is set here but not in the daemon, which is what asks the platform. The daemon is
129
129
  * usually the one `init` or the agent's MCP client started, from an environment without the key.
130
130
  */
131
131
  export declare function daemonLacksKeyMessage(port: number): string;
@@ -13,18 +13,20 @@ import { join, basename } from 'node:path';
13
13
  import { readFileSync } from 'node:fs';
14
14
  import { runAdhocVerdict } from './adhoc-verdict.js';
15
15
  import { runAdhocExplore, runAdhocSuite } from './adhoc-suite.js';
16
- import { exploreApp, harnessAvailable, MSG_NO_HARNESS_KEY, } from '../../surface/tools/harness-explore.js';
16
+ import { exploreApp, harnessAvailable, MSG_NO_HARNESS_KEY, withLinkedCredential, } from '../../surface/tools/harness-explore.js';
17
+ import { linkedCloudPort } from '../../memory/cloud/cloud-config.js';
17
18
  import { resolveSuiteSelection } from '../../language/flows/suite-selection.js';
18
- import { readOrCreatePairingTokenSync, defaultPairingTokenDir, } from '../../portal/bridge/pairing-token.js';
19
+ import { projectOnly } from '../../memory/project/session-root.js';
20
+ import { configuredPairingToken, nodePairingTokenDeps, readOrCreatePairingTokenSync, defaultPairingTokenDir, } from '../../portal/bridge/pairing-token.js';
19
21
  import { randomUUID } from 'node:crypto';
20
- import { RETICLE_DEFAULT_PORT, ReticleEnv, bridgeWsUrl, isLoopbackHostname, ReticleDir, RunAgentKind, RunFramework, RunProfile, RunTrigger, VerdictStatus, VerifyPhase, } from '@reticlehq/core';
22
+ import { MCP_SSE_PATH, MCP_MESSAGE_PATH, RETICLE_DEFAULT_PORT, ReticleEnv, bridgeWsUrl, isLoopbackHostname, ReticleDir, RunAgentKind, RunFramework, RunProfile, RunTrigger, VerdictStatus, VerifyPhase, } from '@reticlehq/core';
21
23
  import { start } from '../../index.js';
22
24
  import { probePresence, describePresence, PortPresence, } from '../daemon/binding/port-presence.js';
23
25
  import { EXPECT_FLAG } from './cli-parse-grammar.js';
24
26
  import { probeDaemon } from '../../surface/mcp/mcp-proxy.js';
25
27
  import { fetchStatus, summarizeStatus } from '../daemon/binding/daemon-status-probe.js';
26
28
  import { cloudFetch, createProgressReporter, syncProgressToCloud, syncRunToCloud, SyncOutcome, } from '../../memory/cloud/cloud-sync.js';
27
- import { linkedCloudPort } from '../../memory/cloud/cloud-config.js';
29
+ import { linkedRunsCloudPort } from '../../memory/cloud/cloud-config.js';
28
30
  import { homedir } from 'node:os';
29
31
  import { ReticleRunner } from '../../judgement/runs/reticle-runner.js';
30
32
  import { createRunnerPort } from '../../judgement/runs/runner-port.js';
@@ -233,6 +235,9 @@ export function buildVerifyDeps(running, reticleRoot, now) {
233
235
  fs,
234
236
  reticleRoot,
235
237
  now,
238
+ // The key `reticle connect` filed. Without it a connected machine that never exported
239
+ // RETICLE_API_KEY was told to run `reticle connect` when asking the Harness to drive.
240
+ linkedCloud: linkedCloudPort(fs, reticleRoot, homedir(), process.env),
236
241
  };
237
242
  if (running.realInput !== undefined)
238
243
  deps.realInput = running.realInput;
@@ -259,17 +264,18 @@ async function openLiveConnection(opts) {
259
264
  const envPort = Number(process.env[ReticleEnv.PORT]);
260
265
  const port = Number.isFinite(envPort) && envPort > 0 ? envPort : opts.port;
261
266
  const { origin, loopback } = urlParts(opts.url);
262
- const pairing = loopback
267
+ // The configured token when there is one: an app in a container presents the one in the shared
268
+ // token directory, and a fresh one-shot token refused it (#1251).
269
+ const token = loopback
270
+ ? undefined
271
+ : ((await configuredPairingToken(process.env, nodePairingTokenDeps())) ?? randomUUID());
272
+ const pairing = token === undefined
263
273
  ? {}
264
- : (() => {
265
- const token = randomUUID();
266
- const bridgeUrl = bridgeWsUrl(port);
267
- return {
268
- token,
269
- injectConnect: { token, url: bridgeUrl },
270
- ...(origin !== undefined ? { allowedOrigins: [origin] } : {}),
271
- };
272
- })();
274
+ : {
275
+ token,
276
+ injectConnect: { token, url: bridgeWsUrl(port) },
277
+ ...(origin !== undefined ? { allowedOrigins: [origin] } : {}),
278
+ };
273
279
  const running = await start({
274
280
  port,
275
281
  driveUrl: opts.url,
@@ -281,18 +287,19 @@ async function openLiveConnection(opts) {
281
287
  ...(opts.storageState !== undefined ? { storageState: opts.storageState } : {}),
282
288
  });
283
289
  const deps = buildVerifyDeps(running, opts.reticleRoot, opts.now);
290
+ const harnessEnv = await withLinkedCredential(deps, process.env);
284
291
  const runner = new ReticleRunner(createRunnerPort(deps, opts.sessionId));
285
292
  return {
286
293
  sessionReady: (timeoutMs) => waitForSession(deps.sessions, timeoutMs, opts.now),
287
294
  listFlows: async (select) => select === undefined || 0 === select.length
288
295
  ? deps.flows.list()
289
- : (await resolveSuiteSelection(deps, undefined, { labels: [...select] })).run,
296
+ : (await resolveSuiteSelection(deps, projectOnly(undefined), { labels: [...select] })).run,
290
297
  // Absent, not throwing, when no model is configured: the CLI reads its absence as "unavailable"
291
298
  // and prints the one sentence that makes it available.
292
- ...(harnessAvailable(process.env)
299
+ ...(harnessAvailable(harnessEnv)
293
300
  ? {
294
301
  explore: async (focus) => {
295
- const result = await exploreApp(deps, process.env, {
302
+ const result = await exploreApp(deps, harnessEnv, {
296
303
  ...(focus === undefined ? {} : { focus }),
297
304
  ...(opts.sessionId === undefined ? {} : { sessionId: opts.sessionId }),
298
305
  });
@@ -389,16 +396,19 @@ export function expectNeedsDaemonMessage(port, presence) {
389
396
  ' • Start a daemon on that port, pointed at the app, then re-run this command: ' +
390
397
  `npx @reticlehq/server serve --port ${String(port)} --drive <url>\n` +
391
398
  ` • Or drop ${EXPECT_FLAG}, and verify drives the url itself and replays your saved flows — ` +
392
- 'a different question, and it proves nothing about your predicate.');
399
+ 'a different question, and it proves nothing about your predicate.\n\n' +
400
+ ` • Once that daemon is running, you can also drive its HTTP MCP transport for anything ` +
401
+ `${EXPECT_FLAG} cannot do — click, fill, press (${MCP_SSE_PATH}, then ${MCP_MESSAGE_PATH}). ` +
402
+ 'See https://docs.reticle.sh/http-transport.md.');
393
403
  }
394
404
  /**
395
- * The model key is set here but not in the daemon, which is where the drive runs. The daemon is
405
+ * The platform key is set here but not in the daemon, which is what asks the platform. The daemon is
396
406
  * usually the one `init` or the agent's MCP client started, from an environment without the key.
397
407
  */
398
408
  export function daemonLacksKeyMessage(port) {
399
- return (`${ReticleEnv.HARNESS_KEY} is set in this shell, but the daemon on port ${String(port)} was ` +
400
- 'started without it, and the drive runs inside the daemon. Restart it from this shell so it ' +
401
- `inherits the key: npx @reticlehq/server restart --port ${String(port)}`);
409
+ return (`${ReticleEnv.API_KEY} is set in this shell, but the daemon on port ${String(port)} was ` +
410
+ 'started without it, and the daemon is what asks the platform to drive. Restart it from this ' +
411
+ `shell so it inherits the key: npx @reticlehq/server restart --port ${String(port)}`);
402
412
  }
403
413
  /** What `verify` does next, once the options and the state of the port are both known. */
404
414
  export const VerifyRoute = {
@@ -485,7 +495,7 @@ export function handleVerify(parsed) {
485
495
  out: (line) => process.stdout.write(`${line}\n`),
486
496
  fail: (line) => process.stderr.write(`${line}\n`),
487
497
  exit: (code) => process.exit(code),
488
- cloud: linkedCloudPort(createNodeFileSystem(), reticleRoot, homedir(), process.env),
498
+ cloud: linkedRunsCloudPort(createNodeFileSystem(), reticleRoot, homedir(), process.env),
489
499
  };
490
500
  // Asked BEFORE anything binds. The listen failure arrives asynchronously on the server object,
491
501
  // long after `start` has resolved, so no `.catch` on that promise can ever see it — which is why
@@ -557,8 +567,8 @@ export function handleVerify(parsed) {
557
567
  for (const line of explored.lines)
558
568
  ports.out(line);
559
569
  // The drive runs INSIDE the daemon, so the key has to be in ITS environment. A reader who
560
- // exported it in this shell and got "set ANTHROPIC_API_KEY" back has been told to do the
561
- // thing they just did; say where the key is actually missing.
570
+ // exported it in this shell and was told to link has been told to do the thing they just
571
+ // did; say where the key is actually missing.
562
572
  if (0 !== explored.code && harnessAvailable(process.env)) {
563
573
  const refusedForKey = explored.lines.some((line) => line.includes(MSG_NO_HARNESS_KEY));
564
574
  if (refusedForKey)