@reclaimprotocol/agent 0.1.1 → 0.1.3
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/lib/cdp/launcher.js +4 -3
- package/lib/consts.d.ts +68 -0
- package/lib/consts.js +134 -0
- package/lib/logger.js +2 -1
- package/lib/mcp/api/operations.gen.d.ts +11 -7
- package/lib/mcp/api/operations.gen.js +356 -199
- package/lib/mcp/server.d.ts +0 -6
- package/lib/mcp/server.js +0 -30
- package/lib/mcp/start.js +7 -7
- package/lib/mcp/tools/agent/build.d.ts +5 -9
- package/lib/mcp/tools/agent/build.js +5 -9
- package/lib/mcp/tools/agent/proof.js +2 -1
- package/lib/mcp/tools/agent/publish.js +166 -77
- package/lib/mcp/tools/authenticate/decrypt-key.d.ts +10 -23
- package/lib/mcp/tools/authenticate/decrypt-key.js +23 -26
- package/lib/mcp/tools/authenticate/eth-key.d.ts +0 -7
- package/lib/mcp/tools/authenticate/eth-key.js +5 -19
- package/lib/mcp/tools/authenticate/issue.js +1 -1
- package/lib/mcp/tools/verification/build.d.ts +1 -1
- package/lib/mcp/tools/verification/build.js +1 -1
- package/lib/mcp/tools/verification/result.d.ts +4 -5
- package/lib/mcp/tools/verification/result.js +17 -17
- package/lib/old/flag.d.ts +4 -1
- package/lib/old/flag.js +4 -3
- package/lib/old/identity.d.ts +0 -10
- package/lib/old/identity.js +8 -7
- package/lib/old/login-cdp.d.ts +0 -3
- package/lib/old/login-cdp.js +2 -4
- package/lib/old/to-register.d.ts +5 -3
- package/lib/old/to-register.js +20 -15
- package/lib/old/tools/authenticate.js +2 -1
- package/lib/old/tools/publish.js +97 -13
- package/lib/paths.js +2 -2
- package/lib/proof/attestor.js +2 -1
- package/lib/proof/proof-core.js +13 -2
- package/lib/provider/injection-doc.d.ts +1 -0
- package/lib/provider/injection-doc.js +17 -0
- package/lib/provider/match-redaction-warning.d.ts +25 -0
- package/lib/provider/match-redaction-warning.js +35 -0
- package/lib/provider/schema.d.ts +16 -0
- package/lib/provider/schema.js +11 -1
- package/lib/provider/to-version.d.ts +59 -7
- package/lib/provider/to-version.js +79 -28
- package/package.json +1 -1
- package/skill/builder-mode.md +8 -2
- package/skill/injection-api.md +42 -0
- package/skill/old-mode.md +4 -2
- package/src/cdp/launcher.ts +5 -4
- package/src/consts.ts +164 -0
- package/src/logger.ts +2 -1
- package/src/mcp/api/operations.gen.ts +358 -203
- package/src/mcp/server.ts +0 -46
- package/src/mcp/start.ts +13 -7
- package/src/mcp/tools/agent/build.ts +5 -9
- package/src/mcp/tools/agent/proof.ts +2 -1
- package/src/mcp/tools/agent/publish.ts +212 -94
- package/src/mcp/tools/authenticate/decrypt-key.ts +24 -29
- package/src/mcp/tools/authenticate/eth-key.ts +5 -25
- package/src/mcp/tools/authenticate/issue.ts +1 -1
- package/src/mcp/tools/verification/build.ts +1 -1
- package/src/mcp/tools/verification/result.ts +16 -17
- package/src/old/flag.ts +4 -3
- package/src/old/identity.ts +8 -7
- package/src/old/login-cdp.ts +2 -5
- package/src/old/to-register.ts +22 -16
- package/src/old/tools/authenticate.ts +2 -1
- package/src/old/tools/publish.ts +124 -20
- package/src/paths.ts +2 -2
- package/src/proof/attestor.ts +2 -1
- package/src/proof/proof-core.ts +13 -3
- package/src/provider/injection-doc.ts +21 -0
- package/src/provider/match-redaction-warning.ts +53 -0
- package/src/provider/schema.ts +23 -0
- package/src/provider/to-version.ts +137 -31
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Non-blocking advisory for a request whose `responseMatches` count is less
|
|
3
|
+
* than its `responseRedactions` count.
|
|
4
|
+
*
|
|
5
|
+
* Some verification clients (notably the InApp SDK) pair the two arrays by
|
|
6
|
+
* index and may not behave correctly when there are fewer matches than
|
|
7
|
+
* redactions. This is NOT enforced — the author keeps full control over what
|
|
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.
|
|
11
|
+
*/
|
|
12
|
+
/** Build advisory warnings for any request with matchCount < redactionCount.
|
|
13
|
+
* Returns an empty array when everything is fine. */
|
|
14
|
+
export function matchRedactionWarnings(requests) {
|
|
15
|
+
const warnings = [];
|
|
16
|
+
for (const req of requests) {
|
|
17
|
+
if (req.matchCount < req.redactionCount) {
|
|
18
|
+
warnings.push(`${req.label}: ${req.matchCount} response match(es) but ` +
|
|
19
|
+
`${req.redactionCount} response redaction(s). Some verification ` +
|
|
20
|
+
'clients (e.g. the InApp SDK) may not work correctly when ' +
|
|
21
|
+
'responseMatches is shorter than responseRedactions — they pair the ' +
|
|
22
|
+
'two by index. This is allowed; add a functional match per redaction ' +
|
|
23
|
+
'if you hit verification issues.');
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return warnings;
|
|
27
|
+
}
|
|
28
|
+
/** Counts for a single drafted request shape (provider or injected request). */
|
|
29
|
+
export function countsFor(label, req) {
|
|
30
|
+
return {
|
|
31
|
+
label,
|
|
32
|
+
matchCount: req.responseMatches?.length ?? 0,
|
|
33
|
+
redactionCount: req.responseRedactions?.length ?? 0,
|
|
34
|
+
};
|
|
35
|
+
}
|
package/lib/provider/schema.d.ts
CHANGED
|
@@ -61,7 +61,23 @@ export interface ReclaimProvider {
|
|
|
61
61
|
responseRedactions?: ResponseRedaction[];
|
|
62
62
|
/** Public param refs (templated into URL/body), populated by draft layer. */
|
|
63
63
|
paramValues?: Record<string, string>;
|
|
64
|
+
/** Redaction mode for the transcript (attestor default: 'key-update'). */
|
|
65
|
+
writeRedactionMode?: HttpParams['writeRedactionMode'];
|
|
66
|
+
/** TLS client options applied to the attestor tunnel. */
|
|
67
|
+
additionalClientOptions?: HttpParams['additionalClientOptions'];
|
|
68
|
+
/** Proxy egress: 2-letter ISO country or `{{param}}`. Blank/omitted ⇒
|
|
69
|
+
* `{{DYNAMIC_GEO}}` on publish (resolved to the verifying user's country).
|
|
70
|
+
*/
|
|
71
|
+
geoLocation?: HttpParams['geoLocation'];
|
|
64
72
|
}
|
|
73
|
+
/** Egress placeholder resolved to the verifying user's country at
|
|
74
|
+
* verification time — the default when a provider declares no explicit egress.
|
|
75
|
+
*/
|
|
76
|
+
export declare const DYNAMIC_GEO = "{{DYNAMIC_GEO}}";
|
|
77
|
+
/** `geoLocation` must never be blank. Trims the input and treats an empty
|
|
78
|
+
* string as unset so callers apply their own default (publish → `DYNAMIC_GEO`;
|
|
79
|
+
* a local proof omits it and uses the tester's egress). */
|
|
80
|
+
export declare function normalizeGeoLocation(value: string | undefined): string | undefined;
|
|
65
81
|
/** A reference to a secret param. Raw value lives only in server memory. */
|
|
66
82
|
export interface SecretParamRef {
|
|
67
83
|
name: string;
|
package/lib/provider/schema.js
CHANGED
|
@@ -1 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
/** Egress placeholder resolved to the verifying user's country at
|
|
2
|
+
* verification time — the default when a provider declares no explicit egress.
|
|
3
|
+
*/
|
|
4
|
+
export const DYNAMIC_GEO = '{{DYNAMIC_GEO}}';
|
|
5
|
+
/** `geoLocation` must never be blank. Trims the input and treats an empty
|
|
6
|
+
* string as unset so callers apply their own default (publish → `DYNAMIC_GEO`;
|
|
7
|
+
* a local proof omits it and uses the tester's egress). */
|
|
8
|
+
export function normalizeGeoLocation(value) {
|
|
9
|
+
const trimmed = value?.trim();
|
|
10
|
+
return trimmed ? trimmed : undefined;
|
|
11
|
+
}
|
|
@@ -1,15 +1,66 @@
|
|
|
1
1
|
import type { ReclaimClient } from '@reclaimprotocol/client';
|
|
2
2
|
import type { CreateProviderVersionRequest, ProviderVersionRef } from '@reclaimprotocol/client/openapi';
|
|
3
|
-
import type { ReclaimProvider } from './schema.ts';
|
|
3
|
+
import type { HttpMethod, ReclaimProvider, ResponseMatch, ResponseRedaction } from './schema.ts';
|
|
4
|
+
/** One entry of the version's `requests[]` (a `RequestSelection`), derived
|
|
5
|
+
* from the openapi type so it can't drift from what the server accepts. */
|
|
6
|
+
type RequestSelection = CreateProviderVersionRequest['requests'][number];
|
|
7
|
+
/** The subset of a draft a request selection is built from — the only fields
|
|
8
|
+
* `providerToRequestSelection` reads. A full `ReclaimProvider` satisfies it,
|
|
9
|
+
* as does a lighter injected-request draft (no `name`/`paramValues`). */
|
|
10
|
+
export interface DraftRequest {
|
|
11
|
+
url: string;
|
|
12
|
+
method: HttpMethod;
|
|
13
|
+
body?: string;
|
|
14
|
+
writeRedactionMode?: ReclaimProvider['writeRedactionMode'];
|
|
15
|
+
additionalClientOptions?: ReclaimProvider['additionalClientOptions'];
|
|
16
|
+
responseMatches?: ResponseMatch[];
|
|
17
|
+
responseRedactions?: ResponseRedaction[];
|
|
18
|
+
}
|
|
19
|
+
/** A request the page's injection script is allowed to fire, plus the
|
|
20
|
+
* pagination/optionality flags of `RequestSelectionTemplate`. Reuses the
|
|
21
|
+
* shared `DraftRequest` shape — no provider `name`/`paramValues` required. */
|
|
22
|
+
export interface AllowedJsRequest extends DraftRequest {
|
|
23
|
+
/** Allow the template to match many requests (e.g. pagination). Default
|
|
24
|
+
* true. */
|
|
25
|
+
multiple?: boolean;
|
|
26
|
+
/** Fail validation if no request matches this template. Default true. */
|
|
27
|
+
required?: boolean;
|
|
28
|
+
}
|
|
29
|
+
/** Optional extras for a create-version body beyond the request list. */
|
|
30
|
+
export interface CreateVersionOptions {
|
|
31
|
+
notes?: string;
|
|
32
|
+
version?: ProviderVersionRef;
|
|
33
|
+
/** JS injected before every page load (`webSettings.jsUserScripts`). */
|
|
34
|
+
jsUserScripts?: string;
|
|
35
|
+
/** Requests the injection script may fire, as `RequestSelectionTemplate`s. */
|
|
36
|
+
allowedJsRequests?: AllowedJsRequest[];
|
|
37
|
+
}
|
|
4
38
|
/**
|
|
5
39
|
* Derive the version's `requiredContext` JSON Schema from the
|
|
6
|
-
* `{{context.<name>}}` placeholders
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
40
|
+
* `{{context.<name>}}` placeholders across EVERY request's url/body (plus any
|
|
41
|
+
* injected requests and the injection script) — mechanical, so a published
|
|
42
|
+
* recipe can never reference a context value the Builder doesn't require.
|
|
43
|
+
* Returns undefined when nothing references context (the field is optional).
|
|
44
|
+
* Authors can refine types/descriptions in the version editor.
|
|
10
45
|
*/
|
|
11
|
-
export declare function deriveRequiredContext(
|
|
12
|
-
|
|
46
|
+
export declare function deriveRequiredContext(providers: ReclaimProvider[], injection?: {
|
|
47
|
+
allowedJsRequests?: AllowedJsRequest[];
|
|
48
|
+
jsUserScripts?: string;
|
|
49
|
+
}): CreateProviderVersionRequest['requiredContext'];
|
|
50
|
+
/**
|
|
51
|
+
* Translate one drafted request into a `RequestSelection`. Shared by the
|
|
52
|
+
* top-level `requests` and by `allowedJsRequests`.
|
|
53
|
+
*
|
|
54
|
+
* `responseMatches`/`responseRedactions` are INDEPENDENT parallel arrays,
|
|
55
|
+
* isomorphic to the attestor's `ProviderParams<'http'>` (no nested fold).
|
|
56
|
+
* `responseRedactions` decide which portions of the response are revealed;
|
|
57
|
+
* `responseMatches` then run against the union of the revealed content. Neither
|
|
58
|
+
* depends on the other's length or order, so both pass through untouched.
|
|
59
|
+
* (The old-devtools translator has its own opt-in index-pairing concern; the
|
|
60
|
+
* builder deliberately does not.)
|
|
61
|
+
*/
|
|
62
|
+
export declare function providerToRequestSelection(provider: DraftRequest): RequestSelection;
|
|
63
|
+
export declare function providerToCreateVersionRequest(providers: ReclaimProvider[], initialUrl: string, opts?: CreateVersionOptions): CreateProviderVersionRequest;
|
|
13
64
|
/**
|
|
14
65
|
* Fetch the current versions for a provider and return the next version above
|
|
15
66
|
* the highest existing one (bumping the patch number), or 1.0.0 when the
|
|
@@ -17,3 +68,4 @@ export declare function providerToCreateVersionRequest(provider: ReclaimProvider
|
|
|
17
68
|
* resetting to 1.0.0 would re-publish a v3 provider as v1.0.0.
|
|
18
69
|
*/
|
|
19
70
|
export declare function nextVersion(client: ReclaimClient, providerId: string): Promise<ProviderVersionRef>;
|
|
71
|
+
export {};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { DYNAMIC_GEO, normalizeGeoLocation } from "./schema.js";
|
|
1
2
|
/**
|
|
2
3
|
* Translate a drafted ReclaimProvider into the body for
|
|
3
4
|
* POST /providers/{providerId}/versions.
|
|
@@ -16,13 +17,21 @@
|
|
|
16
17
|
const CONTEXT_PARAM_RE = /\{\{context\.([A-Za-z_][A-Za-z0-9_]*)\}\}/g;
|
|
17
18
|
/**
|
|
18
19
|
* Derive the version's `requiredContext` JSON Schema from the
|
|
19
|
-
* `{{context.<name>}}` placeholders
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
20
|
+
* `{{context.<name>}}` placeholders across EVERY request's url/body (plus any
|
|
21
|
+
* injected requests and the injection script) — mechanical, so a published
|
|
22
|
+
* recipe can never reference a context value the Builder doesn't require.
|
|
23
|
+
* Returns undefined when nothing references context (the field is optional).
|
|
24
|
+
* Authors can refine types/descriptions in the version editor.
|
|
23
25
|
*/
|
|
24
|
-
export function deriveRequiredContext(
|
|
25
|
-
const
|
|
26
|
+
export function deriveRequiredContext(providers, injection = {}) {
|
|
27
|
+
const parts = [];
|
|
28
|
+
for (const p of [...providers, ...(injection.allowedJsRequests ?? [])]) {
|
|
29
|
+
parts.push(p.url, p.body ?? '');
|
|
30
|
+
}
|
|
31
|
+
if (injection.jsUserScripts) {
|
|
32
|
+
parts.push(injection.jsUserScripts);
|
|
33
|
+
}
|
|
34
|
+
const haystack = parts.join('\n');
|
|
26
35
|
const names = new Set();
|
|
27
36
|
for (const m of haystack.matchAll(CONTEXT_PARAM_RE)) {
|
|
28
37
|
names.add(m[1]);
|
|
@@ -36,36 +45,78 @@ export function deriveRequiredContext(provider) {
|
|
|
36
45
|
}
|
|
37
46
|
return { type: 'object', required: [...names], properties };
|
|
38
47
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
/**
|
|
49
|
+
* Translate one drafted request into a `RequestSelection`. Shared by the
|
|
50
|
+
* top-level `requests` and by `allowedJsRequests`.
|
|
51
|
+
*
|
|
52
|
+
* `responseMatches`/`responseRedactions` are INDEPENDENT parallel arrays,
|
|
53
|
+
* isomorphic to the attestor's `ProviderParams<'http'>` (no nested fold).
|
|
54
|
+
* `responseRedactions` decide which portions of the response are revealed;
|
|
55
|
+
* `responseMatches` then run against the union of the revealed content. Neither
|
|
56
|
+
* depends on the other's length or order, so both pass through untouched.
|
|
57
|
+
* (The old-devtools translator has its own opt-in index-pairing concern; the
|
|
58
|
+
* builder deliberately does not.)
|
|
59
|
+
*/
|
|
60
|
+
export function providerToRequestSelection(provider) {
|
|
61
|
+
const responseMatches = (provider.responseMatches ?? []).map((match) => ({
|
|
62
|
+
type: match.type,
|
|
63
|
+
value: match.value,
|
|
64
|
+
isOptional: false,
|
|
65
|
+
}));
|
|
66
|
+
const responseRedactions = provider.responseRedactions ?? [];
|
|
67
|
+
return {
|
|
68
|
+
url: provider.url,
|
|
69
|
+
method: provider.method,
|
|
70
|
+
credentials: 'include',
|
|
71
|
+
...(provider.body ? { requestBodyTemplate: provider.body } : {}),
|
|
72
|
+
...(provider.writeRedactionMode
|
|
73
|
+
? { writeRedactionMode: provider.writeRedactionMode }
|
|
74
|
+
: {}),
|
|
75
|
+
...(provider.additionalClientOptions
|
|
76
|
+
? { additionalClientOptions: provider.additionalClientOptions }
|
|
77
|
+
: {}),
|
|
78
|
+
...(responseMatches.length > 0 ? { responseMatches } : {}),
|
|
79
|
+
...(responseRedactions.length > 0 ? { responseRedactions } : {}),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
/** First provider that declares an explicit (non-default) egress wins; if none
|
|
83
|
+
* do, fall back to DYNAMIC_GEO (resolved to the verifying user's country). */
|
|
84
|
+
function pickGeoLocation(providers) {
|
|
85
|
+
for (const provider of providers) {
|
|
86
|
+
const geo = normalizeGeoLocation(provider.geoLocation);
|
|
87
|
+
if (geo) {
|
|
88
|
+
return geo;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return DYNAMIC_GEO;
|
|
92
|
+
}
|
|
93
|
+
export function providerToCreateVersionRequest(providers, initialUrl, opts = {}) {
|
|
94
|
+
const { notes, version, jsUserScripts, allowedJsRequests } = opts;
|
|
95
|
+
const requiredContext = deriveRequiredContext(providers, {
|
|
96
|
+
allowedJsRequests,
|
|
97
|
+
jsUserScripts,
|
|
50
98
|
});
|
|
99
|
+
// Each injected request carries its template flags (default true/true per
|
|
100
|
+
// RequestSelectionTemplate) on top of the shared request shape.
|
|
101
|
+
const allowedJs = (allowedJsRequests ?? []).map((req) => ({
|
|
102
|
+
...providerToRequestSelection(req),
|
|
103
|
+
multiple: req.multiple ?? true,
|
|
104
|
+
required: req.required ?? true,
|
|
105
|
+
}));
|
|
106
|
+
const script = jsUserScripts?.trim();
|
|
51
107
|
return {
|
|
52
108
|
version: version ?? { major: 1, minor: 0, patch: 0 },
|
|
53
109
|
initialUrl,
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
method: provider.method,
|
|
58
|
-
credentials: 'include',
|
|
59
|
-
...(provider.body ? { requestBodyTemplate: provider.body } : {}),
|
|
60
|
-
...(matches.length > 0 ? { matches } : {}),
|
|
61
|
-
},
|
|
62
|
-
],
|
|
63
|
-
allowedJsRequests: [],
|
|
110
|
+
// Every drafted provider becomes one request — the recipe runs them all.
|
|
111
|
+
requests: providers.map(providerToRequestSelection),
|
|
112
|
+
allowedJsRequests: allowedJs,
|
|
64
113
|
// Mirror the builder openapi defaults explicitly — the server does not
|
|
65
114
|
// apply schema defaults to request bodies.
|
|
66
115
|
webSettings: {
|
|
67
|
-
geoLocation:
|
|
116
|
+
geoLocation: pickGeoLocation(providers),
|
|
68
117
|
clientOptions: { portal: { useProxy: true } },
|
|
118
|
+
// Only set when non-empty; an empty string would store a no-op script.
|
|
119
|
+
...(script ? { jsUserScripts: script } : {}),
|
|
69
120
|
},
|
|
70
121
|
// Derived from `{{context.<name>}}` placeholders only — a captured
|
|
71
122
|
// provider's `paramValues` are extracted response parameters, not
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reclaimprotocol/agent",
|
|
3
3
|
"title": "Reclaim Agent",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.3",
|
|
5
5
|
"description": "Reclaim Protocol agent — SDK, CLI, and MCP server in one package. SDK + CLI talk to the builder backend; MCP server adds Chrome CDP capture, provider synthesis, replay, and zkTLS proof.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "./lib/index.js",
|
package/skill/builder-mode.md
CHANGED
|
@@ -2,8 +2,14 @@ This server targets the Reclaim BUILDER backend. It exposes three tool layers: g
|
|
|
2
2
|
|
|
3
3
|
**Authentication:** try `get_me` first. On 401, `authenticate({})` opens a device-pairing session — give the user the `attachUrl`, then poll `authenticate({ code })` until LINKED. Tokens are cached ~2 days.
|
|
4
4
|
|
|
5
|
-
**Provider-authoring flow:** `attach_chrome` → `start_capture` → drive the page (`navigate`, `wait_for_page`) → inspect (`list_requests`, `get_request`, `find_requests_containing`) → `propose_provider` → `replay_request` → `run_proof` → `create_provider_version_from_capture` (lands on the draft branch) → `publish_provider_version` (PRIVATE) or `submit_provider_version_for_review` (PUBLIC).
|
|
5
|
+
**Provider-authoring flow:** `attach_chrome` → `start_capture` → drive the page (`navigate`, `wait_for_page`) → inspect (`list_requests`, `get_request`, `find_requests_containing`) → `propose_provider` → `replay_request` → `run_proof` → resolve a `providerId` (**Reuse before create**, below) → `create_provider_version_from_capture` (lands on the draft branch) → `publish_provider_version` (PRIVATE) or `submit_provider_version_for_review` (PUBLIC).
|
|
6
|
+
|
|
7
|
+
**Reuse before create (avoid duplicates):** `create_provider_version_from_capture` requires a `providerId`. Before minting a new one with `create_provider`, search your existing providers with `providers` (by domain / data point) — a site you've already built will be there. If one matches, pass its `providerId` so the capture lands as a NEW VERSION on that provider. Only `create_provider` (then use its returned id) when none exists. Re-running the flow against `create_provider` each time is what stacks duplicate providers in "My providers".
|
|
8
|
+
|
|
9
|
+
**Multiple requests & login-gated data:** one version can carry many requests — `propose_provider` + `run_proof` each target request individually, then publish them together via `create_provider_version_from_capture({ providers: [draftA, draftB, …] })` (every entry is required at verification; the single-`provider` form still works for the one-request case). For data gated behind login/navigation, supply `jsUserScripts` — plain page JavaScript if needed that drives login/waits for user to login/waits for user to do multifactor auth/navigation with standard browser APIs (`window.location`, clicking elements, submitting forms) so the target request fires (page requests are intercepted automatically; `window.Reclaim` is only for rare edge cases) — and/or `allowedJsRequests` (extra requests the script may fire, e.g. pagination). NOTE: the builder verification client installs `jsUserScripts` to run before every page load (before navigating to `initialUrl`), matching the old-devtool `customInjection`; `window.Reclaim` itself is not yet exposed there, so prefer plain browser JS.
|
|
6
10
|
|
|
7
11
|
**Templating:** bare `{{name}}` placeholders are provider params (values extracted from the response by the attestor). Values the CONSUMER must supply at verification time (e.g. a username in the URL path) are templated `{{context.<name>}}` and declared on the version as `requiredContext` — a JSON Schema set via `update_provider_version` while the version is on draft. The Builder validates every session-create's `context` against it (400 on mismatch; extra context fields are always allowed). `requiredContext` is optional — skip it when the recipe has no `{{context.*}}` placeholders. At verification time, requests are matched by exact origin+path: `{{context.<name>}}` segments match the consumer-sent value only, bare `{{name}}` values are extracted from the live traffic (plain-text alignment — works for JSON, XML, HTML, form-encoded, any text). Extract selectors apply client-side in spec order xPath → jsonPath → regex, each to the prior selection, so only the first selector constrains the full response; responses that cannot satisfy it (or miss a required match) are skipped. `propose_provider` returns advisory `verificationWarnings` for author-only decisions — surface them to the dev before publishing.
|
|
8
12
|
|
|
9
|
-
**
|
|
13
|
+
**Discover & resolve (find providers for the user's app):** when a dev wants to add verification but doesn't know which providers to use, run the same flow the Discover UI does. Ask which website their users prove data from, then `discover_data_points({ domain })` to list what's verifiable there. Confirm the data points with the dev, then `discover_resolve({ domain, dataPoints })` → the smallest provider set (`providers`, greedy combine), the `requiredContext` inputs to collect from the dev, and any `uncovered` points (author those with the provider-authoring flow above, then re-resolve). Create the session with `create_verification_session({ providers: [{ providerId }, …], context, verificationClientUrl })`, passing EVERY resolved provider id and the collected context. `merge.mergeable` only signals the set could collapse into one provider — you still pass them all; the Builder runs them in one session.
|
|
14
|
+
|
|
15
|
+
**Verification sessions:** consumers authenticate with a per-org token (`rorg_…`, issued in the dashboard via `POST /orgs/{orgId}/token`) and create sessions via `POST /verifications/sessions`. Results are ECIES-encrypted (secp256k1 + AES-256-GCM) to the org's eth public key when its `canEncryptResult` is enabled — set via `PUT /orgs/{orgId}/keypair` with an eth public key — and delivered to the org's callback subscriptions (plaintext otherwise). Use the org/session tools (`get_verification_session`, `list_verification_events`, `list_org_verification_sessions`) to troubleshoot.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Page injection: `jsUserScripts` / `customInjection`
|
|
2
|
+
|
|
3
|
+
A provider can inject **plain JavaScript** that runs in the page before every
|
|
4
|
+
page load. Builder stores it as `webSettings.jsUserScripts`; old-devtools stores
|
|
5
|
+
it as `customInjection` (identical semantics). It is ordinary browser
|
|
6
|
+
JavaScript — its job is to drive the page so the target API request(s) fire
|
|
7
|
+
during a verification.
|
|
8
|
+
|
|
9
|
+
## What the script is for
|
|
10
|
+
|
|
11
|
+
The verification client already **intercepts the page's network requests
|
|
12
|
+
automatically** — the script does NOT necessarily ned to capture, forward, or "claim" them.
|
|
13
|
+
Its primary job is to get the page into the state where those requests happen, using
|
|
14
|
+
standard browser APIs:
|
|
15
|
+
|
|
16
|
+
- **Navigate** with `window.location.href = '…'` / `location.assign(…)` / the
|
|
17
|
+
history API.
|
|
18
|
+
- **Interact**: select an element and `.click()` it, submit a form, dispatch
|
|
19
|
+
events.
|
|
20
|
+
- **Wait**: poll the DOM until login/mfa completes or content loads, then trigger
|
|
21
|
+
the next step.
|
|
22
|
+
|
|
23
|
+
Write it like any page script. It runs before EVERY page load, so keep it
|
|
24
|
+
idempotent, cheap, and guarded against running at the wrong time.
|
|
25
|
+
|
|
26
|
+
## `window.Reclaim` — rarely needed, use sparingly
|
|
27
|
+
|
|
28
|
+
`window.Reclaim` is an OPTIONAL bridge for a few edge cases only. Most providers
|
|
29
|
+
never touch it. In particular it does **not** do navigation — use the standard
|
|
30
|
+
browser APIs above for that, in plain `jsUserScripts` / `customInjection`.
|
|
31
|
+
|
|
32
|
+
Do not reach for `window.Reclaim` by default. Only use it when an edge case
|
|
33
|
+
genuinely requires it. Its full, authoritative API (every method, with exact
|
|
34
|
+
signatures and semantics) is documented in
|
|
35
|
+
`https://github.com/reclaimprotocol/provider-web-script-dev/blob/main/src/support/env.d.ts` — consult that file rather
|
|
36
|
+
than guessing.
|
|
37
|
+
|
|
38
|
+
> Both backends run the script: the Builder verification client installs
|
|
39
|
+
> `jsUserScripts` to run before every page load (before navigating to the
|
|
40
|
+
> provider's `initialUrl`), and old-devtools runs `customInjection` the same
|
|
41
|
+
> way. (`window.Reclaim` itself is still only forwarded/available in the cases
|
|
42
|
+
> noted above.)
|
package/skill/old-mode.md
CHANGED
|
@@ -4,10 +4,12 @@ This server is running against the LEGACY Reclaim devtools backend (temporary, w
|
|
|
4
4
|
|
|
5
5
|
1. `reclaim_authenticate` — by default opens the dashboard in Chrome, waits for you to sign in, captures the token automatically, and closes the tab (or paste a `token` / pass an eth `ethAddress` to skip the browser). Do this once before publishing or listing.
|
|
6
6
|
2. `attach_chrome` → `start_capture` → drive the page (`navigate`, `wait_for_page`) → inspect (`list_requests`, `get_request`, `find_requests_containing`).
|
|
7
|
-
3. `propose_provider` to draft a provider, `replay_request` to pre-flight, `run_proof` to verify with the attestor. OPRF/`hash` redactions are optional and supported.
|
|
8
|
-
4. `create_provider_version_from_capture` to publish. With NO `providerId` it REGISTERS A NEW provider (re-running creates a duplicate); pass a `providerId` from `get_me_providers` to add a new version to an existing provider instead.
|
|
7
|
+
3. `propose_provider` to draft a provider, `replay_request` to pre-flight, `run_proof` to verify with the attestor. OPRF/`hash` redactions are optional and supported. When the data spans MULTIPLE requests (e.g. a landing call + a details call), propose + `run_proof` EACH request to get one proven draft per request.
|
|
8
|
+
4. `create_provider_version_from_capture` to publish. With NO `providerId` it REGISTERS A NEW provider (re-running creates a duplicate); pass a `providerId` from `get_me_providers` to add a new version to an existing provider instead. For a MULTI-REQUEST provider, pass `providers: [draftA, draftB, …]` (every proven draft) so EACH becomes a parallel `requestData` entry on the one version — passing a single `provider` saves only that one request.
|
|
9
9
|
5. `get_me_providers` lists the providers you own (the only provider-listing tool available in this mode).
|
|
10
10
|
|
|
11
|
+
**Editing / new versions (ownership):** you can only add a version to a provider **you own** — the `providerId` must come from `get_me_providers`. Trying to version a public provider you don't own fails with a permission error; that is expected. To publish your own version of such a site, re-run `create_provider_version_from_capture` **without** `providerId` to register your own copy (a separate provider you own). If versioning a provider you *do* own fails, confirm you authenticated as the account that created it.
|
|
12
|
+
|
|
11
13
|
Only GET/POST endpoints can be captured into providers here. There is no organization, credential-linking, or public-review workflow in this mode.
|
|
12
14
|
|
|
13
15
|
**Templating:** bare `{{name}}` placeholders are provider params (values extracted from the response). Values the CONSUMER must supply at verification time (e.g. a username in the URL path) use the `{{context_<name>}}` form — this backend does not support the dotted `{{context.<name>}}` interpolation the new builder uses.
|
package/src/cdp/launcher.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { type ChildProcess, spawn } from 'node:child_process'
|
|
|
2
2
|
import { existsSync, mkdirSync } from 'node:fs'
|
|
3
3
|
import { homedir, platform } from 'node:os'
|
|
4
4
|
import { join } from 'node:path'
|
|
5
|
+
import { chromePath, localAppData } from '../consts.ts'
|
|
5
6
|
import { reclaimHomeDirPath } from '../paths.ts'
|
|
6
7
|
|
|
7
8
|
export interface LaunchOptions {
|
|
@@ -38,14 +39,14 @@ function browserCandidates(): string[] {
|
|
|
38
39
|
'/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge',
|
|
39
40
|
]
|
|
40
41
|
case 'win32': {
|
|
41
|
-
const
|
|
42
|
-
|
|
42
|
+
const appData =
|
|
43
|
+
localAppData() ?? join(homedir(), 'AppData', 'Local')
|
|
43
44
|
return [
|
|
44
45
|
// System-wide Chrome
|
|
45
46
|
'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
|
|
46
47
|
'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe',
|
|
47
48
|
// Per-user Chrome ("Install for me only")
|
|
48
|
-
join(
|
|
49
|
+
join(appData, 'Google', 'Chrome', 'Application', 'chrome.exe'),
|
|
49
50
|
// Edge — Chromium-based, preinstalled on Win 10/11
|
|
50
51
|
'C:\\Program Files\\Microsoft\\Edge\\Application\\msedge.exe',
|
|
51
52
|
'C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe',
|
|
@@ -87,7 +88,7 @@ export function findChromeBinary(): string {
|
|
|
87
88
|
}
|
|
88
89
|
|
|
89
90
|
export function launchChrome(opts: LaunchOptions): ChildProcess {
|
|
90
|
-
const binary =
|
|
91
|
+
const binary = chromePath() ?? findChromeBinary()
|
|
91
92
|
if(!existsSync(opts.profileDir)) {
|
|
92
93
|
mkdirSync(opts.profileDir, { recursive: true })
|
|
93
94
|
}
|
package/src/consts.ts
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized environment access for `@reclaimprotocol/agent`.
|
|
3
|
+
*
|
|
4
|
+
* This is the ONLY non-test source file under `src/` that reads
|
|
5
|
+
* `process.env`. Every other module imports a getter from here instead of
|
|
6
|
+
* touching `process.env` directly.
|
|
7
|
+
*
|
|
8
|
+
* Almost everything is exposed as a LAZY GETTER FUNCTION rather than an eager
|
|
9
|
+
* `const`, on purpose: several tests mutate env at runtime (mcp-credentials
|
|
10
|
+
* sets/deletes RECLAIM_PRIVATE_KEY / RECLAIM_PRIVATE_KEY_FILE / RECLAIM_HOME;
|
|
11
|
+
* old-devtools sets/deletes USE_OLD_DEVTOOLS), and most of these vars are read
|
|
12
|
+
* at call time anyway. An eager const captured at import time would freeze the
|
|
13
|
+
* value and break those tests. The one eager const that's safe is `LOG_LEVEL`,
|
|
14
|
+
* which is read once at logger construction.
|
|
15
|
+
*
|
|
16
|
+
* Each var's default/fallback literal is co-located with its getter.
|
|
17
|
+
*/
|
|
18
|
+
import { DEFAULT_BASE_URL } from '@reclaimprotocol/client'
|
|
19
|
+
import { homedir } from 'node:os'
|
|
20
|
+
import { join } from 'node:path'
|
|
21
|
+
import { DEFAULT_OLD_API_URL } from './old/client.ts'
|
|
22
|
+
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
// Default / fallback literals (used only as env fallbacks).
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
|
|
27
|
+
/** Public attestor websocket URL — fallback for `RECLAIM_ATTESTOR_URL`. */
|
|
28
|
+
export const FALLBACK_ATTESTOR_URL = 'wss://attestor.reclaimprotocol.org:444/ws'
|
|
29
|
+
|
|
30
|
+
/** The devtools dashboard login page — fallback for `RECLAIM_OLD_LOGIN_URL`. */
|
|
31
|
+
export const DEFAULT_LOGIN_URL = 'https://dev.reclaimprotocol.org'
|
|
32
|
+
|
|
33
|
+
// Re-export the two non-env-fallback defaults so callers can keep importing
|
|
34
|
+
// their URL defaults from a single place if they wish.
|
|
35
|
+
export { DEFAULT_BASE_URL, DEFAULT_OLD_API_URL }
|
|
36
|
+
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
// Logging.
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
|
|
41
|
+
/** Pino log level. Read once at logger construction, so an eager const is
|
|
42
|
+
* safe (no test mutates LOG_LEVEL at runtime). */
|
|
43
|
+
export const LOG_LEVEL = process.env.LOG_LEVEL || 'info'
|
|
44
|
+
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
// Paths.
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
|
|
49
|
+
/** Reclaim cache directory (`~/.reclaim`). `RECLAIM_HOME` overrides it so
|
|
50
|
+
* tests can sandbox writes. */
|
|
51
|
+
export function reclaimHome(): string {
|
|
52
|
+
return process.env.RECLAIM_HOME || join(homedir(), '.reclaim')
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
// Mode flag.
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* `USE_OLD_DEVTOOLS` — single source of truth for which backend the MCP
|
|
61
|
+
* server targets. Defaults to OLD devtools while the builder backend is under
|
|
62
|
+
* development. Builder mode must be opted into EXPLICITLY with
|
|
63
|
+
* `USE_OLD_DEVTOOLS=false`; anything else keeps old mode on.
|
|
64
|
+
*/
|
|
65
|
+
export function useOldDevtools(): boolean {
|
|
66
|
+
return (process.env.USE_OLD_DEVTOOLS ?? '').trim().toLowerCase() !== 'false'
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
70
|
+
// Chrome / CDP launcher.
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
|
|
73
|
+
/** `%LOCALAPPDATA%` on Windows, used to probe a per-user Chrome install. */
|
|
74
|
+
export function localAppData(): string | undefined {
|
|
75
|
+
return process.env['LOCALAPPDATA']
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Explicit Chrome/Edge/Chromium binary path override. */
|
|
79
|
+
export function chromePath(): string | undefined {
|
|
80
|
+
return process.env.RECLAIM_AGENT_CHROME_PATH
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ---------------------------------------------------------------------------
|
|
84
|
+
// MCP server / API clients.
|
|
85
|
+
// ---------------------------------------------------------------------------
|
|
86
|
+
|
|
87
|
+
/** Agent tools (Chrome CDP capture, synthesis, proof) are disabled in hosted
|
|
88
|
+
* deployments by setting `RECLAIM_AGENT_DISABLED=1`. Shared by mcp/start and
|
|
89
|
+
* the old-mode authenticate tool. */
|
|
90
|
+
export function agentDisabled(): boolean {
|
|
91
|
+
return process.env.RECLAIM_AGENT_DISABLED === '1'
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Old-devtools backend base URL. */
|
|
95
|
+
export function oldApiUrl(): string {
|
|
96
|
+
return process.env.RECLAIM_OLD_API_URL || DEFAULT_OLD_API_URL
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Builder backend base URL. Shared by mcp/start and the verification tool. */
|
|
100
|
+
export function apiUrl(): string {
|
|
101
|
+
return process.env.RECLAIM_API_URL || DEFAULT_BASE_URL
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Explicit builder API token (overrides the cached session token). */
|
|
105
|
+
export function apiToken(): string | undefined {
|
|
106
|
+
return process.env.RECLAIM_API_TOKEN
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// ---------------------------------------------------------------------------
|
|
110
|
+
// Proof tracing.
|
|
111
|
+
// ---------------------------------------------------------------------------
|
|
112
|
+
|
|
113
|
+
/** Diagnostic trace capture (`RECLAIM_AGENT_TRACE=1`). Shared by the attestor
|
|
114
|
+
* logger capture and the proof-run dump. */
|
|
115
|
+
export function agentTraceEnabled(): boolean {
|
|
116
|
+
return process.env['RECLAIM_AGENT_TRACE'] === '1'
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** Attestor websocket URL override (`RECLAIM_ATTESTOR_URL`). */
|
|
120
|
+
export function attestorUrlOverride(): string | undefined {
|
|
121
|
+
return process.env['RECLAIM_ATTESTOR_URL']
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// ---------------------------------------------------------------------------
|
|
125
|
+
// Eth proof-owner key resolution.
|
|
126
|
+
// ---------------------------------------------------------------------------
|
|
127
|
+
|
|
128
|
+
/** Raw 0x-hex Ethereum private key (`RECLAIM_PRIVATE_KEY`). */
|
|
129
|
+
export function privateKey(): string | undefined {
|
|
130
|
+
return process.env.RECLAIM_PRIVATE_KEY
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Path to a raw-hex key file (`RECLAIM_PRIVATE_KEY_FILE`). */
|
|
134
|
+
export function privateKeyFile(): string | undefined {
|
|
135
|
+
return process.env.RECLAIM_PRIVATE_KEY_FILE
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// ---------------------------------------------------------------------------
|
|
139
|
+
// Verification reads.
|
|
140
|
+
// ---------------------------------------------------------------------------
|
|
141
|
+
|
|
142
|
+
/** Per-org bearer secret (`rorg_…`) used to authenticate verification reads. */
|
|
143
|
+
export function orgSecret(): string | undefined {
|
|
144
|
+
return process.env.RECLAIM_ORG_SECRET
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// ---------------------------------------------------------------------------
|
|
148
|
+
// Old-devtools identity / login.
|
|
149
|
+
// ---------------------------------------------------------------------------
|
|
150
|
+
|
|
151
|
+
/** Dashboard Firebase bearer token for the old backend. */
|
|
152
|
+
export function oldApiToken(): string | undefined {
|
|
153
|
+
return process.env.RECLAIM_OLD_API_TOKEN
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** An `eth:<addr>` (or bare `0x…`) uid for the old backend. */
|
|
157
|
+
export function oldEthUid(): string | undefined {
|
|
158
|
+
return process.env.RECLAIM_OLD_ETH_UID
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** Override for the old-devtools dashboard login URL. */
|
|
162
|
+
export function oldLoginUrl(): string | undefined {
|
|
163
|
+
return process.env.RECLAIM_OLD_LOGIN_URL
|
|
164
|
+
}
|
package/src/logger.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import pino, { stdTimeFunctions } from 'pino'
|
|
2
|
+
import { LOG_LEVEL } from './consts.ts'
|
|
2
3
|
|
|
3
4
|
export const LOGGER = pino({
|
|
4
5
|
name: '@reclaimprotocol/agent',
|
|
5
6
|
timestamp: stdTimeFunctions.isoTime,
|
|
6
7
|
base: { hostname: undefined, pid: undefined },
|
|
7
8
|
})
|
|
8
|
-
LOGGER.level =
|
|
9
|
+
LOGGER.level = LOG_LEVEL
|