@reclaimprotocol/agent 0.1.0-dev.1

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 (191) hide show
  1. package/README.md +287 -0
  2. package/lib/cdp/capture.d.ts +22 -0
  3. package/lib/cdp/capture.js +159 -0
  4. package/lib/cdp/client.d.ts +9 -0
  5. package/lib/cdp/client.js +11 -0
  6. package/lib/cdp/eviction.d.ts +16 -0
  7. package/lib/cdp/eviction.js +38 -0
  8. package/lib/cdp/launcher.d.ts +14 -0
  9. package/lib/cdp/launcher.js +102 -0
  10. package/lib/cdp/tabs.d.ts +6 -0
  11. package/lib/cdp/tabs.js +10 -0
  12. package/lib/cli/args.d.ts +14 -0
  13. package/lib/cli/args.js +19 -0
  14. package/lib/cli/commands.d.ts +1 -0
  15. package/lib/cli/commands.js +19 -0
  16. package/lib/cli.d.ts +1 -0
  17. package/lib/cli.js +7 -0
  18. package/lib/config/store.d.ts +45 -0
  19. package/lib/config/store.js +46 -0
  20. package/lib/index.d.ts +3 -0
  21. package/lib/index.js +7 -0
  22. package/lib/logger.d.ts +2 -0
  23. package/lib/logger.js +7 -0
  24. package/lib/mcp/api/operations.gen.d.ts +152 -0
  25. package/lib/mcp/api/operations.gen.js +2300 -0
  26. package/lib/mcp/api/tools.d.ts +3 -0
  27. package/lib/mcp/api/tools.js +36 -0
  28. package/lib/mcp/remap_errors.d.ts +2 -0
  29. package/lib/mcp/remap_errors.js +27 -0
  30. package/lib/mcp/server.d.ts +38 -0
  31. package/lib/mcp/server.js +76 -0
  32. package/lib/mcp/start.d.ts +1 -0
  33. package/lib/mcp/start.js +63 -0
  34. package/lib/mcp/tools/agent/analyze.d.ts +3 -0
  35. package/lib/mcp/tools/agent/analyze.js +26 -0
  36. package/lib/mcp/tools/agent/attach.d.ts +11 -0
  37. package/lib/mcp/tools/agent/attach.js +72 -0
  38. package/lib/mcp/tools/agent/build.d.ts +33 -0
  39. package/lib/mcp/tools/agent/build.js +74 -0
  40. package/lib/mcp/tools/agent/capture.d.ts +10 -0
  41. package/lib/mcp/tools/agent/capture.js +142 -0
  42. package/lib/mcp/tools/agent/eval.d.ts +5 -0
  43. package/lib/mcp/tools/agent/eval.js +127 -0
  44. package/lib/mcp/tools/agent/find.d.ts +3 -0
  45. package/lib/mcp/tools/agent/find.js +60 -0
  46. package/lib/mcp/tools/agent/proof.d.ts +3 -0
  47. package/lib/mcp/tools/agent/proof.js +214 -0
  48. package/lib/mcp/tools/agent/propose.d.ts +9 -0
  49. package/lib/mcp/tools/agent/propose.js +75 -0
  50. package/lib/mcp/tools/agent/publish.d.ts +3 -0
  51. package/lib/mcp/tools/agent/publish.js +122 -0
  52. package/lib/mcp/tools/agent/replay.d.ts +3 -0
  53. package/lib/mcp/tools/agent/replay.js +46 -0
  54. package/lib/mcp/tools/agent/session.d.ts +10 -0
  55. package/lib/mcp/tools/agent/session.js +29 -0
  56. package/lib/mcp/tools/credentials/authenticate.d.ts +3 -0
  57. package/lib/mcp/tools/credentials/authenticate.js +108 -0
  58. package/lib/mcp/tools/credentials/build.d.ts +16 -0
  59. package/lib/mcp/tools/credentials/build.js +33 -0
  60. package/lib/mcp/tools/credentials/cache.d.ts +70 -0
  61. package/lib/mcp/tools/credentials/cache.js +114 -0
  62. package/lib/mcp/tools/credentials/env-file.d.ts +25 -0
  63. package/lib/mcp/tools/credentials/env-file.js +71 -0
  64. package/lib/mcp/tools/credentials/import.d.ts +2 -0
  65. package/lib/mcp/tools/credentials/import.js +52 -0
  66. package/lib/mcp/tools/credentials/issue.d.ts +2 -0
  67. package/lib/mcp/tools/credentials/issue.js +84 -0
  68. package/lib/mcp/tools/credentials/list-cached.d.ts +2 -0
  69. package/lib/mcp/tools/credentials/list-cached.js +55 -0
  70. package/lib/mcp/tools/credentials/register.d.ts +18 -0
  71. package/lib/mcp/tools/credentials/register.js +30 -0
  72. package/lib/mcp/tools/credentials/resolve-owner-key.d.ts +12 -0
  73. package/lib/mcp/tools/credentials/resolve-owner-key.js +68 -0
  74. package/lib/mcp/tools/credentials/session-cache.d.ts +14 -0
  75. package/lib/mcp/tools/credentials/session-cache.js +19 -0
  76. package/lib/mcp/tools/credentials/status.d.ts +3 -0
  77. package/lib/mcp/tools/credentials/status.js +62 -0
  78. package/lib/mcp-bin.d.ts +1 -0
  79. package/lib/mcp-bin.js +7 -0
  80. package/lib/old/client.d.ts +67 -0
  81. package/lib/old/client.js +146 -0
  82. package/lib/old/flag.d.ts +11 -0
  83. package/lib/old/flag.js +13 -0
  84. package/lib/old/identity.d.ts +22 -0
  85. package/lib/old/identity.js +47 -0
  86. package/lib/old/instructions.d.ts +7 -0
  87. package/lib/old/instructions.js +29 -0
  88. package/lib/old/login-cdp.d.ts +23 -0
  89. package/lib/old/login-cdp.js +125 -0
  90. package/lib/old/to-register.d.ts +47 -0
  91. package/lib/old/to-register.js +133 -0
  92. package/lib/old/tools/authenticate.d.ts +18 -0
  93. package/lib/old/tools/authenticate.js +127 -0
  94. package/lib/old/tools/build.d.ts +9 -0
  95. package/lib/old/tools/build.js +16 -0
  96. package/lib/old/tools/me-providers.d.ts +10 -0
  97. package/lib/old/tools/me-providers.js +32 -0
  98. package/lib/old/tools/publish.d.ts +16 -0
  99. package/lib/old/tools/publish.js +97 -0
  100. package/lib/paths.d.ts +11 -0
  101. package/lib/paths.js +32 -0
  102. package/lib/pkg.d.ts +3 -0
  103. package/lib/pkg.js +13 -0
  104. package/lib/proof/attestor.d.ts +39 -0
  105. package/lib/proof/attestor.js +207 -0
  106. package/lib/proof/replay.d.ts +8 -0
  107. package/lib/proof/replay.js +73 -0
  108. package/lib/provider/constraints.d.ts +2 -0
  109. package/lib/provider/constraints.js +122 -0
  110. package/lib/provider/draft.d.ts +6 -0
  111. package/lib/provider/draft.js +68 -0
  112. package/lib/provider/graphql.d.ts +2 -0
  113. package/lib/provider/graphql.js +35 -0
  114. package/lib/provider/matcher.d.ts +36 -0
  115. package/lib/provider/matcher.js +436 -0
  116. package/lib/provider/normalize.d.ts +4 -0
  117. package/lib/provider/normalize.js +51 -0
  118. package/lib/provider/schema.d.ts +76 -0
  119. package/lib/provider/schema.js +1 -0
  120. package/lib/provider/secrets.d.ts +5 -0
  121. package/lib/provider/secrets.js +22 -0
  122. package/lib/provider/to-version.d.ts +22 -0
  123. package/lib/provider/to-version.js +68 -0
  124. package/lib/types/openapi.d.ts +4 -0
  125. package/lib/types/openapi.js +1 -0
  126. package/package.json +88 -0
  127. package/scripts/download-zk-files.ts +42 -0
  128. package/scripts/prepend-shebang.ts +17 -0
  129. package/skill/SKILL.md +248 -0
  130. package/src/cdp/capture.ts +215 -0
  131. package/src/cdp/client.ts +20 -0
  132. package/src/cdp/eviction.ts +52 -0
  133. package/src/cdp/launcher.ts +133 -0
  134. package/src/cdp/tabs.ts +22 -0
  135. package/src/cli/args.ts +20 -0
  136. package/src/cli/commands.ts +27 -0
  137. package/src/cli.ts +7 -0
  138. package/src/config/store.ts +86 -0
  139. package/src/index.ts +12 -0
  140. package/src/logger.ts +8 -0
  141. package/src/mcp/api/operations.gen.ts +2397 -0
  142. package/src/mcp/api/tools.ts +45 -0
  143. package/src/mcp/remap_errors.ts +35 -0
  144. package/src/mcp/server.ts +119 -0
  145. package/src/mcp/start.ts +78 -0
  146. package/src/mcp/tools/agent/analyze.ts +34 -0
  147. package/src/mcp/tools/agent/attach.ts +95 -0
  148. package/src/mcp/tools/agent/build.ts +99 -0
  149. package/src/mcp/tools/agent/capture.ts +202 -0
  150. package/src/mcp/tools/agent/eval.ts +161 -0
  151. package/src/mcp/tools/agent/find.ts +73 -0
  152. package/src/mcp/tools/agent/proof.ts +272 -0
  153. package/src/mcp/tools/agent/propose.ts +111 -0
  154. package/src/mcp/tools/agent/publish.ts +155 -0
  155. package/src/mcp/tools/agent/replay.ts +58 -0
  156. package/src/mcp/tools/agent/session.ts +46 -0
  157. package/src/mcp/tools/credentials/authenticate.ts +135 -0
  158. package/src/mcp/tools/credentials/build.ts +37 -0
  159. package/src/mcp/tools/credentials/cache.ts +161 -0
  160. package/src/mcp/tools/credentials/env-file.ts +101 -0
  161. package/src/mcp/tools/credentials/import.ts +72 -0
  162. package/src/mcp/tools/credentials/issue.ts +107 -0
  163. package/src/mcp/tools/credentials/list-cached.ts +66 -0
  164. package/src/mcp/tools/credentials/register.ts +49 -0
  165. package/src/mcp/tools/credentials/resolve-owner-key.ts +78 -0
  166. package/src/mcp/tools/credentials/session-cache.ts +31 -0
  167. package/src/mcp/tools/credentials/status.ts +82 -0
  168. package/src/mcp-bin.ts +7 -0
  169. package/src/old/client.ts +236 -0
  170. package/src/old/flag.ts +13 -0
  171. package/src/old/identity.ts +56 -0
  172. package/src/old/instructions.ts +30 -0
  173. package/src/old/login-cdp.ts +153 -0
  174. package/src/old/to-register.ts +234 -0
  175. package/src/old/tools/authenticate.ts +152 -0
  176. package/src/old/tools/build.ts +21 -0
  177. package/src/old/tools/me-providers.ts +42 -0
  178. package/src/old/tools/publish.ts +135 -0
  179. package/src/paths.ts +37 -0
  180. package/src/pkg.ts +24 -0
  181. package/src/proof/attestor.ts +286 -0
  182. package/src/proof/replay.ts +102 -0
  183. package/src/provider/constraints.ts +150 -0
  184. package/src/provider/draft.ts +105 -0
  185. package/src/provider/graphql.ts +42 -0
  186. package/src/provider/matcher.ts +616 -0
  187. package/src/provider/normalize.ts +65 -0
  188. package/src/provider/schema.ts +92 -0
  189. package/src/provider/secrets.ts +29 -0
  190. package/src/provider/to-version.ts +90 -0
  191. package/src/types/openapi.ts +7 -0
package/README.md ADDED
@@ -0,0 +1,287 @@
1
+ # @reclaimprotocol/agent
2
+
3
+ Chrome-CDP + zkTLS provider-authoring tools. The agent captures network traffic from a browser, synthesizes Reclaim provider definitions from it (`contains` matchers + `jsonPath` / `xPath` / `regex` redactions), runs a local replay diagnostic, and produces a verified attestor proof via `@reclaimprotocol/attestor-core`.
4
+
5
+ This package **is** the Reclaim MCP server (bin: `reclaim-mcp-server`). It depends on `@reclaimprotocol/client` for the typed HTTP client and registers the provider-authoring tools below alongside the backend-facing tools (credentials, publishing) in a single MCP surface. A standalone entry (`src/index.ts`) re-exports the client SDK plus `buildAgentTools` for embedding.
6
+
7
+ ## Install
8
+
9
+ **Prerequisites:** Node ≥ 22.18 and a local Chrome / Chromium / Edge (used for capture and the dashboard login). The first run downloads the ZK circuit files needed by `run_proof` (a `postinstall` step), so allow a moment on first launch.
10
+
11
+ It runs as a **stdio MCP server**. Point any MCP-capable agent at this command:
12
+
13
+ ```bash
14
+ npx -y -p @reclaimprotocol/agent reclaim-mcp-server
15
+ ```
16
+
17
+ > The `-p` (`--package`) flag selects the `reclaim-mcp-server` binary from the package. Prefer a global install? Run `npm i -g @reclaimprotocol/agent` and use `reclaim-mcp-server` directly as the `command` (drop the `npx` wrapper and its `args`).
18
+
19
+ ### Claude Code
20
+
21
+ ```bash
22
+ # project scope → writes .mcp.json; add `-s user` for a global install
23
+ claude mcp add reclaim -- npx -y -p @reclaimprotocol/agent reclaim-mcp-server
24
+ ```
25
+
26
+ …or add it to `.mcp.json` yourself:
27
+
28
+ ```json
29
+ {
30
+ "mcpServers": {
31
+ "reclaim": {
32
+ "command": "npx",
33
+ "args": ["-y", "-p", "@reclaimprotocol/agent", "reclaim-mcp-server"]
34
+ }
35
+ }
36
+ }
37
+ ```
38
+
39
+ ### Cursor
40
+
41
+ Add to `~/.cursor/mcp.json` (global) or `.cursor/mcp.json` (project):
42
+
43
+ ```json
44
+ {
45
+ "mcpServers": {
46
+ "reclaim": {
47
+ "command": "npx",
48
+ "args": ["-y", "-p", "@reclaimprotocol/agent", "reclaim-mcp-server"]
49
+ }
50
+ }
51
+ }
52
+ ```
53
+
54
+ ### opencode
55
+
56
+ Add to `opencode.json` (project) or `~/.config/opencode/opencode.json` (global):
57
+
58
+ ```json
59
+ {
60
+ "$schema": "https://opencode.ai/config.json",
61
+ "mcp": {
62
+ "reclaim": {
63
+ "type": "local",
64
+ "command": ["npx", "-y", "-p", "@reclaimprotocol/agent", "reclaim-mcp-server"],
65
+ "enabled": true
66
+ }
67
+ }
68
+ }
69
+ ```
70
+
71
+ ### Antigravity, Windsurf, VS Code, Cline & other MCP clients
72
+
73
+ These all use the standard `mcpServers` shape — add a server through the app's MCP settings (or its `mcp`/`mcpServers` config file) with:
74
+
75
+ ```json
76
+ {
77
+ "mcpServers": {
78
+ "reclaim": {
79
+ "command": "npx",
80
+ "args": ["-y", "-p", "@reclaimprotocol/agent", "reclaim-mcp-server"]
81
+ }
82
+ }
83
+ }
84
+ ```
85
+
86
+ ### Optional environment
87
+
88
+ Defaults target the production old-devtools backend, so **no env is needed for the common case**. To override, add an `env` block (or `environment` in opencode) to the config above:
89
+
90
+ ```json
91
+ "env": {
92
+ "USE_OLD_DEVTOOLS": "true",
93
+ "RECLAIM_OLD_API_URL": "https://devapi.reclaimprotocol.org"
94
+ }
95
+ ```
96
+
97
+ See [Backends](#backends) for builder mode (`USE_OLD_DEVTOOLS=false`) and the full env-var list.
98
+
99
+ ### Companion skill (optional, recommended)
100
+
101
+ The MCP server is all you need to start — but installing the companion skill makes the agent markedly better at the capture → prove → publish flow (login detection, choosing auth-bound endpoints, OPRF decisions, troubleshooting). See [Skill](#skill) for install steps.
102
+
103
+ ## Using it from your agent
104
+
105
+ Optional but recommended: install the [skill](#skill) so the agent follows the full authoring flow.
106
+
107
+ Then just ask in plain language, e.g.:
108
+
109
+ > "Use Reclaim to create a provider that proves my GitHub follower count."
110
+
111
+ or you can even ask it like:
112
+
113
+ > "I want to verify Github follow count"
114
+
115
+ The agent orchestrates the tools for you:
116
+
117
+ 1. **`reclaim_authenticate`** — a Chrome window opens the Reclaim dashboard; you sign in. The token is captured automatically and the tab closes. (Or it reuses a still-valid cached identity.)
118
+ 2. **`attach_chrome` + `navigate`** — opens the target site (e.g. `github.com`); **`wait_for_page`** waits while you log in there.
119
+ 3. **`start_capture` → `find_requests_containing` → `get_request`** — records network traffic and locates the request carrying your value.
120
+ 4. **`propose_provider`** — drafts the provider (a `contains` match + `jsonPath`/`xPath`/`regex` redaction), keeping any secret headers server-side.
121
+ 5. **`replay_request` → `run_proof`** — pre-flights the draft, then produces a verified zkTLS proof via the attestor.
122
+ 6. **`create_provider_version_from_capture`** — publishes it. **`get_me_providers`** lists what you own; pass a returned `providerId` back to publish a new version of an existing provider.
123
+
124
+ Your only manual steps are signing in when the browser windows open and confirming the value you want to prove.
125
+
126
+ ## Skill
127
+
128
+ Optional but recommended. The companion skill (`skill/SKILL.md`) is a Claude Code **Agent Skill** that teaches the agent the full mode-aware authoring flow — it ships inside the npm package and is also in this repo.
129
+
130
+ ```bash
131
+ # macOS / Linux — from a global install of the package
132
+ npm i -g @reclaimprotocol/agent
133
+ mkdir -p ~/.claude/skills/reclaim-provider
134
+ cp "$(npm root -g)/@reclaimprotocol/agent/skill/SKILL.md" ~/.claude/skills/reclaim-provider/SKILL.md
135
+
136
+ # …or from a checkout of this repo
137
+ cp packages/agent/skill/SKILL.md ~/.claude/skills/reclaim-provider/SKILL.md
138
+ ```
139
+
140
+ ```powershell
141
+ # Windows (PowerShell) — from a global install
142
+ npm i -g @reclaimprotocol/agent
143
+ New-Item -ItemType Directory -Force $env:USERPROFILE\.claude\skills\reclaim-provider | Out-Null
144
+ Copy-Item "$(npm root -g)\@reclaimprotocol\agent\skill\SKILL.md" $env:USERPROFILE\.claude\skills\reclaim-provider\SKILL.md
145
+ ```
146
+
147
+ Claude Code auto-loads it once it's in `~/.claude/skills/`. **Other MCP clients** (Cursor, opencode, Antigravity, …) don't read `SKILL.md` automatically — the server's built-in instructions already cover the essentials, and you can paste or adapt the skill's content into that client's rules / system-prompt for the same depth.
148
+
149
+ ## Provider-authoring tools
150
+
151
+ These capture/synthesis/proof tools are backend-independent and registered in both modes (see [Backends](#backends)):
152
+
153
+ | Tool | Purpose |
154
+ | ------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
155
+ | `attach_chrome` | Connect to Chrome via CDP — dedicated profile or attach to running instance |
156
+ | `list_tabs` | List open Chrome tabs |
157
+ | `navigate`, `wait_for_page`, `eval_in_page` | Drive the tab; poll for login completion |
158
+ | `start_capture`, `stop_capture` | Network capture lifecycle |
159
+ | `list_requests`, `get_request` | Inspect captured traffic |
160
+ | `find_requests_containing` | Deterministic search for a target value across captured responses |
161
+ | `analyze_request_constraints` | Flag concerns (signed, csrf, graphql, etc.) |
162
+ | `propose_provider` | Synthesize a provider — `contains` match + `jsonPath`/`xPath`/`regex` redaction |
163
+ | `replay_request` | Pre-flight replay via Node `fetch` (no attestor); also used for auth-bound check by omitting `secretParamsRef` |
164
+ | `run_proof` | End-to-end zkTLS proof through the attestor SDK |
165
+ | `create_provider_version_from_capture` | Publish a drafted provider to the backend |
166
+ | `get_session`, `reset_session` | Session state |
167
+
168
+ ## Backends
169
+
170
+ The MCP server targets one of two backends, selected by `USE_OLD_DEVTOOLS`:
171
+
172
+ - **Old devtools (default).** The legacy devtools backend is still in production while the new "builder" backend is built, so this is the default — opt into builder with `USE_OLD_DEVTOOLS=false`. In old mode all auto-generated HTTP-API tools are disabled and a small client (`src/old/`) serves:
173
+ - `reclaim_authenticate` — by default opens the dashboard in Chrome via CDP, waits for sign-in, reads the Firebase token from page storage, and closes the tab (or pass a `token` / `ethAddress` to skip the browser).
174
+ - `create_provider_version_from_capture` — registers a new provider, or adds a version to an existing one when given a `providerId`.
175
+ - `get_me_providers` — list the providers you own (the only provider-listing tool in this mode).
176
+ - **Builder (`USE_OLD_DEVTOOLS=false`).** Registers the auto-generated HTTP-API tools plus the builder credential tools (`reclaim_authenticate` device-code flow, `reclaim_status`, and the local credential tools).
177
+
178
+ Relevant env vars: `RECLAIM_OLD_API_URL` (default `https://devapi.reclaimprotocol.org`), `RECLAIM_OLD_API_TOKEN` / `RECLAIM_OLD_ETH_UID` (headless identity), `RECLAIM_OLD_LOGIN_URL` (default `https://dev.reclaimprotocol.org`); builder mode uses `RECLAIM_API_URL` / `RECLAIM_API_TOKEN`.
179
+
180
+ ## Local development (from source)
181
+
182
+ For working on the package inside the monorepo (consumers should use [Install](#install) instead). A workspace install fetches dependencies and runs this package's postinstall, which downloads the ZK circuit resources needed by `@reclaimprotocol/attestor-core` at proof time:
183
+
184
+ ```bash
185
+ npm install
186
+ ```
187
+
188
+ If postinstall is skipped (`npm ci --ignore-scripts`, or a corp policy disabling postinstall), run it manually:
189
+
190
+ ```bash
191
+ npm run download:zk-files --workspace=packages/agent
192
+ ```
193
+
194
+ ## Platform notes
195
+
196
+ - **Node >=22.18** is required (declared in `engines`). The floor is 22.18 because the package's `postinstall` and helper scripts run as bare `.ts` files, and unflagged TypeScript type-stripping landed in Node 22.18.
197
+ - **Chrome 136+ silently disables `--remote-debugging-port`** when launched against the default user profile. `dedicated` mode (the default) sidesteps this by always passing `--user-data-dir=~/.reclaim/chrome-profile`.
198
+
199
+ ## Tests
200
+
201
+ `npm test` runs the unit + integration suite (no Chrome needed).
202
+
203
+ The Chrome CDP end-to-end test at `tests/e2e-chrome.test.ts` is gated behind an env var. It launches headless Chrome with a dedicated temp profile, captures a navigation against a local fixture server, and asserts the synthesiser finds the right request. To run it:
204
+
205
+ ```bash
206
+ # bash / macOS / Linux
207
+ RECLAIM_AGENT_E2E=1 npm test --workspace=packages/agent
208
+
209
+ # Windows PowerShell
210
+ $env:RECLAIM_AGENT_E2E=1; npm test --workspace=packages/agent
211
+ ```
212
+
213
+ Requires Chrome / Chromium / Edge installed somewhere `findChromeBinary` can locate (or `RECLAIM_AGENT_CHROME_PATH` set). The test self-skips if no binary is found.
214
+
215
+ ## Disabling
216
+
217
+ The MCP server registers the provider-authoring (Chrome/CDP) tools by default in both modes. To disable them (e.g. in hosted deployments where no local Chrome is available):
218
+
219
+ ```bash
220
+ RECLAIM_AGENT_DISABLED=1
221
+ ```
222
+
223
+ ## For contributors — wiring a local build into Claude Code / opencode
224
+
225
+ > This section is for working **on this package from a checkout**. Consumers should use the published package via [Install](#install) instead.
226
+
227
+ First build so `lib/mcp-bin.js` exists, then point your agent at that file by absolute path (not `npx`):
228
+
229
+ ```bash
230
+ npm install # once, from the repo root
231
+ npm run build --workspace=packages/agent # rebuild after changes (or `npm run dev` to watch)
232
+ ```
233
+
234
+ ### MCP server
235
+
236
+ **Claude Code** — register the local binary:
237
+
238
+ ```bash
239
+ # from the repo root; -s user for a global install (omit for project scope → .mcp.json)
240
+ claude mcp add reclaim -- node "$(pwd)/packages/agent/lib/mcp-bin.js"
241
+ ```
242
+
243
+ …or add it to `.mcp.json` yourself, using an **absolute path**:
244
+
245
+ ```json
246
+ {
247
+ "mcpServers": {
248
+ "reclaim": {
249
+ "command": "node",
250
+ "args": ["/abs/path/to/builder/packages/agent/lib/mcp-bin.js"]
251
+ }
252
+ }
253
+ }
254
+ ```
255
+
256
+ **opencode** — add to `opencode.json` (project) or `~/.config/opencode/opencode.json` (global), again with an absolute path:
257
+
258
+ ```json
259
+ {
260
+ "$schema": "https://opencode.ai/config.json",
261
+ "mcp": {
262
+ "reclaim": {
263
+ "type": "local",
264
+ "command": ["node", "/absolute/path/to/builder/packages/agent/lib/mcp-bin.js"],
265
+ "enabled": true
266
+ }
267
+ }
268
+ }
269
+ ```
270
+
271
+ To target a specific backend while developing, add an `env` block (Claude Code / `.mcp.json`) or `environment` (opencode) — e.g. `"USE_OLD_DEVTOOLS": "false"` for builder mode. See [Backends](#backends).
272
+
273
+ ### Skill
274
+
275
+ Symlink the source `skill/` folder into `~/.claude/skills/` so edits to `SKILL.md` are picked up live (no re-copy):
276
+
277
+ ```bash
278
+ # macOS / Linux — from the repo root
279
+ ln -s "$(pwd)/packages/agent/skill" ~/.claude/skills/reclaim-provider
280
+ ```
281
+
282
+ ```powershell
283
+ # Windows (PowerShell, Developer Mode or elevated) — from the repo root
284
+ New-Item -ItemType SymbolicLink -Path $env:USERPROFILE\.claude\skills\reclaim-provider -Target (Resolve-Path packages\agent\skill)
285
+ ```
286
+
287
+ Claude Code auto-loads it once linked. opencode doesn't read `SKILL.md` automatically — paste or adapt its content into that client's rules / system-prompt for the same depth.
@@ -0,0 +1,22 @@
1
+ import type CDP from 'chrome-remote-interface';
2
+ import type { Protocol } from 'devtools-protocol';
3
+ import type { CapturedRequest } from '../provider/schema.ts';
4
+ import { BodyStore } from './eviction.ts';
5
+ /** Drop HTTP/2 pseudo-headers (`:authority`, `:method`, `:path`, `:scheme`,
6
+ * `:status`) from a CDP header map. Chrome emits them on HTTP/2 requests,
7
+ * but they're a transport-layer abstraction with no place in our domain:
8
+ * the URL already carries authority/path/scheme, the request object carries
9
+ * method, and Node's `fetch` (undici) throws on any header starting with
10
+ * `:`. Stripping at capture time keeps every downstream consumer
11
+ * (`get_request`, draft, replay, proof) clean by construction. */
12
+ export declare function stripPseudoHeaders(headers: Protocol.Network.Headers): Record<string, string>;
13
+ export interface CaptureSession {
14
+ captureId: string;
15
+ requests: Map<string, CapturedRequest>;
16
+ bodies: BodyStore;
17
+ stop: () => Promise<void>;
18
+ }
19
+ export interface StartCaptureOptions {
20
+ maxBodyBytes?: number;
21
+ }
22
+ export declare function startCapture(tab: CDP.Client, opts?: StartCaptureOptions): Promise<CaptureSession>;
@@ -0,0 +1,159 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { extractGraphqlOp } from "../provider/graphql.js";
3
+ import { BodyStore } from "./eviction.js";
4
+ /** Drop HTTP/2 pseudo-headers (`:authority`, `:method`, `:path`, `:scheme`,
5
+ * `:status`) from a CDP header map. Chrome emits them on HTTP/2 requests,
6
+ * but they're a transport-layer abstraction with no place in our domain:
7
+ * the URL already carries authority/path/scheme, the request object carries
8
+ * method, and Node's `fetch` (undici) throws on any header starting with
9
+ * `:`. Stripping at capture time keeps every downstream consumer
10
+ * (`get_request`, draft, replay, proof) clean by construction. */
11
+ export function stripPseudoHeaders(headers) {
12
+ const out = {};
13
+ for (const [k, v] of Object.entries(headers)) {
14
+ if (!k.startsWith(':')) {
15
+ out[k] = v;
16
+ }
17
+ }
18
+ return out;
19
+ }
20
+ export async function startCapture(tab, opts = {}) {
21
+ const captureId = randomUUID();
22
+ const requests = new Map();
23
+ const inFlight = new Map();
24
+ const bodies = new BodyStore({
25
+ maxBytes: opts.maxBodyBytes ?? 200 * 1024 * 1024,
26
+ });
27
+ // CDP doesn't guarantee `requestWillBeSent` arrives before
28
+ // `requestWillBeSentExtraInfo` (same for the response pair). When extra
29
+ // info lands first, buffer it here and drain on the matching primary
30
+ // event. Without this, headers (Cookie, Authorization, …) get silently
31
+ // dropped for any request whose events race in the unlucky order.
32
+ const pendingRequestExtra = new Map();
33
+ const pendingResponseExtra = new Map();
34
+ const { Network } = tab;
35
+ await Network.enable();
36
+ /** Merge extra headers from the ...ExtraInfo events into either the
37
+ * in-flight entry or the finalized captured request, whichever has
38
+ * arrived. If neither exists yet (extra-info-first race), buffer the
39
+ * headers and drain on the matching primary event. */
40
+ function mergeRequestHeaders(requestId, extra) {
41
+ const clean = stripPseudoHeaders(extra);
42
+ const f = inFlight.get(requestId);
43
+ if (f) {
44
+ f.requestHeaders = { ...f.requestHeaders, ...clean };
45
+ return;
46
+ }
47
+ const r = requests.get(requestId);
48
+ if (r) {
49
+ r.requestHeaders = { ...r.requestHeaders, ...clean };
50
+ return;
51
+ }
52
+ const buffered = pendingRequestExtra.get(requestId) ?? {};
53
+ pendingRequestExtra.set(requestId, { ...buffered, ...clean });
54
+ }
55
+ function mergeResponseHeaders(requestId, extra) {
56
+ const clean = stripPseudoHeaders(extra);
57
+ const r = requests.get(requestId);
58
+ if (r) {
59
+ r.responseHeaders = { ...r.responseHeaders, ...clean };
60
+ return;
61
+ }
62
+ const buffered = pendingResponseExtra.get(requestId) ?? {};
63
+ pendingResponseExtra.set(requestId, { ...buffered, ...clean });
64
+ }
65
+ Network.requestWillBeSent(({ requestId, request }) => {
66
+ const f = {
67
+ requestId,
68
+ method: request.method,
69
+ url: request.url,
70
+ requestHeaders: stripPseudoHeaders(request.headers),
71
+ startedAt: Date.now(),
72
+ };
73
+ if (request.postData !== undefined) {
74
+ f.requestBody = request.postData;
75
+ }
76
+ // Drain any extra-info that arrived before us.
77
+ const buffered = pendingRequestExtra.get(requestId);
78
+ if (buffered) {
79
+ f.requestHeaders = { ...f.requestHeaders, ...buffered };
80
+ pendingRequestExtra.delete(requestId);
81
+ }
82
+ inFlight.set(requestId, f);
83
+ });
84
+ // CDP intentionally omits `Cookie` (and other browser-injected headers)
85
+ // from `requestWillBeSent.request.headers` for privacy. The real
86
+ // outgoing headers — including Cookie — arrive on the matching
87
+ // `requestWillBeSentExtraInfo` event.
88
+ Network.requestWillBeSentExtraInfo(({ requestId, headers }) => {
89
+ mergeRequestHeaders(requestId, headers);
90
+ });
91
+ Network.responseReceived(({ requestId, response }) => {
92
+ const f = inFlight.get(requestId);
93
+ if (!f) {
94
+ return;
95
+ }
96
+ const captured = {
97
+ requestId,
98
+ method: f.method,
99
+ url: f.url,
100
+ status: response.status,
101
+ contentType: response.mimeType,
102
+ size: response.encodedDataLength,
103
+ requestHeaders: f.requestHeaders,
104
+ responseHeaders: stripPseudoHeaders(response.headers),
105
+ finishedAt: 0,
106
+ };
107
+ // Drain any response extra-info that arrived before us.
108
+ const bufferedResp = pendingResponseExtra.get(requestId);
109
+ if (bufferedResp) {
110
+ captured.responseHeaders = {
111
+ ...captured.responseHeaders,
112
+ ...bufferedResp,
113
+ };
114
+ pendingResponseExtra.delete(requestId);
115
+ }
116
+ if (f.requestBody !== undefined) {
117
+ captured.requestBody = f.requestBody;
118
+ }
119
+ const op = extractGraphqlOp(f.requestBody);
120
+ if (op !== undefined) {
121
+ captured.graphqlOp = op;
122
+ }
123
+ requests.set(requestId, captured);
124
+ });
125
+ // Same privacy carve-out applies to `Set-Cookie` and other browser-
126
+ // visible response headers; they arrive on responseReceivedExtraInfo.
127
+ Network.responseReceivedExtraInfo(({ requestId, headers }) => {
128
+ mergeResponseHeaders(requestId, headers);
129
+ });
130
+ Network.loadingFinished(async ({ requestId }) => {
131
+ const r = requests.get(requestId);
132
+ if (!r) {
133
+ return;
134
+ }
135
+ try {
136
+ const { body, base64Encoded } = await Network.getResponseBody({
137
+ requestId,
138
+ });
139
+ const text = base64Encoded
140
+ ? Buffer.from(body, 'base64').toString('utf8')
141
+ : body;
142
+ bodies.set(requestId, text);
143
+ r.responseBody = text;
144
+ r.finishedAt = Date.now();
145
+ }
146
+ catch {
147
+ // Body might be unavailable for
148
+ // some content-types (e.g. websocket). Skip.
149
+ }
150
+ });
151
+ return {
152
+ captureId,
153
+ requests,
154
+ bodies,
155
+ stop: async () => {
156
+ await Network.disable();
157
+ },
158
+ };
159
+ }
@@ -0,0 +1,9 @@
1
+ import CDP from 'chrome-remote-interface';
2
+ export interface CdpHandles {
3
+ /** Browser-level connection (target list, attach). */
4
+ browser: CDP.Client;
5
+ /** Returns a per-target session bound to a tab. */
6
+ attachToTab: (targetId: string) => Promise<CDP.Client>;
7
+ close: () => Promise<void>;
8
+ }
9
+ export declare function connectCdp(port: number): Promise<CdpHandles>;
@@ -0,0 +1,11 @@
1
+ import CDP from 'chrome-remote-interface';
2
+ export async function connectCdp(port) {
3
+ const browser = await CDP({ port });
4
+ return {
5
+ browser,
6
+ attachToTab: (targetId) => CDP({ port, target: targetId }),
7
+ close: async () => {
8
+ await browser.close();
9
+ },
10
+ };
11
+ }
@@ -0,0 +1,16 @@
1
+ export type BodyLookup = {
2
+ available: true;
3
+ body: string;
4
+ } | {
5
+ available: false;
6
+ reason: string;
7
+ };
8
+ export interface BodyStoreOptions {
9
+ maxBytes: number;
10
+ }
11
+ export declare class BodyStore {
12
+ #private;
13
+ constructor(opts: BodyStoreOptions);
14
+ set(requestId: string, body: string): void;
15
+ get(requestId: string): BodyLookup;
16
+ }
@@ -0,0 +1,38 @@
1
+ export class BodyStore {
2
+ #store = new Map(); // insertion order = LRU order
3
+ #bytes = 0;
4
+ #opts;
5
+ constructor(opts) {
6
+ this.#opts = opts;
7
+ }
8
+ set(requestId, body) {
9
+ if (this.#store.has(requestId)) {
10
+ this.#bytes -= byteLen(this.#store.get(requestId));
11
+ this.#store.delete(requestId);
12
+ }
13
+ this.#store.set(requestId, body);
14
+ this.#bytes += byteLen(body);
15
+ while (this.#bytes > this.#opts.maxBytes && this.#store.size > 0) {
16
+ const oldestKey = this.#store.keys().next().value;
17
+ const oldestVal = this.#store.get(oldestKey);
18
+ this.#bytes -= byteLen(oldestVal);
19
+ this.#store.delete(oldestKey);
20
+ }
21
+ }
22
+ get(requestId) {
23
+ const body = this.#store.get(requestId);
24
+ if (body === undefined) {
25
+ return {
26
+ available: false,
27
+ reason: 'evicted from in-memory LRU body store',
28
+ };
29
+ }
30
+ // Refresh LRU position by re-inserting.
31
+ this.#store.delete(requestId);
32
+ this.#store.set(requestId, body);
33
+ return { available: true, body };
34
+ }
35
+ }
36
+ function byteLen(s) {
37
+ return Buffer.byteLength(s, 'utf8');
38
+ }
@@ -0,0 +1,14 @@
1
+ import { type ChildProcess } from 'node:child_process';
2
+ export interface LaunchOptions {
3
+ /** Port for --remote-debugging-port. */
4
+ port: number;
5
+ /** Path to user-data-dir. Created if missing. */
6
+ profileDir: string;
7
+ /** If true, launch without a visible window (--headless=new). Useful
8
+ * for tests and CI; user-driven flows want it false. */
9
+ headless?: boolean;
10
+ }
11
+ export declare function defaultProfileDir(): string;
12
+ export declare function findChromeBinary(): string;
13
+ export declare function launchChrome(opts: LaunchOptions): ChildProcess;
14
+ export declare function waitForCdpReady(port: number, timeoutMs?: number): Promise<void>;
@@ -0,0 +1,102 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { existsSync, mkdirSync } from 'node:fs';
3
+ import { homedir, platform } from 'node:os';
4
+ import { join } from 'node:path';
5
+ import { reclaimHomeDirPath } from "../paths.js";
6
+ /**
7
+ * Build the platform-specific list of Chromium-family binaries to probe.
8
+ * Order matters: Chrome first, then Chromium, then Edge (Edge is
9
+ * Chromium-based, supports CDP, and is preinstalled on Win 10/11 — a
10
+ * useful fallback when Chrome itself isn't installed).
11
+ *
12
+ * On Windows we also probe the per-user Chrome install under
13
+ * %LOCALAPPDATA%, which is what you get when Chrome was installed
14
+ * without admin rights ("Install for me only"). Skipping this was the
15
+ * #1 first-run failure observed in the field.
16
+ */
17
+ function browserCandidates() {
18
+ switch (platform()) {
19
+ case 'darwin':
20
+ return [
21
+ '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
22
+ join(homedir(), 'Applications/Google Chrome.app/Contents/MacOS/Google Chrome'),
23
+ '/Applications/Chromium.app/Contents/MacOS/Chromium',
24
+ '/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge',
25
+ ];
26
+ case 'win32': {
27
+ const localAppData = process.env['LOCALAPPDATA'] ?? join(homedir(), 'AppData', 'Local');
28
+ return [
29
+ // System-wide Chrome
30
+ 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
31
+ 'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe',
32
+ // Per-user Chrome ("Install for me only")
33
+ join(localAppData, 'Google', 'Chrome', 'Application', 'chrome.exe'),
34
+ // Edge — Chromium-based, preinstalled on Win 10/11
35
+ 'C:\\Program Files\\Microsoft\\Edge\\Application\\msedge.exe',
36
+ 'C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe',
37
+ ];
38
+ }
39
+ case 'linux':
40
+ return [
41
+ '/usr/bin/google-chrome',
42
+ '/usr/bin/google-chrome-stable',
43
+ '/usr/bin/chromium',
44
+ '/usr/bin/chromium-browser',
45
+ '/snap/bin/chromium',
46
+ '/usr/bin/microsoft-edge',
47
+ ];
48
+ default:
49
+ return [];
50
+ }
51
+ }
52
+ export function defaultProfileDir() {
53
+ return join(reclaimHomeDirPath(), 'chrome-profile');
54
+ }
55
+ export function findChromeBinary() {
56
+ const candidates = browserCandidates();
57
+ for (const c of candidates) {
58
+ if (existsSync(c)) {
59
+ return c;
60
+ }
61
+ }
62
+ throw new Error(`No Chromium-family browser found on ${platform()}. Tried:\n ` +
63
+ candidates.join('\n ') +
64
+ '\nSet RECLAIM_AGENT_CHROME_PATH to point at ' +
65
+ 'your chrome.exe / msedge.exe / chromium binary.');
66
+ }
67
+ export function launchChrome(opts) {
68
+ const binary = process.env.RECLAIM_AGENT_CHROME_PATH ?? findChromeBinary();
69
+ if (!existsSync(opts.profileDir)) {
70
+ mkdirSync(opts.profileDir, { recursive: true });
71
+ }
72
+ const args = [
73
+ `--remote-debugging-port=${opts.port}`,
74
+ `--user-data-dir=${opts.profileDir}`,
75
+ '--no-first-run',
76
+ '--no-default-browser-check',
77
+ ];
78
+ if (opts.headless) {
79
+ args.push('--headless=new', '--disable-gpu');
80
+ }
81
+ const child = spawn(binary, args, {
82
+ stdio: 'ignore',
83
+ detached: false,
84
+ });
85
+ return child;
86
+ }
87
+ export async function waitForCdpReady(port, timeoutMs = 10_000) {
88
+ const deadline = Date.now() + timeoutMs;
89
+ while (Date.now() < deadline) {
90
+ try {
91
+ const res = await fetch(`http://127.0.0.1:${port}/json/version`);
92
+ if (res.ok) {
93
+ return;
94
+ }
95
+ }
96
+ catch {
97
+ // retry
98
+ }
99
+ await new Promise((r) => setTimeout(r, 150));
100
+ }
101
+ throw new Error(`Chrome CDP did not become ready on port ${port} within ${timeoutMs}ms`);
102
+ }
@@ -0,0 +1,6 @@
1
+ export interface TabInfo {
2
+ tabId: string;
3
+ url: string;
4
+ title: string;
5
+ }
6
+ export declare function listTabs(port: number): Promise<TabInfo[]>;