@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.
- package/README.md +56 -20
- package/lib/authoring/backend.d.ts +5 -4
- package/lib/authoring/tools.js +30 -30
- package/lib/cdp/capture-engine.js +8 -7
- package/lib/cdp/claims.d.ts +21 -0
- package/lib/cdp/claims.js +129 -0
- package/lib/cdp/container.d.ts +85 -0
- package/lib/cdp/container.js +481 -0
- package/lib/cdp/geo.d.ts +10 -0
- package/lib/cdp/geo.js +36 -0
- package/lib/cdp/index.d.ts +1 -0
- package/lib/cdp/index.js +1 -0
- package/lib/live/cloudflared.d.ts +15 -0
- package/lib/live/cloudflared.js +268 -0
- package/lib/live/tunnel.d.ts +26 -0
- package/lib/live/tunnel.js +160 -0
- package/lib/mcp/api/operations.gen.js +34 -26
- package/lib/mcp/api/to-zod.d.ts +1 -1
- package/lib/mcp/api/to-zod.js +1 -1
- package/lib/mcp/api/tools.js +1 -1
- package/lib/mcp/instructions.d.ts +15 -2
- package/lib/mcp/instructions.js +102 -38
- package/lib/mcp/start.js +62 -6
- package/lib/mcp/tools/agent/analyze.js +5 -1
- package/lib/mcp/tools/agent/attach.d.ts +29 -2
- package/lib/mcp/tools/agent/attach.js +310 -24
- package/lib/mcp/tools/agent/build.d.ts +18 -8
- package/lib/mcp/tools/agent/build.js +30 -12
- package/lib/mcp/tools/agent/capture.js +5 -1
- package/lib/mcp/tools/agent/eval.js +3 -1
- package/lib/mcp/tools/agent/find.js +6 -5
- package/lib/mcp/tools/agent/how-it-works.d.ts +15 -7
- package/lib/mcp/tools/agent/how-it-works.js +57 -17
- package/lib/mcp/tools/agent/index.d.ts +1 -2
- package/lib/mcp/tools/agent/index.js +1 -1
- package/lib/mcp/tools/agent/live-view.d.ts +17 -0
- package/lib/mcp/tools/agent/live-view.js +146 -0
- package/lib/mcp/tools/agent/pod-proof.d.ts +8 -0
- package/lib/mcp/tools/agent/pod-proof.js +64 -0
- package/lib/mcp/tools/agent/propose.js +4 -2
- package/lib/mcp/tools/agent/publish.js +47 -77
- package/lib/mcp/tools/agent/session.d.ts +16 -1
- package/lib/mcp/tools/agent/session.js +32 -7
- package/lib/mcp/tools/authenticate/authenticate.js +26 -13
- package/lib/mcp/tools/mode/mode.d.ts +1 -1
- package/lib/mcp/tools/mode/mode.js +10 -22
- package/lib/old/client.d.ts +1 -1
- package/lib/old/client.js +1 -1
- package/lib/old/instructions.d.ts +9 -1
- package/lib/old/instructions.js +4 -6
- package/lib/old/login-cdp.d.ts +2 -2
- package/lib/old/login-cdp.js +46 -6
- package/lib/old/to-register.d.ts +3 -2
- package/lib/old/to-register.js +1 -1
- package/lib/old/tools/authenticate.js +9 -1
- package/lib/old/tools/get-provider-info.js +6 -5
- package/lib/old/tools/issue-app-credentials.js +3 -2
- package/lib/old/tools/publish.d.ts +1 -1
- package/lib/old/tools/publish.js +90 -131
- package/lib/old/tools/update-provider-metadata.d.ts +1 -1
- package/lib/old/tools/update-provider-metadata.js +1 -1
- package/lib/old/tools/verify-sdk-setup.js +1 -1
- package/lib/proof/attestor.d.ts +3 -3
- package/lib/proof/debug-response-dump.js +4 -4
- package/lib/proof/index.d.ts +1 -0
- package/lib/proof/index.js +1 -0
- package/lib/proof/page-replay.d.ts +1 -1
- package/lib/proof/page-replay.js +1 -1
- package/lib/proof/pod-prover.d.ts +47 -0
- package/lib/proof/pod-prover.js +155 -0
- package/lib/provider/dom-xpath.js +11 -9
- package/lib/provider/draft.d.ts +10 -7
- package/lib/provider/draft.js +11 -9
- package/lib/provider/injection-doc.d.ts +23 -1
- package/lib/provider/injection-doc.js +34 -21
- package/lib/provider/match-redaction-warning.d.ts +2 -2
- package/lib/provider/match-redaction-warning.js +3 -3
- package/lib/provider/matcher.js +13 -12
- package/lib/provider/secrets.d.ts +3 -3
- package/lib/provider/secrets.js +3 -3
- package/lib/provider/to-version.d.ts +7 -6
- package/lib/provider/to-version.js +3 -3
- package/lib/tools/registry.d.ts +5 -5
- package/lib/tools/registry.js +5 -5
- package/package.json +3 -3
- package/samples/demo-server/README.md +7 -6
- package/skill/SKILL.builder.md +356 -189
- package/skill/SKILL.common.md +400 -97
- package/skill/SKILL.old.md +340 -155
- package/skill/builder-mode.md +60 -17
- package/skill/hash-validation.md +170 -0
- package/skill/interception.md +236 -0
- package/skill/js-sdk-integration.md +13 -7
- package/skill/old-mode.md +62 -21
- package/skill/user-script.md +61 -0
- package/src/authoring/backend.ts +5 -4
- package/src/authoring/tools.ts +31 -30
- package/src/cdp/capture-engine.ts +8 -7
- package/src/cdp/claims.ts +153 -0
- package/src/cdp/container.ts +593 -0
- package/src/cdp/geo.ts +52 -0
- package/src/cdp/index.ts +1 -0
- package/src/consts.ts +1 -0
- package/src/live/cloudflared.ts +348 -0
- package/src/live/tunnel.ts +208 -0
- package/src/mcp/api/operations.gen.ts +34 -26
- package/src/mcp/api/to-zod.ts +1 -1
- package/src/mcp/api/tools.ts +1 -1
- package/src/mcp/instructions.ts +122 -48
- package/src/mcp/start.ts +69 -5
- package/src/mcp/tools/agent/analyze.ts +5 -1
- package/src/mcp/tools/agent/attach.ts +367 -26
- package/src/mcp/tools/agent/build.ts +35 -16
- package/src/mcp/tools/agent/capture.ts +5 -1
- package/src/mcp/tools/agent/eval.ts +4 -1
- package/src/mcp/tools/agent/find.ts +6 -5
- package/src/mcp/tools/agent/how-it-works.ts +62 -21
- package/src/mcp/tools/agent/index.ts +6 -2
- package/src/mcp/tools/agent/live-view.ts +182 -0
- package/src/mcp/tools/agent/pod-proof.ts +90 -0
- package/src/mcp/tools/agent/propose.ts +4 -2
- package/src/mcp/tools/agent/publish.ts +52 -77
- package/src/mcp/tools/agent/session.ts +33 -6
- package/src/mcp/tools/authenticate/authenticate.ts +28 -13
- package/src/mcp/tools/mode/mode.ts +10 -22
- package/src/old/client.ts +2 -2
- package/src/old/instructions.ts +4 -10
- package/src/old/login-cdp.ts +50 -6
- package/src/old/to-register.ts +3 -2
- package/src/old/tools/authenticate.ts +9 -1
- package/src/old/tools/get-provider-info.ts +6 -5
- package/src/old/tools/issue-app-credentials.ts +3 -2
- package/src/old/tools/publish.ts +95 -131
- package/src/old/tools/update-provider-metadata.ts +1 -1
- package/src/old/tools/verify-sdk-setup.ts +1 -1
- package/src/proof/attestor.ts +3 -3
- package/src/proof/debug-response-dump.ts +4 -4
- package/src/proof/index.ts +8 -0
- package/src/proof/page-replay.ts +1 -1
- package/src/proof/pod-prover.ts +231 -0
- package/src/provider/dom-xpath.ts +11 -9
- package/src/provider/draft.ts +13 -10
- package/src/provider/injection-doc.ts +37 -24
- package/src/provider/match-redaction-warning.ts +3 -3
- package/src/provider/matcher.ts +13 -12
- package/src/provider/secrets.ts +3 -3
- package/src/provider/to-version.ts +11 -9
- package/src/tools/registry.ts +5 -5
- package/skill/injection-api.md +0 -364
package/skill/SKILL.common.md
CHANGED
|
@@ -1,111 +1,414 @@
|
|
|
1
|
-
<!-- section:
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
>
|
|
5
|
-
>
|
|
6
|
-
>
|
|
7
|
-
>
|
|
8
|
-
> "
|
|
9
|
-
>
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
-
|
|
55
|
-
-
|
|
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
|
-
|
|
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
|
|
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`
|
|
64
|
-
|
|
65
|
-
-
|
|
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).**
|
|
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
|
-
**
|
|
70
|
-
|
|
71
|
-
|
|
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
|
-
**
|
|
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
|
-
|
|
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:
|
|
79
|
-
##
|
|
388
|
+
<!-- section: credentials -->
|
|
389
|
+
## Signing credential for the proof
|
|
80
390
|
|
|
81
|
-
|
|
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
|
-
|
|
84
|
-
-
|
|
85
|
-
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
`
|
|
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.
|