@reticlehq/server 3.3.0 → 3.5.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 (326) hide show
  1. package/LICENSE-ENTERPRISE +1 -1
  2. package/README.md +3 -3
  3. package/SKILL.md +12 -14
  4. package/dist/chromium-launch-options.d.ts +4 -3
  5. package/dist/chromium-launch-options.js +6 -2
  6. package/dist/command/cli/adhoc-suite.d.ts +22 -0
  7. package/dist/command/cli/adhoc-suite.js +90 -19
  8. package/dist/command/cli/adhoc-verdict.d.ts +35 -0
  9. package/dist/command/cli/adhoc-verdict.js +90 -3
  10. package/dist/command/cli/answers/gate-hook-message.d.ts +6 -0
  11. package/dist/command/cli/answers/gate-hook-message.js +6 -0
  12. package/dist/command/cli/auth/cloud-keystore.d.ts +0 -2
  13. package/dist/command/cli/auth/cloud-keystore.js +0 -3
  14. package/dist/command/cli/auth/cloud-session.d.ts +6 -2
  15. package/dist/command/cli/auth/cloud-session.js +2 -0
  16. package/dist/command/cli/auth/device-flow.d.ts +2 -2
  17. package/dist/command/cli/cli-doctor.js +7 -5
  18. package/dist/command/cli/cli-flow-commands.d.ts +3 -6
  19. package/dist/command/cli/cli-flow-commands.js +67 -4
  20. package/dist/command/cli/cli-parse.d.ts +8 -1
  21. package/dist/command/cli/cli-parse.js +24 -4
  22. package/dist/command/cli/cli-update-commands.js +8 -1
  23. package/dist/command/cli/cli-verify.d.ts +24 -3
  24. package/dist/command/cli/cli-verify.js +54 -11
  25. package/dist/command/cli/cloud-cli.js +64 -2
  26. package/dist/command/cli/cloud-kit.js +3 -5
  27. package/dist/command/cli/cloud-login.d.ts +1 -1
  28. package/dist/command/cli/cloud-login.js +27 -8
  29. package/dist/command/cli/demo/demo-page.js +6 -3
  30. package/dist/command/cli/doctor/browser/chromium-hint.d.ts +5 -0
  31. package/dist/command/cli/doctor/browser/chromium-hint.js +5 -0
  32. package/dist/command/cli/launch/cli-launch.d.ts +8 -0
  33. package/dist/command/cli/launch/cli-launch.js +21 -9
  34. package/dist/command/cli/launch/sdk-version-match.d.ts +84 -0
  35. package/dist/command/cli/launch/sdk-version-match.js +206 -0
  36. package/dist/command/cli/lifecycle/daemon-lifecycle.js +2 -16
  37. package/dist/command/cli/ports/resolve/cli-port.d.ts +25 -0
  38. package/dist/command/cli/ports/resolve/cli-port.js +125 -15
  39. package/dist/command/cli/setup-mcp-cli.js +21 -13
  40. package/dist/command/cli/tutorial.d.ts +2 -2
  41. package/dist/command/cli/tutorial.js +21 -7
  42. package/dist/command/cli.js +79 -11
  43. package/dist/command/daemon/binding/daemon-status-probe.d.ts +14 -2
  44. package/dist/command/daemon/binding/daemon-status-probe.js +34 -6
  45. package/dist/command/daemon/daemon-resolve.d.ts +9 -0
  46. package/dist/command/daemon/daemon-resolve.js +1 -1
  47. package/dist/command/daemon/lifetime/daemon-usefulness.d.ts +7 -1
  48. package/dist/command/daemon/lifetime/daemon-usefulness.js +30 -7
  49. package/dist/command/dev/stale-issue-guard.d.ts +3 -4
  50. package/dist/command/dev/stale-issue-guard.js +8 -8
  51. package/dist/command/setup/bringup/connect-lease.d.ts +17 -0
  52. package/dist/command/setup/bringup/connect-lease.js +38 -0
  53. package/dist/command/setup/bringup/dev-server-files.d.ts +68 -0
  54. package/dist/command/setup/bringup/dev-server-files.js +131 -0
  55. package/dist/command/setup/bringup/dev-server-log-cap.d.ts +35 -0
  56. package/dist/command/setup/bringup/dev-server-log-cap.js +121 -0
  57. package/dist/command/setup/bringup/dev-server-wait.d.ts +2 -0
  58. package/dist/command/setup/bringup/dev-server-wait.js +1 -1
  59. package/dist/command/setup/bringup/ensure-daemon.d.ts +65 -4
  60. package/dist/command/setup/bringup/ensure-daemon.js +135 -16
  61. package/dist/command/setup/bringup/static-page-server.d.ts +35 -0
  62. package/dist/command/setup/bringup/static-page-server.js +151 -0
  63. package/dist/command/setup/desktop-shape.d.ts +4 -0
  64. package/dist/command/setup/desktop-shape.js +18 -2
  65. package/dist/command/setup/init/init-port.d.ts +15 -0
  66. package/dist/command/setup/init/init-port.js +44 -0
  67. package/dist/command/setup/init/init-runtime.d.ts +1 -1
  68. package/dist/command/setup/init/init-runtime.js +91 -9
  69. package/dist/command/setup/node-effects.d.ts +27 -14
  70. package/dist/command/setup/node-effects.js +86 -45
  71. package/dist/command/setup/probe/loopback-probe-urls.d.ts +7 -0
  72. package/dist/command/setup/probe/loopback-probe-urls.js +40 -0
  73. package/dist/command/setup/probe/page-probe.d.ts +13 -2
  74. package/dist/command/setup/probe/page-probe.js +18 -3
  75. package/dist/command/setup/run-setup.d.ts +28 -1
  76. package/dist/command/setup/run-setup.js +103 -15
  77. package/dist/command/setup/session-pick.d.ts +13 -0
  78. package/dist/command/setup/session-pick.js +20 -0
  79. package/dist/command/setup/setup-command.d.ts +2 -14
  80. package/dist/command/setup/setup-command.js +105 -31
  81. package/dist/command/setup/setup-mcp.d.ts +2 -0
  82. package/dist/command/setup/setup-mcp.js +17 -8
  83. package/dist/command/setup/terminal/wait-progress.d.ts +8 -0
  84. package/dist/command/setup/terminal/wait-progress.js +17 -0
  85. package/dist/command/version/peer-announce.d.ts +6 -2
  86. package/dist/command/version/peer-announce.js +8 -6
  87. package/dist/command/version/version-nudge.d.ts +17 -3
  88. package/dist/command/version/version-nudge.js +25 -4
  89. package/dist/features/crawl/crawl-empty.js +1 -1
  90. package/dist/features/crawl/crawl-tools.d.ts +6 -0
  91. package/dist/features/crawl/crawl-tools.js +57 -0
  92. package/dist/features/crawl/crawl.js +171 -157
  93. package/dist/features/exhaust/app-coverage.d.ts +33 -0
  94. package/dist/features/exhaust/app-coverage.js +31 -0
  95. package/dist/features/exhaust/code-coverage.d.ts +47 -0
  96. package/dist/features/exhaust/code-coverage.js +77 -0
  97. package/dist/features/exhaust/exhaust-run.d.ts +33 -0
  98. package/dist/features/exhaust/exhaust-run.js +82 -0
  99. package/dist/features/exhaust/explorer.d.ts +103 -0
  100. package/dist/features/exhaust/explorer.js +195 -0
  101. package/dist/features/exhaust/failure-branches.d.ts +33 -0
  102. package/dist/features/exhaust/failure-branches.js +92 -0
  103. package/dist/features/exhaust/ledger.d.ts +202 -0
  104. package/dist/features/exhaust/ledger.js +252 -0
  105. package/dist/features/exhaust/session-fold.d.ts +31 -0
  106. package/dist/features/exhaust/session-fold.js +79 -0
  107. package/dist/features/exhaust/session-port.d.ts +36 -0
  108. package/dist/features/exhaust/session-port.js +102 -0
  109. package/dist/features/harness/drive-report.d.ts +0 -9
  110. package/dist/features/harness/drive-report.js +18 -0
  111. package/dist/features/harness/driver.js +4 -7
  112. package/dist/features/harness/fill-values.d.ts +10 -0
  113. package/dist/features/harness/fill-values.js +50 -11
  114. package/dist/features/harness/jev-driver.js +30 -13
  115. package/dist/features/harness/openai-driver.d.ts +12 -0
  116. package/dist/features/harness/openai-driver.js +21 -12
  117. package/dist/features/harness/platform-config.js +5 -6
  118. package/dist/features/license/license.d.ts +2 -2
  119. package/dist/index.d.ts +9 -0
  120. package/dist/index.js +56 -19
  121. package/dist/judgement/capsule/capsule-store.d.ts +4 -4
  122. package/dist/judgement/domain/domain-model.js +3 -1
  123. package/dist/judgement/oracles/self-instrument.js +4 -2
  124. package/dist/judgement/runs/artifact/replay-mapping.d.ts +2 -2
  125. package/dist/judgement/runs/artifact/replay-mapping.js +44 -9
  126. package/dist/judgement/runs/verification-sync.d.ts +3 -1
  127. package/dist/judgement/runs/verification-sync.js +1 -1
  128. package/dist/language/flows/change/affected.d.ts +9 -0
  129. package/dist/language/flows/change/affected.js +35 -0
  130. package/dist/language/flows/change/gate.d.ts +20 -0
  131. package/dist/language/flows/change/gate.js +12 -1
  132. package/dist/language/flows/flow-author.d.ts +7 -0
  133. package/dist/language/flows/flow-author.js +19 -0
  134. package/dist/language/flows/flow-expect-grammar.js +1 -1
  135. package/dist/language/flows/flow-journey.d.ts +54 -0
  136. package/dist/language/flows/flow-journey.js +119 -0
  137. package/dist/language/flows/flow-replay-run.d.ts +2 -3
  138. package/dist/language/flows/flow-replay-run.js +13 -47
  139. package/dist/language/flows/flow-replay.d.ts +22 -8
  140. package/dist/language/flows/flow-replay.js +55 -9
  141. package/dist/language/flows/flow-result.d.ts +2 -0
  142. package/dist/language/flows/flow-result.js +25 -0
  143. package/dist/language/flows/flow-tools.js +17 -5
  144. package/dist/language/flows/flows.d.ts +10 -1
  145. package/dist/language/flows/flows.js +90 -14
  146. package/dist/language/flows/recording/replay-halt.js +3 -1
  147. package/dist/language/flows/recording/tape/recordings.d.ts +30 -1
  148. package/dist/language/flows/recording/tape/recordings.js +55 -1
  149. package/dist/language/flows/replay.d.ts +19 -0
  150. package/dist/language/flows/replay.js +42 -6
  151. package/dist/language/flows/verify-change-tools.js +5 -2
  152. package/dist/launch-chromium.d.ts +80 -0
  153. package/dist/launch-chromium.js +218 -0
  154. package/dist/memory/cloud/cloud-config.d.ts +11 -2
  155. package/dist/memory/cloud/cloud-config.js +47 -7
  156. package/dist/memory/cloud/cloud-sync.d.ts +4 -1
  157. package/dist/memory/cloud/cloud-sync.js +6 -9
  158. package/dist/memory/cloud/harness-offer.js +5 -6
  159. package/dist/memory/cloud/harness-switch.js +5 -6
  160. package/dist/memory/cloud/sync-cycle.d.ts +77 -3
  161. package/dist/memory/cloud/sync-cycle.js +325 -55
  162. package/dist/memory/cloud/sync-daemon.js +24 -20
  163. package/dist/memory/cloud/sync-disk.js +16 -2
  164. package/dist/memory/intent/intent-shard.d.ts +10 -32
  165. package/dist/memory/intent/intent-shard.js +0 -10
  166. package/dist/memory/intent/intent-tools.js +8 -3
  167. package/dist/memory/journal/drive-flow.d.ts +18 -13
  168. package/dist/memory/journal/drive-flow.js +45 -37
  169. package/dist/memory/journal/envelope.d.ts +8 -8
  170. package/dist/memory/journal/journal-query.d.ts +14 -0
  171. package/dist/memory/journal/journal-query.js +15 -0
  172. package/dist/memory/journal/on-disk/workspace-tiers.js +2 -0
  173. package/dist/memory/journal/session-end.d.ts +9 -1
  174. package/dist/memory/journal/session-end.js +24 -12
  175. package/dist/memory/project/artifact-root-resolver.d.ts +10 -0
  176. package/dist/memory/project/artifact-root-resolver.js +46 -19
  177. package/dist/memory/project/dir/reticle-dir.d.ts +2 -0
  178. package/dist/memory/project/dir/reticle-dir.js +1 -0
  179. package/dist/memory/project/project-tools.js +3 -1
  180. package/dist/portal/bridge/auth-failure-reason.d.ts +1 -1
  181. package/dist/portal/bridge/auth-failure-reason.js +4 -2
  182. package/dist/portal/bridge/bridge.d.ts +12 -3
  183. package/dist/portal/bridge/bridge.js +49 -3
  184. package/dist/portal/input/js-coverage.d.ts +25 -0
  185. package/dist/portal/input/js-coverage.js +30 -0
  186. package/dist/portal/input/real-input.d.ts +11 -0
  187. package/dist/portal/input/real-input.js +29 -4
  188. package/dist/portal/input/viewport-tools.js +16 -6
  189. package/dist/portal/pool/playwright-launcher.d.ts +6 -0
  190. package/dist/portal/pool/playwright-launcher.js +20 -13
  191. package/dist/portal/session/dev-server/dev-command.d.ts +2 -2
  192. package/dist/portal/session/dev-server/dev-command.js +30 -12
  193. package/dist/portal/session/dev-server/dev-server-probe.js +17 -1
  194. package/dist/portal/session/dev-server/route-status-probe.d.ts +24 -0
  195. package/dist/portal/session/dev-server/route-status-probe.js +57 -0
  196. package/dist/portal/session/facts/observed-state.d.ts +11 -0
  197. package/dist/portal/session/facts/observed-state.js +34 -0
  198. package/dist/portal/session/no-session-diagnosis.d.ts +29 -0
  199. package/dist/portal/session/no-session-diagnosis.js +62 -5
  200. package/dist/portal/session/no-session-next-action.d.ts +13 -0
  201. package/dist/portal/session/no-session-next-action.js +48 -9
  202. package/dist/portal/session/no-session-watch.d.ts +18 -0
  203. package/dist/portal/session/no-session-watch.js +88 -2
  204. package/dist/portal/session/session-health.js +3 -2
  205. package/dist/portal/session/session-manager.d.ts +20 -6
  206. package/dist/portal/session/session-manager.js +19 -3
  207. package/dist/portal/session/session-reaper.d.ts +28 -0
  208. package/dist/portal/session/session-reaper.js +49 -1
  209. package/dist/portal/session/session.d.ts +10 -8
  210. package/dist/portal/session/session.js +24 -32
  211. package/dist/surface/http-server.d.ts +3 -2
  212. package/dist/surface/http-server.js +8 -5
  213. package/dist/surface/mcp/mcp-proxy.d.ts +2 -2
  214. package/dist/surface/mcp/mcp-proxy.js +2 -2
  215. package/dist/surface/mcp/mcp.js +5 -3
  216. package/dist/surface/mcp/proxy/proxy-daemon-probe.d.ts +13 -1
  217. package/dist/surface/mcp/proxy/proxy-daemon-probe.js +27 -2
  218. package/dist/surface/mcp/proxy/proxy-log.js +3 -5
  219. package/dist/surface/mcp/server-instructions.js +9 -7
  220. package/dist/surface/tools/act/after-match.d.ts +17 -0
  221. package/dist/surface/tools/act/after-match.js +30 -0
  222. package/dist/surface/tools/act/durable.d.ts +69 -0
  223. package/dist/surface/tools/act/durable.js +139 -0
  224. package/dist/surface/tools/act/navigation/navigate-arrival.d.ts +13 -6
  225. package/dist/surface/tools/act/navigation/navigate-arrival.js +47 -10
  226. package/dist/surface/tools/act/navigation/navigate-result.d.ts +1 -12
  227. package/dist/surface/tools/act/navigation/navigate-result.js +21 -3
  228. package/dist/surface/tools/act/proved-controls.d.ts +9 -0
  229. package/dist/surface/tools/act/proved-controls.js +11 -0
  230. package/dist/surface/tools/act/sequence-grade.d.ts +5 -0
  231. package/dist/surface/tools/act/sequence-grade.js +32 -1
  232. package/dist/surface/tools/act-capsule.d.ts +7 -0
  233. package/dist/surface/tools/act-capsule.js +46 -1
  234. package/dist/surface/tools/act-sequence-tool.js +25 -8
  235. package/dist/surface/tools/act-tools.js +28 -52
  236. package/dist/surface/tools/assert/assert-grade.js +12 -1
  237. package/dist/surface/tools/assert/assert-verdict.js +2 -2
  238. package/dist/surface/tools/browser-tools.js +14 -2
  239. package/dist/surface/tools/coverage-tools.js +27 -1
  240. package/dist/surface/tools/dynamic-tools.js +29 -2
  241. package/dist/surface/tools/error-recovery.js +5 -6
  242. package/dist/surface/tools/feedback-invite.js +6 -4
  243. package/dist/surface/tools/harness-explore.d.ts +3 -2
  244. package/dist/surface/tools/harness-explore.js +12 -7
  245. package/dist/surface/tools/invoke-tool.js +12 -8
  246. package/dist/surface/tools/lease-tools.js +15 -6
  247. package/dist/surface/tools/live-call-text.js +4 -1
  248. package/dist/surface/tools/observe-tools.js +9 -1
  249. package/dist/surface/tools/tool-kit.d.ts +10 -0
  250. package/dist/surface/tools/tools.js +4 -0
  251. package/dist/telemetry/argument-shape.d.ts +0 -2
  252. package/dist/telemetry/argument-shape.js +1 -5
  253. package/dist/telemetry/error-class.d.ts +2 -3
  254. package/dist/telemetry/error-class.js +2 -3
  255. package/dist/telemetry/harness-drive.d.ts +0 -2
  256. package/dist/telemetry/harness-drive.js +0 -4
  257. package/dist/telemetry/onboarding-firsts.d.ts +2 -2
  258. package/dist/telemetry/onboarding-firsts.js +2 -2
  259. package/dist/wire-journal.d.ts +3 -0
  260. package/dist/wire-journal.js +4 -0
  261. package/dist/workspace-packages.d.ts +0 -2
  262. package/dist/workspace-packages.js +1 -9
  263. package/docs/README.md +4 -2
  264. package/docs/actions.mdx +3 -4
  265. package/docs/agent-cheatsheet.md +4 -4
  266. package/docs/architecture.md +6 -6
  267. package/docs/best-practices.mdx +1 -1
  268. package/docs/capabilities.mdx +32 -1
  269. package/docs/cli/cloud.mdx +20 -8
  270. package/docs/cli/doctor.mdx +4 -0
  271. package/docs/cli/feedback.mdx +2 -2
  272. package/docs/cli/gate.mdx +9 -4
  273. package/docs/cli/update.mdx +10 -0
  274. package/docs/cli.mdx +22 -7
  275. package/docs/desktop.mdx +14 -10
  276. package/docs/environment.mdx +87 -0
  277. package/docs/faq.mdx +18 -16
  278. package/docs/first-drive.md +5 -13
  279. package/docs/flows.md +21 -2
  280. package/docs/frameworks.mdx +29 -10
  281. package/docs/getting-started.md +15 -5
  282. package/docs/http-transport.md +6 -3
  283. package/docs/human-control.md +2 -2
  284. package/docs/index.mdx +16 -6
  285. package/docs/install-agentic.mdx +2 -2
  286. package/docs/install-manual.mdx +1 -1
  287. package/docs/integration-patterns.md +3 -3
  288. package/docs/multi-agent-testing.md +1 -1
  289. package/docs/packages/babel-plugin.mdx +1 -1
  290. package/docs/packages/browser.mdx +2 -2
  291. package/docs/packages/next.mdx +3 -0
  292. package/docs/packages/react.mdx +2 -2
  293. package/docs/packages/tauri.mdx +1 -1
  294. package/docs/packages/vite-plugin.mdx +4 -2
  295. package/docs/packages.mdx +9 -9
  296. package/docs/predicates.mdx +50 -5
  297. package/docs/quickstart.mdx +12 -10
  298. package/docs/recipes.mdx +2 -2
  299. package/docs/state-management.mdx +2 -2
  300. package/docs/system-map.md +1 -1
  301. package/docs/telemetry.md +1 -1
  302. package/docs/token-efficiency.md +3 -3
  303. package/docs/tools/act-and-wait.mdx +1 -0
  304. package/docs/tools/act-sequence.mdx +1 -1
  305. package/docs/tools/act.mdx +1 -1
  306. package/docs/tools/console.mdx +1 -1
  307. package/docs/tools/inspect.mdx +1 -1
  308. package/docs/tools/network.mdx +1 -1
  309. package/docs/tools/query.mdx +1 -1
  310. package/docs/tools/sessions.mdx +1 -1
  311. package/docs/tools/snapshot.mdx +1 -1
  312. package/docs/tools/state.mdx +1 -1
  313. package/docs/tools/wait-for.mdx +1 -1
  314. package/docs/usage.md +7 -7
  315. package/docs/use-cases.mdx +157 -0
  316. package/docs/vs-screenshots.mdx +3 -3
  317. package/docs/what-is-recorded.md +2 -2
  318. package/docs/what-is-reticle.mdx +17 -20
  319. package/docs/why-reticle.mdx +14 -21
  320. package/package.json +10 -9
  321. package/dist/judgement/capsule/minimize.d.ts +0 -12
  322. package/dist/judgement/capsule/minimize.js +0 -22
  323. package/dist/judgement/phenomena/phenomena.d.ts +0 -27
  324. package/dist/judgement/phenomena/phenomena.js +0 -101
  325. package/dist/language/flows/flow-report.d.ts +0 -22
  326. package/dist/language/flows/flow-report.js +0 -106
@@ -14,7 +14,7 @@ Reticle has three moving parts: a dev-only **SDK** inside your page that instrum
14
14
 
15
15
  Your app, in dev, embeds a tiny **SDK** that instruments the page (DOM, network, console, routing, framework state) and opens a WebSocket to a local **bridge**. The bridge runs inside the Reticle **server**, which also exposes an **MCP server**, the standard protocol AI agents speak.
16
16
 
17
- Your coding agent calls MCP tools (`reticle_query`, `reticle_act`, `reticle_assert`, …); the server turns them into commands over the WebSocket; the SDK executes them in the page and streams back structured events. The agent thus **looks, acts, observes, and asserts** on the real running app, never on a screenshot.
17
+ Your coding agent calls MCP tools (`reticle_look { action: "find" }`, `reticle_act`, `reticle_assert`, …); the server turns them into commands over the WebSocket; the SDK executes them in the page and streams back structured events. The agent thus **looks, acts, observes, and asserts** on the real running app, never on a screenshot.
18
18
 
19
19
  ```mermaid
20
20
  flowchart LR
@@ -23,7 +23,7 @@ flowchart LR
23
23
  B["@reticlehq/browser<br/>SDK in your app<br/>(the DOM)"]
24
24
  D[(".reticle/<br/>flows · baselines<br/>runs · contract")]
25
25
 
26
- A -- "MCP (stdio/SSE)<br/>reticle_query / act / assert" --> S
26
+ A -- "MCP (stdio/SSE)<br/>reticle_look / act / assert" --> S
27
27
  S -- "tool results" --> A
28
28
  S <-- "WebSocket<br/>commands / events" --> B
29
29
  S <--> D
@@ -37,7 +37,7 @@ Reticle is a pnpm + Turborepo monorepo. The split is not cosmetic: each boundary
37
37
 
38
38
  | Package | Runs in | Responsibility | Hard rule |
39
39
  | --- | --- | --- | --- |
40
- | `@reticlehq/core` | both | The **wire contract**: every constant + zod schema crossing any boundary | Depends only on `zod` |
40
+ | `@reticlehq/core` | both | The **wire contract**: every constant + zod schema crossing any boundary | Depends only on `zod` and `open-verification` |
41
41
  | `@reticlehq/browser` | the browser | Instrument the page; execute commands; emit events | Never imports Node APIs |
42
42
  | `@reticlehq/server` | Node | The bridge, the MCP server, the `reticle` CLI, flow/run storage | Never imports DOM APIs |
43
43
  | `@reticlehq/react` | the browser | The SDK **kit** you install in a browser app: re-exports the browser sensor (so one install gives both `reticle` and `install`) and maps a DOM node → React component → source `file:line` | Core works without the source-mapping half |
@@ -59,8 +59,8 @@ The browser and server both import it; neither can invent a message the other do
59
59
 
60
60
  1. **Connect.** In dev, your app calls `reticle.connect({ session })`. The SDK opens a WebSocket to the bridge (`ws://localhost:4400/reticle` by default) and sends a `HELLO` carrying the session id, protocol version, and (if configured) a pairing token. Each browser tab uses `SESSION_AUTO` (a unique id), so multiple apps/tabs never collide.
61
61
  2. **Capture.** The SDK installs observers: a `MutationObserver` for DOM changes, wrappers around `fetch`/`XHR` for network, a console hook, a history hook for routing, and registries the app opts into (`registerStore`, `registerCapabilities`, `reticle.signal`). Events flow into a bounded **ring buffer**, so recent history is always available and memory is capped.
62
- 3. **Look / act.** The agent calls an MCP tool. `reticle_query` finds an element by role/text/testid/ component and returns a stable **ref**. `reticle_act` dispatches an action against that ref and returns an **effect** report (did it land, did the DOM mutate, did focus move…). The server sends the command over the WebSocket; the SDK runs it and replies.
63
- 4. **Observe.** After an action, the agent reads what happened with `reticle_network`, `reticle_console`, `reticle_state`, or the reaction digest from `reticle_act_and_wait`. The server pulls the relevant slice of the ring buffer (scoped to a cursor so stale events can't leak in) and returns a compact summary.
62
+ 3. **Look / act.** The agent calls an MCP tool. `reticle_look { action: "find" }` finds an element by role/text/testid/ component and returns a stable **ref**. `reticle_act` dispatches an action against that ref and returns an **effect** report (did it land, did the DOM mutate, did focus move…). The server sends the command over the WebSocket; the SDK runs it and replies.
63
+ 4. **Observe.** After an action, the agent reads what happened with `reticle_observe { action: "network" }`, `reticle_observe { action: "console" }`, `reticle_look { action: "state" }`, or the reaction digest from `reticle_act_and_wait`. The server pulls the relevant slice of the ring buffer (scoped to a cursor so stale events can't leak in) and returns a compact summary.
64
64
  5. **Assert.** `reticle_assert` (and a flow's declared `success`) evaluates a **predicate** over program truth (a network call that returned 200, a store value, a `signal` the app emitted), not just "an element exists." This is the difference between "looks done" and "is done."
65
65
 
66
66
  ---
@@ -87,7 +87,7 @@ The same verification runs over and over, every commit, every CI run. Reticle re
87
87
 
88
88
  ### 4. Dev-only, localhost-only, your app data stays local
89
89
 
90
- The SDK is tree-shaken out of production builds and connects only to a local bridge. The bridge binds to loopback by default; exposing it beyond localhost _requires_ a pairing token (the server refuses to bind a non-loopback host without one). Every environment variable that gates a security control is a single named constant, so a typo can't silently disable auth. Nothing from the app under test ever leaves your machine: no DOM, network, console, state, or source. The CLI reports anonymous, opt-out usage metrics only (a random id + event names like `invoke`/`session_start`; no code, no PII; see [telemetry](telemetry.md)); opt out with `reticle telemetry disable`, `RETICLE_TELEMETRY=0`, or `DO_NOT_TRACK=1`. The one thing that carries free text is feedback you or your agent deliberately send (`reticle feedback` / `reticle_feedback`). It is never collected passively, it is redacted before sending, and it is disabled separately with `RETICLE_FEEDBACK=0`.
90
+ The SDK is tree-shaken out of production builds and connects only to a local bridge. The bridge binds to loopback by default; exposing it beyond localhost _requires_ a pairing token (the server refuses to bind a non-loopback host without one). Every environment variable that gates a security control is a single named constant, so a typo can't silently disable auth. Nothing from the app under test ever leaves your machine: no DOM, network, console, state, or source. The CLI reports anonymous, opt-out usage metrics only (a random id + event names like `invoke`/`session_start`; no code, no PII; see [telemetry](telemetry.md)); opt out with `reticle telemetry disable`, `RETICLE_TELEMETRY=0`, or `DO_NOT_TRACK=1`. The one thing that carries free text is feedback you or your agent deliberately send (`reticle feedback` / `reticle_session { action: "feedback" }`). It is never collected passively, it is redacted before sending, and it is disabled separately with `RETICLE_FEEDBACK=0`.
91
91
 
92
92
  ---
93
93
 
@@ -196,7 +196,7 @@ Sessions accumulate. Four stale ones pointing at dev servers you restarted an ho
196
196
 
197
197
  ## Confirm a navigation landed
198
198
 
199
- `reticle_navigate` returns `confirmed: false` by design. A navigation destroys the document the SDK lives in, so it can only report dispatch. Check [`reticle_sessions`](/tools/sessions) for a session at the new URL before acting, and re-snapshot: refs do not survive a new document.
199
+ `reticle_navigate` returns `confirmed: false` by design. A navigation destroys the document the SDK lives in, so it can only report dispatch. Check [`reticle_session { action: "list" }`](/tools/sessions) for a session at the new URL before acting, and re-snapshot: refs do not survive a new document.
200
200
 
201
201
  ## Start with what the app says about itself
202
202
 
@@ -55,7 +55,7 @@ Useful at the end of a drive, when the agent is about to report success. "I veri
55
55
  }
56
56
  ```
57
57
 
58
- Note the token redaction. Credential-shaped values are stripped here exactly as they are in [`reticle_network`](/tools/network), so reading storage never puts a session token in your context.
58
+ Note the token redaction. Credential-shaped values are stripped here exactly as they are in [`reticle_observe { action: "network" }`](/tools/network), so reading storage never puts a session token in your context.
59
59
 
60
60
  localStorage, sessionStorage and readable cookies.
61
61
 
@@ -87,6 +87,24 @@ Toasts that auto-dismiss, debounced search, polling, session timeouts, retry bac
87
87
 
88
88
  Freeze the clock, advance it by exactly the interval, assert the consequence. No sleeping, no guessing, and the same result on a fast laptop and a loaded CI runner.
89
89
 
90
+ <Warning>
91
+ Freeze the clock **before** the action that schedules the timer. Reticle replaces the page's
92
+ `setTimeout` and `setInterval` when the clock is frozen, so timers that already exist keep running
93
+ on the browser's real clock and are not fired by `advanceMs`.
94
+
95
+ If a timer is created during page mount, freeze the clock first and then reload the page so the mount-time timer is created under the fake clock.
96
+
97
+ </Warning>
98
+
99
+ The clock virtualizes `Date.now()`, but not every time API. `new Date()` and `performance.now()` continue to use real time. `performance.now()` is intentionally left real because React's scheduler depends on it.
100
+
101
+ The reliable pattern is:
102
+
103
+ 1. Freeze the clock.
104
+ 2. Perform the action that schedules the timer, or reload the page if the timer is created during mount.
105
+ 3. Advance by the required interval.
106
+ 4. Assert the consequence.
107
+
90
108
  ### Crawl: click everything, report anomalies
91
109
 
92
110
  ```json
@@ -123,6 +141,19 @@ This is the tool for an app you did not write and do not understand yet.
123
141
  25.
124
142
  </Warning>
125
143
 
144
+ Controls Reticle judges destructive are blocked unless you pass `confirmDangerous: true`. That covers the controls on the page the crawl started on. A control one of its clicks revealed, such as the "Yes, delete" behind a "Delete", stays blocked, so one crawl cannot finish a deletion you did not look at.
145
+
146
+ ### Covering everything: `exhaustive`
147
+
148
+ `{ "action": "crawl", "exhaustive": true }` goes past one page. It walks every state reachable from where you start, reaching each one by replaying the path that found it from a fresh load, so it never depends on "back" working. It fills text fields before it tries a form's buttons (from `RETICLE_SECRET_<FIELD>` when set, so it gets past a login, and only on the origin the crawl started on), and treats controls that differ only by a number, like table rows, as one. A control with the same name on a different screen is tried again there. Every write it finds is then driven again with the request failing. A write counts as **branched** only when the request was actually sent and broken. With the write failing, the app must not end up where it goes when the write succeeds; if it does, you get `failure-unhandled`. That part needs a browser Reticle drives (`reticle drive`).
149
+
150
+ The run folds into the app-wide coverage ledger, which `reticle_verify { action: "coverage", app: true }` also reports. It has six levels, each with what is missing: routes **reached**, controls **touched**, controls **proved** (an `act_and_wait` came back `yes`), journeys **completed**, writes **branched** (failure path driven), and app functions **executed** (V8 coverage, driven browser only, collected from the first coverage request or exhaustive crawl onward, so an ordinary drive pays nothing for it). `frontier` is the number of states found and not yet explored. Zero means every state the explorer could tell apart was explored, not that every reachable state was: a state is its route plus the names of its controls, so two screens that differ only in field values, in how many of a control they show, or in what the same controls do count as one, and the second is never visited. `reticle gate` blocks when a level falls below the best it has reached, and when a changed file never ran. A changed file whose coverage was taken before the edit is reported as `coverageStale` instead, because its counts describe the old code. When a drop was intended, `reticle gate --accept-coverage` records today's levels as the new best.
151
+
152
+ <Warning>
153
+ Exhaustive really does click everything, repeatedly, and submits forms. `maxSteps` bounds it,
154
+ defaulting to 400 actions.
155
+ </Warning>
156
+
126
157
  ### Reconcile: what the API said versus what rendered
127
158
 
128
159
  ```json
@@ -1,16 +1,18 @@
1
1
  ---
2
2
  title: reticle cloud commands
3
- description: Sign in, link a repository, and read runs and regressions from Reticle Cloud.
3
+ description: Connect a project in one command, then read runs and regressions from Reticle Cloud.
4
4
  icon: cloud
5
5
  ---
6
6
 
7
- Ten subcommands bind this repository to Reticle Cloud so runs and flows land on a dashboard. They dispatch before the local parser, so `reticle login` is one tool, not a second binary.
7
+ From your app directory, `reticle connect --project "My App"` wires the app, opens browser sign-in approval if needed, links the named cloud project, and syncs the local history already on this machine. Run the [installer](/quickstart) once on the machine first. The commands below remain available when you need to sign in or link separately.
8
8
 
9
9
  ```bash
10
- npx @reticlehq/server <login|logout|whoami|link|project|config|push|runs|regression|share>
10
+ npx @reticlehq/server connect --project "My App"
11
11
  ```
12
12
 
13
- These are thin clients over the `/v1` API. The logic is server-side; these verbs surface it.
13
+ Use `--url https://your-cloud-origin` for a self-hosted cloud. `connect` checks a saved sign-in against that exact host before reuse; if it has expired, the browser approval opens again. It mints or reuses a project-scoped key, so there is no key to paste. A newly wired app may have no run to sync yet: ask your agent to verify one flow and the linked daemon will sync the result. Run `reticle whoami` to check the binding and `reticle push` to send anything still pending.
14
+
15
+ The other cloud commands are thin clients over the `/v1` API. Their logic is server-side.
14
16
 
15
17
  ## Where credentials live
16
18
 
@@ -20,7 +22,13 @@ These are thin clients over the `/v1` API. The logic is server-side; these verbs
20
22
  | `~/.reticle/credentials.json` | Project-scoped API keys from `reticle link`, keyed by `projectId` | Yes |
21
23
  | `<repo>/.reticle/cloud.json` | `{ projectId, projectName, url, sync, verify }`. The non-secret binding | No, commit it |
22
24
 
23
- Auth for any command is `RETICLE_API_KEY` from the environment (the agent path) if set, otherwise the login token. `RETICLE_CLOUD_URL` overrides the endpoint; the default is `http://localhost:8890`. When a command falls back to that default without `RETICLE_CLOUD_URL` (or a saved session URL), it prints a one-line notice on stderr; stdout stays pure JSON for agents.
25
+ Auth for any command is `RETICLE_API_KEY` from the environment (the agent path) if set, otherwise the login token. `RETICLE_CLOUD_URL` (or its short form `RETICLE_URL`) overrides the endpoint; without it, a command uses the saved session's host, and otherwise the hosted service, `https://app.reticle.sh`.
26
+
27
+ ## In CI
28
+
29
+ CI needs `RETICLE_API_KEY` and nothing else: no `login`, no `link`. Commit `.reticle/cloud.json`, store the key as a secret, and the daemon and `reticle push` sync to the project the binding names. A key stored in `credentials.json` wins over the environment when both exist.
30
+
31
+ The environment key is only sent to the host it belongs to: `RETICLE_CLOUD_URL` when set, otherwise the hosted service. A repo linked to a self-hosted install therefore also needs `RETICLE_CLOUD_URL` set to that install in CI. A repo with no `cloud.json` and a key in the environment syncs to the hosted service, or to `RETICLE_CLOUD_URL`.
24
32
 
25
33
  ## The commands
26
34
 
@@ -101,9 +109,13 @@ reticle: no .reticle/cloud.json here ... run `reticle link` first
101
109
 
102
110
  ### `reticle push`
103
111
 
104
- Sends local run artifacts from `.reticle` to the linked project, honoring the sync policy. With `sync.runs` off it does nothing and says so. Prints `{ ok, project, sent, pulled }`, where `sent` carries the accepted counts and, when the server refused anything, a `rejected` array naming each artifact's index and reason.
112
+ Sends local run artifacts from `.reticle` to the linked project, honoring the sync policy. With `sync.runs` off it does nothing and says so. Prints `{ ok, project, sent, pulled }`, where `sent` carries the accepted counts and, when the server refused anything, a `rejected` array naming each run's index and reason and a `refused` array for flows, capsules and derived records. `sent.records` lists only the records the server accepted.
113
+
114
+ Runs go up in batches, so a large backlog is never one oversized request.
115
+
116
+ What the platform cannot read is not sent at all, and a `notSent` array says why: a record kind an older platform does not know yet (this one alone leaves `ok` true), or a flow or run in a file version it does not read. Sending those would only be refused again on every cycle.
105
117
 
106
- **A rejection is a failed push.** If the server refuses any artifact, `ok` is `false` and the command exits `1`, even when other artifacts in the same bundle landed. A refused artifact never reaches the dashboard and is re-offered on every cycle, so partial loss is still loss. `ok: true` and exit `0` mean everything offered was accepted, which includes the common case of a repo that was already up to date. The accepted counts still report what did land, so a partial push is visible as one.
118
+ **A rejection is a failed push.** If the server refuses any artifact, `ok` is `false` and the command exits `1`, even when other artifacts in the same bundle landed. The reason is kept as `sync.lastError` in `reticle whoami`, and the background daemon logs it once, until a cycle refuses nothing. A refused run is not offered again until its file changes or the platform reports that it reads more: until then it is listed under `notRetried`, and the summary reads `refused, not retried: N run(s) (reason)`. A refused artifact never reaches the dashboard and is re-offered on every cycle, so partial loss is still loss. `ok: true` and exit `0` mean everything offered was accepted, which includes the common case of a repo that was already up to date. The accepted counts still report what did land, so a partial push is visible as one.
107
119
 
108
120
  ### `reticle runs`
109
121
 
@@ -130,7 +142,7 @@ reticle: usage: reticle share <runId>
130
142
  `push`, `runs` and `regression` all need a project to talk to. Without one they exit `1` with the same line on stderr, measured:
131
143
 
132
144
  ```
133
- reticle: cloud not attached here ... run `reticle link` (or set RETICLE_CLOUD_URL/KEY)
145
+ reticle: cloud not attached here ... run `reticle link` (or set RETICLE_API_KEY)
134
146
  ```
135
147
 
136
148
  ## Exit codes
@@ -72,6 +72,10 @@ All three captures below are real, taken by pointing `PLAYWRIGHT_BROWSERS_PATH`
72
72
  chromium ✓ installed (chromium-1234)
73
73
  ```
74
74
 
75
+ **`✓ using the installed Google Chrome`** (or Microsoft Edge) means the pinned build is absent and Reticle drives the browser you already have instead, through Playwright's channel. Nothing to do: the line also names the command that adds Playwright's own build, if you want it.
76
+
77
+ When neither is there, Reticle does not refuse. The first command that needs a browser (a lease, `verify`, a drive) runs the bundled Playwright's own pinned install for you, announced on stderr so `reticle mcp`'s stdout stays clean. The two `✗` lines below are what you see when that has not happened yet, or failed.
78
+
75
79
  **`✗ wrong revision`** means builds are installed and none of them is the wanted one. One pinned download away. The line names the revision Playwright wants, then every revision the root actually holds, then the exact command:
76
80
 
77
81
  ```
@@ -11,7 +11,7 @@ npx @reticlehq/server feedback [--rating 1-5] [--bug] "what worked, what didn't"
11
11
  npx @reticlehq/server feedback --agent --kind <bug|gap|ambiguity|feature_request|improvement> "what happened"
12
12
  ```
13
13
 
14
- The `--agent` form is for an agent that cannot reach the `reticle_feedback` tool: during `init`, while instrumentation is half wired, or when the daemon will not start. Those are the reports that are hardest to hear and most needed, because every other path requires the tool surface to already work.
14
+ The `--agent` form is for an agent that cannot reach the `reticle_session { action: "feedback" }` tool: during `init`, while instrumentation is half wired, or when the daemon will not start. Those are the reports that are hardest to hear and most needed, because every other path requires the tool surface to already work.
15
15
 
16
16
  ## Flags
17
17
 
@@ -63,5 +63,5 @@ npx @reticlehq/server feedback --agent --kind gap \
63
63
  icon="comments"
64
64
  href="/tools/session-and-feedback"
65
65
  >
66
- `reticle_feedback` when the tools are reachable.
66
+ `reticle_session { action: "feedback" }` when the tools are reachable.
67
67
  </Card>
package/docs/cli/gate.mdx CHANGED
@@ -7,7 +7,7 @@ icon: shield-halved
7
7
  `reticle gate` is what makes verification unavoidable. It computes the [affected](/cli/affected) flows, looks for a passing artifact for each, and fails the command if any is missing. An agent that edits a covered file cannot call itself finished without re-verifying.
8
8
 
9
9
  ```bash
10
- npx @reticlehq/server gate [--since <ref>] [file...]
10
+ npx @reticlehq/server gate [--since <ref>] [--accept-coverage] [file...]
11
11
  ```
12
12
 
13
13
  ## Arguments and flags
@@ -16,6 +16,7 @@ npx @reticlehq/server gate [--since <ref>] [file...]
16
16
  | --- | --- | --- | --- |
17
17
  | `file...` | strings, positional | none | Explicit changed files |
18
18
  | `--since <ref>` | string | `HEAD` when no files are given | Also include files git reports as changed since this ref |
19
+ | `--accept-coverage` | boolean | off | Accept today's coverage levels as the new best before judging. Use it when a drop was intended, such as a control you removed on purpose. The run reports what it accepted as `coverageAccepted` |
19
20
 
20
21
  ## What blocks and what does not
21
22
 
@@ -24,6 +25,10 @@ npx @reticlehq/server gate [--since <ref>] [file...]
24
25
  | `uncovered` | Yes | An affected flow with no passing artifact |
25
26
  | `downgraded` | Yes | A flow whose assertions were weakened since its last passing run. A green bought by weakening the test is the gaming move this exists to stop |
26
27
  | `deletedCoverage` | Yes | A flow that covered a changed file and has since vanished. Coverage deleted, not satisfied |
28
+ | `unflowed` | Yes | A changed interactive component that no saved flow drives. Only once the project has saved flows, so a first day never blocks |
29
+ | `coverageRegressed` | Yes | A [coverage](/capabilities) level fell below the best it has reached: something new appeared that nothing covers. Cover it, or accept the drop with `--accept-coverage` |
30
+ | `unexecuted` | Yes | A changed file the browser loaded during a drive and never ran a function of |
31
+ | `coverageStale` | No | A changed file whose code coverage was taken before its last edit. Its counts describe the old code, so `unexecuted` cannot judge it either way. Take coverage again after driving the change to measure it |
27
32
  | `quarantined` | No | Flows the flake ledger has quarantined. Surfaced, never blocking, because one unexplained flake that blocks a merge destroys trust in the red |
28
33
 
29
34
  ## What it prints
@@ -41,14 +46,14 @@ One `reticle_gate` line. Real capture from this repository, with the flow lists
41
46
  }
42
47
  ```
43
48
 
44
- `downgraded` and `deletedCoverage` appear only when they are non-empty, and neither was present in that run. The CLI prints this as a single line of JSON; it is pretty-printed here to be readable.
49
+ `downgraded`, `deletedCoverage`, `unflowed`, `coverageRegressed`, `unexecuted`, `coverageStale` and `coverageAccepted` appear only when they are non-empty, and none was present in that run. The coverage categories need a coverage ledger, which a project gets from `reticle_verify { action: "crawl", exhaustive: true }` or `reticle_verify { action: "coverage", app: true }`; without one they never fire. The CLI prints this as a single line of JSON; it is pretty-printed here to be readable.
45
50
 
46
51
  ## Exit codes
47
52
 
48
53
  | Code | Meaning |
49
54
  | --- | --- |
50
- | `0` | Every affected, non-flaky flow has a passing artifact and nothing was weakened or deleted |
51
- | `1` | Something is uncovered, downgraded or deleted. Also `1` on any internal fault: the gate fails closed |
55
+ | `0` | Every affected, non-flaky flow has a passing artifact, nothing was weakened or deleted, and coverage held |
56
+ | `1` | Something is uncovered, downgraded, deleted, unflowed, unexecuted, or coverage fell. Also `1` on any internal fault: the gate fails closed |
52
57
 
53
58
  Measured on this repository, `npx @reticlehq/server gate --since HEAD~1` exited `1` with the output above: 0 of 47 flows covered, 39 uncovered and 8 quarantined.
54
59
 
@@ -31,6 +31,16 @@ No flags.
31
31
 
32
32
  The SDK sync is best effort and non-fatal on purpose. This command gets run from wherever the human happens to be standing, which is often not an app at all.
33
33
 
34
+ ## Stuck on an older Windows release
35
+
36
+ Reticle 3.1.0 and older cannot apply this update on Windows and may fail with `spawn EINVAL`. Those releases run their own outdated updater, so retrying the command will not help. Run the installer directly from PowerShell instead:
37
+
38
+ ```powershell
39
+ irm https://raw.githubusercontent.com/reticlehq/reticle/main/install/install.ps1 | iex
40
+ ```
41
+
42
+ This installs the current server without going through the old updater.
43
+
34
44
  ## Exit codes
35
45
 
36
46
  | Code | Meaning |
package/docs/cli.mdx CHANGED
@@ -13,13 +13,9 @@ npx @reticlehq/server <command>
13
13
  ```
14
14
 
15
15
  <Warning>
16
- **Always invoke it as `npx @reticlehq/server <command>`.** The tables below name the commands as
17
- `reticle <command>`, because `reticle` is the bin this package installs and that is what the CLI
18
- prints about itself. It is **not** a package on npm: the bare name `reticle` belongs to an
19
- unrelated author, so putting `npx` in front of it downloads and runs their code instead of ours.
20
- Once
21
- `@reticlehq/server` is a dependency of your project, `reticle <command>` works from an npm script
22
- or with the bin on your PATH.
16
+ If you used the installer, run `reticle <command>`: it put that bin on your PATH. Otherwise use
17
+ `npx @reticlehq/server <command>`. Do not put `npx` before the bare `reticle` bin name: the npm
18
+ package with that name belongs to an unrelated author.
23
19
  </Warning>
24
20
 
25
21
  <Note>
@@ -30,6 +26,16 @@ npx @reticlehq/server <command>
30
26
 
31
27
  ## Setup and diagnosis
32
28
 
29
+ ### `reticle connect`
30
+
31
+ From an app directory, one command wires an unwired app, proves a browser session, signs in through browser approval if needed, links the chosen cloud project, and syncs existing local history. A saved sign-in is reused only after the intended cloud host accepts it. Run `reticle init` instead for local-only verification.
32
+
33
+ ```bash
34
+ npx @reticlehq/server connect --project "My App"
35
+ ```
36
+
37
+ For a self-hosted cloud, add `--url <cloud origin>`. If the app was already wired, `connect` keeps its local setup and refreshes the cloud link. A fresh app has no run to send until you verify a flow with your agent.
38
+
33
39
  ### `reticle init`
34
40
 
35
41
  Wires Reticle into the project in the current directory, then boots the app and proves a session connected. It exits non-zero if nothing connected, and prints what is left to do. It does not drive: proving a flow is the FIRST RUN, `reticle verify <url> --explore --persona "<who does what>"`.
@@ -112,6 +118,15 @@ policy https://github.com/reticlehq/reticle/blob/main/docs/telemetry.md
112
118
 
113
119
  A bare `reticle` with no arguments is not help. It is `reticle serve`.
114
120
 
121
+ <Note>
122
+ When the Reticle SDK installed in the project is a different **major** version from the CLI you ran,
123
+ the CLI hands the same command to `npx @reticlehq/server@<that version>`, so an unpinned
124
+ `npx @reticlehq/server` never pairs a daemon with an SDK it cannot talk to. `init`, `update`,
125
+ `rollback`, `setup install`, `version` and `help` always run as typed, and so does a command whose
126
+ version you pinned yourself (`npx @reticlehq/server@<version> …`). Set `RETICLE_NO_VERSION_MATCH=1` to
127
+ turn it off.
128
+ </Note>
129
+
115
130
  <Warning>
116
131
  `reticle verify` binds the bridge port itself, so it fails if a daemon is already listening, which
117
132
  is the normal state once your agent has started one. Stop the daemon first, or point it elsewhere
package/docs/desktop.mdx CHANGED
@@ -88,7 +88,7 @@ The rows in **bold** are re-proven by a committed battery on every change, `pnpm
88
88
  | Capability | Electron | Tauri | Note |
89
89
  | --- | --- | --- | --- |
90
90
  | sessions, snapshot, query, inspect | yes | yes | `inspect` returns `src/App.tsx:104` in a dev build; a packaged production renderer has no source map, so it reports `n/a` |
91
- | capabilities, state (live store) | yes | yes | `reticle_state` reads the real store |
91
+ | capabilities, state (live store) | yes | yes | `reticle_look { action: "state" }` reads the real store |
92
92
  | act (click/fill/type/select) | yes | yes | |
93
93
  | **act_and_wait, wait_for, assert** | yes | yes | signal / state / route / net / console predicates |
94
94
  | console errors | yes | yes | catches what the UI never shows |
@@ -133,7 +133,7 @@ export default defineConfig({
133
133
  });
134
134
  ```
135
135
 
136
- `desktop: true` does the two things a desktop shell needs and a web app must never get: the plugin also runs for `vite build` (a packaged renderer is a production build with **no dev server**, so the default serve-only gating would ship an app with no `connect()` at all), and `connect()` is called with `allowInProduction` so the SDK's production backstop does not refuse to start. Keep it behind your own dev-only build so an instrumented bundle can never reach a release binary.
136
+ `desktop: true` does two things a desktop shell needs: `connect()` is injected into the renderer's entry module, so a renderer loaded with no dev server still resolves it, and it is called with `allowInProduction`, because a packaged renderer reports `NODE_ENV=production` and the SDK's production backstop would otherwise refuse to start. It instruments the dev server, and a `vite build` **only when that build runs in a non-production mode**: `vite build --mode development`, for a packaged renderer you want to drive with no dev server. A production-mode build, which is what `vite build` and `electron-vite build` do by default, is stubbed exactly like a web bundle: it carries no SDK and never dials `ws://localhost`, so a release binary built the normal way is clean.
137
137
 
138
138
  Nothing to add in your app code. You can still call `reticle.connect()` by hand and pass `inject: false` if you want control.
139
139
 
@@ -157,10 +157,12 @@ That line is what makes your main-process calls visible. It has to live in the p
157
157
 
158
158
  **Preload sandboxing.** A sandboxed preload cannot resolve `node_modules`, so the bare `require` above fails with `module not found: @reticlehq/electron/preload`. Either bundle your preload (electron-vite and Electron Forge do this by default; the require is inlined at build time and sandboxing stays on), or set `sandbox: false` in `webPreferences`.
159
159
 
160
- **Packaged renderers.** An app that loads its renderer with `loadFile` runs on `file://`, which is a production Vite build. Pass `allowInProduction: true` to `connect()` for that mode, or keep the SDK gated behind `import.meta.env.DEV` so it never enters the shipped binary at all.
160
+ **Packaged renderers.** An app that loads its renderer with `loadFile` runs on `file://` with no dev server. To drive one, build its renderer with `vite build --mode development` (the plugin then instruments it, `allowInProduction` included). The default production-mode build ships no Reticle code at all.
161
161
 
162
162
  Working example: [`apps/electron-smoke`](https://github.com/reticlehq/reticle/tree/main/apps/electron-smoke).
163
163
 
164
+ **Electron Forge.** `reticle init` recognises Forge's Vite template (`vite.renderer.config.*` and no `vite.config.*`) and wires it the same three ways: `reticle()` in the renderer config, the preload shim, and `installReticleCapture` in main. The plugin goes in **without** `desktop: true`, because `electron-forge start` serves the renderer through a real Vite dev server, so the ordinary serve-time injection reaches the window and `electron-forge package` ships a renderer with no Reticle code. `init` starts the app with the script that runs `electron-forge start`, not the renderer's dev script. A vanilla renderer gets `@reticlehq/browser` rather than the React kit.
165
+
164
166
  ## Tauri setup
165
167
 
166
168
  Frontend side, nothing desktop-specific:
@@ -214,7 +216,7 @@ Alternatively, since an Electron renderer _is_ Chromium, `--remote-debugging-por
214
216
 
215
217
  ```toml
216
218
  [dependencies]
217
- reticle-tauri = "3.3"
219
+ reticle-tauri = "3.5"
218
220
  ```
219
221
 
220
222
  ```rust
@@ -283,12 +285,12 @@ A packaged renderer runs on `file://`, where `pushState('/settings')` rewrites t
283
285
 
284
286
  ## What IPC looks like to an agent
285
287
 
286
- A desktop app reaches its backend over IPC, not HTTP. `fetch`/`XHR` patching cannot see that, so without the IPC observer every backend call in your app is a blind spot: `reticle_network` returns nothing, `act_and_wait` has no in-flight request to settle on, and `assert { net }` is vacuously true. That is a false green by construction.
288
+ A desktop app reaches its backend over IPC, not HTTP. `fetch`/`XHR` patching cannot see that, so without the IPC observer every backend call in your app is a blind spot: `reticle_observe { action: "network" }` returns nothing, `act_and_wait` has no in-flight request to settle on, and `assert { net }` is vacuously true. That is a false green by construction.
287
289
 
288
290
  Reticle records each IPC call as an ordinary request, so the tools you already use work unchanged:
289
291
 
290
292
  ```jsonc
291
- // reticle_network { urlContains: "ipc://" }
293
+ // reticle_observe { action: "network", urlContains: "ipc://" }
292
294
  {
293
295
  "calls": [
294
296
  { "method": "ipc", "url": "ipc://todos:load", "status": 200, "ms": 134 },
@@ -297,7 +299,7 @@ Reticle records each IPC call as an ordinary request, so the tools you already u
297
299
  }
298
300
  ```
299
301
 
300
- IPC has no status code. `200`/`500` are synthetic, mapped from whether the call succeeded or failed, precisely so that `reticle_network { status: 500 }` and `assert { kind: "net", status: 500 }` keep working. On Tauri you will see `status: 500` next to `statusText: "OK"`. That is not a bug: the transport really did answer 200, and the 500 is the command's own verdict. `ok` is authoritative, and on Electron `error` carries the message your main process returned:
302
+ IPC has no status code. `200`/`500` are synthetic, mapped from whether the call succeeded or failed, precisely so that `reticle_observe { action: "network", status: 500 }` and `assert { kind: "net", status: 500 }` keep working. On Tauri you will see `status: 500` next to `statusText: "OK"`. That is not a bug: the transport really did answer 200, and the 500 is the command's own verdict. `ok` is authoritative, and on Electron `error` carries the message your main process returned:
301
303
 
302
304
  ```jsonc
303
305
  // reticle_assert { predicate: { kind: "net", urlContains: "ipc://todos:archive", status: 500 } }
@@ -363,9 +365,11 @@ Playwright MCP is faster. It is also structurally unable to see an IPC failure,
363
365
  Tauri, no: WKWebView exposes no CDP endpoint, so Playwright cannot attach at all.
364
366
  </Accordion>
365
367
  <Accordion title="Does the SDK end up in my shipped binary?">
366
- Only if you put it there. Keep the plugin behind a dev-only build, or gate `connect()` on
367
- `import.meta.env.DEV`. `desktop: true` exists because a packaged renderer is a production build,
368
- so it opts that one build into instrumentation deliberately.
368
+ No, not from a normal build. With `desktop: true` a production-mode `vite build` or
369
+ `electron-vite build` is stubbed like a web bundle: no SDK, no `ws://localhost` dial. Only a
370
+ build you run in another mode (`vite build --mode development`) is instrumented, which is how
371
+ you drive a packaged renderer with no dev server. A `connect()` you wrote by hand is yours to
372
+ gate on `import.meta.env.DEV`.
369
373
  </Accordion>
370
374
  <Accordion title="Why does a Tauri response show status 500 with statusText OK?">
371
375
  The IPC transport really did answer HTTP 200. The 500 is Reticle translating the Rust command's
@@ -0,0 +1,87 @@
1
+ ---
2
+ title: Environment variables
3
+ description: Every environment variable Reticle reads, what it changes, and which process reads it.
4
+ icon: sliders
5
+ ---
6
+
7
+ Reticle works with none of these set. Each one changes a single behaviour, and most are read once, by the process named in the table: the daemon at startup, the CLI command you run, or a build plugin when your dev server starts. A daemon that is already running keeps the values it started with, so after changing a daemon setting run `npx @reticlehq/server restart`.
8
+
9
+ ## Connection and ports
10
+
11
+ | Variable | Read by | What it does |
12
+ | --- | --- | --- |
13
+ | `RETICLE_PORT` | daemon, CLI, build plugins | The bridge and daemon port. `.reticle.json`'s `port` sets it per project; this overrides it. |
14
+ | `RETICLE_HOST` | daemon | The bridge bind host. Loopback by default. Anything else exposes the bridge beyond this machine, so it is opt-in. |
15
+ | `RETICLE_TOKEN` | daemon | A shared secret the page must present when it connects. The pairing token covers this automatically; set it only for a hand-wired setup. |
16
+ | `RETICLE_PAIRING_TOKEN_DIR` | daemon, `init`, build plugins | Where the auto-provisioned pairing token lives. Defaults to `~/.reticle`. Relocate it for CI or a read-only home directory. |
17
+ | `RETICLE_ALLOWED_ORIGINS` | daemon | Comma-separated WebSocket origins the bridge accepts. |
18
+ | `RETICLE_STATE_DIR` | daemon, CLI | Daemon state: pid files, the discovery registry and logs. Defaults to `~/.reticle`. |
19
+ | `RETICLE_DAEMON_READY_TIMEOUT_MS` | MCP proxy | How long the MCP proxy waits for a daemon to come up before reporting it unreachable. |
20
+ | `RETICLE_DEV` | `@reticlehq/next` | Instrument even when `NODE_ENV` says production, for `NODE_ENV=production next dev`. |
21
+
22
+ ## Daemon lifetime
23
+
24
+ | Variable | Read by | What it does |
25
+ | --- | --- | --- |
26
+ | `RETICLE_IDLE_SHUTDOWN_MS` | daemon | How long the daemon waits with no agent, no page and no lease before it exits. `0` keeps it running. |
27
+ | `RETICLE_IDLE_ATTACHED_MS` | daemon | The same wait while an agent is attached, which is longer so a slow install or a thinking human is not cut off. Derived from the base when unset. |
28
+ | `RETICLE_MCP_PROXY_IDLE_MS` | MCP proxy | How long an abandoned MCP stdio proxy waits before it exits. `0` disables the watcher. |
29
+ | `RETICLE_JOURNAL` | daemon | Turns the causal journal off (`0`, `false`, `off`) or on. On by default. |
30
+
31
+ ## Browser
32
+
33
+ | Variable | Read by | What it does |
34
+ | --- | --- | --- |
35
+ | `RETICLE_CDP_URL` | daemon | Attach to a browser that is already running, over CDP, instead of launching one. |
36
+ | `RETICLE_MAX_CONTEXTS` | daemon | The most leased headless browser contexts the pool runs at once. |
37
+ | `RETICLE_MAX_MESSAGES_PER_SECOND` | daemon | Events per second a page may send before the bridge starts sampling them. It never disconnects. Raise it for a legitimately busy app, such as a streaming dashboard. |
38
+
39
+ ## Tools and output
40
+
41
+ | Variable | Read by | What it does |
42
+ | --- | --- | --- |
43
+ | `RETICLE_ADVERTISE_ALL_TOOLS` | daemon | Advertise every tool directly instead of the default surface. Everything is reachable either way, through `reticle_run`. |
44
+ | `RETICLE_VERIFY_SURFACE` | daemon | Advertise the smallest surface that can still produce a verdict. |
45
+ | `RETICLE_TOOL_PROFILE` | daemon | Retired. Any value it used to accept still resolves to a sensible surface, and the daemon says which one it used instead. |
46
+ | `RETICLE_ENCODING` | daemon | How tool results are written for the agent. Compact JSON by default; `pretty` indents it, and `toon` is a denser tabular form. The typed result is the same in every encoding. |
47
+ | `RETICLE_NO_UPSELL` | daemon | Silences every upgrade hint. |
48
+
49
+ ## Gate and CI
50
+
51
+ | Variable | Read by | What it does |
52
+ | --- | --- | --- |
53
+ | `RETICLE_SKIP_GATE` | `reticle gate --hook` | Lets an agent's stop hook proceed past a failing gate. The skip is printed and recorded, never silent. It does not affect `reticle gate` without `--hook`, which is what CI runs. |
54
+ | `RETICLE_VERIFY_TOKEN` | `reticle serve --http` | The bearer token the optional HTTP verify endpoint requires. |
55
+ | `RETICLE_SECRET_<FIELD>` | replay, verify, crawl | The value typed into a password or secret field at replay. A recorded flow never stores it. See [what is recorded](/what-is-recorded). |
56
+
57
+ ## Cloud and the autonomous drive
58
+
59
+ | Variable | Read by | What it does |
60
+ | --- | --- | --- |
61
+ | `RETICLE_API_KEY` | CLI, daemon | The API key minted on the platform. |
62
+ | `RETICLE_CLOUD_KEY` | CLI, daemon | The key's earlier name. Still honoured. |
63
+ | `RETICLE_CLOUD_URL`, `RETICLE_URL` | CLI, daemon | The platform host. `RETICLE_CLOUD_URL` wins when both are set. |
64
+ | `RETICLE_SYNC_INTERVAL_MS` | `reticle sync --watch` | How often the watching sync runs. Every 60 seconds by default. |
65
+ | `ANTHROPIC_API_KEY` | autonomous drive | The key the drive's model uses when no coding agent is in the loop. Without it, the drive is unavailable and everything else is unaffected. |
66
+ | `RETICLE_HARNESS_MODEL` | autonomous drive | The model the drive uses. A small one by default. |
67
+ | `RETICLE_HARNESS_BASE_URL` | autonomous drive | A proxy or gateway in front of the model API. |
68
+ | `RETICLE_HARNESS_MAX_STEPS` | autonomous drive | A hard ceiling on model turns in one drive. It bounds cost. |
69
+ | `RETICLE_HARNESS_DRIVER` | autonomous drive | Which model drives: `anthropic` (the default) or `jev`. |
70
+ | `JEV_API_KEY`, `RETICLE_HARNESS_JEV_URL` | autonomous drive | A direct key and base URL for the `jev` driver. The usual path is `RETICLE_API_KEY` through the platform. |
71
+ | `OPENAI_API_KEY`, `RETICLE_HARNESS_OPENAI_MODEL` | autonomous drive | A direct key and model for the OpenAI driver. |
72
+
73
+ ## Licensing
74
+
75
+ | Variable | Read by | What it does |
76
+ | --- | --- | --- |
77
+ | `RETICLE_LICENSE_KEY` | daemon | The Enterprise licence key. See [licence activation](/license-activation). |
78
+
79
+ ## Telemetry and feedback
80
+
81
+ | Variable | Read by | What it does |
82
+ | --- | --- | --- |
83
+ | `RETICLE_TELEMETRY` | CLI, daemon | `0`, `false` or `off` turns anonymous usage telemetry off. `DO_NOT_TRACK` is honoured too. See [telemetry](/telemetry). |
84
+ | `RETICLE_TELEMETRY_FILE` | CLI, daemon | Write telemetry to a local JSONL file and send nothing, so you can read exactly what would be sent. |
85
+ | `RETICLE_TELEMETRY_URL`, `RETICLE_TELEMETRY_KEY` | CLI, daemon | Send telemetry to a different host or project, such as an EU region or your own instance. |
86
+ | `RETICLE_FEEDBACK` | daemon | Turns off the feedback channel only. Usage telemetry follows `RETICLE_TELEMETRY`. |
87
+ | `RETICLE_TRACE` | daemon, CLI | Verbose internal tracing for people working on Reticle itself. Off by default. |
package/docs/faq.mdx CHANGED
@@ -23,22 +23,16 @@ You get considerably better results by adding `data-testid` attributes and by [e
23
23
  </Accordion>
24
24
 
25
25
  <Accordion title="Does it work without React?">
26
- The core is framework-agnostic: DOM, network, routing, console, animations, snapshots and actions are gated against a vanilla TypeScript app.
26
+ Yes. The core is framework-agnostic: DOM, network, routing, console, animations, snapshots and actions are gated against a vanilla TypeScript app. What varies by framework is how `connect()` gets in and whether you get a source pointer on top.
27
27
 
28
- Support beyond that is uneven, and we would rather say so than let you find out:
29
-
30
- | Framework | Status |
31
- | --- | --- |
32
- | React, Next.js, Remix, Astro | Supported, each with an app and a CI gate |
33
- | SvelteKit | Wired end to end. `init` writes the client hook, the plugin stamps `.svelte` source. But **no CI gate**, so unverified rather than supported |
34
- | Vue | A Pinia store adapter and nothing else. No detection, no `.vue` stamping, no gate |
28
+ [Frameworks](/frameworks) is the one list of what is proven for each, from "driven to a verdict in CI" to "install gated" to "wired, unverified".
35
29
 
36
30
  </Accordion>
37
31
 
38
32
  <Accordion title="Can it tell me whether my UI looks good?">
39
- No. Reticle verifies behaviour, not aesthetics. Visual correctness and "does this feel right" remain a human job, or a visual-diff tool's.
33
+ Reticle verifies behaviour, not aesthetics. Visual correctness and "does this feel right" remain a human job, or a visual-diff tool's, and Reticle pairs well with one.
40
34
 
41
- It does read design tokens. [`reticle_inspect`](/tools/inspect) reports whether a colour came from your theme or from someone's clipboard. But that is a consistency check, not taste.
35
+ It does read design tokens. [`reticle_look { action: "element" }`](/tools/inspect) reports whether a colour came from your theme or from someone's clipboard. But that is a consistency check, not taste.
42
36
 
43
37
  </Accordion>
44
38
 
@@ -58,7 +52,7 @@ Measured on the same app: Reticle caught 10 of 10 injected bugs, Playwright MCP
58
52
  </Accordion>
59
53
 
60
54
  <Accordion title="Can I run several apps or tabs at once?">
61
- Yes. Each is a session. Pass `sessionId` to any tool when more than one is connected, and [`reticle_sessions`](/tools/sessions) lists them.
55
+ Yes. Each is a session. Pass `sessionId` to any tool when more than one is connected, and [`reticle_session { action: "list" }`](/tools/sessions) lists them.
62
56
 
63
57
  Reticle scopes to your project and prefers the active tab, and it **refuses rather than guesses** when the choice is ambiguous. A refusal you can act on beats a silent pick of the wrong tab.
64
58
 
@@ -74,9 +68,9 @@ If you believe an `unknown` is wrong, report it. Reticle's own response says as
74
68
  </Accordion>
75
69
 
76
70
  <Accordion title="Does my data leave the machine?">
77
- Your app's data, no. Reticle is dev-only, localhost-only and offline. Credential-shaped values are redacted before they leave the page. You will see `[REDACTED]` in request bodies without configuring anything.
71
+ Your app's data, no. Reticle is dev-only and localhost-only, and nothing from the app under test (DOM, network bodies, console, state, source) is sent. Credential-shaped values are redacted before they leave the page. You will see `[REDACTED]` in request bodies without configuring anything.
78
72
 
79
- Anonymous usage telemetry is sent separately and can be turned off with `reticle telemetry disable`.
73
+ Anonymous usage counts are sent by default, separately, and carry nothing from your app. `reticle telemetry disable`, `RETICLE_TELEMETRY=0` or `DO_NOT_TRACK=1` turns them off.
80
74
 
81
75
  <Card title="Exactly what is collected" icon="chart-line" href="/telemetry">
82
76
  The complete list. If it is not on that page, it is not sent.
@@ -85,7 +79,7 @@ Anonymous usage telemetry is sent separately and can be turned off with `reticle
85
79
 
86
80
  <Accordion title="Does it work with WebGL / react-three-fiber?">
87
81
  Partially. Reticle's model of an app is DOM + store + network. A WebGL or react-three-fiber
88
- subtree is a single `<canvas>` to `reticle_query` / `reticle_snapshot`: Reticle cannot pick faces
82
+ subtree is a single `<canvas>` to `reticle_look { action: "find" }` / `reticle_look { action: "page" }`: Reticle cannot pick faces
89
83
  or drive the camera inside the scene.
90
84
 
91
85
  DOM, registered store state, and network **around** the canvas still work, and that is often where the consequential bugs live (import counts, mesh stats, solver results that contradict the UI). `init` prints this limit when it detects react-three-fiber so an adopting team does not discover it only after trying to verify the feature they care about.
@@ -93,9 +87,9 @@ DOM, registered store state, and network **around** the canvas still work, and t
93
87
  </Accordion>
94
88
 
95
89
  <Accordion title="Is Reticle proven to make agents fix more bugs?">
96
- No, and we are not going to claim it. A controlled test of fix-rate did **not** show an improvement. The benchmark was confounded in ways we can point at, and we consider the question open.
90
+ No. A fix-loop test on a small fixture found the same fix rate with and without Reticle, at two model tiers, and the Reticle arm made more tool calls. The fixture was small enough to read whole and the pass check looked for a string rather than behaviour, so the question is open. [Why Reticle](/why-reticle#what-the-fix-rate-test-measured) has the details.
97
91
 
98
- What is measured is detection: 10 of 10 injected regressions caught, with zero false alarms on a known-good control, against 9 and 8 for the alternatives.
92
+ What Reticle is built to improve is the verdict: in the 88-bug head-to-head against a Playwright script it reported one false green to the script's 29, and on a known-good control it raised no false alarms.
99
93
 
100
94
  </Accordion>
101
95
 
@@ -108,4 +102,12 @@ What is measured is detection: 10 of 10 injected regressions caught, with zero f
108
102
  Which tools are advertised directly, and why each earned its slot.
109
103
  </Card>
110
104
  </Accordion>
105
+
106
+ <Accordion title="Can I use it for security, accessibility, performance or SEO checks?">
107
+ Yes, for how your own running app behaves: access control by role, calls that must never fire, CSP violations, roles and focus, request counts and long tasks, titles, headings, links and redirects. It pairs with a vulnerability scanner, an accessibility audit tool such as axe and an SEO crawler, which cover what an in-app check does not.
108
+
109
+ <Card title="Use cases" icon="list-check" href="/use-cases">
110
+ What each one checks, with a call you can run.
111
+ </Card>
112
+ </Accordion>
111
113
  </AccordionGroup>