@reclaimprotocol/agent 0.1.22 → 0.1.24

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 (163) hide show
  1. package/README.md +55 -20
  2. package/lib/authoring/backend.d.ts +13 -10
  3. package/lib/authoring/tools.js +106 -9
  4. package/lib/cdp/browser-proxy.d.ts +78 -0
  5. package/lib/cdp/browser-proxy.js +186 -0
  6. package/lib/cdp/capture-engine.js +8 -7
  7. package/lib/cdp/client.d.ts +10 -0
  8. package/lib/cdp/geo.d.ts +10 -0
  9. package/lib/cdp/geo.js +36 -0
  10. package/lib/cdp/index.d.ts +1 -0
  11. package/lib/cdp/index.js +1 -0
  12. package/lib/live/cloudflared.d.ts +15 -0
  13. package/lib/live/cloudflared.js +268 -0
  14. package/lib/live/page.d.ts +20 -0
  15. package/lib/live/page.js +134 -0
  16. package/lib/live/tunnel.d.ts +23 -0
  17. package/lib/live/tunnel.js +120 -0
  18. package/lib/live/viewer.d.ts +28 -0
  19. package/lib/live/viewer.js +232 -0
  20. package/lib/mcp/api/operations.gen.js +34 -26
  21. package/lib/mcp/api/to-zod.d.ts +1 -1
  22. package/lib/mcp/api/to-zod.js +1 -1
  23. package/lib/mcp/api/tools.js +1 -1
  24. package/lib/mcp/instructions.d.ts +15 -2
  25. package/lib/mcp/instructions.js +101 -38
  26. package/lib/mcp/start.js +4 -3
  27. package/lib/mcp/tools/agent/analyze.js +5 -1
  28. package/lib/mcp/tools/agent/attach.d.ts +8 -1
  29. package/lib/mcp/tools/agent/attach.js +166 -22
  30. package/lib/mcp/tools/agent/build.d.ts +13 -8
  31. package/lib/mcp/tools/agent/build.js +22 -11
  32. package/lib/mcp/tools/agent/capture.js +5 -1
  33. package/lib/mcp/tools/agent/eval.js +3 -1
  34. package/lib/mcp/tools/agent/find.js +6 -5
  35. package/lib/mcp/tools/agent/how-it-works.d.ts +15 -7
  36. package/lib/mcp/tools/agent/how-it-works.js +57 -17
  37. package/lib/mcp/tools/agent/live-view.d.ts +21 -0
  38. package/lib/mcp/tools/agent/live-view.js +153 -0
  39. package/lib/mcp/tools/agent/pod-proof.d.ts +8 -0
  40. package/lib/mcp/tools/agent/pod-proof.js +64 -0
  41. package/lib/mcp/tools/agent/propose.js +29 -3
  42. package/lib/mcp/tools/agent/proxy.d.ts +3 -0
  43. package/lib/mcp/tools/agent/proxy.js +199 -0
  44. package/lib/mcp/tools/agent/publish.js +49 -77
  45. package/lib/mcp/tools/agent/session.d.ts +2 -1
  46. package/lib/mcp/tools/agent/session.js +26 -7
  47. package/lib/mcp/tools/agent/shared.js +17 -8
  48. package/lib/mcp/tools/authenticate/authenticate.d.ts +11 -0
  49. package/lib/mcp/tools/authenticate/authenticate.js +143 -121
  50. package/lib/mcp/tools/authenticate/builder-access.d.ts +8 -3
  51. package/lib/mcp/tools/authenticate/builder-access.js +8 -3
  52. package/lib/mcp/tools/mode/mode.d.ts +1 -1
  53. package/lib/mcp/tools/mode/mode.js +26 -7
  54. package/lib/old/client.d.ts +9 -0
  55. package/lib/old/client.js +19 -1
  56. package/lib/old/instructions.d.ts +9 -1
  57. package/lib/old/instructions.js +4 -6
  58. package/lib/old/login-cdp.d.ts +2 -2
  59. package/lib/old/login-cdp.js +46 -6
  60. package/lib/old/to-register.d.ts +20 -8
  61. package/lib/old/to-register.js +7 -1
  62. package/lib/old/tools/authenticate.js +9 -1
  63. package/lib/old/tools/get-provider-info.js +6 -5
  64. package/lib/old/tools/issue-app-credentials.js +3 -2
  65. package/lib/old/tools/me-providers.js +3 -1
  66. package/lib/old/tools/publish.d.ts +1 -1
  67. package/lib/old/tools/publish.js +132 -129
  68. package/lib/old/tools/update-provider-metadata.d.ts +1 -1
  69. package/lib/old/tools/update-provider-metadata.js +1 -1
  70. package/lib/old/tools/verify-sdk-setup.js +1 -1
  71. package/lib/proof/attestor.d.ts +3 -3
  72. package/lib/proof/debug-response-dump.js +4 -4
  73. package/lib/proof/index.d.ts +1 -0
  74. package/lib/proof/index.js +1 -0
  75. package/lib/proof/page-replay.d.ts +36 -0
  76. package/lib/proof/page-replay.js +76 -0
  77. package/lib/proof/pod-prover.d.ts +47 -0
  78. package/lib/proof/pod-prover.js +155 -0
  79. package/lib/proof/replay.d.ts +28 -0
  80. package/lib/proof/replay.js +39 -18
  81. package/lib/provider/dom-xpath.js +11 -9
  82. package/lib/provider/draft.d.ts +13 -8
  83. package/lib/provider/draft.js +47 -13
  84. package/lib/provider/injection-doc.d.ts +23 -1
  85. package/lib/provider/injection-doc.js +34 -21
  86. package/lib/provider/match-redaction-warning.d.ts +2 -2
  87. package/lib/provider/match-redaction-warning.js +3 -3
  88. package/lib/provider/matcher.js +122 -12
  89. package/lib/provider/secrets.d.ts +3 -3
  90. package/lib/provider/secrets.js +3 -3
  91. package/lib/provider/to-version.d.ts +12 -9
  92. package/lib/provider/to-version.js +16 -4
  93. package/lib/tools/registry.d.ts +5 -5
  94. package/lib/tools/registry.js +5 -5
  95. package/package.json +4 -3
  96. package/samples/demo-server/README.md +7 -6
  97. package/skill/SKILL.builder.md +356 -189
  98. package/skill/SKILL.common.md +314 -79
  99. package/skill/SKILL.old.md +340 -155
  100. package/skill/builder-mode.md +58 -17
  101. package/skill/hash-validation.md +170 -0
  102. package/skill/interception.md +235 -0
  103. package/skill/js-sdk-integration.md +13 -7
  104. package/skill/old-mode.md +53 -12
  105. package/skill/user-script.md +61 -0
  106. package/src/authoring/backend.ts +13 -10
  107. package/src/authoring/tools.ts +128 -9
  108. package/src/cdp/capture-engine.ts +8 -7
  109. package/src/cdp/client.ts +8 -0
  110. package/src/cdp/geo.ts +52 -0
  111. package/src/cdp/index.ts +1 -0
  112. package/src/consts.ts +1 -0
  113. package/src/live/cloudflared.ts +348 -0
  114. package/src/live/page.ts +145 -0
  115. package/src/live/tunnel.ts +157 -0
  116. package/src/live/viewer.ts +293 -0
  117. package/src/mcp/api/operations.gen.ts +34 -26
  118. package/src/mcp/api/to-zod.ts +1 -1
  119. package/src/mcp/api/tools.ts +1 -1
  120. package/src/mcp/instructions.ts +121 -48
  121. package/src/mcp/start.ts +4 -3
  122. package/src/mcp/tools/agent/analyze.ts +5 -1
  123. package/src/mcp/tools/agent/attach.ts +194 -22
  124. package/src/mcp/tools/agent/build.ts +26 -15
  125. package/src/mcp/tools/agent/capture.ts +5 -1
  126. package/src/mcp/tools/agent/eval.ts +4 -1
  127. package/src/mcp/tools/agent/find.ts +6 -5
  128. package/src/mcp/tools/agent/how-it-works.ts +62 -21
  129. package/src/mcp/tools/agent/live-view.ts +198 -0
  130. package/src/mcp/tools/agent/pod-proof.ts +90 -0
  131. package/src/mcp/tools/agent/propose.ts +31 -3
  132. package/src/mcp/tools/agent/publish.ts +54 -78
  133. package/src/mcp/tools/agent/session.ts +26 -5
  134. package/src/mcp/tools/agent/shared.ts +19 -8
  135. package/src/mcp/tools/authenticate/authenticate.ts +167 -165
  136. package/src/mcp/tools/authenticate/builder-access.ts +8 -3
  137. package/src/mcp/tools/mode/mode.ts +28 -7
  138. package/src/old/client.ts +30 -1
  139. package/src/old/instructions.ts +4 -10
  140. package/src/old/login-cdp.ts +50 -6
  141. package/src/old/to-register.ts +28 -8
  142. package/src/old/tools/authenticate.ts +9 -1
  143. package/src/old/tools/get-provider-info.ts +6 -5
  144. package/src/old/tools/issue-app-credentials.ts +3 -2
  145. package/src/old/tools/me-providers.ts +3 -1
  146. package/src/old/tools/publish.ts +140 -130
  147. package/src/old/tools/update-provider-metadata.ts +1 -1
  148. package/src/old/tools/verify-sdk-setup.ts +1 -1
  149. package/src/proof/attestor.ts +3 -3
  150. package/src/proof/debug-response-dump.ts +4 -4
  151. package/src/proof/index.ts +8 -0
  152. package/src/proof/page-replay.ts +131 -0
  153. package/src/proof/pod-prover.ts +231 -0
  154. package/src/proof/replay.ts +60 -19
  155. package/src/provider/dom-xpath.ts +11 -9
  156. package/src/provider/draft.ts +56 -13
  157. package/src/provider/injection-doc.ts +37 -24
  158. package/src/provider/match-redaction-warning.ts +3 -3
  159. package/src/provider/matcher.ts +151 -10
  160. package/src/provider/secrets.ts +3 -3
  161. package/src/provider/to-version.ts +30 -13
  162. package/src/tools/registry.ts +5 -5
  163. package/skill/injection-api.md +0 -335
@@ -1,102 +1,337 @@
1
- <!-- section: header -->
2
- # Authoring a Reclaim Provider
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.
3
10
 
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.
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.
13
14
 
14
- 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.
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.
15
21
 
16
- <!-- section: step-attach -->
17
- 1. **Attach.** Call `attach_browser`. If it returns `needsChoice: true`, ask the dev which browser source they want:
18
- - `dedicated` — agent auto-launches Chrome with a dedicated profile under `~/.reclaim/chrome-profile`. Easiest local path; works out of the box.
19
- - `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`.
20
- - `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.
21
- - `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.
22
+ <!-- section: browser -->
23
+ ## Choosing a browser
22
24
 
23
- Re-call with the chosen `mode` and its required fields.
25
+ **Ask the developer which browser they want, then call `attach_browser` ONCE
26
+ with their chosen `mode`.** Never pick for them, and never assume last session's
27
+ choice still applies. You don't need a discovery call — the four options are
28
+ spelled out in the tool's own description (and summarized below). Calling with
29
+ no `mode` returns the same list as `needsChoice: true` + `options`, if you'd
30
+ rather relay it verbatim.
24
31
 
25
- <!-- section: step-identify-value -->
26
- 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).
32
+ | mode | what it is | needs | live view |
33
+ | --- | --- | --- | --- |
34
+ | `dedicated` | Local Chrome the agent launches with its own profile (`~/.reclaim/chrome-profile`). Easiest local path. | nothing | `share_browser_view` on request |
35
+ | `attach` | A Chrome the developer is already running. | `port` (default 9222) | `share_browser_view` on request |
36
+ | `builder` | Quota-accounted remote Popcorn browser. Only way to browse from a **specific country**. | Builder auth, `orgId` | hosted `liveViewUrl`, returned by `attach_browser` |
37
+ | `custom` | Any CDP websocket the developer supplies. | `cdpUrl` | `share_browser_view` on request |
27
38
 
28
- <!-- section: step-find-request -->
29
- 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
+ **Say the live-view column out loud when you offer the choice.** How the
40
+ developer watches the browser is part of picking one, and a local browser looks
41
+ like it can't be shared at all unless you tell them `share_browser_view`
42
+ exists.
30
43
 
31
- **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`).
44
+ **`attach` gotcha (Chrome 136+, May 2025):** the developer's Chrome must be
45
+ started with BOTH `--remote-debugging-port=9222` AND
46
+ `--user-data-dir=<non-default-path>`. The debug flag is silently ignored on the
47
+ default profile. If `attach` fails, say this verbatim and offer `dedicated`.
32
48
 
33
- When ranking candidates from `find_requests_containing`, prefer ones that:
34
- - Have `Cookie`, `Authorization`, or other secret headers (analyze_request_constraints will surface these via `splitSecrets`).
35
- - Have URL paths suggesting authenticated data: `/me`, `/user`, `/settings`, `/api/...`, `/graphql` with viewer queries.
36
- - Return JSON keyed to the dev's identity (login/username/id fields next to the target value).
49
+ **`builder` specifics:**
37
50
 
38
- 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.
51
+ - 401 → authenticate first (`authenticate_builder` in old-devtools mode, the
52
+ native `authenticate` flow in Builder mode), then retry.
53
+ - `orgId` may be omitted: it auto-resolves when the account has exactly one org,
54
+ otherwise comes back `needsChoice` with an `organizations` list — **ask the
55
+ developer which one**, don't guess or take the first.
56
+ - **Geolocation is optional.** Omitting `countryCode` returns `needsChoice` with
57
+ a `suggestedCountryCode` (the developer's own country, from their public IP)
58
+ and costs nothing — no browser is allocated. Offer the developer three things:
59
+ the suggested country, a country the site must be seen from, or skipping geo
60
+ entirely (`skipCountry: true`, the deployment's default egress). The country
61
+ is **fixed for the session's lifetime** and can't be changed after allocation.
62
+ When a site is geo-restricted or a recipe pins a `geoLocation`, recommend a
63
+ remote browser in that country rather than working around it locally. If the
64
+ deployment has no proxy configured the browser is still allocated, just
65
+ without the requested country.
66
+ - Costs one VERIFICATION-pool use under the org's normal plan restrictions.
67
+ - Return the FULL `liveViewUrl` to the developer **verbatim, on its own line** —
68
+ never abbreviated, elided, or wrapped in prose that truncates it.
69
+ - Close it when the work is done (`dispose_browser`) so the quota isn't wasted —
70
+ but only after the developer confirms, never unprompted.
71
+
72
+ **`custom` specifics:** the `cdpUrl` is a credential — never repeat, log, or
73
+ persist it. If a `liveViewUrl` comes back, share it full and verbatim.
74
+
75
+ ### Sharing a local browser (`share_browser_view`)
76
+
77
+ A `dedicated`, `attach`, or `custom` browser runs on the developer's machine,
78
+ so only they can see it. `share_browser_view` puts the tab behind a public
79
+ HTTPS URL through a cloudflared quick tunnel — use it when the person who has
80
+ to act is not at that machine: a login or 2FA only they can complete, or a
81
+ colleague watching a capture.
82
+
83
+ - **`builder` mode already has one.** The Popcorn browser returns a hosted
84
+ `liveViewUrl` from `attach_browser`; share that instead. The tool refuses
85
+ here rather than opening a second tunnel.
86
+ - **The URL is a credential.** It carries an unguessable token and needs no
87
+ login, so whoever holds it can see the tab — and, by default, click and type
88
+ in it as the signed-in user. Give it to one person, and say plainly what it
89
+ grants.
90
+ - **Return it verbatim, on its own line**, exactly as with `liveViewUrl`.
91
+ - `interactive: false` streams a watch-only view. Prefer it when the developer
92
+ only needs to see progress.
93
+ - **Call `stop_browser_view` as soon as they're done.** The tunnel stays open
94
+ until you do. `dispose_browser` and `reset_session` also close it.
95
+ - **cloudflared is handled for you.** An existing install on PATH wins;
96
+ otherwise it's fetched once into `~/.reclaim/bin` on the first share, never
97
+ at install time. Say so when it happens — it's a ~37 MB download onto their
98
+ machine. `RECLAIM_CLOUDFLARED_PATH` points at their own copy instead.
99
+
100
+ **When a local attach errors or `waitForCdpReady` reports a dead port**, walk
101
+ these before retrying:
102
+
103
+ - **Chrome binary not found** (the error lists the paths it tried). Set
104
+ `RECLAIM_AGENT_CHROME_PATH` to the real binary — on Windows
105
+ `setx RECLAIM_AGENT_CHROME_PATH "C:\path\to\chrome.exe"` (`setx`, not `$env:`,
106
+ so future shells inherit it); on macOS/Linux `export` it. **Then fully restart
107
+ Claude Code** — MCP servers snapshot env vars at launch.
108
+ - **Pre-existing `chrome.exe` hijacks the launch (Windows).** A new launch with
109
+ `--remote-debugging-port` gets delegated to the running process group and the
110
+ port silently never opens. `taskkill /F /IM chrome.exe`, then retry.
111
+ - **Chrome 136+ default-profile rule** (above) — `dedicated` is always safe.
112
+
113
+ <!-- section: login-detect -->
114
+ **Check login state, wait if needed.** `eval_in_page(captureId, ...)` with a
115
+ site-specific check.
116
+
117
+ **Combine a positive signal (element present only when logged in) with a
118
+ negative one (login button absent), AND them together.** Single signals are too
119
+ prone to false positives — `<meta>` tags in particular exist on every page
120
+ regardless of login state; only their `.content` is populated, so
121
+ `!!document.querySelector('meta[name="user-login"]')` is true when logged out.
122
+ Never use existence-only checks on meta tags or other persistent-but-empty
123
+ elements.
124
+
125
+ - GitHub: `(document.querySelector('meta[name="user-login"]')?.content?.length || 0) > 0 && !document.querySelector('a.HeaderMenu-link[href="/login"]')`
126
+ - X / Twitter: `!!document.querySelector('[data-testid="AppTabBar_Profile_Link"]') && !document.querySelector('[data-testid="loginButton"]')`
127
+ - Generic: `<logged-in-only-element> && !<logged-out-only-element>`
128
+
129
+ Truthy → move on. Falsy → **navigate to the login URL yourself** so the
130
+ developer lands on the form (`https://<host>/login`, `/signin`,
131
+ `https://<host>/i/flow/login` for X). Tell them once: _"I've opened the login
132
+ page on `<site>` — please log in (2FA included). I'll detect when you're
133
+ done."_ Then `wait_for_page(captureId, expression="<same expression>",
134
+ timeoutMs=180000)`; it polls every second. **Never** say "tell me when you're
135
+ done" — wait with the tool.
136
+
137
+ **Self-check:** if your detector says "already logged in" on a freshly launched
138
+ dedicated profile, the detector is wrong — a new profile has an empty cookie
139
+ jar. Rewrite the expression.
140
+
141
+ <!-- section: identify-value -->
142
+ **Identify the value from the DOM.** `eval_in_page` to read what's displayed
143
+ (for example, the username from `meta[name="user-login"]`'s `content`).
144
+ Unambiguous → proceed silently. Only ask the developer when there are genuinely
145
+ multiple readings (the page shows both "John Doe" and "@jdoe" and you must pick
146
+ one).
147
+
148
+ <!-- section: find-request -->
149
+ **Find the request.** `find_requests_containing(captureId, value)`. For each top
150
+ candidate (≤5) call `analyze_request_constraints` and surface its `concerns` to
151
+ the developer **verbatim** — don't paraphrase or omit; they're the developer's
152
+ signal that a provider is worth pursuing. Skip candidates flagged
153
+ `signedRequest` or `botChallenge` unless nothing else exists; `csrfDependency`,
154
+ `shortLivedToken` and `graphql` are informational — surface and proceed.
155
+
156
+ **Strongly prefer authenticated, user-specific endpoints over public pages.** A
157
+ proof against `https://github.com/<username>` attests only "this URL returned
158
+ this value at this time" — anyone could produce it, so it says nothing about who
159
+ the prover is. Rank candidates that:
160
+
161
+ - carry `Cookie`, `Authorization`, or other secret headers;
162
+ - have auth-shaped paths (`/me`, `/user`, `/settings`, `/api/...`, a `viewer`
163
+ GraphQL query);
164
+ - return JSON keyed to the developer's identity (login/username/id next to the
165
+ value).
166
+
167
+ If every candidate is a public HTML page, tell the developer: _"The value
168
+ appears on a public page, so a proof against this endpoint won't bind to your
169
+ identity — anyone could prove the same fact. I can still build it if you only
170
+ want to attest the value, but an identity-bound proof needs an authenticated
171
+ request. Keep digging, or proceed anyway?"_ Then wait for their call.
172
+
173
+ <!-- section: oprf -->
174
+ **Decide whether to OPRF the value** (privacy from the attestor) before calling
175
+ `propose_provider`:
176
+
177
+ - **No hash** (default) — value travels in `paramValues` + `{{name}}`; the
178
+ attestor sees the cleartext substring. Right for non-sensitive values (public
179
+ IPs, public usernames, public repo names).
180
+ - **`oprf-raw`** — server-side OPRF. **The only variant that runs on Windows /
181
+ without external deps.** Weakest privacy of the three (the attestor still
182
+ participates) — say so honestly.
183
+ - **`oprf`** — client-side ZK OPRF using gnark. Strongest. Needs a gnark install
184
+ (Linux/macOS in practice). Don't suggest it on Windows.
185
+ - **`oprf-mpc`** — TEE-based MPC. Niche; only if the developer raises it.
186
+
187
+ **Skip the question** when the value is clearly non-sensitive, and default to
188
+ no hash. When it is or could be sensitive (email, real name, private id —
189
+ anything the developer wouldn't post publicly), put the four options to them.
190
+
191
+ **Hard limits:**
192
+
193
+ 1. OPRF-hashed values cap at **62 bytes**. Longer → propose a tighter `target`
194
+ (for example, only the email local-part) or no hash.
195
+ 2. **OPRF and the request URL are mutually exclusive for the same value.** Any
196
+ value templated into the request URL is signed into the attestor's context in
197
+ the clear regardless of hashing, so hashing it doesn't just fail to help — it
198
+ doesn't verify. If the target's value also appears in the captured URL, drop
199
+ `hash` or capture a request whose URL doesn't carry it.
200
+ 3. Never pass `hash` for a free-form target (unicode / spaces / punctuation).
201
+
202
+ `propose_provider` throws a clean error on all three — retrying the same call
203
+ won't help.
39
204
 
40
205
  <!-- section: rewrite-shape-block -->
41
- **Do NOT rewrite the shape.** The synthesizer's output is the canonical Reclaim shape — `contains` template + paramValues + xPath/jsonPath redaction (optionally + regex+hash). Specifically:
42
- - Do not replace the `contains` template with a hand-crafted regex.
43
- - 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.)
44
- - Do not remove `paramValues` — that's how the template resolves at proof time.
45
- - 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.
46
- - 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.
206
+ **Do NOT rewrite the shape.** The synthesizer's output IS the canonical
207
+ Reclaim shape — `contains` template + paramValues + xPath/jsonPath redaction
208
+ (+ regex/hash). Specifically:
209
+
210
+ - Don't replace the `contains` template with a hand-written regex.
211
+ - Don't hand-write a named-capture regex unless `hash` was requested AND the
212
+ synthesizer didn't already emit one (it does, for any digit/slug/email
213
+ target).
214
+ - Don't remove `paramValues` — that's how the template resolves at proof
215
+ time.
216
+ - The redaction's xPath/jsonPath is what the prover *reveals*; the match's
217
+ template is what the attestor *verifies* post-substitution. Different
218
+ jobs — don't conflate them.
219
+ - Don't prefix the match template's param with `%CTX%` — that namespace is
220
+ for consumer-supplied URL/body params only. Match templates are extraction
221
+ params and stay bare.
47
222
 
48
- 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.
223
+ Permitted edits: narrowing the `contains` template, a more distinctive
224
+ xPath class/id when the synthesizer's pick is too generic, fixing `name`,
225
+ adding/removing secret headers. Tempted to rewrite from scratch? Call
226
+ `propose_provider` again with different inputs instead.
227
+
228
+ <!-- section: secrets -->
229
+ **Marking a value secret.** Name ANY param with `SECRET` anywhere in the key
230
+ (case-insensitive) — a bare `{{SECRET_x}}` as much as a consumer-supplied
231
+ `{{%CTX%SECRET_apiKey}}` — and it routes to `secretParams.paramValues`
232
+ (`classifyParamName`/`splitSecretParamValues`), the same convention
233
+ `reclaim-inapp-sdk`/`reclaim-portal` use. **A secret param's value never appears
234
+ in the final proof.**
235
+
236
+ **Headers work by the OPPOSITE rule — an allowlist, not a name check.** Any
237
+ header not on a small fixed public list (`user-agent`, `accept`,
238
+ `accept-language`, `accept-encoding`, `sec-fetch-*`, `origin`,
239
+ `x-requested-with`, `sec-ch-ua*`) is secret by default, including
240
+ `Cookie`/`Authorization` and anything custom (`classifyHeader`/`splitSecrets`).
241
+
242
+ **The URL character budget.** A secret value CAN be used in the URL,
243
+ `geoLocation`, body, matches and redactions — write-redaction genuinely hides
244
+ it. But in the URL it only holds up to a small **TOTAL** budget summed across
245
+ every secret value in that URL (`secretUrlCharBudget`), not a per-value
246
+ allowance: **2 characters under `key-update`** (the default), **12 under `zk`**.
247
+ Over budget the value is effectively visible to the attestor no matter how it's
248
+ marked, and claim creation fails outright. Since the real value doesn't exist
249
+ until a verification session supplies one, this is checked at **verification**
250
+ time (`assertSecretParamValuesFitUrlBudget`), not publish time — except for a
251
+ captured secret *header* whose value also sits in the URL, which is already
252
+ known and so gets checked at draft time (`assertNoSecretUrlConflict`).
49
253
 
50
254
  <!-- section: no-rename-param -->
51
- **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.
255
+ **Do not rename the match-template param.** The synthesizer's `{{name}}` in
256
+ `responseMatches[].value` is an _extraction_ template — it stays bare and never
257
+ takes the `%CTX%` prefix. Only URL/body placeholders whose values the consumer
258
+ supplies are context params.
52
259
 
53
260
  <!-- section: prove-tail -->
54
- **On any `verified: false` response, the result includes (inline):**
55
- - `error` — the SDK's thrown error message verbatim
56
- - `sentParams` — the exact `params` object the SDK saw, post-template substitution (lets you spot mutation / substitution surprises)
261
+ **On any `verified: false` the result includes, inline:** `error` (the SDK's
262
+ thrown message verbatim) and `sentParams` (the exact `params` the SDK saw,
263
+ post-substitution — this is where you spot mutation surprises).
57
264
 
58
- **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.
265
+ **The attestor is reliable. If it errors, the cause is the provider, the
266
+ network, or prover-side code — not "the attestor can't handle this site".**
267
+ Do not invent failure modes like "the HTML is too large", "TLS records are
268
+ chunked", "it matches against gzipped bytes" (it doesn't —
269
+ `propose_provider` always sets `Accept-Encoding: identity`), or "xPath
270
+ doesn't work on this DOM". They're wrong and they waste the developer's
271
+ time. Instead: (1) show `error` verbatim; (2) if `sentParams` differs from
272
+ what you expected, surface that; (3) only then hypothesize, tied to that
273
+ evidence.
59
274
 
60
- **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:
61
- 1. Show the `error` field to the dev verbatim.
62
- 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.
63
- 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.
275
+ **Deeper tracing (opt-in).** With `RECLAIM_AGENT_TRACE=1` in the MCP
276
+ server's env, `run_proof` also captures the SDK's full pino stream into
277
+ `result.attestorLogs` and dumps `~/.reclaim/proof-runs/<timestamp>.json`
278
+ (`runLogPath`). Off by default — suggest it for a recurring failure.
64
279
 
65
- **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.
280
+ **Do not auto-retry with a different `attestorUrl`.** There is one live
281
+ public attestor; backup hostnames (`attestor-2.*`, and so on) **do not
282
+ exist**. `attestorUrl` is only for a developer-supplied private/staging URL.
66
283
 
67
- **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.
284
+ **"ZK circuit resources are missing"** (points at a `circuit.wasm` path) —
285
+ the npm postinstall didn't run (`--ignore-scripts`, or corp policy). Tell
286
+ the developer to run `npm run download:zk-files` from the project root and
287
+ retry `run_proof`. No re-capture, no server restart.
68
288
 
69
- <!-- section: concerns-section -->
70
- ## Concerns the developer should see verbatim
289
+ <!-- section: never-do-common -->
290
+ - Never echo a raw `Authorization`, `Cookie`, or `X-*-Token` value back to the
291
+ developer or write one to a file. `list_requests`,
292
+ `find_requests_containing`, `propose_provider` and every draft/replay/proof
293
+ result are secret-free by construction. `get_request` is the one exception —
294
+ it returns the request's RAW headers so you can inspect them. Read them,
295
+ never repeat them.
296
+ - Never call `run_proof` before `replay_request` succeeds. Attestor calls are
297
+ expensive.
298
+ - Never pretend a proof succeeded when `verified: false`. Show the error and
299
+ iterate.
300
+ - Never write a literal value into `responseMatches.value` — that bakes the
301
+ original user into the provider. The synthesizer always emits `{{name}}` +
302
+ paramValues.
303
+ - **Never hand-craft `responseMatches`/`responseRedactions`.** Typing a regex by
304
+ hand, writing `(?<name>...)` by hand, or copying the captured value into the
305
+ match — all stop signs. And never accept "the xPath looks too narrow, let me
306
+ write a regex": the synthesizer already pairs the anchor with a
307
+ structure-anchored named-capture regex. If extraction looks wrong, re-run
308
+ `propose_provider` with a better request/target.
309
+ - Never rename or `%CTX%`-prefix the match-template param (see `params`).
71
310
 
72
- 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.
311
+ <!-- section: credentials -->
312
+ ## Signing credential for the proof
73
313
 
74
- <!-- section: never-do-common -->
75
- - 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.
76
- - Never call `run_proof` before `replay_request` succeeds. Attestor calls are expensive.
77
- - Never pretend a proof succeeded when `verified: false` came back. Show the error and iterate.
78
- - 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.
79
- - 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`).
80
- - 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.
81
-
82
- <!-- section: first-time-issues-intro -->
83
- ## Common first-time issues — surface these proactively
84
-
85
- If a local `attach_browser` call errors, times out, or `waitForCdpReady` reports the port isn't answering, walk through these in order before retrying:
86
-
87
- - **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:
88
- ```powershell
89
- setx RECLAIM_AGENT_CHROME_PATH "C:\path\to\chrome.exe"
90
- ```
91
- `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.
92
- - **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.
93
- - **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.
94
-
95
- <!-- section: credential-intro -->
96
- **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.
97
-
98
- <!-- section: credential-step2 -->
99
- 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.
100
-
101
- <!-- section: mcp-config-bullet -->
102
- - **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.
314
+ `run_proof` needs an `ownerAddress` — the eth proof-owner key whose private half
315
+ signs the claim. It does **no** resolution itself. The private key is resolved
316
+ locally (`RECLAIM_PRIVATE_KEY` env → a `RECLAIM_PRIVATE_KEY=0x…` line in the
317
+ project `.env` → a file at `RECLAIM_PRIVATE_KEY_FILE`) and never enters the
318
+ conversation.
319
+
320
+ 1. **`resolve_owner_key`** returns only the derived address.
321
+ - `{ ready: true, address }` → call `run_proof` with it. No prompts.
322
+ - `{ ready: false }` → ask the developer once: **their own key** →
323
+ `import_credentials({ privateKey: "0x…" })` (a `keyFile` path also works),
324
+ or **a fresh one** → `issue_credentials({})` (a fine default for test
325
+ proofs — owner identity doesn't matter). Either writes to the project
326
+ `.env` (mode 0600, added to `.gitignore`), never to `~/.reclaim`. Then
327
+ re-run `resolve_owner_key`.
328
+ 2. Fully local — no network, no registration. The key is read live from `.env`
329
+ on every `run_proof`, so **no restart** is needed after issuing/importing,
330
+ and the existing draftId stays valid.
331
+
332
+ - **Advanced:** `RECLAIM_PRIVATE_KEY` in the MCP server env works too, but env
333
+ vars are snapshotted at Claude Code startup — that route needs a full restart,
334
+ which invalidates every draftId. Prefer the `.env` flow.
335
+ - **Multiple MCP config files exist on Windows.** `~/.claude.json` takes
336
+ precedence over `~/.claude/mcp.json`. Point developers at `claude mcp list` /
337
+ `claude mcp add --scope user` rather than hand-editing JSON.