@reclaimprotocol/agent 0.1.33 → 0.1.35
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 +3 -3
- package/lib/authoring/backend.d.ts +13 -0
- package/lib/authoring/index.d.ts +1 -1
- package/lib/authoring/tools.js +78 -42
- package/lib/config/store.d.ts +10 -0
- package/lib/mcp/api/operations.gen.d.ts +238 -4
- package/lib/mcp/api/operations.gen.js +6967 -672
- package/lib/mcp/api/tools.js +21 -8
- package/lib/mcp/instructions.js +12 -12
- package/lib/mcp/server.d.ts +1 -1
- package/lib/mcp/server.js +3 -0
- package/lib/mcp/start.js +8 -7
- package/lib/mcp/success-response.js +5 -1
- package/lib/mcp/tools/agent/analyze.js +5 -4
- package/lib/mcp/tools/agent/attach.js +11 -7
- package/lib/mcp/tools/agent/build.js +5 -1
- package/lib/mcp/tools/agent/capture.d.ts +2 -2
- package/lib/mcp/tools/agent/capture.js +15 -11
- package/lib/mcp/tools/agent/check-attestor.d.ts +5 -0
- package/lib/mcp/tools/agent/check-attestor.js +37 -0
- package/lib/mcp/tools/agent/eval.js +4 -3
- package/lib/mcp/tools/agent/find.js +9 -10
- package/lib/mcp/tools/agent/how-it-works.js +1 -0
- package/lib/mcp/tools/agent/live-view.js +49 -22
- package/lib/mcp/tools/agent/pod-proof.js +7 -11
- package/lib/mcp/tools/agent/proof.d.ts +12 -0
- package/lib/mcp/tools/agent/proof.js +32 -0
- package/lib/mcp/tools/agent/propose.js +4 -3
- package/lib/mcp/tools/agent/publish.d.ts +2 -1
- package/lib/mcp/tools/agent/publish.js +12 -6
- package/lib/mcp/tools/agent/replay.js +10 -7
- package/lib/mcp/tools/agent/session.js +16 -8
- package/lib/mcp/tools/agent/shared.d.ts +2 -1
- package/lib/mcp/tools/agent/shared.js +29 -24
- package/lib/mcp/tools/agent/start-integration.d.ts +15 -0
- package/lib/mcp/tools/agent/start-integration.js +62 -0
- package/lib/mcp/tools/authenticate/authenticate.js +9 -11
- package/lib/mcp/tools/authenticate/build.js +2 -0
- package/lib/mcp/tools/authenticate/builder-access.js +1 -0
- package/lib/mcp/tools/authenticate/eth-key.d.ts +8 -0
- package/lib/mcp/tools/authenticate/eth-key.js +27 -15
- package/lib/mcp/tools/authenticate/import.js +18 -31
- package/lib/mcp/tools/authenticate/issue-org-token.d.ts +8 -0
- package/lib/mcp/tools/authenticate/issue-org-token.js +60 -0
- package/lib/mcp/tools/authenticate/issue.js +9 -9
- package/lib/mcp/tools/authenticate/org-key-store.d.ts +10 -0
- package/lib/mcp/tools/authenticate/org-key-store.js +23 -0
- package/lib/mcp/tools/authenticate/resolve-owner-key.js +10 -12
- package/lib/mcp/tools/mode/mode.js +2 -0
- package/lib/mcp/tools/verification/build.d.ts +3 -1
- package/lib/mcp/tools/verification/build.js +10 -2
- package/lib/mcp/tools/verification/callback-listener.d.ts +51 -0
- package/lib/mcp/tools/verification/callback-listener.js +163 -0
- package/lib/mcp/tools/verification/index.d.ts +2 -0
- package/lib/mcp/tools/verification/index.js +2 -0
- package/lib/mcp/tools/verification/proxy-callback-events.d.ts +59 -0
- package/lib/mcp/tools/verification/proxy-callback-events.js +300 -0
- package/lib/mcp/tools/verification/read-callback-events.d.ts +3 -0
- package/lib/mcp/tools/verification/read-callback-events.js +135 -0
- package/lib/mcp/tools/verification/result.d.ts +6 -0
- package/lib/mcp/tools/verification/result.js +11 -8
- package/lib/proof/check-attestor.d.ts +67 -0
- package/lib/proof/check-attestor.js +156 -0
- package/lib/tools/registry.d.ts +12 -0
- package/lib/tools/registry.js +1 -0
- package/package.json +2 -2
- package/skill/SKILL.builder.md +84 -75
- package/skill/SKILL.common.md +23 -10
- package/skill/builder-mode.md +140 -138
- package/skill/start-integration.md +74 -0
- package/skill/user-script.md +64 -0
- package/skill/verification-client-integration.md +40 -28
- package/src/authoring/backend.ts +15 -0
- package/src/authoring/index.ts +2 -0
- package/src/authoring/tools.ts +86 -42
- package/src/config/store.ts +11 -0
- package/src/mcp/api/operations.gen.ts +7011 -704
- package/src/mcp/api/tools.ts +21 -8
- package/src/mcp/instructions.ts +12 -12
- package/src/mcp/server.ts +4 -0
- package/src/mcp/start.ts +9 -7
- package/src/mcp/success-response.ts +5 -1
- package/src/mcp/tools/agent/analyze.ts +5 -4
- package/src/mcp/tools/agent/attach.ts +11 -7
- package/src/mcp/tools/agent/build.ts +7 -1
- package/src/mcp/tools/agent/capture.ts +20 -12
- package/src/mcp/tools/agent/check-attestor.ts +49 -0
- package/src/mcp/tools/agent/eval.ts +4 -3
- package/src/mcp/tools/agent/find.ts +9 -10
- package/src/mcp/tools/agent/how-it-works.ts +1 -0
- package/src/mcp/tools/agent/live-view.ts +49 -22
- package/src/mcp/tools/agent/pod-proof.ts +7 -11
- package/src/mcp/tools/agent/proof.ts +55 -0
- package/src/mcp/tools/agent/propose.ts +4 -3
- package/src/mcp/tools/agent/publish.ts +12 -4
- package/src/mcp/tools/agent/replay.ts +10 -7
- package/src/mcp/tools/agent/session.ts +16 -8
- package/src/mcp/tools/agent/shared.ts +39 -24
- package/src/mcp/tools/agent/start-integration.ts +84 -0
- package/src/mcp/tools/authenticate/authenticate.ts +9 -11
- package/src/mcp/tools/authenticate/build.ts +2 -0
- package/src/mcp/tools/authenticate/builder-access.ts +1 -0
- package/src/mcp/tools/authenticate/eth-key.ts +48 -18
- package/src/mcp/tools/authenticate/import.ts +21 -36
- package/src/mcp/tools/authenticate/issue-org-token.ts +84 -0
- package/src/mcp/tools/authenticate/issue.ts +9 -9
- package/src/mcp/tools/authenticate/org-key-store.ts +25 -0
- package/src/mcp/tools/authenticate/resolve-owner-key.ts +10 -12
- package/src/mcp/tools/mode/mode.ts +2 -0
- package/src/mcp/tools/verification/build.ts +10 -2
- package/src/mcp/tools/verification/callback-listener.ts +203 -0
- package/src/mcp/tools/verification/index.ts +2 -0
- package/src/mcp/tools/verification/proxy-callback-events.ts +374 -0
- package/src/mcp/tools/verification/read-callback-events.ts +160 -0
- package/src/mcp/tools/verification/result.ts +11 -8
- package/src/proof/check-attestor.ts +209 -0
- package/src/tools/registry.ts +14 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type ProofResult } from '../../../proof/attestor.ts';
|
|
2
|
+
import { type AttestorCheckResult, type CheckAttestorOptions } from '../../../proof/check-attestor.ts';
|
|
2
3
|
/**
|
|
3
4
|
* The MCP's rich `run_proof` view: persist the full claim to disk, return
|
|
4
5
|
* identifier + extractedParameters + a prettified claim (request dropped, byte
|
|
@@ -11,3 +12,14 @@ export declare function shapeRichProofResult(result: ProofResult, opts: {
|
|
|
11
12
|
hasSecrets: boolean;
|
|
12
13
|
attestorUrl?: string;
|
|
13
14
|
}): Record<string, unknown>;
|
|
15
|
+
export interface ProofDiagnosis {
|
|
16
|
+
attestor: AttestorCheckResult;
|
|
17
|
+
likelyCause: string;
|
|
18
|
+
next: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* When an unverified proof failed with a network-like error, runs the
|
|
22
|
+
* attestor check once and says whether the attestor or the target site is the
|
|
23
|
+
* likely cause. Returns `undefined` for verified proofs and other errors.
|
|
24
|
+
*/
|
|
25
|
+
export declare function diagnoseNetworkFailure(result: Pick<ProofResult, 'verified' | 'error' | 'errorKind'>, check: (opts: CheckAttestorOptions) => Promise<AttestorCheckResult>, attestorUrl?: string): Promise<ProofDiagnosis | undefined>;
|
|
@@ -3,6 +3,7 @@ import { join } from 'node:path';
|
|
|
3
3
|
import { agentTraceEnabled } from "../../../consts.js";
|
|
4
4
|
import { reclaimHomeDirPath } from "../../../paths.js";
|
|
5
5
|
import { tailLogs, warnsAndErrors, } from "../../../proof/attestor.js";
|
|
6
|
+
import { isNetworkError, } from "../../../proof/check-attestor.js";
|
|
6
7
|
function claimsDir() {
|
|
7
8
|
return join(reclaimHomeDirPath(), 'claims');
|
|
8
9
|
}
|
|
@@ -157,3 +158,34 @@ export function shapeRichProofResult(result, opts) {
|
|
|
157
158
|
}
|
|
158
159
|
return out;
|
|
159
160
|
}
|
|
161
|
+
/**
|
|
162
|
+
* When an unverified proof failed with a network-like error, runs the
|
|
163
|
+
* attestor check once and says whether the attestor or the target site is the
|
|
164
|
+
* likely cause. Returns `undefined` for verified proofs and other errors.
|
|
165
|
+
*/
|
|
166
|
+
export async function diagnoseNetworkFailure(result, check, attestorUrl) {
|
|
167
|
+
if (result.verified
|
|
168
|
+
|| result.errorKind !== undefined
|
|
169
|
+
|| !isNetworkError(result.error)) {
|
|
170
|
+
return undefined;
|
|
171
|
+
}
|
|
172
|
+
const attestor = await check(attestorUrl ? { attestorUrl } : {});
|
|
173
|
+
if (attestor.ok) {
|
|
174
|
+
return {
|
|
175
|
+
attestor,
|
|
176
|
+
likelyCause: 'The attestor works from this machine, so the target site likely '
|
|
177
|
+
+ 'blocks or drops the attestor\'s network (geo or IP restriction).',
|
|
178
|
+
next: 'If the browser attach mode is "builder", call `run_proof_via_pod`. '
|
|
179
|
+
+ 'Otherwise tell the developer and suggest asking the Reclaim team. '
|
|
180
|
+
+ 'Do not retry the same proof.',
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
return {
|
|
184
|
+
attestor,
|
|
185
|
+
likelyCause: 'The attestor check also failed, so this machine cannot reach the '
|
|
186
|
+
+ 'attestor (network, VPN, firewall, or proxy).',
|
|
187
|
+
next: 'Tell the developer. To see the connection details, suggest '
|
|
188
|
+
+ 'restarting the MCP server with `RECLAIM_AGENT_TRACE=1`. Do not '
|
|
189
|
+
+ 'retry the same proof.',
|
|
190
|
+
};
|
|
191
|
+
}
|
|
@@ -14,9 +14,9 @@ export function proposeTool(resolveBackend) {
|
|
|
14
14
|
return defineTool({
|
|
15
15
|
name: 'propose_provider',
|
|
16
16
|
description: 'Drafts a provider from one authenticated captured request and '
|
|
17
|
-
+ 'keeps captured secrets server-side. Use `targets` for
|
|
18
|
-
+ '
|
|
19
|
-
+ 'and secret-free recipe. Set `shortenNames: false` to preserve an '
|
|
17
|
+
+ 'keeps captured secrets server-side. Use `targets` for the data '
|
|
18
|
+
+ 'points the claim needs from that response; returns a `draftId` '
|
|
19
|
+
+ 'and a secret-free recipe. Set `shortenNames: false` to preserve an '
|
|
20
20
|
+ 'explicit parameter name, including a hidden `REQ_` or `URL_` '
|
|
21
21
|
+ 'prefix. For matching and naming guidance, call '
|
|
22
22
|
+ 'how_it_works({ topic: "draft" }) and topic `params`.',
|
|
@@ -54,6 +54,7 @@ export function proposeTool(resolveBackend) {
|
|
|
54
54
|
target: { type: 'string' },
|
|
55
55
|
hash: { type: 'string', enum: ['oprf-raw', 'oprf', 'oprf-mpc'] },
|
|
56
56
|
}, ['requestId', 'name']),
|
|
57
|
+
annotations: { openWorldHint: false, destructiveHint: false },
|
|
57
58
|
}, async (args) => {
|
|
58
59
|
const backend = resolveBackend(args);
|
|
59
60
|
const requestId = String(args.requestId);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ReclaimClient } from '@reclaimprotocol/client/api';
|
|
2
|
+
import type { KeptScriptRef } from '../../../authoring/index.ts';
|
|
2
3
|
import { type RegisteredTool } from '../../server.ts';
|
|
3
4
|
import type { AttachState } from './attach.ts';
|
|
4
5
|
export declare function createVersionFromCaptureTool(client: ReclaimClient, attachRef?: {
|
|
5
6
|
current?: AttachState;
|
|
6
|
-
}): RegisteredTool;
|
|
7
|
+
}, kept?: KeptScriptRef): RegisteredTool;
|
|
@@ -165,11 +165,13 @@ const BROWSER_REQUEST_ACTION_SCHEMA = {
|
|
|
165
165
|
},
|
|
166
166
|
required: ['id', 'origin', 'path', 'method'],
|
|
167
167
|
};
|
|
168
|
-
export function createVersionFromCaptureTool(client, attachRef) {
|
|
168
|
+
export function createVersionFromCaptureTool(client, attachRef, kept) {
|
|
169
169
|
return defineTool({
|
|
170
170
|
name: 'create_provider_version_from_capture',
|
|
171
171
|
description: 'Creates a new immutable provider version after `run_proof` succeeds. '
|
|
172
|
-
+ '
|
|
172
|
+
+ 'Publishes the navigation script kept by `test_user_script` as '
|
|
173
|
+
+ '`jsUserScripts` unless `jsUserScripts` is passed. Pass exactly '
|
|
174
|
+
+ 'one `provider` or `providers` value returned by '
|
|
173
175
|
+ '`propose_provider`; do not rebuild the recipe. The default is a '
|
|
174
176
|
+ 'patch bump, or use `bump` or an explicitly requested higher '
|
|
175
177
|
+ '`version`. The new version remains a draft until published or '
|
|
@@ -230,7 +232,9 @@ export function createVersionFromCaptureTool(client, attachRef) {
|
|
|
230
232
|
},
|
|
231
233
|
jsUserScripts: {
|
|
232
234
|
type: 'string',
|
|
233
|
-
description:
|
|
235
|
+
description: 'Defaults to the script kept by `test_user_script` with ' +
|
|
236
|
+
'`keep: true`; pass it only to override. ' +
|
|
237
|
+
USER_SCRIPT_HINT +
|
|
234
238
|
' Stored as webSettings.jsUserScripts; the Builder ' +
|
|
235
239
|
'verification client runs it before every page load ' +
|
|
236
240
|
'(including before navigating to initialUrl), matching ' +
|
|
@@ -288,7 +292,9 @@ export function createVersionFromCaptureTool(client, attachRef) {
|
|
|
288
292
|
},
|
|
289
293
|
required: ['providerId', 'initialUrl'],
|
|
290
294
|
},
|
|
295
|
+
annotations: { openWorldHint: true, destructiveHint: false },
|
|
291
296
|
}, async (args) => {
|
|
297
|
+
const jsUserScripts = args.jsUserScripts ?? kept?.keptScript?.script;
|
|
292
298
|
// Exactly one of `provider` / `providers` — normalize to an array.
|
|
293
299
|
const hasMany = Array.isArray(args.providers) && args.providers.length > 0;
|
|
294
300
|
const hasOne = !!args.provider;
|
|
@@ -306,7 +312,7 @@ export function createVersionFromCaptureTool(client, attachRef) {
|
|
|
306
312
|
notes: args.notes,
|
|
307
313
|
version: ver,
|
|
308
314
|
requiredContext: args.requiredContext,
|
|
309
|
-
jsUserScripts
|
|
315
|
+
jsUserScripts,
|
|
310
316
|
allowedJsRequests: args.allowedJsRequests,
|
|
311
317
|
browserRequestActions: args.browserRequestActions,
|
|
312
318
|
interceptorOptions: args.interceptorOptions,
|
|
@@ -332,11 +338,11 @@ export function createVersionFromCaptureTool(client, attachRef) {
|
|
|
332
338
|
const attached = attachRef?.current;
|
|
333
339
|
notes.push(...postPublishBrowserNotes(attached, {
|
|
334
340
|
initialUrl: args.initialUrl,
|
|
335
|
-
hasUserScript: !!
|
|
341
|
+
hasUserScript: !!jsUserScripts?.trim(),
|
|
336
342
|
hasInterceptor: !!args.interceptorOptions,
|
|
337
343
|
}));
|
|
338
344
|
// Let the developer know how and when the script runs.
|
|
339
|
-
if (
|
|
345
|
+
if (jsUserScripts?.trim()) {
|
|
340
346
|
notes.push('jsUserScripts will run in the Builder verification client before ' +
|
|
341
347
|
'every page load (before navigating to initialUrl), matching ' +
|
|
342
348
|
'old-devtool customInjection. window.Reclaim is available for ' +
|
|
@@ -10,17 +10,20 @@ import { defineTool } from "../../../tools/registry.js";
|
|
|
10
10
|
export function replayTool(resolveBackend) {
|
|
11
11
|
return defineTool({
|
|
12
12
|
name: 'replay_request',
|
|
13
|
-
description: '
|
|
14
|
-
+ '
|
|
15
|
-
+ '
|
|
16
|
-
+ '
|
|
17
|
-
+ '
|
|
18
|
-
+ '
|
|
19
|
-
+ '
|
|
13
|
+
description: 'Re-sends the drafted request from inside the attached authoring '
|
|
14
|
+
+ 'browser (not from this machine) and checks that its selectors '
|
|
15
|
+
+ 'extract the target data points. Captured secrets stay server-side '
|
|
16
|
+
+ 'and are not returned. Returns diagnostics. Run it before '
|
|
17
|
+
+ '`run_proof`. With `withoutSecrets: true`, sends the request with '
|
|
18
|
+
+ 'no cookies or auth headers, like an anonymous visitor. If the '
|
|
19
|
+
+ 'values still extract (`valid: true`), the endpoint is public and '
|
|
20
|
+
+ 'the proof would not bind to the user: choose an auth-bound '
|
|
21
|
+
+ 'request instead.',
|
|
20
22
|
inputSchema: objectSchema({
|
|
21
23
|
draftId: { type: 'string' },
|
|
22
24
|
withoutSecrets: { type: 'boolean' },
|
|
23
25
|
}, ['draftId']),
|
|
26
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
24
27
|
}, async (args) => {
|
|
25
28
|
const backend = resolveBackend(args);
|
|
26
29
|
const draft = backend.drafts.get(String(args.draftId));
|
|
@@ -5,10 +5,13 @@ export function sessionTools(attachRef, captures) {
|
|
|
5
5
|
return [
|
|
6
6
|
defineTool({
|
|
7
7
|
name: 'get_session',
|
|
8
|
-
description: '
|
|
9
|
-
+ 'persisted browser config
|
|
10
|
-
+ '
|
|
8
|
+
description: 'Returns the current attach state, the live captureIds, the '
|
|
9
|
+
+ 'persisted browser config, and the navigation script kept by '
|
|
10
|
+
+ '`test_user_script`. Call it to check whether a browser is still '
|
|
11
|
+
+ 'attached before reattaching, or to recover a captureId or the '
|
|
12
|
+
+ 'kept script.',
|
|
11
13
|
inputSchema: { type: 'object', properties: {} },
|
|
14
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
12
15
|
}, async () => ({
|
|
13
16
|
config: attachRef.config.read()?.browser_agent,
|
|
14
17
|
attached: Boolean(attachRef.current),
|
|
@@ -19,22 +22,27 @@ export function sessionTools(attachRef, captures) {
|
|
|
19
22
|
orgId: attachRef.current?.orgId,
|
|
20
23
|
captures: [...captures.sessions.keys()],
|
|
21
24
|
activeTab: captures.activeTab,
|
|
25
|
+
keptUserScript: captures.keptScript?.script,
|
|
22
26
|
})),
|
|
23
27
|
defineTool({
|
|
24
28
|
name: 'dispose_browser',
|
|
25
|
-
description: '
|
|
26
|
-
+ '
|
|
27
|
-
+ '
|
|
29
|
+
description: 'Stops captures, disconnects the current browser, and deletes a '
|
|
30
|
+
+ 'Builder-hosted browser session at once. Clears the kept '
|
|
31
|
+
+ 'navigation script. Reclaim disconnects a custom CDP browser but '
|
|
32
|
+
+ 'never deletes it.',
|
|
28
33
|
inputSchema: { type: 'object', properties: {} },
|
|
34
|
+
annotations: { destructiveHint: true, openWorldHint: true },
|
|
29
35
|
}, async () => {
|
|
30
36
|
await resetAll(attachRef, captures);
|
|
31
37
|
return { disposed: true };
|
|
32
38
|
}),
|
|
33
39
|
defineTool({
|
|
34
40
|
name: 'reset_session',
|
|
35
|
-
description: '
|
|
36
|
-
+ '
|
|
41
|
+
description: 'Stops all captures and clears capture state, dropping every '
|
|
42
|
+
+ 'draft and its in-memory secrets. Also disconnects the browser '
|
|
43
|
+
+ 'and clears the kept navigation script.',
|
|
37
44
|
inputSchema: { type: 'object', properties: {} },
|
|
45
|
+
annotations: { destructiveHint: true, openWorldHint: true },
|
|
38
46
|
}, async () => {
|
|
39
47
|
await resetAll(attachRef, captures);
|
|
40
48
|
return { reset: true };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { checkAttestor } from '../../../proof/check-attestor.ts';
|
|
1
2
|
import type { RegisteredTool } from '../../server.ts';
|
|
2
3
|
import type { CaptureStore } from './capture.ts';
|
|
3
4
|
/** The shared authoring tools mapped to the MCP's shape: resolve each call's
|
|
@@ -5,4 +6,4 @@ import type { CaptureStore } from './capture.ts';
|
|
|
5
6
|
* takes `captureId`; `run_proof` also gets the credential-aware contract. The
|
|
6
7
|
* tools are authored plain in `../../../authoring`; this only wraps them with
|
|
7
8
|
* `extendTool` (handlers untouched — loose arg parse keeps `captureId`). */
|
|
8
|
-
export declare function sharedAuthoringTools(captures: CaptureStore): RegisteredTool[];
|
|
9
|
+
export declare function sharedAuthoringTools(captures: CaptureStore, check?: typeof checkAttestor): RegisteredTool[];
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { authoringTools, extendTool, } from "../../../authoring/index.js";
|
|
2
2
|
import { runAttestorProof } from "../../../proof/attestor.js";
|
|
3
|
+
import { checkAttestor } from "../../../proof/check-attestor.js";
|
|
3
4
|
import { replayProviderInPage } from "../../../proof/page-replay.js";
|
|
4
|
-
import { shapeRichProofResult } from "./proof.js";
|
|
5
|
+
import { diagnoseNetworkFailure, shapeRichProofResult } from "./proof.js";
|
|
5
6
|
/**
|
|
6
7
|
* OPRF mode this LOCAL (non-TEE) attestor can actually run. Recipes carry
|
|
7
8
|
* `oprf-mpc` — the canonical/publish mode a TEE expects — but the local dev
|
|
@@ -32,12 +33,13 @@ function withLocalOprfMode(provider) {
|
|
|
32
33
|
/** The local backend for one capture: drive its tab over CDP, read its
|
|
33
34
|
* captured requests, prove via the in-process attestor, replay by re-fetching
|
|
34
35
|
* FROM INSIDE the tab (real cookies/session — see proof/page-replay.ts). */
|
|
35
|
-
function localBackend(entry) {
|
|
36
|
+
function localBackend(entry, kept, check) {
|
|
36
37
|
const { tabClient, session, drafts } = entry;
|
|
37
38
|
const sendCdp = (method, params) => {
|
|
38
39
|
return tabClient.send(method, params ?? {});
|
|
39
40
|
};
|
|
40
41
|
return {
|
|
42
|
+
kept,
|
|
41
43
|
capture: session,
|
|
42
44
|
drafts,
|
|
43
45
|
sendCdp,
|
|
@@ -75,11 +77,13 @@ function localBackend(entry) {
|
|
|
75
77
|
...(opts?.ownerAddress ? { ownerAddress: opts.ownerAddress } : {}),
|
|
76
78
|
...(opts?.attestorUrl ? { attestorUrl: opts.attestorUrl } : {}),
|
|
77
79
|
});
|
|
78
|
-
|
|
80
|
+
const shaped = shapeRichProofResult(result, {
|
|
79
81
|
provider: draft.provider,
|
|
80
82
|
hasSecrets: Object.keys(draft.secrets).length > 0,
|
|
81
83
|
...(opts?.attestorUrl ? { attestorUrl: opts.attestorUrl } : {}),
|
|
82
84
|
});
|
|
85
|
+
const diagnosis = await diagnoseNetworkFailure(result, check, opts?.attestorUrl);
|
|
86
|
+
return diagnosis ? { ...shaped, diagnosis } : shaped;
|
|
83
87
|
},
|
|
84
88
|
};
|
|
85
89
|
}
|
|
@@ -87,25 +91,26 @@ function localBackend(entry) {
|
|
|
87
91
|
* tool by the host below (the cloud's TEE signs in-enclave, so it needs none
|
|
88
92
|
* of this). */
|
|
89
93
|
const RUN_PROOF_EXTRAS = {
|
|
90
|
-
description: '
|
|
91
|
-
+ '
|
|
92
|
-
+ '
|
|
93
|
-
+ '
|
|
94
|
-
+ '
|
|
95
|
-
+ '
|
|
96
|
-
+ '
|
|
97
|
-
+ '
|
|
98
|
-
+ '`attestorUrl`
|
|
99
|
-
+ '
|
|
100
|
-
+ '
|
|
101
|
-
+ '
|
|
102
|
-
+ '
|
|
103
|
-
+ '
|
|
104
|
-
+ '
|
|
105
|
-
+ '
|
|
106
|
-
+ '
|
|
107
|
-
+
|
|
108
|
-
+ '
|
|
94
|
+
description: 'Proves the drafted recipe with a full zkTLS proof through the attestor '
|
|
95
|
+
+ 'SDK. Call it after `replay_request` looks right; attestor calls are '
|
|
96
|
+
+ 'expensive. Call `resolve_owner_key` first and pass its address as '
|
|
97
|
+
+ '`ownerAddress`. Writes the full claim to '
|
|
98
|
+
+ '`~/.reclaim/claims/<timestamp>.json`. Returns `identifier`, '
|
|
99
|
+
+ '`extractedParameters`, `claimPath`, and a readable `claim` (request '
|
|
100
|
+
+ 'removed, byte arrays as base64). Set `RECLAIM_AGENT_TRACE=1` to also '
|
|
101
|
+
+ 'get `runLogPath`, a diagnostic dump with the attestor log stream. '
|
|
102
|
+
+ 'Set `attestorUrl` only if the developer supplied one; never invent '
|
|
103
|
+
+ 'backup URLs. If `errorKind` is `missing-owner-key`, the address has '
|
|
104
|
+
+ 'no known private key: run `resolve_owner_key` again, or issue or '
|
|
105
|
+
+ 'import a credential. Unlike `replay_request`, which re-sends from '
|
|
106
|
+
+ 'inside the attached browser tab, the attestor calls the target from '
|
|
107
|
+
+ 'its own network and IP. A request that replayed can still fail here '
|
|
108
|
+
+ 'for geo- or IP-bound content, such as country-locked pages or '
|
|
109
|
+
+ 'IP-allowlisted APIs. That is a property of the site, not a draft '
|
|
110
|
+
+ 'bug. After a network-like failure ("connection closed", '
|
|
111
|
+
+ '`ERROR_NETWORK_ERROR`, timeout), the result includes `diagnosis` '
|
|
112
|
+
+ 'with `attestor` (a `check_attestor` result), `likelyCause`, and '
|
|
113
|
+
+ '`next`. Follow `next`; do not retry the same proof.',
|
|
109
114
|
properties: {
|
|
110
115
|
ownerAddress: {
|
|
111
116
|
type: 'string',
|
|
@@ -123,7 +128,7 @@ const RUN_PROOF_EXTRAS = {
|
|
|
123
128
|
* takes `captureId`; `run_proof` also gets the credential-aware contract. The
|
|
124
129
|
* tools are authored plain in `../../../authoring`; this only wraps them with
|
|
125
130
|
* `extendTool` (handlers untouched — loose arg parse keeps `captureId`). */
|
|
126
|
-
export function sharedAuthoringTools(captures) {
|
|
131
|
+
export function sharedAuthoringTools(captures, check = checkAttestor) {
|
|
127
132
|
const resolveBackend = (args) => {
|
|
128
133
|
const captureId = String(args.captureId);
|
|
129
134
|
const entry = captures.sessions.get(captureId);
|
|
@@ -131,7 +136,7 @@ export function sharedAuthoringTools(captures) {
|
|
|
131
136
|
throw new Error(`no capture ${captureId} — call start_capture first `
|
|
132
137
|
+ '(it has been reset or never started).');
|
|
133
138
|
}
|
|
134
|
-
return localBackend(entry);
|
|
139
|
+
return localBackend(entry, captures, check);
|
|
135
140
|
};
|
|
136
141
|
return authoringTools(resolveBackend).map((tool) => {
|
|
137
142
|
if (tool.definition.name === 'run_proof') {
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type RegisteredTool } from '../../server.ts';
|
|
2
|
+
interface StartIntegrationArgs {
|
|
3
|
+
goal: string;
|
|
4
|
+
orgId?: string;
|
|
5
|
+
providerIds?: string[];
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Returns the plan a coding agent follows to add Reclaim verification to the
|
|
9
|
+
* developer's app, from sign-in to a tested Verify button. The plan lives in
|
|
10
|
+
* `skill/start-integration.md`; this tool fills in the inputs.
|
|
11
|
+
*/
|
|
12
|
+
export declare function startIntegrationTool(): RegisteredTool;
|
|
13
|
+
/** Fill the plan template. Exported for tests. */
|
|
14
|
+
export declare function renderStartIntegration(args: StartIntegrationArgs): string;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
import { defineTool } from "../../server.js";
|
|
4
|
+
/**
|
|
5
|
+
* Returns the plan a coding agent follows to add Reclaim verification to the
|
|
6
|
+
* developer's app, from sign-in to a tested Verify button. The plan lives in
|
|
7
|
+
* `skill/start-integration.md`; this tool fills in the inputs.
|
|
8
|
+
*/
|
|
9
|
+
export function startIntegrationTool() {
|
|
10
|
+
return defineTool({
|
|
11
|
+
name: 'start_integration',
|
|
12
|
+
description: 'Returns a step-by-step plan for adding Reclaim verification to the '
|
|
13
|
+
+ 'current codebase: sign in, set up the app (session route, callback, '
|
|
14
|
+
+ 'Verify button, local event proxy) in parallel with building or '
|
|
15
|
+
+ 'choosing the provider, then wire the provider and test. Call it '
|
|
16
|
+
+ 'first when a developer wants their users to prove something, and '
|
|
17
|
+
+ 'follow the plan to the end. Changes nothing itself.',
|
|
18
|
+
inputSchema: {
|
|
19
|
+
type: 'object',
|
|
20
|
+
required: ['goal'],
|
|
21
|
+
properties: {
|
|
22
|
+
goal: {
|
|
23
|
+
type: 'string',
|
|
24
|
+
minLength: 1,
|
|
25
|
+
maxLength: 300,
|
|
26
|
+
description: 'What the developer\'s users prove, for example, "they have '
|
|
27
|
+
+ 'a GitHub account with 100 followers".',
|
|
28
|
+
},
|
|
29
|
+
orgId: {
|
|
30
|
+
type: 'string',
|
|
31
|
+
format: 'uuid',
|
|
32
|
+
description: 'Organization to use. Omit to choose from the list.',
|
|
33
|
+
},
|
|
34
|
+
providerIds: {
|
|
35
|
+
type: 'array',
|
|
36
|
+
maxItems: 10,
|
|
37
|
+
items: { type: 'string' },
|
|
38
|
+
description: 'Existing provider IDs to use. Omit to build a new provider.',
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
43
|
+
}, async (args) => renderStartIntegration(args));
|
|
44
|
+
}
|
|
45
|
+
/** Fill the plan template. Exported for tests. */
|
|
46
|
+
export function renderStartIntegration(args) {
|
|
47
|
+
const providerIds = args.providerIds?.length
|
|
48
|
+
? args.providerIds.map((id) => `\`${id}\``).join(', ')
|
|
49
|
+
: undefined;
|
|
50
|
+
const values = {
|
|
51
|
+
goal: args.goal,
|
|
52
|
+
orgId: args.orgId,
|
|
53
|
+
providerIds,
|
|
54
|
+
};
|
|
55
|
+
const template = readFileSync(fileURLToPath(new URL('../../../../skill/start-integration.md', import.meta.url)), 'utf8');
|
|
56
|
+
return template
|
|
57
|
+
// Keep a conditional block only when its input was given (or not given).
|
|
58
|
+
.replace(/<!-- (if|ifnot):(\w+) -->\n([\s\S]*?)<!-- endif -->\n/g, (_, kind, name, body) => {
|
|
59
|
+
return (kind === 'if') === Boolean(values[name]) ? body : '';
|
|
60
|
+
})
|
|
61
|
+
.replace(/\{\{(\w+)\}\}/g, (_, name) => values[name] ?? '');
|
|
62
|
+
}
|
|
@@ -16,18 +16,16 @@ export function authenticateTool(client, name = 'reclaim_authenticate') {
|
|
|
16
16
|
+ 'get_me_providers; those need the separate old-devtools login, '
|
|
17
17
|
+ 'reclaim_authenticate. Check both with get_devtools_mode. '
|
|
18
18
|
: '')
|
|
19
|
-
+ '
|
|
20
|
-
+ '
|
|
21
|
-
+ '
|
|
22
|
-
'
|
|
23
|
-
'
|
|
24
|
-
'
|
|
25
|
-
'
|
|
26
|
-
'
|
|
27
|
-
'issuing an org secret). Programmatic verification calls use a ' +
|
|
28
|
-
'separate org secret ' +
|
|
29
|
-
'(rorg_…, set as RECLAIM_ORG_SECRET).',
|
|
19
|
+
+ 'Signs the developer in to Builder. Opens the Builder sign-in page '
|
|
20
|
+
+ 'in their browser (OAuth 2.0 authorization code with PKCE, '
|
|
21
|
+
+ 'redirecting to a localhost port this server opens) and waits for '
|
|
22
|
+
+ 'them to approve. Saves the access token in ~/.reclaim/config.json '
|
|
23
|
+
+ 'with mode 0600 and uses it for this session; the token is never '
|
|
24
|
+
+ 'returned. The token authorizes organization-management tools such '
|
|
25
|
+
+ 'as `list_orgs`. Programmatic verification tools use a separate '
|
|
26
|
+
+ 'organization key; see `issue_org_token`. Call with no arguments.',
|
|
30
27
|
inputSchema: { type: 'object', properties: {} },
|
|
28
|
+
annotations: { openWorldHint: true, idempotentHint: true },
|
|
31
29
|
}, async () => runOAuthLoopback(client));
|
|
32
30
|
}
|
|
33
31
|
const _customFetchForOauth = async (url, options) => {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { authenticateTool } from "./authenticate.js";
|
|
2
2
|
import { importTool } from "./import.js";
|
|
3
3
|
import { issueTool } from "./issue.js";
|
|
4
|
+
import { issueOrgTokenTool } from "./issue-org-token.js";
|
|
4
5
|
import { resolveOwnerKeyTool } from "./resolve-owner-key.js";
|
|
5
6
|
/**
|
|
6
7
|
* Local-only key tools — no backend client needed. These manage the eth
|
|
@@ -23,6 +24,7 @@ export function buildLocalCredentialTools() {
|
|
|
23
24
|
export function buildCredentialTools(client) {
|
|
24
25
|
return [
|
|
25
26
|
authenticateTool(client),
|
|
27
|
+
issueOrgTokenTool(client),
|
|
26
28
|
...buildLocalCredentialTools(),
|
|
27
29
|
];
|
|
28
30
|
}
|
|
@@ -26,6 +26,7 @@ export function buildBuilderOrganizationTools(client) {
|
|
|
26
26
|
cursor: { type: 'string', maxLength: 500 },
|
|
27
27
|
},
|
|
28
28
|
},
|
|
29
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
29
30
|
}, async ({ cursor }) => {
|
|
30
31
|
const { data } = await client.call('ListOrgs', {
|
|
31
32
|
query: { ...(cursor ? { cursor } : {}) },
|
|
@@ -19,6 +19,14 @@ export declare function normalizeEthHex(value: string): string;
|
|
|
19
19
|
* ask for one (see `resolve_owner_key`).
|
|
20
20
|
*/
|
|
21
21
|
export declare function resolveEthKey(projectDir?: string): ResolvedEthKey | undefined;
|
|
22
|
+
/** Append `entry` to the project `.gitignore` if not already present. */
|
|
23
|
+
export declare function ensureGitignored(projectDir: string, entry: string): void;
|
|
24
|
+
/**
|
|
25
|
+
* Set `name=value` in a dotenv file, replacing an existing line or appending
|
|
26
|
+
* one. Writes with mode 0600 and adds the file to the project `.gitignore`.
|
|
27
|
+
* `envFile` is relative to `projectDir`.
|
|
28
|
+
*/
|
|
29
|
+
export declare function upsertEnvVar(projectDir: string, envFile: string, name: string, value: string): string;
|
|
22
30
|
/**
|
|
23
31
|
* Persist a raw eth private key to the project `.env` as
|
|
24
32
|
* `RECLAIM_PRIVATE_KEY=0x…` (upsert; mode 0600; `.env` added to `.gitignore`).
|
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
*/
|
|
17
17
|
import { deriveFromPrivateKey } from '@reclaimprotocol/client';
|
|
18
18
|
import assert from 'node:assert';
|
|
19
|
-
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
20
|
-
import { isAbsolute, join } from 'node:path';
|
|
19
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync, } from 'node:fs';
|
|
20
|
+
import { dirname, isAbsolute, join, relative, resolve } from 'node:path';
|
|
21
21
|
import { privateKey, privateKeyFile } from "../../../consts.js";
|
|
22
22
|
import { findProjectRoot } from "../../../paths.js";
|
|
23
23
|
const ENV_VAR = 'RECLAIM_PRIVATE_KEY';
|
|
@@ -80,7 +80,7 @@ export function resolveEthKey(projectDir = findProjectRoot(process.cwd())) {
|
|
|
80
80
|
return undefined;
|
|
81
81
|
}
|
|
82
82
|
/** Append `entry` to the project `.gitignore` if not already present. */
|
|
83
|
-
function ensureGitignored(projectDir, entry) {
|
|
83
|
+
export function ensureGitignored(projectDir, entry) {
|
|
84
84
|
const path = join(projectDir, '.gitignore');
|
|
85
85
|
const current = existsSync(path) ? readFileSync(path, 'utf8') : '';
|
|
86
86
|
if (current.split('\n').some((l) => l.trim() === entry)) {
|
|
@@ -90,26 +90,38 @@ function ensureGitignored(projectDir, entry) {
|
|
|
90
90
|
writeFileSync(path, current + prefix + entry + '\n');
|
|
91
91
|
}
|
|
92
92
|
/**
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
* hex). Returns the derived address and the `.env` path.
|
|
93
|
+
* Set `name=value` in a dotenv file, replacing an existing line or appending
|
|
94
|
+
* one. Writes with mode 0600 and adds the file to the project `.gitignore`.
|
|
95
|
+
* `envFile` is relative to `projectDir`.
|
|
97
96
|
*/
|
|
98
|
-
export function
|
|
99
|
-
const
|
|
100
|
-
const
|
|
101
|
-
const line = `${ENV_VAR}=${hex}`;
|
|
97
|
+
export function upsertEnvVar(projectDir, envFile, name, value) {
|
|
98
|
+
const envPath = resolve(projectDir, envFile);
|
|
99
|
+
const line = `${name}=${value}`;
|
|
102
100
|
const existing = existsSync(envPath) ? readFileSync(envPath, 'utf8') : '';
|
|
103
|
-
const re = new RegExp(`^\\s*${
|
|
101
|
+
const re = new RegExp(`^\\s*${name}\\s*=.*$`, 'm');
|
|
104
102
|
let next;
|
|
105
103
|
if (re.test(existing)) {
|
|
106
|
-
|
|
104
|
+
// A function replacer keeps `$` sequences in the value literal.
|
|
105
|
+
next = existing.replace(re, () => line);
|
|
107
106
|
}
|
|
108
107
|
else {
|
|
109
108
|
const prefix = existing && !existing.endsWith('\n') ? '\n' : '';
|
|
110
109
|
next = existing + prefix + line + '\n';
|
|
111
110
|
}
|
|
111
|
+
mkdirSync(dirname(envPath), { recursive: true });
|
|
112
112
|
writeFileSync(envPath, next, { mode: 0o600 });
|
|
113
|
-
|
|
114
|
-
|
|
113
|
+
chmodSync(envPath, 0o600);
|
|
114
|
+
ensureGitignored(projectDir, relative(projectDir, envPath));
|
|
115
|
+
return envPath;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Persist a raw eth private key to the project `.env` as
|
|
119
|
+
* `RECLAIM_PRIVATE_KEY=0x…` (upsert; mode 0600; `.env` added to `.gitignore`).
|
|
120
|
+
* Used by `issue_credentials` (generate) and `import_credentials` (supplied
|
|
121
|
+
* hex). Returns the derived address and the `.env` path.
|
|
122
|
+
*/
|
|
123
|
+
export function writeEthKey(privateKeyHex, projectDir = findProjectRoot(process.cwd())) {
|
|
124
|
+
const hex = normalizeEthHex(privateKeyHex);
|
|
125
|
+
const path = upsertEnvVar(projectDir, ENV_FILE, ENV_VAR, hex);
|
|
126
|
+
return { path, address: deriveFromPrivateKey(hex).address };
|
|
115
127
|
}
|