@reclaimprotocol/agent 0.1.23 → 0.1.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. package/README.md +56 -20
  2. package/lib/authoring/backend.d.ts +5 -4
  3. package/lib/authoring/tools.js +30 -30
  4. package/lib/cdp/capture-engine.js +8 -7
  5. package/lib/cdp/claims.d.ts +21 -0
  6. package/lib/cdp/claims.js +129 -0
  7. package/lib/cdp/container.d.ts +85 -0
  8. package/lib/cdp/container.js +481 -0
  9. package/lib/cdp/geo.d.ts +10 -0
  10. package/lib/cdp/geo.js +36 -0
  11. package/lib/cdp/index.d.ts +1 -0
  12. package/lib/cdp/index.js +1 -0
  13. package/lib/live/cloudflared.d.ts +15 -0
  14. package/lib/live/cloudflared.js +268 -0
  15. package/lib/live/tunnel.d.ts +26 -0
  16. package/lib/live/tunnel.js +160 -0
  17. package/lib/mcp/api/operations.gen.js +34 -26
  18. package/lib/mcp/api/to-zod.d.ts +1 -1
  19. package/lib/mcp/api/to-zod.js +1 -1
  20. package/lib/mcp/api/tools.js +1 -1
  21. package/lib/mcp/instructions.d.ts +15 -2
  22. package/lib/mcp/instructions.js +102 -38
  23. package/lib/mcp/start.js +62 -6
  24. package/lib/mcp/tools/agent/analyze.js +5 -1
  25. package/lib/mcp/tools/agent/attach.d.ts +29 -2
  26. package/lib/mcp/tools/agent/attach.js +310 -24
  27. package/lib/mcp/tools/agent/build.d.ts +18 -8
  28. package/lib/mcp/tools/agent/build.js +30 -12
  29. package/lib/mcp/tools/agent/capture.js +5 -1
  30. package/lib/mcp/tools/agent/eval.js +3 -1
  31. package/lib/mcp/tools/agent/find.js +6 -5
  32. package/lib/mcp/tools/agent/how-it-works.d.ts +15 -7
  33. package/lib/mcp/tools/agent/how-it-works.js +57 -17
  34. package/lib/mcp/tools/agent/index.d.ts +1 -2
  35. package/lib/mcp/tools/agent/index.js +1 -1
  36. package/lib/mcp/tools/agent/live-view.d.ts +17 -0
  37. package/lib/mcp/tools/agent/live-view.js +146 -0
  38. package/lib/mcp/tools/agent/pod-proof.d.ts +8 -0
  39. package/lib/mcp/tools/agent/pod-proof.js +64 -0
  40. package/lib/mcp/tools/agent/propose.js +4 -2
  41. package/lib/mcp/tools/agent/publish.js +47 -77
  42. package/lib/mcp/tools/agent/session.d.ts +16 -1
  43. package/lib/mcp/tools/agent/session.js +32 -7
  44. package/lib/mcp/tools/authenticate/authenticate.js +26 -13
  45. package/lib/mcp/tools/mode/mode.d.ts +1 -1
  46. package/lib/mcp/tools/mode/mode.js +10 -22
  47. package/lib/old/client.d.ts +1 -1
  48. package/lib/old/client.js +1 -1
  49. package/lib/old/instructions.d.ts +9 -1
  50. package/lib/old/instructions.js +4 -6
  51. package/lib/old/login-cdp.d.ts +2 -2
  52. package/lib/old/login-cdp.js +46 -6
  53. package/lib/old/to-register.d.ts +3 -2
  54. package/lib/old/to-register.js +1 -1
  55. package/lib/old/tools/authenticate.js +9 -1
  56. package/lib/old/tools/get-provider-info.js +6 -5
  57. package/lib/old/tools/issue-app-credentials.js +3 -2
  58. package/lib/old/tools/publish.d.ts +1 -1
  59. package/lib/old/tools/publish.js +90 -131
  60. package/lib/old/tools/update-provider-metadata.d.ts +1 -1
  61. package/lib/old/tools/update-provider-metadata.js +1 -1
  62. package/lib/old/tools/verify-sdk-setup.js +1 -1
  63. package/lib/proof/attestor.d.ts +3 -3
  64. package/lib/proof/debug-response-dump.js +4 -4
  65. package/lib/proof/index.d.ts +1 -0
  66. package/lib/proof/index.js +1 -0
  67. package/lib/proof/page-replay.d.ts +1 -1
  68. package/lib/proof/page-replay.js +1 -1
  69. package/lib/proof/pod-prover.d.ts +47 -0
  70. package/lib/proof/pod-prover.js +155 -0
  71. package/lib/provider/dom-xpath.js +11 -9
  72. package/lib/provider/draft.d.ts +10 -7
  73. package/lib/provider/draft.js +11 -9
  74. package/lib/provider/injection-doc.d.ts +23 -1
  75. package/lib/provider/injection-doc.js +34 -21
  76. package/lib/provider/match-redaction-warning.d.ts +2 -2
  77. package/lib/provider/match-redaction-warning.js +3 -3
  78. package/lib/provider/matcher.js +13 -12
  79. package/lib/provider/secrets.d.ts +3 -3
  80. package/lib/provider/secrets.js +3 -3
  81. package/lib/provider/to-version.d.ts +7 -6
  82. package/lib/provider/to-version.js +3 -3
  83. package/lib/tools/registry.d.ts +5 -5
  84. package/lib/tools/registry.js +5 -5
  85. package/package.json +3 -3
  86. package/samples/demo-server/README.md +7 -6
  87. package/skill/SKILL.builder.md +356 -189
  88. package/skill/SKILL.common.md +400 -97
  89. package/skill/SKILL.old.md +340 -155
  90. package/skill/builder-mode.md +60 -17
  91. package/skill/hash-validation.md +170 -0
  92. package/skill/interception.md +236 -0
  93. package/skill/js-sdk-integration.md +13 -7
  94. package/skill/old-mode.md +62 -21
  95. package/skill/user-script.md +61 -0
  96. package/src/authoring/backend.ts +5 -4
  97. package/src/authoring/tools.ts +31 -30
  98. package/src/cdp/capture-engine.ts +8 -7
  99. package/src/cdp/claims.ts +153 -0
  100. package/src/cdp/container.ts +593 -0
  101. package/src/cdp/geo.ts +52 -0
  102. package/src/cdp/index.ts +1 -0
  103. package/src/consts.ts +1 -0
  104. package/src/live/cloudflared.ts +348 -0
  105. package/src/live/tunnel.ts +208 -0
  106. package/src/mcp/api/operations.gen.ts +34 -26
  107. package/src/mcp/api/to-zod.ts +1 -1
  108. package/src/mcp/api/tools.ts +1 -1
  109. package/src/mcp/instructions.ts +122 -48
  110. package/src/mcp/start.ts +69 -5
  111. package/src/mcp/tools/agent/analyze.ts +5 -1
  112. package/src/mcp/tools/agent/attach.ts +367 -26
  113. package/src/mcp/tools/agent/build.ts +35 -16
  114. package/src/mcp/tools/agent/capture.ts +5 -1
  115. package/src/mcp/tools/agent/eval.ts +4 -1
  116. package/src/mcp/tools/agent/find.ts +6 -5
  117. package/src/mcp/tools/agent/how-it-works.ts +62 -21
  118. package/src/mcp/tools/agent/index.ts +6 -2
  119. package/src/mcp/tools/agent/live-view.ts +182 -0
  120. package/src/mcp/tools/agent/pod-proof.ts +90 -0
  121. package/src/mcp/tools/agent/propose.ts +4 -2
  122. package/src/mcp/tools/agent/publish.ts +52 -77
  123. package/src/mcp/tools/agent/session.ts +33 -6
  124. package/src/mcp/tools/authenticate/authenticate.ts +28 -13
  125. package/src/mcp/tools/mode/mode.ts +10 -22
  126. package/src/old/client.ts +2 -2
  127. package/src/old/instructions.ts +4 -10
  128. package/src/old/login-cdp.ts +50 -6
  129. package/src/old/to-register.ts +3 -2
  130. package/src/old/tools/authenticate.ts +9 -1
  131. package/src/old/tools/get-provider-info.ts +6 -5
  132. package/src/old/tools/issue-app-credentials.ts +3 -2
  133. package/src/old/tools/publish.ts +95 -131
  134. package/src/old/tools/update-provider-metadata.ts +1 -1
  135. package/src/old/tools/verify-sdk-setup.ts +1 -1
  136. package/src/proof/attestor.ts +3 -3
  137. package/src/proof/debug-response-dump.ts +4 -4
  138. package/src/proof/index.ts +8 -0
  139. package/src/proof/page-replay.ts +1 -1
  140. package/src/proof/pod-prover.ts +231 -0
  141. package/src/provider/dom-xpath.ts +11 -9
  142. package/src/provider/draft.ts +13 -10
  143. package/src/provider/injection-doc.ts +37 -24
  144. package/src/provider/match-redaction-warning.ts +3 -3
  145. package/src/provider/matcher.ts +13 -12
  146. package/src/provider/secrets.ts +3 -3
  147. package/src/provider/to-version.ts +11 -9
  148. package/src/tools/registry.ts +5 -5
  149. package/skill/injection-api.md +0 -364
@@ -0,0 +1,155 @@
1
+ /**
2
+ * PopcornPodProver — run the zkTLS proof inside a Popcorn browser session's
3
+ * OWN TEE pod instead of the in-process attestor SDK (`runAttestorProof`).
4
+ * POSTs the recipe + captured secrets to the pod's `/reclaim/prove` (the same
5
+ * attestor TEE+MPC protocol) and re-shapes the response into the shared
6
+ * {@link ProofResult}, so every caller consumes proof results identically
7
+ * regardless of which path produced them.
8
+ *
9
+ * This is the pod's OWN api for creating a proof from a live session — used
10
+ * by both the app's server-driven verification runner and the MCP's
11
+ * `run_proof_via_pod` tool. Proving here (rather than locally) matters
12
+ * specifically when the session is country-routed: Popcorn allocated the
13
+ * browser with an exit country, and this is the SAME enclave holding that
14
+ * browser's TLS session, so the proof reaches the target from the identical
15
+ * exit IP the browser used — a local attestor SDK proof runs from the
16
+ * app/agent's OWN network instead.
17
+ *
18
+ * Contract (verified against ../popcorn-images oapi.go + ../reclaim-tee):
19
+ * POST {apiUrl}/reclaim/prove body { provider_params_json, config_json? }
20
+ * provider_params_json = JSON of { name, params, secretParams, context? }
21
+ * → 200 { session_id, claim, signature } (ReclaimProveResult, snake_case):
22
+ * claim has provider/parameters/owner/timestamp_s/context/identifier/epoch;
23
+ * signature has attestor_address + claim_signature(b64) +
24
+ * result_signature(b64). A proof failure/timeout is a 500 whose `message`
25
+ * carries the reason.
26
+ *
27
+ * The pod API is unauthenticated (gateway/session is the trust boundary); call
28
+ * it over the same internal host used for CDP. Consumers verify
29
+ * `claim_signature` only (see `@reclaimprotocol/client`'s `verifyNestedProof`),
30
+ * so this hands back a {@link AttestorClaim} whose hex `claimSignature` the
31
+ * shared `wrapProofAsJws` (app-side) turns into the nested result proof.
32
+ * `result_signature` is unused by any consumer.
33
+ */
34
+ import { bytesToHex } from '@noble/hashes/utils.js';
35
+ /**
36
+ * The cloud "replay": validate that a drafted recipe's selectors extract the
37
+ * expected value from a captured response, inside the TEE — `POST {apiUrl}
38
+ * /reclaim/validate-extraction`. Reuses the prover's own redaction engine, so
39
+ * a pass means the selectors won't diverge at proof time. No proof/secrets.
40
+ */
41
+ export async function popcornValidateExtraction(opts) {
42
+ const doFetch = opts.fetchImpl ?? fetch;
43
+ const base = opts.apiUrl.replace(/\/$/, '');
44
+ try {
45
+ const res = await doFetch(`${base}/reclaim/validate-extraction`, {
46
+ method: 'POST',
47
+ headers: { 'Content-Type': 'application/json' },
48
+ body: JSON.stringify({
49
+ responseBody: opts.responseBody,
50
+ expectedValue: opts.expectedValue,
51
+ ...(opts.xPath ? { xPath: opts.xPath } : {}),
52
+ ...(opts.jsonPath ? { jsonPath: opts.jsonPath } : {}),
53
+ ...(opts.regex ? { regex: opts.regex } : {}),
54
+ }),
55
+ });
56
+ if (!res.ok) {
57
+ const detail = await res.text().catch(() => '');
58
+ return { valid: false, error: `${res.status}: ${detail.slice(0, 300)}` };
59
+ }
60
+ const body = (await res.json());
61
+ return body;
62
+ }
63
+ catch (err) {
64
+ const error = err instanceof Error ? err.message : String(err);
65
+ return { valid: false, error };
66
+ }
67
+ }
68
+ /** Decode standard base64 (Go `base64.StdEncoding` from Popcorn) to bytes. */
69
+ function b64ToBytes(b64) {
70
+ return new Uint8Array(Buffer.from(b64, 'base64'));
71
+ }
72
+ /**
73
+ * Prove a single request through the Popcorn TEE. Returns the shared
74
+ * {@link ProofResult}; on any failure `verified` is false and `error` carries
75
+ * the reason (never throws for an expected proof failure).
76
+ */
77
+ export async function popcornProve(opts) {
78
+ const { apiUrl, params, secretParams, context } = opts;
79
+ const doFetch = opts.fetchImpl ?? fetch;
80
+ const base = apiUrl.replace(/\/$/, '');
81
+ const providerParamsJson = JSON.stringify({
82
+ name: 'http',
83
+ params,
84
+ secretParams,
85
+ ...(context ? { context: JSON.stringify(context) } : {}),
86
+ });
87
+ const fail = (error) => ({
88
+ proof: undefined,
89
+ extractedValue: '',
90
+ extractedParameters: {},
91
+ verified: false,
92
+ error,
93
+ });
94
+ let resp;
95
+ let raw = '';
96
+ try {
97
+ const res = await doFetch(`${base}/reclaim/prove`, {
98
+ method: 'POST',
99
+ headers: { 'Content-Type': 'application/json' },
100
+ body: JSON.stringify({ provider_params_json: providerParamsJson }),
101
+ });
102
+ // Read the body as text first: a proof failure/timeout is a 500 whose
103
+ // `message` we want to surface verbatim, and on an unexpected 200 shape we
104
+ // echo the raw body instead of a blind "no claim/signature".
105
+ raw = await res.text().catch(() => '');
106
+ if (!res.ok) {
107
+ return fail(`/reclaim/prove ${res.status}: ${raw.slice(0, 300)}`);
108
+ }
109
+ resp = JSON.parse(raw);
110
+ }
111
+ catch (err) {
112
+ return fail(err instanceof Error ? err.message : String(err));
113
+ }
114
+ const claim = resp.claim;
115
+ const sig = resp.signature;
116
+ if (!claim || !sig?.attestor_address || !sig?.claim_signature) {
117
+ return fail('popcorn /reclaim/prove returned no claim/signature: '
118
+ + raw.slice(0, 300));
119
+ }
120
+ // claim.context is the JSON string carrying { extractedParameters, ... } —
121
+ // the same place the in-process path reads them from.
122
+ let extractedParameters = {};
123
+ if (typeof claim.context === 'string') {
124
+ try {
125
+ const ctx = JSON.parse(claim.context);
126
+ extractedParameters = ctx.extractedParameters ?? {};
127
+ }
128
+ catch {
129
+ // leave empty if context isn't valid JSON
130
+ }
131
+ }
132
+ const attestorClaim = {
133
+ claim: {
134
+ provider: claim.provider ?? '',
135
+ parameters: claim.parameters ?? '',
136
+ owner: claim.owner ?? '',
137
+ timestampS: claim.timestamp_s ?? 0,
138
+ context: claim.context ?? '',
139
+ identifier: claim.identifier ?? '',
140
+ epoch: claim.epoch ?? 0,
141
+ },
142
+ attestorAddress: sig.attestor_address,
143
+ // Consumer-side verification expects hex; Popcorn signs in base64.
144
+ claimSignature: '0x' + bytesToHex(b64ToBytes(sig.claim_signature)),
145
+ };
146
+ return {
147
+ proof: resp,
148
+ verified: true,
149
+ extractedParameters,
150
+ extractedValue: Object.values(extractedParameters)[0] ?? '',
151
+ ...(claim.identifier ? { identifier: claim.identifier } : {}),
152
+ ...(claim.owner ? { owner: claim.owner } : {}),
153
+ attestorClaim,
154
+ };
155
+ }
@@ -137,9 +137,10 @@ export function elementAnchor(html, targets) {
137
137
  /** The tightest element that DIRECTLY holds a target. Prefers a COMPLETE-value
138
138
  * occurrence — an attribute whose value IS the target, or a text child that IS
139
139
  * the target — over one where the target is merely embedded in a longer string
140
- * (e.g. a userid inside a `1234567+user@…` noreply address). The complete
141
- * field is the semantic source and yields a portable anchor; the embedded one
142
- * is incidental. Smallest byte span wins within the chosen tier. */
140
+ * (for example, a userid inside a `1234567+user@…` noreply address). The
141
+ * complete field is the semantic source and yields a portable anchor; the
142
+ * embedded one is incidental. Smallest byte span wins within the chosen
143
+ * tier. */
143
144
  function findElement(root, targets) {
144
145
  const complete = [];
145
146
  const embedded = [];
@@ -196,8 +197,8 @@ function* candidateXPaths(el) {
196
197
  }
197
198
  // A uniquely-identified DIRECT child pins the parent without a positional
198
199
  // path (`//span[span[@id="labels"]]`). Self-verification discards it when the
199
- // child id/attr isn't unique — e.g. GitHub reuses `id="labels"` per email row
200
- // — so it never silently matches the wrong node.
200
+ // child id/attr isn't unique — for example, GitHub reuses `id="labels"` per
201
+ // email row — so it never silently matches the wrong node.
201
202
  for (const child of el.children) {
202
203
  if (!isTag(child)) {
203
204
  continue;
@@ -234,8 +235,9 @@ function distinctiveClass(cls) {
234
235
  .filter(Boolean)
235
236
  .sort((x, y) => y.length - x.length)[0];
236
237
  }
237
- /** Positional path from `anc` (exclusive) down to `el` (inclusive), e.g.
238
- * `/ul[1]/li[3]/a[1]`; empty string when `anc` is not an ancestor of `el`. */
238
+ /** Positional path from `anc` (exclusive) down to `el` (inclusive), for
239
+ * example `/ul[1]/li[3]/a[1]`. Empty string when `anc` is not an ancestor
240
+ * of `el`. */
239
241
  function relativeSteps(anc, el) {
240
242
  const parts = [];
241
243
  let node = el;
@@ -311,8 +313,8 @@ function absolutePath(el) {
311
313
  }
312
314
  /** True when `expression` selects EXACTLY `el` (one match) against `doc`. The
313
315
  * attestor applies the redaction regex to EVERY xPath match and throws if any
314
- * fails, so a non-unique selector (e.g. a class shared by sibling rows) is
315
- * unusable — it must resolve to the single target element. */
316
+ * fails, so a non-unique selector (for example, a class shared by sibling
317
+ * rows) is unusable — it must resolve to the single target element. */
316
318
  function matchesUniquely(doc, expression, el) {
317
319
  try {
318
320
  const engine = xpath;
@@ -14,7 +14,8 @@ export interface DraftTarget {
14
14
  }
15
15
  /** The realized extraction for one target — its value paired with the
16
16
  * redaction that reveals it (absent when matched literally). Lets a consumer
17
- * validate each value against its own selector (e.g. the cloud replay). */
17
+ * validate each value against its own selector (for example, the cloud
18
+ * replay). */
18
19
  export interface Extraction {
19
20
  name: string;
20
21
  value: string;
@@ -30,12 +31,14 @@ export interface DraftResult {
30
31
  * match or redaction — either a neighbouring target's value or a target's own
31
32
  * value left un-templated. The matcher builds each target's window
32
33
  * independently and drops the per-user WORDS it can see, but it cannot know a
33
- * neighbouring value is itself user-specific (e.g. a name sharing a text node
34
- * with an id — a `<b>{{id}}</b>` sitting beside a `<td>{{name}}</td>`).
35
- * Cross-checking every value against every match here catches that: a portable
36
- * provider contains NO captured value as a literal — each appears only as its
37
- * `{{param}}`. Throwing discards the draft so the agent re-captures or
38
- * re-proposes rather than shipping a provider pinned to this one user. */
34
+ * neighbouring value is itself user-specific (for example, a name sharing a
35
+ * text node with an id — a `<b>{{id}}</b>` sitting beside a
36
+ * `<td>{{name}}</td>`). Cross-checking every value against every match here
37
+ * catches
38
+ * that: a portable provider contains NO captured value as a literal — each
39
+ * appears only as its `{{param}}`. Throwing discards the draft so the agent
40
+ * re-captures or re-proposes rather than shipping a provider pinned to this
41
+ * one user. */
39
42
  export declare function assertPortableMatches(matches: ResponseMatch[], redactions: ResponseRedaction[], targets: DraftTarget[]): void;
40
43
  /** Reject a draft that OPRF-hashes a target whose value ALSO appears in the
41
44
  * request URL. The whole point of `hash` is that the attestor never sees
@@ -63,12 +63,14 @@ function sanitizeRequestHeaders(headers) {
63
63
  * match or redaction — either a neighbouring target's value or a target's own
64
64
  * value left un-templated. The matcher builds each target's window
65
65
  * independently and drops the per-user WORDS it can see, but it cannot know a
66
- * neighbouring value is itself user-specific (e.g. a name sharing a text node
67
- * with an id — a `<b>{{id}}</b>` sitting beside a `<td>{{name}}</td>`).
68
- * Cross-checking every value against every match here catches that: a portable
69
- * provider contains NO captured value as a literal — each appears only as its
70
- * `{{param}}`. Throwing discards the draft so the agent re-captures or
71
- * re-proposes rather than shipping a provider pinned to this one user. */
66
+ * neighbouring value is itself user-specific (for example, a name sharing a
67
+ * text node with an id — a `<b>{{id}}</b>` sitting beside a
68
+ * `<td>{{name}}</td>`). Cross-checking every value against every match here
69
+ * catches
70
+ * that: a portable provider contains NO captured value as a literal — each
71
+ * appears only as its `{{param}}`. Throwing discards the draft so the agent
72
+ * re-captures or re-proposes rather than shipping a provider pinned to this
73
+ * one user. */
72
74
  export function assertPortableMatches(matches, redactions, targets) {
73
75
  // A baked email LOCAL-PART ("jane.doe@…") pins the provider to one user
74
76
  // even when only the domain is captured as a param — the classic result of
@@ -209,7 +211,7 @@ function resolveParamName(targetName, shorten) {
209
211
  + 'shortenNames:false — it becomes a regex capture-group name AND '
210
212
  + 'a paramValues key, so it must start with a letter/underscore and '
211
213
  + 'contain only letters, digits, and underscores (we recommend '
212
- + 'lower_snake_case, e.g. "employer_name"). Rename it, or drop '
214
+ + 'lower_snake_case, for example, "employer_name"). Rename it, or drop '
213
215
  + '`shortenNames: false` to auto-derive a safe one.');
214
216
  }
215
217
  return targetName;
@@ -241,8 +243,8 @@ export function draftProvider(req, targets, name, hash, opts) {
241
243
  + `param name "${paramName}"`
242
244
  + (shortenNames
243
245
  ? ' (names are shortened to their last alphanumeric token) — '
244
- + 'give them more distinct names, e.g. "firstName"/"surname" '
245
- + 'instead of "first_name"/"last_name"'
246
+ + 'give them more distinct names, for example '
247
+ + '"firstName"/"surname" instead of "first_name"/"last_name"'
246
248
  : ' — give them distinct names, or drop `shortenNames: false` '
247
249
  + 'to auto-derive distinct ones'));
248
250
  }
@@ -1 +1,23 @@
1
- export declare const INJECTION_GUIDE: string;
1
+ /**
2
+ * Short field hints for the user-script inputs on both publish tools
3
+ * (`webSettings.jsUserScripts` on the builder, `customInjection` on
4
+ * old-devtools).
5
+ *
6
+ * These used to inline the whole `skill/injection-api.md` guide (~22 KB) into
7
+ * the tool's input schema. That guide is needed by a small minority of
8
+ * providers, but an input-schema description is sent to the model on EVERY
9
+ * request — it was roughly half the old-devtools tool surface on its own. The
10
+ * prose now lives in `skill/user-script.md`, `skill/interception.md` and
11
+ * `skill/hash-validation.md`, served on demand as `how_it_works` topics; what
12
+ * stays here is the pointer plus the one warning that must be seen BEFORE the
13
+ * field is filled in, since acting on it wrongly is worse than not acting.
14
+ *
15
+ * Shared by both publish tools so the two can't drift apart.
16
+ */
17
+ /** What the script is and where the real guidance lives. */
18
+ export declare const USER_SCRIPT_HINT: string;
19
+ /** Prefixes the `allowedJsRequests` / `allowedInjectedRequestData` field. The
20
+ * trap it describes is silent and total — a mis-declared entry makes
21
+ * `verifyProof({ providerId })` reject EVERY real proof — so the warning
22
+ * itself stays in the schema even though its explanation moved to a topic. */
23
+ export declare const ALLOWED_REQUESTS_HINT: string;
@@ -1,24 +1,37 @@
1
1
  /**
2
- * Guidance for authoring page-injection scripts (`webSettings.jsUserScripts` on
3
- * the builder, `customInjection` on old-devtools), surfaced to the authoring
4
- * model from the publish tool description. The prose lives in
5
- * `skill/injection-api.md` so it can be edited as markdown without touching
6
- * code — the same pattern as `mcp/instructions.ts`. The path resolves the same
7
- * from `src/provider` (dev) and `lib/provider` (published): both are two levels
8
- * below the package root.
2
+ * Short field hints for the user-script inputs on both publish tools
3
+ * (`webSettings.jsUserScripts` on the builder, `customInjection` on
4
+ * old-devtools).
9
5
  *
10
- * The guide leads with plain browser JavaScript (navigation/interaction via
11
- * standard APIs; the client already intercepts page requests) and treats
12
- * `window.Reclaim` as a rarely-needed edge-case bridge, pointing at the
13
- * authoritative `src/support/env.d.ts` rather than reproducing its surface.
14
- * It also covers the HAWKEYE-style live-interception path (`injectionType` /
15
- * `interceptorType`, `disableRequestReplay` / `isDocumentRequestReplayEnabled`)
16
- * and `bodySniff`, reverse-engineered from the old-devtools Prisma schema,
17
- * controller code, and dashboard field hints. The replay flag IS settable via
18
- * the publish tools (`disableRequestReplay` on old-devtools — add-version or
19
- * the register follow-up; `interceptorOptions` on the builder), so this is
20
- * recognition + authoring guidance for the remaining internals.
6
+ * These used to inline the whole `skill/injection-api.md` guide (~22 KB) into
7
+ * the tool's input schema. That guide is needed by a small minority of
8
+ * providers, but an input-schema description is sent to the model on EVERY
9
+ * request — it was roughly half the old-devtools tool surface on its own. The
10
+ * prose now lives in `skill/user-script.md`, `skill/interception.md` and
11
+ * `skill/hash-validation.md`, served on demand as `how_it_works` topics; what
12
+ * stays here is the pointer plus the one warning that must be seen BEFORE the
13
+ * field is filled in, since acting on it wrongly is worse than not acting.
14
+ *
15
+ * Shared by both publish tools so the two can't drift apart.
21
16
  */
22
- import { readFileSync } from 'node:fs';
23
- import { fileURLToPath } from 'node:url';
24
- export const INJECTION_GUIDE = readFileSync(fileURLToPath(new URL('../../skill/injection-api.md', import.meta.url)), 'utf8').trim();
17
+ /** What the script is and where the real guidance lives. */
18
+ export const USER_SCRIPT_HINT = 'Plain JavaScript injected into the provider page before every page load. '
19
+ + 'Use standard browser APIs (window.location, element.click(), forms) to '
20
+ + 'drive login/navigation so the target request fires — the client already '
21
+ + 'intercepts page requests, and window.Reclaim is only for rare edge cases, '
22
+ + 'not navigation. Full guide: how_it_works({ topic: "user-script" }).';
23
+ /** Prefixes the `allowedJsRequests` / `allowedInjectedRequestData` field. The
24
+ * trap it describes is silent and total — a mis-declared entry makes
25
+ * `verifyProof({ providerId })` reject EVERY real proof — so the warning
26
+ * itself stays in the schema even though its explanation moved to a topic. */
27
+ export const ALLOWED_REQUESTS_HINT = '⚠️ Only populate this if you intend to gate '
28
+ + '`verifyProof(proof, { providerId })`: each entry feeds a client-side '
29
+ + 'hash-equality check and its `url` is hashed VERBATIM (no regex/template '
30
+ + 'resolution, even with urlType REGEX/TEMPLATE). If the real captured URL '
31
+ + 'carries ANY per-session part (nonce, CSRF token, cache-busting '
32
+ + 'timestamp), no declared spec can ever hash-match a real proof — '
33
+ + 'populating it then makes verifyProof reject every real proof, which is '
34
+ + 'worse than leaving it empty. Diff the same endpoint\'s captured URL '
35
+ + 'across two sessions first. Mechanics, the body-templating rules and the '
36
+ + 'dangerouslyDisableContentValidation fallback: '
37
+ + 'how_it_works({ topic: "hash-validation" }).';
@@ -6,8 +6,8 @@
6
6
  * index and may not behave correctly when there are fewer matches than
7
7
  * redactions. This is NOT enforced — the author keeps full control over what
8
8
  * they publish in either old-devtools or builder mode. The tools surface these
9
- * strings so the dev can make an informed decision (e.g. add a functional match
10
- * per redaction) rather than being blocked.
9
+ * strings so the dev can make an informed decision (for example, add a
10
+ * functional match per redaction) rather than being blocked.
11
11
  */
12
12
  /** One request's match/redaction counts, with a human label for the message. */
13
13
  export interface RequestCounts {
@@ -6,8 +6,8 @@
6
6
  * index and may not behave correctly when there are fewer matches than
7
7
  * redactions. This is NOT enforced — the author keeps full control over what
8
8
  * they publish in either old-devtools or builder mode. The tools surface these
9
- * strings so the dev can make an informed decision (e.g. add a functional match
10
- * per redaction) rather than being blocked.
9
+ * strings so the dev can make an informed decision (for example, add a
10
+ * functional match per redaction) rather than being blocked.
11
11
  */
12
12
  /** Build advisory warnings for any request with matchCount < redactionCount.
13
13
  * Returns an empty array when everything is fine. */
@@ -17,7 +17,7 @@ export function matchRedactionWarnings(requests) {
17
17
  if (req.matchCount < req.redactionCount) {
18
18
  warnings.push(`${req.label}: ${req.matchCount} response match(es) but ` +
19
19
  `${req.redactionCount} response redaction(s). Some verification ` +
20
- 'clients (e.g. the InApp SDK) may not work correctly when ' +
20
+ 'clients (for example, the InApp SDK) may not work correctly when ' +
21
21
  'responseMatches is shorter than responseRedactions — they pair the ' +
22
22
  'two by index. This is allowed; add a functional match per redaction ' +
23
23
  'if you hit verification issues.');
@@ -31,7 +31,7 @@ export function synthesizeMatcher(body, contentType, target, paramName, hash) {
31
31
  throw new Error('OPRF hashing requires a target with a known-safe character class '
32
32
  + '(digits, slug, or email). '
33
33
  + `Got "${target}" which is free-form. Use a tighter target `
34
- + '(e.g. just the alphanumeric portion of the value) '
34
+ + '(for example, just the alphanumeric portion of the value) '
35
35
  + 'or call propose_provider without `hash`.');
36
36
  }
37
37
  const valueClass = inferValueClass(target) ?? '[^"<>\\n]+';
@@ -136,10 +136,10 @@ function lastPathKey(jsonPath) {
136
136
  * otherwise). Returns undefined when the value isn't preceded by a JSON key.
137
137
  *
138
138
  * When `key` is given, anchors the search on occurrences of the KEY (not the
139
- * value) — so a short/common value elsewhere in the body (e.g. the digit "5"
140
- * inside `"investment_amount":354.85`) can't be mistaken for this field. Only
141
- * falls back to a whole-body value search when there's no resolvable key
142
- * (e.g. `findJsonPath` returned an array-index leaf).
139
+ * value) — so a short/common value elsewhere in the body (for example, the
140
+ * digit "5" inside `"investment_amount":354.85`) can't be mistaken for this
141
+ * field. Only falls back to a whole-body value search when there's no
142
+ * resolvable key (for example, `findJsonPath` returned an array-index leaf).
143
143
  */
144
144
  function jsonKeyPrefix(text, target, key) {
145
145
  if (key !== undefined) {
@@ -357,9 +357,9 @@ function portableWindow(markup, valueIdx, valueLen) {
357
357
  /** Value is element text content. Left anchor: if only punctuation/whitespace
358
358
  * sits between the opening tag and the value, fold the stable opening tag in;
359
359
  * but if WORDS precede the value in the same text node they may be per-user
360
- * (e.g. `<li>Full Name ( {{id}} )` — the name), so drop through the last
361
- * alphanumeric and keep only the trailing punctuation as the anchor. Right:
362
- * include a closing tag, but stop at a sibling. */
360
+ * (for example, `<li>Full Name ( {{id}} )` — the name), so drop through the
361
+ * last alphanumeric and keep only the trailing punctuation as the anchor.
362
+ * Right: include a closing tag, but stop at a sibling. */
363
363
  function textWindow(markup, valueIdx, valueEnd, openTagGt) {
364
364
  const openTagStart = markup.lastIndexOf('<', openTagGt);
365
365
  const openTag = openTagStart >= 0
@@ -495,9 +495,9 @@ function contentMatcher(body, target, paramName, valueClass, hash) {
495
495
  * delimiter is kept unless it's whitespace (which the template also drops).
496
496
  */
497
497
  /** Indices where `cand` occurs in `body` as a WHOLE match of `valueClass` —
498
- * i.e. not a substring inside a longer run of the same class (the digit "5"
499
- * inside "354.85" is never a whole `\d+` match; "354" and "85" are). Only
500
- * meaningful for a structural (non-free-form) class. */
498
+ * that is, not a substring inside a longer run of the same class (the digit "5"
499
+ * inside "354.85" is never a whole `\d+` match; "354" and "85" are). Only
500
+ * meaningful for a structural (non-free-form) class. */
501
501
  function wholeTokenIndices(body, cand, valueClass) {
502
502
  const indices = [];
503
503
  let re;
@@ -645,7 +645,8 @@ function inferValueClass(target) {
645
645
  if (/^[0-9]+$/.test(target)) {
646
646
  return '\\d+';
647
647
  }
648
- // Numbers with thousands separators / decimals, e.g. "1,908" or "12,345.6".
648
+ // Numbers with thousands separators / decimals, for example, "1,908" or
649
+ // "12,345.6".
649
650
  if (/^[0-9][0-9,.]*[0-9]$/.test(target)) {
650
651
  return '[0-9,.]+';
651
652
  }
@@ -18,9 +18,9 @@ export declare function splitSecrets(headers: Record<string, string>): {
18
18
  * still visible to the attestor regardless of secret-marking. */
19
19
  export declare function secretUrlCharBudget(writeRedactionMode: ReclaimProvider['writeRedactionMode']): number;
20
20
  /** A `paramValues`/context key is secret iff its name contains `SECRET`
21
- * (case-insensitive) — e.g. `SECRET_apiKey`, `context.SECRET_apiKey`. Same
22
- * naming-convention classifier reclaim-inapp-sdk and reclaim-portal use for
23
- * their `witnessParams`/`paramValues` maps (no separate boolean flag). */
21
+ * (case-insensitive) — for example, `SECRET_apiKey`, `context.SECRET_apiKey`.
22
+ * Same naming-convention classifier reclaim-inapp-sdk and reclaim-portal use
23
+ * for their `witnessParams`/`paramValues` maps (no separate boolean flag). */
24
24
  export declare function classifyParamName(name: string): boolean;
25
25
  /** Split a `paramValues` map (bare `{{name}}` / `{{context.<name>}}` entries)
26
26
  * into public (→ `params.paramValues`, revealed to the attestor) and secret
@@ -108,9 +108,9 @@ export function secretUrlCharBudget(writeRedactionMode) {
108
108
  return writeRedactionMode === 'zk' ? 12 : 2;
109
109
  }
110
110
  /** A `paramValues`/context key is secret iff its name contains `SECRET`
111
- * (case-insensitive) — e.g. `SECRET_apiKey`, `context.SECRET_apiKey`. Same
112
- * naming-convention classifier reclaim-inapp-sdk and reclaim-portal use for
113
- * their `witnessParams`/`paramValues` maps (no separate boolean flag). */
111
+ * (case-insensitive) — for example, `SECRET_apiKey`, `context.SECRET_apiKey`.
112
+ * Same naming-convention classifier reclaim-inapp-sdk and reclaim-portal use
113
+ * for their `witnessParams`/`paramValues` maps (no separate boolean flag). */
114
114
  export function classifyParamName(name) {
115
115
  return name.toUpperCase().includes('SECRET');
116
116
  }
@@ -27,14 +27,14 @@ export interface DraftRequest {
27
27
  * pagination/optionality flags of `RequestSelectionTemplate`. Reuses the
28
28
  * shared `DraftRequest` shape — no provider `name`/`paramValues` required. */
29
29
  export interface AllowedJsRequest extends DraftRequest {
30
- /** Allow the template to match many requests (e.g. pagination). Default
31
- * true. */
30
+ /** Allow the template to match many requests (for example, pagination).
31
+ * Default true. */
32
32
  multiple?: boolean;
33
33
  /** Fail validation if no request matches this template. Default true. */
34
34
  required?: boolean;
35
35
  /** Names of `${var}` placeholders in this template's `responseMatches`/
36
36
  * `responseRedactions` that get substituted at verify time from the
37
- * submitted proof's own witness params (e.g. an index list a
37
+ * submitted proof's own witness params (for example, an index list a
38
38
  * `Reclaim.requestClaim` call attached alongside its `{{var}}`
39
39
  * extractions). Omit (or leave empty) when the template has no such
40
40
  * placeholders — verification then matches it literally as-is. */
@@ -59,12 +59,13 @@ export interface CreateVersionOptions {
59
59
  * is explicitly passed (builder's schema has no `NONE` interceptorType;
60
60
  * omitting `inapp` entirely IS the "no interception" signal, so omitting
61
61
  * this must omit `inapp`, not default it to HAWKEYE). When it IS passed,
62
- * `isDocumentRequestReplayEnabled` is still ALWAYS force-disabled
63
- * regardless of what's passed here — see `providerToCreateVersionRequest`;
64
- * there is no author-facing way to enable it. */
62
+ * `isDocumentRequestReplayEnabled` defaults to `false` (disabled) unless
63
+ * explicitly overridden here — see `providerToCreateVersionRequest`. Only
64
+ * meaningful for `HAWKEYE`/`MSWJS`; inert for `CDP`. */
65
65
  interceptorOptions?: {
66
66
  interceptorType: 'HAWKEYE' | 'MSWJS' | 'CDP';
67
67
  interceptorSettings?: string;
68
+ isDocumentRequestReplayEnabled?: boolean;
68
69
  };
69
70
  }
70
71
  /**
@@ -137,14 +137,14 @@ export function providerToCreateVersionRequest(providers, initialUrl, opts = {})
137
137
  // omitting `interceptorOptions` must omit `inapp` too, or every
138
138
  // standard (non-interception) provider would incorrectly publish
139
139
  // as HAWKEYE. Within that block, `isDocumentRequestReplayEnabled`
140
- // is still force-disabled — see `CreateVersionOptions
141
- // .interceptorOptions`'s doc comment; there is no way to enable it.
140
+ // defaults to disabled but is author-overridable — see
141
+ // `CreateVersionOptions.interceptorOptions`'s doc comment.
142
142
  ...(interceptorOptions
143
143
  ? {
144
144
  inapp: {
145
145
  interceptorOptions: {
146
146
  interceptorType: interceptorOptions.interceptorType,
147
- isDocumentRequestReplayEnabled: false,
147
+ isDocumentRequestReplayEnabled: interceptorOptions.isDocumentRequestReplayEnabled ?? false,
148
148
  ...(interceptorOptions.interceptorSettings
149
149
  ? {
150
150
  interceptorSettings: interceptorOptions.interceptorSettings,
@@ -30,11 +30,11 @@ export declare function errorAsToolText(err: unknown): string;
30
30
  /**
31
31
  * Adapt a tool to a host by WIDENING its input schema — add properties, mark
32
32
  * some required, optionally override the description — without touching the
33
- * handler. A host maps whichever shared tools it needs through this (e.g. the
34
- * MCP adds its `captureId` session handle + the credential-aware `run_proof`
35
- * contract). Args parse loosely, so the extra fields flow through to the
36
- * handler (its backend resolver reads them); the base tool stays untouched for
37
- * hosts that don't need them.
33
+ * handler. A host maps whichever shared tools it needs through this (for
34
+ * example, the MCP adds its `captureId` session handle + the credential-aware
35
+ * `run_proof` contract). Args parse loosely, so the extra fields flow through
36
+ * to the handler (its backend resolver reads them); the base tool stays
37
+ * untouched for hosts that don't need them.
38
38
  */
39
39
  export declare function extendTool(tool: RegisteredTool, extra: {
40
40
  description?: string;
@@ -44,11 +44,11 @@ export function errorAsToolText(err) {
44
44
  /**
45
45
  * Adapt a tool to a host by WIDENING its input schema — add properties, mark
46
46
  * some required, optionally override the description — without touching the
47
- * handler. A host maps whichever shared tools it needs through this (e.g. the
48
- * MCP adds its `captureId` session handle + the credential-aware `run_proof`
49
- * contract). Args parse loosely, so the extra fields flow through to the
50
- * handler (its backend resolver reads them); the base tool stays untouched for
51
- * hosts that don't need them.
47
+ * handler. A host maps whichever shared tools it needs through this (for
48
+ * example, the MCP adds its `captureId` session handle + the credential-aware
49
+ * `run_proof` contract). Args parse loosely, so the extra fields flow through
50
+ * to the handler (its backend resolver reads them); the base tool stays
51
+ * untouched for hosts that don't need them.
52
52
  */
53
53
  export function extendTool(tool, extra) {
54
54
  const base = tool.definition;
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@reclaimprotocol/agent",
3
3
  "title": "Reclaim Agent",
4
- "version": "0.1.23",
5
- "description": "Reclaim Protocol's tools for agents — Reclaim Protocol lets your app verify **data points** about its users — a follower count, an account balance, an order history. This package gives your AI coding agent the tools to **find, build, test and fix the _data verification providers_** (the recipes that verify a data point) by inspecting and replaying captured requests, checking sessions, evaluate proofs, publishing updates, and much more.",
4
+ "version": "0.1.25",
5
+ "description": "Reclaim Protocol tools for AI coding agents. Reclaim lets your app verify data points about its users, such as a follower count, an account balance, or an order history. This package is the Reclaim MCP server: it gives your agent the tools to find, build, test, and fix data verification providers by capturing and replaying browser requests, drafting provider recipes, running zkTLS proofs, and publishing provider versions.",
6
6
  "type": "module",
7
7
  "main": "./lib/index.js",
8
8
  "types": "./lib/index.d.ts",
@@ -83,7 +83,7 @@
83
83
  "@modelcontextprotocol/sdk": "^1.29.0",
84
84
  "@noble/hashes": "^2.2.0",
85
85
  "@reclaimprotocol/attestor-core": "^5.0.8",
86
- "@reclaimprotocol/client": "0.1.23",
86
+ "@reclaimprotocol/client": "0.1.25",
87
87
  "@reclaimprotocol/tls": "^0.1.3",
88
88
  "chrome-remote-interface": "^0.33.0",
89
89
  "domhandler": "^6.0.0",
@@ -6,13 +6,14 @@
6
6
  existing app.
7
7
  3. Set `PUBLIC_URL` in `.env` to a URL Reclaim's servers can reach — pick
8
8
  whichever fits:
9
- - **ngrok**: in a separate terminal, run `ngrok http 3000` (install from
10
- https://ngrok.com if needed) and copy the
9
+ - **ngrok**: in a separate terminal, run `ngrok http 3000` (install it
10
+ from the [ngrok site](https://ngrok.com) if needed) and copy the
11
11
  `https://<id>.ngrok-free.app` URL it prints.
12
- - **A dev/staging server you can deploy to quickly**: if you already have
13
- one reachable from the internet, deploy this there and use its URL
14
- instead — no local tunnel needed.
15
- This step is REQUIRED — `localhost` is never reachable from Reclaim's
12
+ - **A development or staging server you can deploy to quickly**: if you
13
+ already have one reachable from the internet, deploy this there and use
14
+ its URL instead — no local tunnel needed.
15
+
16
+ This step is REQUIRED: `localhost` is never reachable from Reclaim's
16
17
  servers.
17
18
  4. `npm start`, then open http://localhost:3000/verify (or your deployed
18
19
  URL's `/verify`) and click "Start verification".