Reticle skill
Install, instrument and verify this running web app from the inside (DOM, network, routing, console and framework state) instead of screenshots or guessing.
by reticlehq·Apache-2.0 license·★ 1,154 Stars on the repo·GitHub ↗
npx degit reticlehq/reticle/plugin#main ~/.claude/skills/pluginChecked ·commit main
Files of Reticle
Show the full text169 lines
Reticle
Reticle embeds a dev-only SDK in the running app and exposes it to you as reticle_* MCP tools. You look, act, observe, and assert against the real app. No screenshots.
Finish the setup steps
The plugin already registered the MCP server, so this file runs with no client restart. Finish the setup steps; ask the user only when a step needs their decision. Running init, fixing wiring it could not, starting the dev server and opening the browser are setup steps, not decisions.
The repo already answers which framework, package manager and port, so work those out rather than asking. Say what you did in one line.
Two places always need the user:
- No recognisable dev script in
package.json. Say so; do not invent one. - 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.
initwriting a pre-approval rule for thereticleserver 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.
Run this branch first
cat .reticle.json 2>/dev/null || echo NOT_FOUND
NOT_FOUND→ ONBOARD, then VERIFY.- File exists → VERIFY.
Either way you are finished only when a verdict exists: from reticle_act_and_wait, reticle_assert, or reticle_act { steps } where a step declares expect. Config files are not an install, and a listed session is not a result.
ONBOARD
One command wires the project. A second one proves a flow.
npx @reticlehq/server@latest init
It detects the framework and package manager, wires the build config, installs the SDK, registers the MCP server, starts the dev server, opens the app, and waits for a session to connect from inside it. That connection IS the proof onboarding worked: the SDK is in the page and the tools have something to talk to. It exits non-zero if nothing connected, and prints exactly what is left to do.
Then prove a flow. That is the FIRST RUN, and it is a separate call:
reticle_verify { action: "explore", persona: "<who does what>" }
It drives the app with a model inside the daemon and RECORDS what it drove, so every later check replays that flow with no model in the loop.
What YOU decide, and pass in
The command reads the repository. It cannot read the request, and these live only there.
| flag | what only you know |
|---|---|
persona: "<what>" (on the FIRST RUN, not on init) |
which journey proves the thing the user asked for. Code can list the buttons; it cannot know checkout matters and the theme toggle does not. |
--env KEY=VALUE |
what the app needs to reach a usable state: the key from .env.example, the mock backend, the variable that skips an auth wall. Repeatable. |
--app <dir> |
which app in a monorepo. It can list the servable ones; only the request says which is being worked on. |
Add --license <key> if the user gave you one: it writes RETICLE_LICENSE_KEY to .env and keeps .env out of git.
Framework, package manager, port, editor and MCP client are answerable from the repo you are sitting in, so work them out rather than asking.
Then read what it gives you back
A non-zero exit is a to-do list, not a failed install. The command names the cause and prints the REMAINING steps from wherever it stopped; it will not tell you to redo a phase that already worked. Do those and re-run, which is safe.
It is not finished until a verdict exists. Writing files is not an install, and neither is a connected session.
If that command could not run it
Do not choose this path. It is not the thorough version of the one above; it is what you fall back to when the command physically could not do the work. Use it only when init exited without ever printing starting: or ▸ WATCH (an older CLI that stops after writing files), or when it stopped in the same place twice after you did what it asked. A ⚠ in the report is not a reason: re-run the command, which is idempotent and names what is still outstanding.
curl https://docs.reticle.sh/install-manual.md # register the MCP, wire the SDK, prove it
curl https://docs.reticle.sh/troubleshooting.md # nothing connected, click did nothing, verdict unknown
VERIFY
Verdicts come from reticle_act_and_wait, reticle_assert, and reticle_act { steps } when a step declares expect. Everything else (a bare act, look, navigate, observe) moves or reads the app and proves nothing, however many tools it used.
verified: "unknown" is not a pass. It means Reticle drove the app and could not tell what happened, so report it as unknown. verified: "no-fault" is not a pass either: the page settled and no channel complained, but nothing was declared to prove. Never weaken a check to make it pass. That converts a real signal into a false one, which is the failure this product exists to prevent.
Take the cheapest path that answers the question
Stop at the first row that fits.
| The question | The call | Calls |
|---|---|---|
| "Did my edit break anything?" | reticle_verify({ action: "change", files: ["src/App.tsx"] }) |
1 |
| "Does this known journey still work?" | reticle_run({ tool: "reticle_flow_replay", args: { flowName: "login" } }) |
1 |
| "Does this new behaviour work?" | reticle_act { steps: [...] } to the last page, then reticle_act_and_wait on the step that ENDS the journey |
2 |
| No MCP reachable at all | npx @reticlehq/server verify <url> in the shell |
1, no MCP |
reticle_flow_replay is not on the advertised tool list. It is reached through reticle_run exactly as written, which is the supported call shape and why you have to be told it exists. reticle_verify {action:"change"} answers unknown when no saved flow covers the files you changed: nothing ran, so nothing was proved. That is the honest answer and the signal to record one, never a pass.
Driving by hand
Four calls for a login, not fourteen. Every call is a full model turn, and in a client that approves each one it is also a click.
reticle_look({ action: "page", mode: "interactive" })once, for the whole flow. Elements are addressable by role and name, so you do not need to adddata-testidanywhere.reticle_act { steps: [...] }for the setup: every fill and every intermediate click in ONE call.reticle_act_and_wait({ ref, action, until })for the step that ENDS the journey (the confirmation, the saved record, the last page), not the first click that looks like success.untilnames that end state before the action fires, which is the difference between a check and a rationalisation. Passdurable: truewhen the change should survive a reload. A step on the way with a consequence of its own (a save, a submit) gets its ownexpectinsteps, so a journey that breaks at step three fails at step three.reticle_look({ action: "state" })once at the end. If it comes back empty orhasCapabilitiesis false, the capabilities fileinitgenerated registered nothing, which is what itsℹ AGENT: finish the capabilities fileline was telling you. Finish it yourself before you drive: opensrc/reticle-dev.*, register the app's store if it has one, and list thetestidsthe flow you picked actually touches. Do not offer, and do not ask; it is a few lines in a fileinitalready wrote for you. Never report a clean install over an empty state read.
Assert what the app did, not what it shows. A UI that renders the value it just sent, rather than the value the server returned, passes every DOM-level check ever written:
until: { kind: "allOf", predicates: [
{ kind: "net", method: "POST", urlContains: "/api/refund", status: 200, bodyContains: "\"refunded\":1187.01" },
{ kind: "element", query: { testid: "receipt" } },
{ kind: "console", level: "error", absent: true },
]}
Then report what you drove, what it produced, and the file:line for anything broken.
License key
Enterprise features are unlocked by a signed key, verified locally with Ed25519 and no network call. If the user hands you one, at any point:
- Append
RETICLE_LICENSE_KEY=<key>to the project's.env(create it if absent). The CLI loads a project-local.envon every invocation, so nothing else has to change. - Make sure
.envis in.gitignore. A license key in git is a leaked credential, so check before you write and add the line if it is missing. - Confirm:
npx @reticlehq/server licenseprintsactive,eval,missingorexpired.
Never echo the key back in your reply, and never put it in a commit, a comment, or a feedback report. Details: curl https://docs.reticle.sh/enterprise.md.
Feedback is first-class
Reticle is built for agents. You are its user, and your experience is the only signal that decides what gets built or fixed, and it disappears when your context does. The moment Reticle breaks, lies, cannot see something you needed, costs too many calls, or gives you trouble during setup:
reticle_session { action: "feedback" } { kind, text }
kind is one of bug, gap, ambiguity, feature_request, improvement. One call, then carry on. If the tools are unreachable, file the same report from the shell:
npx @reticlehq/server feedback --agent --kind <bug|gap|ambiguity|feature_request|improvement> "what happened"
Nothing is too minor. Report defects in Reticle; a bug you find in the app under test is Reticle working, and belongs in your answer to the user. Never put app source, secrets, or user data in a report.
Going deeper
Fetch the one page that answers the question rather than re-reading this file. Appending .md to any docs URL returns its source with no site chrome.
curl https://docs.reticle.sh/llms.txt # every page title and URL; read this first
curl https://docs.reticle.sh/frameworks.md # per-framework SDK wiring
curl https://docs.reticle.sh/troubleshooting.md # nothing connected, click did nothing, verdict unknown
curl https://docs.reticle.sh/agent-cheatsheet.md # the verify loop on one screen
curl https://docs.reticle.sh/predicates.md # every `until` predicate
| 1 | |
| 2 | name reticle |
| 3 | description Install, instrument and verify this running web app from the inside (DOM, network, routing, console and framework state) instead of screenshots or guessing. Drives one real flow end to end and returns a verdict with the file:line to fix. Use when the user asks to set up or install Reticle, when a user-facing change needs proving before you call it done, when a test passes but the UI is broken, or when the user types /reticle. |
| 4 | license Apache-2.0 |
| 5 | metadata |
| 6 | version 3.5.0 |
| 7 | homepage https://www.reticle.sh |
| 8 | repository https://github.com/reticlehq/reticle |
| 9 | |
| 10 | |
| 11 | # Reticle |
| 12 | |
| 13 | Reticle embeds a dev-only SDK in the running app and exposes it to you as `reticle_*` MCP tools. You look, act, observe, and assert against the real app. No screenshots. |
| 14 | |
| 15 | ## Finish the setup steps |
| 16 | |
| 17 | **The plugin already registered the MCP server, so this file runs with no client restart.** Finish the setup steps; ask the user only when a step needs their decision. Running `init`, fixing wiring it could not, starting the dev server and opening the browser are setup steps, not decisions. |
| 18 | |
| 19 | The repo already answers which framework, package manager and port, so work those out rather than asking. Say what you did in one line. |
| 20 | |
| 21 | Two places always need the user: |
| 22 | |
| 23 | **No recognisable dev script in `package.json`.** Say so; do not invent one. |
| 24 | **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. |
| 25 | |
| 26 | ## Run this branch first |
| 27 | |
| 28 | |
| 29 | cat .reticle.json 2>/dev/null || echo NOT_FOUND |
| 30 | |
| 31 | |
| 32 | `NOT_FOUND` → **ONBOARD**, then **VERIFY**. |
| 33 | File exists → **VERIFY**. |
| 34 | |
| 35 | Either way you are finished only when a verdict exists: from `reticle_act_and_wait`, `reticle_assert`, or `reticle_act { steps }` where a step declares `expect`. Config files are not an install, and a listed session is not a result. |
| 36 | |
| 37 | |
| 38 | |
| 39 | # ONBOARD |
| 40 | |
| 41 | **One command wires the project. A second one proves a flow.** |
| 42 | |
| 43 | |
| 44 | npx @reticlehq/server@latest init |
| 45 | |
| 46 | |
| 47 | It detects the framework and package manager, wires the build config, installs the SDK, registers the MCP server, starts the dev server, opens the app, and waits for a session to connect from inside it. That connection IS the proof onboarding worked: the SDK is in the page and the tools have something to talk to. It exits non-zero if nothing connected, and prints exactly what is left to do. |
| 48 | |
| 49 | **Then prove a flow. That is the FIRST RUN, and it is a separate call:** |
| 50 | |
| 51 | |
| 52 | reticle_verify { action: "explore", persona: "<who does what>" } |
| 53 | |
| 54 | |
| 55 | It drives the app with a model inside the daemon and RECORDS what it drove, so every later check replays that flow with no model in the loop. |
| 56 | |
| 57 | ## What YOU decide, and pass in |
| 58 | |
| 59 | The command reads the repository. It cannot read the request, and these live only there. |
| 60 | |
| 61 | | flag | what only you know | |
| 62 | | --- | --- | |
| 63 | | `persona: "<what>"` (on the FIRST RUN, not on `init`) | which journey proves the thing the user asked for. Code can list the buttons; it cannot know checkout matters and the theme toggle does not. | |
| 64 | | `--env KEY=VALUE` | what the app needs to reach a usable state: the key from `.env.example`, the mock backend, the variable that skips an auth wall. Repeatable. | |
| 65 | | `--app <dir>` | which app in a monorepo. It can list the servable ones; only the request says which is being worked on. | |
| 66 | |
| 67 | Add `--license <key>` if the user gave you one: it writes `RETICLE_LICENSE_KEY` to `.env` and keeps `.env` out of git. |
| 68 | |
| 69 | Framework, package manager, port, editor and MCP client are answerable from the repo you are sitting in, so work them out rather than asking. |
| 70 | |
| 71 | ## Then read what it gives you back |
| 72 | |
| 73 | A non-zero exit is a to-do list, not a failed install. The command names the cause and prints the REMAINING steps from wherever it stopped; it will not tell you to redo a phase that already worked. Do those and re-run, which is safe. |
| 74 | |
| 75 | **It is not finished until a verdict exists.** Writing files is not an install, and neither is a connected session. |
| 76 | |
| 77 | ## If that command could not run it |
| 78 | |
| 79 | **Do not choose this path.** It is not the thorough version of the one above; it is what you fall back to when the command physically could not do the work. Use it only when `init` exited without ever printing `starting:` or `▸ WATCH` (an older CLI that stops after writing files), or when it stopped in the same place twice after you did what it asked. A `⚠` in the report is not a reason: re-run the command, which is idempotent and names what is still outstanding. |
| 80 | |
| 81 | |
| 82 | curl https://docs.reticle.sh/install-manual.md # register the MCP, wire the SDK, prove it |
| 83 | curl https://docs.reticle.sh/troubleshooting.md # nothing connected, click did nothing, verdict unknown |
| 84 | |
| 85 | |
| 86 | |
| 87 | |
| 88 | # VERIFY |
| 89 | |
| 90 | **Verdicts come from `reticle_act_and_wait`, `reticle_assert`, and `reticle_act { steps }` when a step declares `expect`.** Everything else (a bare `act`, `look`, `navigate`, `observe`) moves or reads the app and proves nothing, however many tools it used. |
| 91 | |
| 92 | `verified: "unknown"` is not a pass. It means Reticle drove the app and could not tell what happened, so report it as unknown. `verified: "no-fault"` is not a pass either: the page settled and no channel complained, but nothing was declared to prove. **Never weaken a check to make it pass.** That converts a real signal into a false one, which is the failure this product exists to prevent. |
| 93 | |
| 94 | ## Take the cheapest path that answers the question |
| 95 | |
| 96 | Stop at the first row that fits. |
| 97 | |
| 98 | | The question | The call | Calls | |
| 99 | | --- | --- | --- | |
| 100 | | "Did my edit break anything?" | `reticle_verify({ action: "change", files: ["src/App.tsx"] })` | 1 | |
| 101 | | "Does this known journey still work?" | `reticle_run({ tool: "reticle_flow_replay", args: { flowName: "login" } })` | 1 | |
| 102 | | "Does this new behaviour work?" | `reticle_act { steps: [...] }` to the last page, then `reticle_act_and_wait` on the step that ENDS the journey | 2 | |
| 103 | | No MCP reachable at all | `npx @reticlehq/server verify <url>` in the shell | 1, no MCP | |
| 104 | |
| 105 | `reticle_flow_replay` is **not on the advertised tool list**. It is reached through `reticle_run` exactly as written, which is the supported call shape and why you have to be told it exists. `reticle_verify {action:"change"}` answers `unknown` when no saved flow covers the files you changed: nothing ran, so nothing was proved. That is the honest answer and the signal to record one, never a pass. |
| 106 | |
| 107 | ## Driving by hand |
| 108 | |
| 109 | Four calls for a login, not fourteen. Every call is a full model turn, and in a client that approves each one it is also a click. |
| 110 | |
| 111 | `reticle_look({ action: "page", mode: "interactive" })` **once**, for the whole flow. Elements are addressable by role and name, so you do not need to add `data-testid` anywhere. |
| 112 | `reticle_act { steps: [...] }` for the setup: every fill and every intermediate click in ONE call. |
| 113 | `reticle_act_and_wait({ ref, action, until })` for the step that ENDS the journey (the confirmation, the saved record, the last page), not the first click that looks like success. `until` names that end state **before** the action fires, which is the difference between a check and a rationalisation. Pass `durable: true` when the change should survive a reload. A step on the way with a consequence of its own (a save, a submit) gets its own `expect` in `steps`, so a journey that breaks at step three fails at step three. |
| 114 | `reticle_look({ action: "state" })` once at the end. If it comes back empty or `hasCapabilities` is false, the capabilities file `init` generated registered nothing, which is what its `ℹ AGENT: finish the capabilities file` line was telling you. Finish it yourself before you drive: open `src/reticle-dev.*`, register the app's store if it has one, and list the `testids` the flow you picked actually touches. Do not offer, and do not ask; it is a few lines in a file `init` already wrote for you. Never report a clean install over an empty state read. |
| 115 | |
| 116 | Assert what the app _did_, not what it _shows_. A UI that renders the value it just sent, rather than the value the server returned, passes every DOM-level check ever written: |
| 117 | |
| 118 | |
| 119 | until: { kind: "allOf", predicates: [ |
| 120 | { kind: "net", method: "POST", urlContains: "/api/refund", status: 200, bodyContains: "\"refunded\":1187.01" }, |
| 121 | { kind: "element", query: { testid: "receipt" } }, |
| 122 | { kind: "console", level: "error", absent: true }, |
| 123 | ]} |
| 124 | |
| 125 | |
| 126 | Then report what you drove, what it produced, and the `file:line` for anything broken. |
| 127 | |
| 128 | |
| 129 | |
| 130 | # License key |
| 131 | |
| 132 | Enterprise features are unlocked by a signed key, verified locally with Ed25519 and **no network call**. If the user hands you one, at any point: |
| 133 | |
| 134 | Append `RETICLE_LICENSE_KEY=<key>` to the project's `.env` (create it if absent). The CLI loads a project-local `.env` on every invocation, so nothing else has to change. |
| 135 | Make sure `.env` is in `.gitignore`. A license key in git is a leaked credential, so check before you write and add the line if it is missing. |
| 136 | Confirm: `npx @reticlehq/server license` prints `active`, `eval`, `missing` or `expired`. |
| 137 | |
| 138 | Never echo the key back in your reply, and never put it in a commit, a comment, or a feedback report. Details: `curl https://docs.reticle.sh/enterprise.md`. |
| 139 | |
| 140 | |
| 141 | |
| 142 | # Feedback is first-class |
| 143 | |
| 144 | Reticle is built for agents. You are its user, and your experience is the only signal that decides what gets built or fixed, and it disappears when your context does. The moment Reticle breaks, lies, cannot see something you needed, costs too many calls, or gives you trouble during setup: |
| 145 | |
| 146 | |
| 147 | reticle_session { action: "feedback" } { kind, text } |
| 148 | |
| 149 | |
| 150 | `kind` is one of `bug`, `gap`, `ambiguity`, `feature_request`, `improvement`. One call, then carry on. If the tools are unreachable, file the same report from the shell: |
| 151 | |
| 152 | |
| 153 | npx @reticlehq/server feedback --agent --kind <bug|gap|ambiguity|feature_request|improvement> "what happened" |
| 154 | |
| 155 | |
| 156 | Nothing is too minor. Report defects in **Reticle**; a bug you find in the app under test is Reticle working, and belongs in your answer to the user. Never put app source, secrets, or user data in a report. |
| 157 | |
| 158 | # Going deeper |
| 159 | |
| 160 | Fetch the one page that answers the question rather than re-reading this file. **Appending `.md` to any docs URL returns its source with no site chrome.** |
| 161 | |
| 162 | |
| 163 | curl https://docs.reticle.sh/llms.txt # every page title and URL; read this first |
| 164 | curl https://docs.reticle.sh/frameworks.md # per-framework SDK wiring |
| 165 | curl https://docs.reticle.sh/troubleshooting.md # nothing connected, click did nothing, verdict unknown |
| 166 | curl https://docs.reticle.sh/agent-cheatsheet.md # the verify loop on one screen |
| 167 | curl https://docs.reticle.sh/predicates.md # every `until` predicate |
| 168 | |
| 169 |
Discussion
Browse more free Claude skills.