@reclaimprotocol/agent 0.1.23 → 0.1.25

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 (149) hide show
  1. package/README.md +56 -20
  2. package/lib/authoring/backend.d.ts +5 -4
  3. package/lib/authoring/tools.js +30 -30
  4. package/lib/cdp/capture-engine.js +8 -7
  5. package/lib/cdp/claims.d.ts +21 -0
  6. package/lib/cdp/claims.js +129 -0
  7. package/lib/cdp/container.d.ts +85 -0
  8. package/lib/cdp/container.js +481 -0
  9. package/lib/cdp/geo.d.ts +10 -0
  10. package/lib/cdp/geo.js +36 -0
  11. package/lib/cdp/index.d.ts +1 -0
  12. package/lib/cdp/index.js +1 -0
  13. package/lib/live/cloudflared.d.ts +15 -0
  14. package/lib/live/cloudflared.js +268 -0
  15. package/lib/live/tunnel.d.ts +26 -0
  16. package/lib/live/tunnel.js +160 -0
  17. package/lib/mcp/api/operations.gen.js +34 -26
  18. package/lib/mcp/api/to-zod.d.ts +1 -1
  19. package/lib/mcp/api/to-zod.js +1 -1
  20. package/lib/mcp/api/tools.js +1 -1
  21. package/lib/mcp/instructions.d.ts +15 -2
  22. package/lib/mcp/instructions.js +102 -38
  23. package/lib/mcp/start.js +62 -6
  24. package/lib/mcp/tools/agent/analyze.js +5 -1
  25. package/lib/mcp/tools/agent/attach.d.ts +29 -2
  26. package/lib/mcp/tools/agent/attach.js +310 -24
  27. package/lib/mcp/tools/agent/build.d.ts +18 -8
  28. package/lib/mcp/tools/agent/build.js +30 -12
  29. package/lib/mcp/tools/agent/capture.js +5 -1
  30. package/lib/mcp/tools/agent/eval.js +3 -1
  31. package/lib/mcp/tools/agent/find.js +6 -5
  32. package/lib/mcp/tools/agent/how-it-works.d.ts +15 -7
  33. package/lib/mcp/tools/agent/how-it-works.js +57 -17
  34. package/lib/mcp/tools/agent/index.d.ts +1 -2
  35. package/lib/mcp/tools/agent/index.js +1 -1
  36. package/lib/mcp/tools/agent/live-view.d.ts +17 -0
  37. package/lib/mcp/tools/agent/live-view.js +146 -0
  38. package/lib/mcp/tools/agent/pod-proof.d.ts +8 -0
  39. package/lib/mcp/tools/agent/pod-proof.js +64 -0
  40. package/lib/mcp/tools/agent/propose.js +4 -2
  41. package/lib/mcp/tools/agent/publish.js +47 -77
  42. package/lib/mcp/tools/agent/session.d.ts +16 -1
  43. package/lib/mcp/tools/agent/session.js +32 -7
  44. package/lib/mcp/tools/authenticate/authenticate.js +26 -13
  45. package/lib/mcp/tools/mode/mode.d.ts +1 -1
  46. package/lib/mcp/tools/mode/mode.js +10 -22
  47. package/lib/old/client.d.ts +1 -1
  48. package/lib/old/client.js +1 -1
  49. package/lib/old/instructions.d.ts +9 -1
  50. package/lib/old/instructions.js +4 -6
  51. package/lib/old/login-cdp.d.ts +2 -2
  52. package/lib/old/login-cdp.js +46 -6
  53. package/lib/old/to-register.d.ts +3 -2
  54. package/lib/old/to-register.js +1 -1
  55. package/lib/old/tools/authenticate.js +9 -1
  56. package/lib/old/tools/get-provider-info.js +6 -5
  57. package/lib/old/tools/issue-app-credentials.js +3 -2
  58. package/lib/old/tools/publish.d.ts +1 -1
  59. package/lib/old/tools/publish.js +90 -131
  60. package/lib/old/tools/update-provider-metadata.d.ts +1 -1
  61. package/lib/old/tools/update-provider-metadata.js +1 -1
  62. package/lib/old/tools/verify-sdk-setup.js +1 -1
  63. package/lib/proof/attestor.d.ts +3 -3
  64. package/lib/proof/debug-response-dump.js +4 -4
  65. package/lib/proof/index.d.ts +1 -0
  66. package/lib/proof/index.js +1 -0
  67. package/lib/proof/page-replay.d.ts +1 -1
  68. package/lib/proof/page-replay.js +1 -1
  69. package/lib/proof/pod-prover.d.ts +47 -0
  70. package/lib/proof/pod-prover.js +155 -0
  71. package/lib/provider/dom-xpath.js +11 -9
  72. package/lib/provider/draft.d.ts +10 -7
  73. package/lib/provider/draft.js +11 -9
  74. package/lib/provider/injection-doc.d.ts +23 -1
  75. package/lib/provider/injection-doc.js +34 -21
  76. package/lib/provider/match-redaction-warning.d.ts +2 -2
  77. package/lib/provider/match-redaction-warning.js +3 -3
  78. package/lib/provider/matcher.js +13 -12
  79. package/lib/provider/secrets.d.ts +3 -3
  80. package/lib/provider/secrets.js +3 -3
  81. package/lib/provider/to-version.d.ts +7 -6
  82. package/lib/provider/to-version.js +3 -3
  83. package/lib/tools/registry.d.ts +5 -5
  84. package/lib/tools/registry.js +5 -5
  85. package/package.json +3 -3
  86. package/samples/demo-server/README.md +7 -6
  87. package/skill/SKILL.builder.md +356 -189
  88. package/skill/SKILL.common.md +400 -97
  89. package/skill/SKILL.old.md +340 -155
  90. package/skill/builder-mode.md +60 -17
  91. package/skill/hash-validation.md +170 -0
  92. package/skill/interception.md +236 -0
  93. package/skill/js-sdk-integration.md +13 -7
  94. package/skill/old-mode.md +62 -21
  95. package/skill/user-script.md +61 -0
  96. package/src/authoring/backend.ts +5 -4
  97. package/src/authoring/tools.ts +31 -30
  98. package/src/cdp/capture-engine.ts +8 -7
  99. package/src/cdp/claims.ts +153 -0
  100. package/src/cdp/container.ts +593 -0
  101. package/src/cdp/geo.ts +52 -0
  102. package/src/cdp/index.ts +1 -0
  103. package/src/consts.ts +1 -0
  104. package/src/live/cloudflared.ts +348 -0
  105. package/src/live/tunnel.ts +208 -0
  106. package/src/mcp/api/operations.gen.ts +34 -26
  107. package/src/mcp/api/to-zod.ts +1 -1
  108. package/src/mcp/api/tools.ts +1 -1
  109. package/src/mcp/instructions.ts +122 -48
  110. package/src/mcp/start.ts +69 -5
  111. package/src/mcp/tools/agent/analyze.ts +5 -1
  112. package/src/mcp/tools/agent/attach.ts +367 -26
  113. package/src/mcp/tools/agent/build.ts +35 -16
  114. package/src/mcp/tools/agent/capture.ts +5 -1
  115. package/src/mcp/tools/agent/eval.ts +4 -1
  116. package/src/mcp/tools/agent/find.ts +6 -5
  117. package/src/mcp/tools/agent/how-it-works.ts +62 -21
  118. package/src/mcp/tools/agent/index.ts +6 -2
  119. package/src/mcp/tools/agent/live-view.ts +182 -0
  120. package/src/mcp/tools/agent/pod-proof.ts +90 -0
  121. package/src/mcp/tools/agent/propose.ts +4 -2
  122. package/src/mcp/tools/agent/publish.ts +52 -77
  123. package/src/mcp/tools/agent/session.ts +33 -6
  124. package/src/mcp/tools/authenticate/authenticate.ts +28 -13
  125. package/src/mcp/tools/mode/mode.ts +10 -22
  126. package/src/old/client.ts +2 -2
  127. package/src/old/instructions.ts +4 -10
  128. package/src/old/login-cdp.ts +50 -6
  129. package/src/old/to-register.ts +3 -2
  130. package/src/old/tools/authenticate.ts +9 -1
  131. package/src/old/tools/get-provider-info.ts +6 -5
  132. package/src/old/tools/issue-app-credentials.ts +3 -2
  133. package/src/old/tools/publish.ts +95 -131
  134. package/src/old/tools/update-provider-metadata.ts +1 -1
  135. package/src/old/tools/verify-sdk-setup.ts +1 -1
  136. package/src/proof/attestor.ts +3 -3
  137. package/src/proof/debug-response-dump.ts +4 -4
  138. package/src/proof/index.ts +8 -0
  139. package/src/proof/page-replay.ts +1 -1
  140. package/src/proof/pod-prover.ts +231 -0
  141. package/src/provider/dom-xpath.ts +11 -9
  142. package/src/provider/draft.ts +13 -10
  143. package/src/provider/injection-doc.ts +37 -24
  144. package/src/provider/match-redaction-warning.ts +3 -3
  145. package/src/provider/matcher.ts +13 -12
  146. package/src/provider/secrets.ts +3 -3
  147. package/src/provider/to-version.ts +11 -9
  148. package/src/tools/registry.ts +5 -5
  149. package/skill/injection-api.md +0 -364
@@ -1,111 +1,414 @@
1
- <!-- section: header -->
2
- # Authoring a Reclaim Provider
3
-
4
- > **Terminology note:** A **provider** is the recipe you author here — the
5
- > instructions for extracting a data point from a website (the
6
- > `responseMatches` / `responseRedactions` spec, identified by a
7
- > `providerId`). Always call it a **provider**; never call a provider a
8
- > "verification." A **verification** (or **verification session**) is the
9
- > separate runtime act in which an end-user proves a data point *using* a
10
- > provider — reserve that word for the session/result, never for the recipe.
11
- > If a developer calls the recipe a "verification," gently use "provider"
12
- > yourself rather than correcting them.
13
-
14
- > **Terminology note:** "JS injection", "custom injection", "user script",
15
- > and "web script" all refer to the SAME thing when talking about a
16
- > provider — the plain browser JS that runs before every page load to drive
17
- > navigation for data gated behind login/routing. Builder's schema field is
18
- > `jsUserScripts`; old-devtools' equivalent field is `customInjection`
19
- > (identical semantics, different name). Prefer **"user script"** in your
20
- > own prose regardless of which backend you're on; see `injection-api.md`
21
- > for the full field-level reference.
22
-
23
- You drive the browser through the capture → draft → replay → prove loop using the `reclaim-agent` MCP server. **Your default stance is driver, not orchestrator.** Only pause to ask the dev for things that genuinely require a human: typing credentials, completing 2FA, solving a captcha, confirming an ambiguous value. Navigation, reloads, DOM inspection, request capture — all yours.
24
-
25
- <!-- section: step-attach -->
26
- 1. **Attach.** Call `attach_browser`. If it returns `needsChoice: true`, ask the dev which browser source they want:
27
- - `dedicated` — agent auto-launches Chrome with a dedicated profile under `~/.reclaim/chrome-profile`. Easiest local path; works out of the box.
28
- - `attach` — connect to a Chrome the dev is already running. **Important Chrome 136+ gotcha:** the dev's Chrome must be launched with BOTH `--remote-debugging-port=9222` AND `--user-data-dir=<some-non-default-path>`. Since Chrome 136 (May 2025), the debug-port flag is silently ignored when the default user profile is in use. If `attach` fails, tell them this verbatim and offer to switch to `dedicated`.
29
- - `builder` — allocate a quota-accounted remote Popcorn browser. If Builder answers 401, authenticate with `authenticate_builder` in old-devtools mode or the existing `authenticate` flow in Builder mode, then retry. You can omit `orgId` and call `attach_browser` directly: it auto-resolves to the account's only organization, or — if the account belongs to more than one — comes back with `needsChoice: true` and an `organizations` list; **ask the dev which one to use** (don't guess or default to the first) and re-call `attach_browser` with that `orgId`. (`list_builder_organizations` in old-devtools mode / `list_orgs` in Builder mode still work directly if you want the list up front instead.) Return the FULL `liveViewUrl` to the dev verbatim — on its own line, never abbreviated, elided, or wrapped in prose that truncates it — so it stays one clean, copy-pasteable string; they'll share it with the volunteer operating the browser. This works in both publishing modes and consumes one VERIFICATION-pool use under the chosen organization's normal plan restrictions.
30
- - `custom` — connect to a `ws://` or `wss://` CDP URL supplied by the dev. The URL is a credential: never repeat it, log it, or persist it. An optional `liveViewUrl` may be returned — if so, share it FULL and verbatim, same as `builder` above.
31
-
32
- Re-call with the chosen `mode` and its required fields.
33
-
34
- <!-- section: step-identify-value -->
35
- 6. **Identify the value from the DOM.** `eval_in_page` to read the displayed value (e.g. the logged-in username from `meta[name="user-login"]`'s `content` attribute). If the value is unambiguous, proceed without asking. Only confirm with the dev when there are multiple plausible interpretations (e.g. the page shows both a display name "John Doe" and a handle "@jdoe" and you need to pick which to prove).
36
-
37
- <!-- section: step-find-request -->
38
- 7. **Find the request.** Call `find_requests_containing(captureId, value)`. For each top candidate (limit ≤ 5), call `analyze_request_constraints` and surface its `concerns` to the dev verbatim. Skip candidates flagged `signedRequest` or `botChallenge` unless none others are available. Concerns flagged `csrfDependency`, `shortLivedToken`, or `graphql` are informational — surface them and proceed.
39
-
40
- **Strongly prefer authenticated, user-specific endpoints over public pages.** A proof against a public endpoint (e.g. `https://github.com/<username>`) attests only that "this URL returned this value at this time" — anyone in the world could fetch the same URL and prove the same thing. The proof says nothing about who the prover _is_. To prove a fact about the dev, the captured request needs to be one whose **response varies with authentication** — typically an API endpoint that returns the logged-in user's data (e.g. `GET https://api.github.com/user`, `https://twitter.com/i/api/.../UserByScreenName?...&forCurrentUser=true`).
41
-
42
- When ranking candidates from `find_requests_containing`, prefer ones that:
43
- - Have `Cookie`, `Authorization`, or other secret headers (analyze_request_constraints will surface these via `splitSecrets`).
44
- - Have URL paths suggesting authenticated data: `/me`, `/user`, `/settings`, `/api/...`, `/graphql` with viewer queries.
45
- - Return JSON keyed to the dev's identity (login/username/id fields next to the target value).
46
-
47
- If the only candidates are public-page HTML responses, tell the dev: "The value appears on a public page, so a proof against this endpoint won't bind to your identity — anyone could prove the same fact. I can still build it if you just want to attest the value, but for an identity-bound proof we need to capture an authenticated request. Want to keep digging, or proceed anyway?" Then wait for their call.
1
+ <!-- section: terminology -->
2
+ > **provider** = the recipe you author (the `responseMatches`/`responseRedactions`
3
+ > spec behind a `providerId`). **verification** = the runtime act of an end-user
4
+ > proving a data point *using* a provider. Never call a provider a
5
+ > "verification". If the developer does, use "provider" yourself.
6
+ >
7
+ > **user script** = the plain browser JS that runs before every page load
8
+ > ("JS injection", "custom injection", "web script" all mean this). Builder's
9
+ > field is `jsUserScripts`; old-devtools' is `customInjection`. Same thing.
10
+
11
+ **Your stance is driver, not orchestrator.** Navigation, reloads, DOM
12
+ inspection, request capture — all yours. Pause only for what needs a human:
13
+ typing credentials, 2FA, captchas, confirming a genuinely ambiguous value.
14
+
15
+ **When you're stuck, read before you guess.** Every failure mode here has a
16
+ topic. Pull the matching one with `how_it_works({ topic })` and offer the
17
+ developer what it says — a site that won't load points at `interception`, a
18
+ failing proof at `prove`, a dead browser at `browser`, an expired draft at
19
+ `troubleshooting`. Inventing a cause wastes their time; the guide usually
20
+ already names it.
21
+
22
+ <!-- section: browser -->
23
+ ## Choosing a browser
24
+
25
+ - Choosing one — ask who signs in, then offer the matching pair
26
+ - Sharing the container's live view with someone remote
27
+ - What happens when the session ends
28
+ - When the container will not start
29
+
30
+ **Ask one question first, before you list anything:**
31
+
32
+ > Will you be doing this yourself, or does someone else need to sign in — the
33
+ > person whose account it is, say?
34
+
35
+ That answer decides half the list, and a developer can answer it without knowing
36
+ what CDP is. Only then offer the matching pair, call `attach_browser` ONCE with
37
+ the `mode` they pick, and never pick for them or assume last session's choice
38
+ still applies.
39
+
40
+ **They're doing it themselves** — no link, no Docker, nothing to install:
41
+
42
+ | mode | what it is | needs |
43
+ | --- | --- | --- |
44
+ | `dedicated` | Chrome the agent launches with its own profile (`~/.reclaim/chrome-profile`), which persists, so a site stays signed in. The default choice. | nothing |
45
+ | `attach` | The Chrome they already have open, with its tabs, extensions and logins. | `port` (default 9222) |
46
+
47
+ Neither can be shared, and neither needs to be: the browser is on their screen.
48
+
49
+ **Someone else has to sign in, or the site only works on a phone** — these two
50
+ are the only modes that produce a link another person can open:
51
+
52
+ | mode | what it is | needs | costs |
53
+ | --- | --- | --- | --- |
54
+ | `container` | The Reclaim runtime in Docker, on their machine. `share_browser_view` turns its viewer into a public link. | Docker running | free — their Docker, their bandwidth. 432 MB the first time, about 2 GB of RAM, 7 to 17 seconds to start |
55
+ | `builder` | A browser in Reclaim's cloud. The link comes back immediately, with nothing to install. Also the only way to browse from a **specific country**. | Builder auth, `orgId` | **chargeable** — billed against the org's quota |
56
+
57
+ **Say which one costs money.** They are otherwise interchangeable from the
58
+ developer's point of view, so price and "do you have Docker" are the whole
59
+ decision. Do not quietly pick the billed one.
60
+
61
+ `custom` (any `ws://`/`wss://` CDP endpoint they supply) exists for someone who
62
+ was handed an endpoint and knows why. Don't offer it unprompted; it cannot be
63
+ shared either.
64
+
65
+ **`attach` gotcha (Chrome 136+, May 2025):** the developer's Chrome must be
66
+ started with BOTH `--remote-debugging-port=9222` AND
67
+ `--user-data-dir=<non-default-path>`. The debug flag is silently ignored on the
68
+ default profile. If `attach` fails, say this verbatim and offer `dedicated`.
69
+
70
+ **`builder` specifics:**
71
+
72
+ - 401 → authenticate first (`authenticate_builder` in old-devtools mode, the
73
+ native `authenticate` flow in Builder mode), then retry.
74
+ - `orgId` may be omitted: it auto-resolves when the account has exactly one org,
75
+ otherwise comes back `needsChoice` with an `organizations` list — **ask the
76
+ developer which one**, don't guess or take the first.
77
+ - **Geolocation is optional.** Omitting `countryCode` returns `needsChoice` with
78
+ a `suggestedCountryCode` (the developer's own country, from their public IP)
79
+ and costs nothing — no browser is allocated. Offer the developer three things:
80
+ the suggested country, a country the site must be seen from, or skipping geo
81
+ entirely (`skipCountry: true`, the deployment's default egress). The country
82
+ is **fixed for the session's lifetime** and can't be changed after allocation.
83
+ When a site is geo-restricted or a recipe pins a `geoLocation`, recommend a
84
+ remote browser in that country rather than working around it locally. If the
85
+ deployment has no proxy configured the browser is still allocated, just
86
+ without the requested country.
87
+ - Costs one VERIFICATION-pool use under the org's normal plan restrictions.
88
+ - Return the FULL `liveViewUrl` to the developer **verbatim, on its own line** —
89
+ never abbreviated, elided, or wrapped in prose that truncates it.
90
+ - Close it when the work is done (`dispose_browser`) so the quota isn't wasted —
91
+ but only after the developer confirms, never unprompted.
92
+
93
+ **`custom` specifics:** the `cdpUrl` is a credential — never repeat, log, or
94
+ persist it. If a `liveViewUrl` comes back, share it full and verbatim.
95
+
96
+ ### Sharing the container's live view (`share_browser_view`)
97
+
98
+ A `container` browser serves its own viewer on loopback, so the developer can
99
+ open it the moment `attach_browser` returns. `share_browser_view` puts that same
100
+ viewer behind a public HTTPS URL through a cloudflared quick tunnel — for when
101
+ the person who has to act is not at that machine: a login or 2FA only they can
102
+ complete, or a colleague watching a capture.
103
+
104
+ **Offer it whenever a login is coming and the developer is not the one holding
105
+ the credentials.** Nobody asks for a feature they have no way of knowing exists.
106
+ Ask plainly:
107
+
108
+ > Do you want a link to this browser? You can open it yourself, or send it to
109
+ > whoever holds the login — they can watch and click in it, including from a
110
+ > phone.
111
+
112
+ If they say yes, call `share_browser_view` and **relay the full URL verbatim, on
113
+ its own line.**
114
+
115
+ - **`container` is the only mode this applies to.** `builder` returns a hosted
116
+ `liveViewUrl` from `attach_browser` — relay that instead, full and verbatim.
117
+ `dedicated`, `attach`, and `custom` have no view to share at all; if someone
118
+ else needs to see the browser, say so and offer `container`.
119
+ - **The URL is the ONLY credential.** The hostname Cloudflare hands out is the
120
+ whole secret: there is no token and no login behind it, so whoever learns the
121
+ URL has the browser, as the signed-in user. Give it to one person, say plainly
122
+ what it grants, and stop the share when they are done rather than leaving it
123
+ open. Tell them the watcher sees everything typed into that browser, including
124
+ a password — so if the point is for the DEVELOPER to log in, they should do it
125
+ before you share, not after.
126
+ - **It shows the whole browser, not one tab.** A sign-in that opens a separate
127
+ window is visible too.
128
+ - **Opening it on a phone lays the page out for that phone**, with touch,
129
+ pinch-zoom and the soft keyboard. That is the reason to reach for `container`
130
+ when a site only works on mobile.
131
+ - **Call `stop_browser_view` as soon as they're done.** That closes the tunnel
132
+ only — the browser keeps running. `dispose_browser` and `reset_session` stop
133
+ the container itself.
134
+ - **If the URL will not load**, the tunnel may not have registered with
135
+ Cloudflare — the hostname exists before the route does. Have them reload after
136
+ 10 seconds; if it still fails, `stop_browser_view` then `share_browser_view`
137
+ again, since a quick tunnel does occasionally fail to come up. A
138
+ `propagating: true` in the result is that case, and `cloudflaredLog` carries
139
+ the reason.
140
+ - **cloudflared is handled for you.** An existing install on PATH wins;
141
+ otherwise it's fetched once into `~/.reclaim/bin` on the first share, never at
142
+ install time. Say so when it happens — it's a 37 MB download onto their
143
+ machine. `RECLAIM_CLOUDFLARED_PATH` points at their own copy instead.
144
+
145
+ ### How containers are shared, and when they close
146
+
147
+ - **Closing the agent releases the browser.** The server stops the container and
148
+ the tunnel when its connection drops, so a public URL never outlives the
149
+ session that made it. `dispose_browser` does the same on demand.
150
+ - **Re-attaching in one session reuses the same browser** — under a second,
151
+ rather than the twenty a fresh one costs — so whatever it is signed into is
152
+ still there.
153
+ - **A second session working at the same time gets its own container.** They
154
+ cannot share one: the live view streams the whole screen, so two sessions in
155
+ one container would see each other's browsing, and two browsers on one profile
156
+ corrupt the cookie jar they share. Each concurrent session therefore has its
157
+ own profile, and starts out signed in to nothing.
158
+ - **Four at once is the ceiling**, because each is about 2 GB of RAM. Past that,
159
+ `attach_browser` says so and points at `dedicated`.
160
+ - **A crash cannot strand one for long.** A killed session runs no cleanup, so
161
+ the next `container` attach removes any container whose owning process is gone.
162
+ One belonging to a session that is still alive is never touched.
163
+
164
+ ### When the container will not start
165
+
166
+ - **Docker is not running.** The error says so and names the other browser
167
+ modes. Docker is not something you can install for them.
168
+ - **Apple Silicon.** The image is amd64 and cannot be built for arm64, so it
169
+ runs translated. OrbStack does this itself; Docker Desktop needs "Use Rosetta
170
+ for x86/amd64 emulation" turned on. Under QEMU instead of Rosetta, Chromium
171
+ crashes on launch.
172
+ - **First run looks stuck.** It is downloading 432 MB. Say that rather than
173
+ letting it look hung.
174
+ - A container left over from a crashed run is **reused if it is still serving**,
175
+ and replaced if it is not — a second attach does not need a different port.
176
+
177
+ **When a local attach errors or `waitForCdpReady` reports a dead port**, walk
178
+ these before retrying:
179
+
180
+ - **Chrome binary not found** (the error lists the paths it tried). Set
181
+ `RECLAIM_AGENT_CHROME_PATH` to the real binary — on Windows
182
+ `setx RECLAIM_AGENT_CHROME_PATH "C:\path\to\chrome.exe"` (`setx`, not `$env:`,
183
+ so future shells inherit it); on macOS/Linux `export` it. **Then fully restart
184
+ Claude Code** — MCP servers snapshot env vars at launch.
185
+ - **Pre-existing `chrome.exe` hijacks the launch (Windows).** A new launch with
186
+ `--remote-debugging-port` gets delegated to the running process group and the
187
+ port silently never opens. `taskkill /F /IM chrome.exe`, then retry.
188
+ - **Chrome 136+ default-profile rule** (above) — `dedicated` is always safe.
189
+
190
+ <!-- section: login-detect -->
191
+ **Check login state, wait if needed.** `eval_in_page(captureId, ...)` with a
192
+ site-specific check.
193
+
194
+ **Combine a positive signal (element present only when logged in) with a
195
+ negative one (login button absent), AND them together.** Single signals are too
196
+ prone to false positives — `<meta>` tags in particular exist on every page
197
+ regardless of login state; only their `.content` is populated, so
198
+ `!!document.querySelector('meta[name="user-login"]')` is true when logged out.
199
+ Never use existence-only checks on meta tags or other persistent-but-empty
200
+ elements.
201
+
202
+ - GitHub: `(document.querySelector('meta[name="user-login"]')?.content?.length || 0) > 0 && !document.querySelector('a.HeaderMenu-link[href="/login"]')`
203
+ - X / Twitter: `!!document.querySelector('[data-testid="AppTabBar_Profile_Link"]') && !document.querySelector('[data-testid="loginButton"]')`
204
+ - Generic: `<logged-in-only-element> && !<logged-out-only-element>`
205
+
206
+ Truthy → move on. Falsy → **navigate to the login URL yourself** so the
207
+ developer lands on the form (`https://<host>/login`, `/signin`,
208
+ `https://<host>/i/flow/login` for X). Tell them once: _"I've opened the login
209
+ page on `<site>` — please log in (2FA included). I'll detect when you're
210
+ done."_ Then `wait_for_page(captureId, expression="<same expression>",
211
+ timeoutMs=180000)`; it polls every second. **Never** say "tell me when you're
212
+ done" — wait with the tool.
213
+
214
+ **Self-check:** if your detector says "already logged in" on a freshly launched
215
+ dedicated profile, the detector is wrong — a new profile has an empty cookie
216
+ jar. Rewrite the expression.
217
+
218
+ <!-- section: identify-value -->
219
+ **Identify the value from the DOM.** `eval_in_page` to read what's displayed
220
+ (for example, the username from `meta[name="user-login"]`'s `content`).
221
+ Unambiguous → proceed silently. Only ask the developer when there are genuinely
222
+ multiple readings (the page shows both "John Doe" and "@jdoe" and you must pick
223
+ one).
224
+
225
+ <!-- section: find-request -->
226
+ **Find the request.** `find_requests_containing(captureId, value)`. For each top
227
+ candidate (≤5) call `analyze_request_constraints` and surface its `concerns` to
228
+ the developer **verbatim** — don't paraphrase or omit; they're the developer's
229
+ signal that a provider is worth pursuing. Skip candidates flagged
230
+ `signedRequest` or `botChallenge` unless nothing else exists; `csrfDependency`,
231
+ `shortLivedToken` and `graphql` are informational — surface and proceed.
232
+
233
+ **Strongly prefer authenticated, user-specific endpoints over public pages.** A
234
+ proof against `https://github.com/<username>` attests only "this URL returned
235
+ this value at this time" — anyone could produce it, so it says nothing about who
236
+ the prover is. Rank candidates that:
237
+
238
+ - carry `Cookie`, `Authorization`, or other secret headers;
239
+ - have auth-shaped paths (`/me`, `/user`, `/settings`, `/api/...`, a `viewer`
240
+ GraphQL query);
241
+ - return JSON keyed to the developer's identity (login/username/id next to the
242
+ value).
243
+
244
+ If every candidate is a public HTML page, tell the developer: _"The value
245
+ appears on a public page, so a proof against this endpoint won't bind to your
246
+ identity — anyone could prove the same fact. I can still build it if you only
247
+ want to attest the value, but an identity-bound proof needs an authenticated
248
+ request. Keep digging, or proceed anyway?"_ Then wait for their call.
249
+
250
+ <!-- section: oprf -->
251
+ **Decide whether to OPRF the value** (privacy from the attestor) before calling
252
+ `propose_provider`:
253
+
254
+ - **No hash** (default) — value travels in `paramValues` + `{{name}}`; the
255
+ attestor sees the cleartext substring. Right for non-sensitive values (public
256
+ IPs, public usernames, public repo names).
257
+ - **`oprf-raw`** — server-side OPRF. **The only variant that runs on Windows /
258
+ without external deps.** Weakest privacy of the three (the attestor still
259
+ participates) — say so honestly.
260
+ - **`oprf`** — client-side ZK OPRF using gnark. Strongest. Needs a gnark install
261
+ (Linux/macOS in practice). Don't suggest it on Windows.
262
+ - **`oprf-mpc`** — TEE-based MPC. Niche; only if the developer raises it.
263
+
264
+ **Skip the question** when the value is clearly non-sensitive, and default to
265
+ no hash. When it is or could be sensitive (email, real name, private id —
266
+ anything the developer wouldn't post publicly), put the four options to them.
267
+
268
+ **Hard limits:**
269
+
270
+ 1. OPRF-hashed values cap at **62 bytes**. Longer → propose a tighter `target`
271
+ (for example, only the email local-part) or no hash.
272
+ 2. **OPRF and the request URL are mutually exclusive for the same value.** Any
273
+ value templated into the request URL is signed into the attestor's context in
274
+ the clear regardless of hashing, so hashing it doesn't just fail to help — it
275
+ doesn't verify. If the target's value also appears in the captured URL, drop
276
+ `hash` or capture a request whose URL doesn't carry it.
277
+ 3. Never pass `hash` for a free-form target (unicode / spaces / punctuation).
278
+
279
+ `propose_provider` throws a clean error on all three — retrying the same call
280
+ won't help.
48
281
 
49
282
  <!-- section: rewrite-shape-block -->
50
- **Do NOT rewrite the shape.** The synthesizer's output is the canonical Reclaim shape — `contains` template + paramValues + xPath/jsonPath redaction (optionally + regex+hash). Specifically:
51
- - Do not replace the `contains` template with a hand-crafted regex.
52
- - Do not hand-write a named-capture regex unless `hash` was requested AND the synthesizer's output doesn't already have one. (It will, for any digit/slug/email-shaped target.)
53
- - Do not remove `paramValues` — that's how the template resolves at proof time.
54
- - The redaction's xPath/jsonPath is what the prover reveals; the match's template is what the attestor verifies post-substitution. They serve different purposes — don't conflate them.
55
- - Do not prefix the match-template's param with `context.`/`context_` — that namespace is reserved for consumer-supplied URL/body params (step 10). Match templates are extraction params and stay bare.
283
+ **Do NOT rewrite the shape.** The synthesizer's output IS the canonical
284
+ Reclaim shape — `contains` template + paramValues + xPath/jsonPath redaction
285
+ (+ regex/hash). Specifically:
286
+
287
+ - Don't replace the `contains` template with a hand-written regex.
288
+ - Don't hand-write a named-capture regex unless `hash` was requested AND the
289
+ synthesizer didn't already emit one (it does, for any digit/slug/email
290
+ target).
291
+ - Don't remove `paramValues` — that's how the template resolves at proof
292
+ time.
293
+ - The redaction's xPath/jsonPath is what the prover *reveals*; the match's
294
+ template is what the attestor *verifies* post-substitution. Different
295
+ jobs — don't conflate them.
296
+ - Don't prefix the match template's param with `%CTX%` — that namespace is
297
+ for consumer-supplied URL/body params only. Match templates are extraction
298
+ params and stay bare.
299
+
300
+ Permitted edits: narrowing the `contains` template, a more distinctive
301
+ xPath class/id when the synthesizer's pick is too generic, fixing `name`,
302
+ adding/removing secret headers. Tempted to rewrite from scratch? Call
303
+ `propose_provider` again with different inputs instead.
56
304
 
57
- Permitted edits: narrowing the `contains` template, picking a more distinctive xPath class/id if the synthesizer's pick is too generic, fixing the `name` field, adding/removing secret headers. If you're tempted to rewrite the provider from scratch, call `propose_provider` again with different inputs instead.
305
+ <!-- section: secrets -->
306
+ **Marking a value secret.** Name ANY param with `SECRET` anywhere in the key
307
+ (case-insensitive) — a bare `{{SECRET_x}}` as much as a consumer-supplied
308
+ `{{%CTX%SECRET_apiKey}}` — and it routes to `secretParams.paramValues`
309
+ (`classifyParamName`/`splitSecretParamValues`), the same convention
310
+ `reclaim-inapp-sdk`/`reclaim-portal` use. **A secret param's value never appears
311
+ in the final proof.**
312
+
313
+ **Headers work by the OPPOSITE rule — an allowlist, not a name check.** Any
314
+ header not on a small fixed public list (`user-agent`, `accept`,
315
+ `accept-language`, `accept-encoding`, `sec-fetch-*`, `origin`,
316
+ `x-requested-with`, `sec-ch-ua*`) is secret by default, including
317
+ `Cookie`/`Authorization` and anything custom (`classifyHeader`/`splitSecrets`).
318
+
319
+ **The URL character budget.** A secret value CAN be used in the URL,
320
+ `geoLocation`, body, matches and redactions — write-redaction genuinely hides
321
+ it. But in the URL it only holds up to a small **TOTAL** budget summed across
322
+ every secret value in that URL (`secretUrlCharBudget`), not a per-value
323
+ allowance: **2 characters under `key-update`** (the default), **12 under `zk`**.
324
+ Over budget the value is effectively visible to the attestor no matter how it's
325
+ marked, and claim creation fails outright. Since the real value doesn't exist
326
+ until a verification session supplies one, this is checked at **verification**
327
+ time (`assertSecretParamValuesFitUrlBudget`), not publish time — except for a
328
+ captured secret *header* whose value also sits in the URL, which is already
329
+ known and so gets checked at draft time (`assertNoSecretUrlConflict`).
58
330
 
59
331
  <!-- section: no-rename-param -->
60
- **Do not rename the match-template param.** The synthesizer's `{{name}}` in `responseMatches[].value` is an _extraction_ template (the attestor pulls the value out of the response) — it stays bare and never gets the `context.` prefix. Only URL/body placeholders whose values the Consumer must supply are context params.
332
+ **Do not rename the match-template param.** The synthesizer's `{{name}}` in
333
+ `responseMatches[].value` is an _extraction_ template — it stays bare and never
334
+ takes the `%CTX%` prefix. Only URL/body placeholders whose values the consumer
335
+ supplies are context params.
61
336
 
62
337
  <!-- section: prove-tail -->
63
- **On any `verified: false` response, the result includes (inline):**
64
- - `error` — the SDK's thrown error message verbatim
65
- - `sentParams` — the exact `params` object the SDK saw, post-template substitution (lets you spot mutation / substitution surprises)
338
+ **On any `verified: false` the result includes, inline:** `error` (the SDK's
339
+ thrown message verbatim) and `sentParams` (the exact `params` the SDK saw,
340
+ post-substitution — this is where you spot mutation surprises).
341
+
342
+ **The attestor is reliable. If it errors, the cause is the provider, the
343
+ network, or prover-side code — not "the attestor can't handle this site".**
344
+ Do not invent failure modes like "the HTML is too large", "TLS records are
345
+ chunked", "it matches against gzipped bytes" (it doesn't —
346
+ `propose_provider` always sets `Accept-Encoding: identity`), or "xPath
347
+ doesn't work on this DOM". They're wrong and they waste the developer's
348
+ time. Instead: (1) show `error` verbatim; (2) if `sentParams` differs from
349
+ what you expected, surface that; (3) only then hypothesize, tied to that
350
+ evidence.
66
351
 
67
- **Deeper tracing (opt-in).** If the dev launches Claude Code with `RECLAIM_AGENT_TRACE=1` in the MCP server's env, `run_proof` ALSO captures the SDK's full pino log stream into `result.attestorLogs` and persists a per-call dump to `~/.reclaim/proof-runs/<timestamp>.json` (surfaced as `runLogPath`). Off by default because the trace stream is verbose. Suggest turning it on when investigating a recurring proof failure.
352
+ **Deeper tracing (opt-in).** With `RECLAIM_AGENT_TRACE=1` in the MCP
353
+ server's env, `run_proof` also captures the SDK's full pino stream into
354
+ `result.attestorLogs` and dumps `~/.reclaim/proof-runs/<timestamp>.json`
355
+ (`runLogPath`). Off by default — suggest it for a recurring failure.
68
356
 
69
- **The attestor is reliable. If it errors, the cause is in the provider, the network, or the prover-side code — not "the attestor can't handle this site".** Do not invent failure modes like "HTML is too large", "TLS records are chunked", "the attestor matches against gzipped bytes" (it doesn't if the provider has `Accept-Encoding: identity`, which `propose_provider` always sets), or "xPath doesn't work on this DOM". These are wrong and they waste the dev's time. Instead:
70
- 1. Show the `error` field to the dev verbatim.
71
- 2. If `sentParams` differs from what you expected — e.g. `{{name}}` substituted to the wrong value, headers got mutated, redactions look different from `propose_provider`'s output — surface that.
72
- 3. Only then propose a hypothesis, and tie it to that evidence. If the diagnosis is unclear, suggest the dev set `RECLAIM_AGENT_TRACE=1` and rerun for full attestor logs.
357
+ **Do not auto-retry with a different `attestorUrl`.** There is one live
358
+ public attestor; backup hostnames (`attestor-2.*`, and so on) **do not
359
+ exist**. `attestorUrl` is only for a developer-supplied private/staging URL.
73
360
 
74
- **Do not auto-retry with a different `attestorUrl`.** There is one live public attestor right now; backup hostnames (`attestor-2.*`, etc.) **do not exist**. `attestorUrl` is only for when the dev explicitly supplies a private/staging URL.
361
+ **"ZK circuit resources are missing"** (points at a `circuit.wasm` path) —
362
+ the npm postinstall didn't run (`--ignore-scripts`, or corp policy). Tell
363
+ the developer to run `npm run download:zk-files` from the project root and
364
+ retry `run_proof`. No re-capture, no server restart.
75
365
 
76
- **Missing ZK circuit files:** if the error says "ZK circuit resources are missing" and points at a `circuit.wasm` path, the npm postinstall didn't run (commonly because the dev used `npm install --ignore-scripts` or a corp policy disabled it). Tell the dev to run `npm run download:zk-files` from the project root and then retry `run_proof` — no need to redo capture or propose. The MCP server doesn't need a restart either; the next `run_proof` call will see the files.
366
+ <!-- section: never-do-common -->
367
+ - Never echo a raw `Authorization`, `Cookie`, or `X-*-Token` value back to the
368
+ developer or write one to a file. `list_requests`,
369
+ `find_requests_containing`, `propose_provider` and every draft/replay/proof
370
+ result are secret-free by construction. `get_request` is the one exception —
371
+ it returns the request's RAW headers so you can inspect them. Read them,
372
+ never repeat them.
373
+ - Never call `run_proof` before `replay_request` succeeds. Attestor calls are
374
+ expensive.
375
+ - Never pretend a proof succeeded when `verified: false`. Show the error and
376
+ iterate.
377
+ - Never write a literal value into `responseMatches.value` — that bakes the
378
+ original user into the provider. The synthesizer always emits `{{name}}` +
379
+ paramValues.
380
+ - **Never hand-craft `responseMatches`/`responseRedactions`.** Typing a regex by
381
+ hand, writing `(?<name>...)` by hand, or copying the captured value into the
382
+ match — all stop signs. And never accept "the xPath looks too narrow, let me
383
+ write a regex": the synthesizer already pairs the anchor with a
384
+ structure-anchored named-capture regex. If extraction looks wrong, re-run
385
+ `propose_provider` with a better request/target.
386
+ - Never rename or `%CTX%`-prefix the match-template param (see `params`).
77
387
 
78
- <!-- section: concerns-section -->
79
- ## Concerns the developer should see verbatim
388
+ <!-- section: credentials -->
389
+ ## Signing credential for the proof
80
390
 
81
- When `analyze_request_constraints` returns concerns, repeat each `message` to the dev exactly. Don't paraphrase, don't omit. These are the dev's signal that a provider is or isn't worth pursuing.
391
+ `run_proof` needs an `ownerAddress` — the eth proof-owner key whose private half
392
+ signs the claim. It does **no** resolution itself. The private key is resolved
393
+ locally (`RECLAIM_PRIVATE_KEY` env → a `RECLAIM_PRIVATE_KEY=0x…` line in the
394
+ project `.env` → a file at `RECLAIM_PRIVATE_KEY_FILE`) and never enters the
395
+ conversation.
82
396
 
83
- <!-- section: never-do-common -->
84
- - Never echo a raw `Authorization`, `Cookie`, or `X-*-Token` value back to the dev or write one to any file. The MCP guarantees these don't appear in tool responses — keep that guarantee.
85
- - Never call `run_proof` before `replay_request` succeeds. Attestor calls are expensive.
86
- - Never pretend a proof succeeded when `verified: false` came back. Show the error and iterate.
87
- - Never write a literal value into `responseMatches.value` — the synthesizer always emits `{{name}}` templates with paramValues. A literal value bakes the original user into the provider.
88
- - Never pass `hash` to `propose_provider` for a free-form (unicode / spaces / punctuation) target — the synthesizer will error, and the right move is either to use a tighter `target` or accept cleartext (no `hash`).
89
- - Never request `hash` for a target whose value also appears in the request URL — `propose_provider` throws (see step 8); the fix is dropping `hash` or capturing a request whose URL doesn't carry the value, not retrying the same call.
90
-
91
- <!-- section: first-time-issues-intro -->
92
- ## Common first-time issues — surface these proactively
93
-
94
- If a local `attach_browser` call errors, times out, or `waitForCdpReady` reports the port isn't answering, walk through these in order before retrying:
95
-
96
- - **Browser binary not found.** If the error lists candidate paths it tried (Program Files Chrome, LOCALAPPDATA Chrome, Edge), every one missed. Either the dev has Chrome installed somewhere unusual, or doesn't have Chrome/Edge at all. Walk them through setting `RECLAIM_AGENT_CHROME_PATH` to the actual binary path:
97
- ```powershell
98
- setx RECLAIM_AGENT_CHROME_PATH "C:\path\to\chrome.exe"
99
- ```
100
- `setx` (not `$env:`) writes to the user-level Windows env, so future shells and future Claude Code launches inherit it. **Then fully restart Claude Code** — MCP servers snapshot env vars at launch, so the running reclaim-agent has the old empty value. After restart, the new MCP process picks up the env. On macOS/Linux: `export RECLAIM_AGENT_CHROME_PATH=/path/to/chrome` in the shell, then restart Claude Code from that same shell so the var propagates.
101
- - **Pre-existing `chrome.exe` processes hijack the new launch (Windows).** If any Chrome is already running, a new launch with `--remote-debugging-port` gets delegated to the existing process group and the debug port silently doesn't open. Tell the dev to kill all `chrome.exe` first (`taskkill /F /IM chrome.exe` in cmd/PowerShell, or via Task Manager), then retry. Affects both `attach` and `dedicated` modes when the existing instance was launched with the same profile dir.
102
- - **Chrome 136+ silently disables remote debugging on the default user profile.** Already noted in step 1, but worth raising again here: the only fix is a non-default `--user-data-dir`. `dedicated` mode is always safe; `attach` mode needs the dev to have launched with both flags.
103
-
104
- <!-- section: credential-intro -->
105
- **Credential for signing the proof.** `run_proof` requires an `ownerAddress` — the eth proof-owner key whose private half signs the claim. It does **no** resolution itself; get the address from `resolve_owner_key` first. The private key is resolved locally (`RECLAIM_PRIVATE_KEY` env → a `RECLAIM_PRIVATE_KEY=0x…` line in the project `.env` → a file at `RECLAIM_PRIVATE_KEY_FILE`) and never enters the conversation.
106
-
107
- <!-- section: credential-step2 -->
108
- 2. All fully local — no network, no server registration. The key is read live from `.env` on each `run_proof`, so no restart is needed after issuing/importing.
109
-
110
- <!-- section: mcp-config-bullet -->
111
- - **Multiple MCP config files exist on Windows.** `~/.claude.json` takes precedence over `~/.claude/mcp.json`. When advising config edits, point them at the right file or tell them to use `claude mcp list` / `claude mcp add --scope user` rather than editing JSON by hand.
397
+ 1. **`resolve_owner_key`** returns only the derived address.
398
+ - `{ ready: true, address }` → call `run_proof` with it. No prompts.
399
+ - `{ ready: false }` → ask the developer once: **their own key** →
400
+ `import_credentials({ privateKey: "0x…" })` (a `keyFile` path also works),
401
+ or **a fresh one** → `issue_credentials({})` (a fine default for test
402
+ proofs — owner identity doesn't matter). Either writes to the project
403
+ `.env` (mode 0600, added to `.gitignore`), never to `~/.reclaim`. Then
404
+ re-run `resolve_owner_key`.
405
+ 2. Fully local — no network, no registration. The key is read live from `.env`
406
+ on every `run_proof`, so **no restart** is needed after issuing/importing,
407
+ and the existing draftId stays valid.
408
+
409
+ - **Advanced:** `RECLAIM_PRIVATE_KEY` in the MCP server env works too, but env
410
+ vars are snapshotted at Claude Code startup — that route needs a full restart,
411
+ which invalidates every draftId. Prefer the `.env` flow.
412
+ - **Multiple MCP config files exist on Windows.** `~/.claude.json` takes
413
+ precedence over `~/.claude/mcp.json`. Point developers at `claude mcp list` /
414
+ `claude mcp add --scope user` rather than hand-editing JSON.