@reclaimprotocol/agent 0.1.22 → 0.1.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +55 -20
- package/lib/authoring/backend.d.ts +13 -10
- package/lib/authoring/tools.js +106 -9
- package/lib/cdp/browser-proxy.d.ts +78 -0
- package/lib/cdp/browser-proxy.js +186 -0
- package/lib/cdp/capture-engine.js +8 -7
- package/lib/cdp/client.d.ts +10 -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/page.d.ts +20 -0
- package/lib/live/page.js +134 -0
- package/lib/live/tunnel.d.ts +23 -0
- package/lib/live/tunnel.js +120 -0
- package/lib/live/viewer.d.ts +28 -0
- package/lib/live/viewer.js +232 -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 +101 -38
- package/lib/mcp/start.js +4 -3
- package/lib/mcp/tools/agent/analyze.js +5 -1
- package/lib/mcp/tools/agent/attach.d.ts +8 -1
- package/lib/mcp/tools/agent/attach.js +166 -22
- package/lib/mcp/tools/agent/build.d.ts +13 -8
- package/lib/mcp/tools/agent/build.js +22 -11
- 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/live-view.d.ts +21 -0
- package/lib/mcp/tools/agent/live-view.js +153 -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 +29 -3
- package/lib/mcp/tools/agent/proxy.d.ts +3 -0
- package/lib/mcp/tools/agent/proxy.js +199 -0
- package/lib/mcp/tools/agent/publish.js +49 -77
- package/lib/mcp/tools/agent/session.d.ts +2 -1
- package/lib/mcp/tools/agent/session.js +26 -7
- package/lib/mcp/tools/agent/shared.js +17 -8
- package/lib/mcp/tools/authenticate/authenticate.d.ts +11 -0
- package/lib/mcp/tools/authenticate/authenticate.js +143 -121
- package/lib/mcp/tools/authenticate/builder-access.d.ts +8 -3
- package/lib/mcp/tools/authenticate/builder-access.js +8 -3
- package/lib/mcp/tools/mode/mode.d.ts +1 -1
- package/lib/mcp/tools/mode/mode.js +26 -7
- package/lib/old/client.d.ts +9 -0
- package/lib/old/client.js +19 -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 +20 -8
- package/lib/old/to-register.js +7 -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/me-providers.js +3 -1
- package/lib/old/tools/publish.d.ts +1 -1
- package/lib/old/tools/publish.js +132 -129
- 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 +36 -0
- package/lib/proof/page-replay.js +76 -0
- package/lib/proof/pod-prover.d.ts +47 -0
- package/lib/proof/pod-prover.js +155 -0
- package/lib/proof/replay.d.ts +28 -0
- package/lib/proof/replay.js +39 -18
- package/lib/provider/dom-xpath.js +11 -9
- package/lib/provider/draft.d.ts +13 -8
- package/lib/provider/draft.js +47 -13
- 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 +122 -12
- package/lib/provider/secrets.d.ts +3 -3
- package/lib/provider/secrets.js +3 -3
- package/lib/provider/to-version.d.ts +12 -9
- package/lib/provider/to-version.js +16 -4
- package/lib/tools/registry.d.ts +5 -5
- package/lib/tools/registry.js +5 -5
- package/package.json +4 -3
- package/samples/demo-server/README.md +7 -6
- package/skill/SKILL.builder.md +356 -189
- package/skill/SKILL.common.md +314 -79
- package/skill/SKILL.old.md +340 -155
- package/skill/builder-mode.md +58 -17
- package/skill/hash-validation.md +170 -0
- package/skill/interception.md +235 -0
- package/skill/js-sdk-integration.md +13 -7
- package/skill/old-mode.md +53 -12
- package/skill/user-script.md +61 -0
- package/src/authoring/backend.ts +13 -10
- package/src/authoring/tools.ts +128 -9
- package/src/cdp/capture-engine.ts +8 -7
- package/src/cdp/client.ts +8 -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/page.ts +145 -0
- package/src/live/tunnel.ts +157 -0
- package/src/live/viewer.ts +293 -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 +121 -48
- package/src/mcp/start.ts +4 -3
- package/src/mcp/tools/agent/analyze.ts +5 -1
- package/src/mcp/tools/agent/attach.ts +194 -22
- package/src/mcp/tools/agent/build.ts +26 -15
- 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/live-view.ts +198 -0
- package/src/mcp/tools/agent/pod-proof.ts +90 -0
- package/src/mcp/tools/agent/propose.ts +31 -3
- package/src/mcp/tools/agent/publish.ts +54 -78
- package/src/mcp/tools/agent/session.ts +26 -5
- package/src/mcp/tools/agent/shared.ts +19 -8
- package/src/mcp/tools/authenticate/authenticate.ts +167 -165
- package/src/mcp/tools/authenticate/builder-access.ts +8 -3
- package/src/mcp/tools/mode/mode.ts +28 -7
- package/src/old/client.ts +30 -1
- package/src/old/instructions.ts +4 -10
- package/src/old/login-cdp.ts +50 -6
- package/src/old/to-register.ts +28 -8
- 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/me-providers.ts +3 -1
- package/src/old/tools/publish.ts +140 -130
- 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 +131 -0
- package/src/proof/pod-prover.ts +231 -0
- package/src/proof/replay.ts +60 -19
- package/src/provider/dom-xpath.ts +11 -9
- package/src/provider/draft.ts +56 -13
- package/src/provider/injection-doc.ts +37 -24
- package/src/provider/match-redaction-warning.ts +3 -3
- package/src/provider/matcher.ts +151 -10
- package/src/provider/secrets.ts +3 -3
- package/src/provider/to-version.ts +30 -13
- package/src/tools/registry.ts +5 -5
- package/skill/injection-api.md +0 -335
|
@@ -1 +1,23 @@
|
|
|
1
|
-
|
|
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
|
-
*
|
|
3
|
-
* the builder, `customInjection` on
|
|
4
|
-
*
|
|
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
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* `
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
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
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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.');
|
package/lib/provider/matcher.js
CHANGED
|
@@ -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
|
-
+ '(
|
|
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]+';
|
|
@@ -117,18 +117,34 @@ function jsonMatcher(body, target, paramName, valueClass, hash) {
|
|
|
117
117
|
if (!jsonPath) {
|
|
118
118
|
return undefined;
|
|
119
119
|
}
|
|
120
|
-
const anchor = jsonKeyPrefix(body, target);
|
|
120
|
+
const anchor = jsonKeyPrefix(body, target, lastPathKey(jsonPath));
|
|
121
121
|
if (!anchor) {
|
|
122
122
|
return undefined;
|
|
123
123
|
}
|
|
124
124
|
return keyValueResult(anchor.prefix, anchor.close, target, paramName, valueClass, { jsonPath }, hash);
|
|
125
125
|
}
|
|
126
|
+
/** The trailing object-property name of a jsonPath (`$.total_records` →
|
|
127
|
+
* `"total_records"`), or undefined for an array-index/bracket leaf, which
|
|
128
|
+
* has no literal key to anchor a search on. */
|
|
129
|
+
function lastPathKey(jsonPath) {
|
|
130
|
+
const m = jsonPath ? /\.([A-Za-z_][A-Za-z0-9_]*)$/.exec(jsonPath) : null;
|
|
131
|
+
return m?.[1];
|
|
132
|
+
}
|
|
126
133
|
/**
|
|
127
134
|
* The literal `"key":"` (or `"key":` for a raw number/bool) immediately before
|
|
128
135
|
* `target` in `text`, plus the closing quote (`"` for a string value, `''`
|
|
129
136
|
* otherwise). Returns undefined when the value isn't preceded by a JSON key.
|
|
137
|
+
*
|
|
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 (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).
|
|
130
143
|
*/
|
|
131
|
-
function jsonKeyPrefix(text, target) {
|
|
144
|
+
function jsonKeyPrefix(text, target, key) {
|
|
145
|
+
if (key !== undefined) {
|
|
146
|
+
return jsonKeyPrefixByKey(text, target, key);
|
|
147
|
+
}
|
|
132
148
|
const { idx } = firstIndex(text, target);
|
|
133
149
|
if (idx < 0) {
|
|
134
150
|
return undefined;
|
|
@@ -157,6 +173,61 @@ function jsonKeyPrefix(text, target) {
|
|
|
157
173
|
}
|
|
158
174
|
return { prefix: text.slice(keyStart, idx), close: quoted ? '"' : '' };
|
|
159
175
|
}
|
|
176
|
+
/** `jsonKeyPrefix`'s key-anchored path: scan occurrences of `"key"`, and for
|
|
177
|
+
* each, check whether the value that follows its `:` matches `target` (or a
|
|
178
|
+
* normalized candidate) before accepting it. */
|
|
179
|
+
function jsonKeyPrefixByKey(text, target, key) {
|
|
180
|
+
const keyToken = `"${key}"`;
|
|
181
|
+
let from = 0;
|
|
182
|
+
for (;;) {
|
|
183
|
+
const keyIdx = text.indexOf(keyToken, from);
|
|
184
|
+
if (keyIdx < 0) {
|
|
185
|
+
return undefined;
|
|
186
|
+
}
|
|
187
|
+
from = keyIdx + keyToken.length;
|
|
188
|
+
let i = from;
|
|
189
|
+
while (i < text.length && /\s/.test(text[i] ?? '')) {
|
|
190
|
+
i++;
|
|
191
|
+
}
|
|
192
|
+
if (text[i] !== ':') {
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
i++;
|
|
196
|
+
while (i < text.length && /\s/.test(text[i] ?? '')) {
|
|
197
|
+
i++;
|
|
198
|
+
}
|
|
199
|
+
const matched = matchJsonValueAt(text, i, target);
|
|
200
|
+
if (matched) {
|
|
201
|
+
return {
|
|
202
|
+
prefix: text.slice(keyIdx, matched.valueStart),
|
|
203
|
+
close: matched.close,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
/** Whether the JSON value starting at `pos` in `text` — quoted string or a
|
|
209
|
+
* raw number/bool/null token — equals `target` (or a normalized candidate).
|
|
210
|
+
* Returns the position right after any opening quote (matching `firstIndex`'s
|
|
211
|
+
* convention) and the delimiter to close the match template with. */
|
|
212
|
+
function matchJsonValueAt(text, pos, target) {
|
|
213
|
+
const candidates = [target, ...normalizeCandidates(target)];
|
|
214
|
+
if (text[pos] === '"') {
|
|
215
|
+
let j = pos + 1;
|
|
216
|
+
while (j < text.length && text[j] !== '"') {
|
|
217
|
+
j += text[j] === '\\' ? 2 : 1;
|
|
218
|
+
}
|
|
219
|
+
const raw = text.slice(pos + 1, j);
|
|
220
|
+
return candidates.includes(raw)
|
|
221
|
+
? { valueStart: pos + 1, close: '"' }
|
|
222
|
+
: undefined;
|
|
223
|
+
}
|
|
224
|
+
let j = pos;
|
|
225
|
+
while (j < text.length && !/[,}\]\s]/.test(text[j] ?? '')) {
|
|
226
|
+
j++;
|
|
227
|
+
}
|
|
228
|
+
const raw = text.slice(pos, j);
|
|
229
|
+
return candidates.includes(raw) ? { valueStart: pos, close: '' } : undefined;
|
|
230
|
+
}
|
|
160
231
|
function findJsonPath(body, target) {
|
|
161
232
|
let parsed;
|
|
162
233
|
try {
|
|
@@ -226,7 +297,9 @@ function htmlMatcher(body, target, paramName, valueClass, hash) {
|
|
|
226
297
|
const scriptJson = scriptJsonContent(revealSlice);
|
|
227
298
|
if (scriptJson) {
|
|
228
299
|
const jsonPath = findJsonPath(scriptJson, target);
|
|
229
|
-
const kp = jsonPath
|
|
300
|
+
const kp = jsonPath
|
|
301
|
+
? jsonKeyPrefix(scriptJson, target, lastPathKey(jsonPath))
|
|
302
|
+
: undefined;
|
|
230
303
|
if (jsonPath && kp) {
|
|
231
304
|
return keyValueResult(kp.prefix, kp.close, target, paramName, valueClass, { xPath: anchor.xPath, jsonPath }, hash);
|
|
232
305
|
}
|
|
@@ -284,9 +357,9 @@ function portableWindow(markup, valueIdx, valueLen) {
|
|
|
284
357
|
/** Value is element text content. Left anchor: if only punctuation/whitespace
|
|
285
358
|
* sits between the opening tag and the value, fold the stable opening tag in;
|
|
286
359
|
* but if WORDS precede the value in the same text node they may be per-user
|
|
287
|
-
* (
|
|
288
|
-
* alphanumeric and keep only the trailing punctuation as the anchor.
|
|
289
|
-
* 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. */
|
|
290
363
|
function textWindow(markup, valueIdx, valueEnd, openTagGt) {
|
|
291
364
|
const openTagStart = markup.lastIndexOf('<', openTagGt);
|
|
292
365
|
const openTag = openTagStart >= 0
|
|
@@ -396,7 +469,7 @@ function contentMatcher(body, target, paramName, valueClass, hash) {
|
|
|
396
469
|
return {
|
|
397
470
|
match: {
|
|
398
471
|
type: 'contains',
|
|
399
|
-
value: buildContextualTemplate(body, target, paramName),
|
|
472
|
+
value: buildContextualTemplate(body, target, paramName, valueClass),
|
|
400
473
|
},
|
|
401
474
|
redaction,
|
|
402
475
|
paramValues: { [paramName]: target },
|
|
@@ -421,12 +494,47 @@ function contentMatcher(body, target, paramName, valueClass, hash) {
|
|
|
421
494
|
* must CONTAIN the match template (buildContextualTemplate), so the trailing
|
|
422
495
|
* delimiter is kept unless it's whitespace (which the template also drops).
|
|
423
496
|
*/
|
|
497
|
+
/** Indices where `cand` occurs in `body` as a WHOLE match of `valueClass` —
|
|
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
|
+
function wholeTokenIndices(body, cand, valueClass) {
|
|
502
|
+
const indices = [];
|
|
503
|
+
let re;
|
|
504
|
+
try {
|
|
505
|
+
re = new RegExp(valueClass, 'g');
|
|
506
|
+
}
|
|
507
|
+
catch {
|
|
508
|
+
return indices;
|
|
509
|
+
}
|
|
510
|
+
let m;
|
|
511
|
+
while ((m = re.exec(body))) {
|
|
512
|
+
if (m[0] === cand) {
|
|
513
|
+
indices.push(m.index);
|
|
514
|
+
}
|
|
515
|
+
if (re.lastIndex === m.index) {
|
|
516
|
+
re.lastIndex++;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
return indices;
|
|
520
|
+
}
|
|
424
521
|
function buildContentRegex(body, target, paramName, valueClass) {
|
|
522
|
+
const structural = inferValueClass(target) !== undefined;
|
|
425
523
|
for (const cand of normalizeCandidates(target)) {
|
|
426
|
-
const
|
|
524
|
+
const occurrences = structural
|
|
525
|
+
? wholeTokenIndices(body, cand, valueClass)
|
|
526
|
+
: undefined;
|
|
527
|
+
const idx = occurrences ? occurrences[0] ?? -1 : body.indexOf(cand);
|
|
427
528
|
if (idx < 0) {
|
|
428
529
|
continue;
|
|
429
530
|
}
|
|
531
|
+
if (occurrences && occurrences.length > 1
|
|
532
|
+
&& cand.replace(/[^A-Za-z0-9]/g, '').length < 4) {
|
|
533
|
+
throw new Error(`target value "${target}" occurs ${occurrences.length} times as a `
|
|
534
|
+
+ 'standalone token with no JSON key or HTML anchor to '
|
|
535
|
+
+ "disambiguate — capture a request where it's unique, or use a "
|
|
536
|
+
+ 'longer/more specific target.');
|
|
537
|
+
}
|
|
430
538
|
const endIdx = idx + cand.length;
|
|
431
539
|
const rawRight = findRightContext(body, endIdx);
|
|
432
540
|
const right = /^\s/.test(rawRight) ? '' : escapeRegexFlexWs(rawRight);
|
|
@@ -456,8 +564,9 @@ function buildContentRegex(body, target, paramName, valueClass) {
|
|
|
456
564
|
* literal contains can't flex whitespace); stable context (a JSON key, an HTML
|
|
457
565
|
* opening tag) is kept so the match anchors on it.
|
|
458
566
|
*/
|
|
459
|
-
function buildContextualTemplate(body, target, paramName) {
|
|
460
|
-
const idx = body
|
|
567
|
+
function buildContextualTemplate(body, target, paramName, valueClass) {
|
|
568
|
+
const idx = wholeTokenIndices(body, target, valueClass)[0]
|
|
569
|
+
?? body.indexOf(target);
|
|
461
570
|
if (idx < 0) {
|
|
462
571
|
return `{{${paramName}}}`;
|
|
463
572
|
}
|
|
@@ -536,7 +645,8 @@ function inferValueClass(target) {
|
|
|
536
645
|
if (/^[0-9]+$/.test(target)) {
|
|
537
646
|
return '\\d+';
|
|
538
647
|
}
|
|
539
|
-
// Numbers with thousands separators / decimals,
|
|
648
|
+
// Numbers with thousands separators / decimals, for example, "1,908" or
|
|
649
|
+
// "12,345.6".
|
|
540
650
|
if (/^[0-9][0-9,.]*[0-9]$/.test(target)) {
|
|
541
651
|
return '[0-9,.]+';
|
|
542
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) —
|
|
22
|
-
* naming-convention classifier reclaim-inapp-sdk and reclaim-portal use
|
|
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
|
package/lib/provider/secrets.js
CHANGED
|
@@ -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) —
|
|
112
|
-
* naming-convention classifier reclaim-inapp-sdk and reclaim-portal use
|
|
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 (
|
|
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 (
|
|
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. */
|
|
@@ -54,15 +54,18 @@ export interface CreateVersionOptions {
|
|
|
54
54
|
jsUserScripts?: string;
|
|
55
55
|
/** Requests the injection script may fire, as `RequestSelectionTemplate`s. */
|
|
56
56
|
allowedJsRequests?: AllowedJsRequest[];
|
|
57
|
-
/** In-app interception options, mapped
|
|
58
|
-
* `webSettings.clientOptions.inapp.interceptorOptions
|
|
59
|
-
* is
|
|
60
|
-
* `
|
|
61
|
-
* `
|
|
57
|
+
/** In-app interception options, mapped to
|
|
58
|
+
* `webSettings.clientOptions.inapp.interceptorOptions` — ONLY when this
|
|
59
|
+
* is explicitly passed (builder's schema has no `NONE` interceptorType;
|
|
60
|
+
* omitting `inapp` entirely IS the "no interception" signal, so omitting
|
|
61
|
+
* this must omit `inapp`, not default it to HAWKEYE). When it IS passed,
|
|
62
|
+
* `isDocumentRequestReplayEnabled` defaults to `false` (disabled) unless
|
|
63
|
+
* explicitly overridden here — see `providerToCreateVersionRequest`. Only
|
|
64
|
+
* meaningful for `HAWKEYE`/`MSWJS`; inert for `CDP`. */
|
|
62
65
|
interceptorOptions?: {
|
|
63
66
|
interceptorType: 'HAWKEYE' | 'MSWJS' | 'CDP';
|
|
64
|
-
isDocumentRequestReplayEnabled?: boolean;
|
|
65
67
|
interceptorSettings?: string;
|
|
68
|
+
isDocumentRequestReplayEnabled?: boolean;
|
|
66
69
|
};
|
|
67
70
|
}
|
|
68
71
|
/**
|
|
@@ -130,14 +130,26 @@ export function providerToCreateVersionRequest(providers, initialUrl, opts = {})
|
|
|
130
130
|
geoLocation: pickGeoLocation(providers),
|
|
131
131
|
clientOptions: {
|
|
132
132
|
portal: { useProxy: true },
|
|
133
|
+
// Builder's schema has NO "NONE" interceptorType — the real
|
|
134
|
+
// no-interception signal is OMITTING `inapp` entirely (it's not
|
|
135
|
+
// `required` on `InAppClientOptions`). So `inapp` is only built at
|
|
136
|
+
// ALL when the author explicitly opts into a real interceptorType;
|
|
137
|
+
// omitting `interceptorOptions` must omit `inapp` too, or every
|
|
138
|
+
// standard (non-interception) provider would incorrectly publish
|
|
139
|
+
// as HAWKEYE. Within that block, `isDocumentRequestReplayEnabled`
|
|
140
|
+
// defaults to disabled but is author-overridable — see
|
|
141
|
+
// `CreateVersionOptions.interceptorOptions`'s doc comment.
|
|
133
142
|
...(interceptorOptions
|
|
134
143
|
? {
|
|
135
144
|
inapp: {
|
|
136
145
|
interceptorOptions: {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
146
|
+
interceptorType: interceptorOptions.interceptorType,
|
|
147
|
+
isDocumentRequestReplayEnabled: interceptorOptions.isDocumentRequestReplayEnabled ?? false,
|
|
148
|
+
...(interceptorOptions.interceptorSettings
|
|
149
|
+
? {
|
|
150
|
+
interceptorSettings: interceptorOptions.interceptorSettings,
|
|
151
|
+
}
|
|
152
|
+
: {}),
|
|
141
153
|
},
|
|
142
154
|
},
|
|
143
155
|
}
|
package/lib/tools/registry.d.ts
CHANGED
|
@@ -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 (
|
|
34
|
-
* MCP adds its `captureId` session handle + the credential-aware
|
|
35
|
-
* contract). Args parse loosely, so the extra fields flow through
|
|
36
|
-
* handler (its backend resolver reads them); the base tool stays
|
|
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;
|
package/lib/tools/registry.js
CHANGED
|
@@ -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 (
|
|
48
|
-
* MCP adds its `captureId` session handle + the credential-aware
|
|
49
|
-
* contract). Args parse loosely, so the extra fields flow through
|
|
50
|
-
* handler (its backend resolver reads them); the base tool stays
|
|
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.
|
|
5
|
-
"description": "Reclaim Protocol
|
|
4
|
+
"version": "0.1.24",
|
|
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,10 +83,11 @@
|
|
|
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.
|
|
86
|
+
"@reclaimprotocol/client": "0.1.24",
|
|
87
87
|
"@reclaimprotocol/tls": "^0.1.3",
|
|
88
88
|
"chrome-remote-interface": "^0.33.0",
|
|
89
89
|
"domhandler": "^6.0.0",
|
|
90
|
+
"openid-client": "^6.8.5",
|
|
90
91
|
"parse5": "^8.0.0",
|
|
91
92
|
"parse5-htmlparser2-tree-adapter": "^8.0.0",
|
|
92
93
|
"pino": "^9.14.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
|
|
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
|
|
13
|
-
one reachable from the internet, deploy this there and use
|
|
14
|
-
instead — no local tunnel needed.
|
|
15
|
-
|
|
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".
|