@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
|
@@ -7,6 +7,11 @@ import {
|
|
|
7
7
|
tailLogs,
|
|
8
8
|
warnsAndErrors,
|
|
9
9
|
} from '../../../proof/attestor.ts'
|
|
10
|
+
import {
|
|
11
|
+
type AttestorCheckResult,
|
|
12
|
+
type CheckAttestorOptions,
|
|
13
|
+
isNetworkError,
|
|
14
|
+
} from '../../../proof/check-attestor.ts'
|
|
10
15
|
|
|
11
16
|
function claimsDir(): string {
|
|
12
17
|
return join(reclaimHomeDirPath(), 'claims')
|
|
@@ -192,3 +197,53 @@ export function shapeRichProofResult(
|
|
|
192
197
|
|
|
193
198
|
return out
|
|
194
199
|
}
|
|
200
|
+
|
|
201
|
+
export interface ProofDiagnosis {
|
|
202
|
+
attestor: AttestorCheckResult
|
|
203
|
+
likelyCause: string
|
|
204
|
+
next: string
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* When an unverified proof failed with a network-like error, runs the
|
|
209
|
+
* attestor check once and says whether the attestor or the target site is the
|
|
210
|
+
* likely cause. Returns `undefined` for verified proofs and other errors.
|
|
211
|
+
*/
|
|
212
|
+
export async function diagnoseNetworkFailure(
|
|
213
|
+
result: Pick<ProofResult, 'verified' | 'error' | 'errorKind'>,
|
|
214
|
+
check: (opts: CheckAttestorOptions) => Promise<AttestorCheckResult>,
|
|
215
|
+
attestorUrl?: string,
|
|
216
|
+
): Promise<ProofDiagnosis | undefined> {
|
|
217
|
+
if(
|
|
218
|
+
result.verified
|
|
219
|
+
|| result.errorKind !== undefined
|
|
220
|
+
|| !isNetworkError(result.error)
|
|
221
|
+
) {
|
|
222
|
+
return undefined
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const attestor = await check(attestorUrl ? { attestorUrl } : {})
|
|
226
|
+
if(attestor.ok) {
|
|
227
|
+
return {
|
|
228
|
+
attestor,
|
|
229
|
+
likelyCause:
|
|
230
|
+
'The attestor works from this machine, so the target site likely '
|
|
231
|
+
+ 'blocks or drops the attestor\'s network (geo or IP restriction).',
|
|
232
|
+
next:
|
|
233
|
+
'If the browser attach mode is "builder", call `run_proof_via_pod`. '
|
|
234
|
+
+ 'Otherwise tell the developer and suggest asking the Reclaim team. '
|
|
235
|
+
+ 'Do not retry the same proof.',
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return {
|
|
240
|
+
attestor,
|
|
241
|
+
likelyCause:
|
|
242
|
+
'The attestor check also failed, so this machine cannot reach the '
|
|
243
|
+
+ 'attestor (network, VPN, firewall, or proxy).',
|
|
244
|
+
next:
|
|
245
|
+
'Tell the developer. To see the connection details, suggest '
|
|
246
|
+
+ 'restarting the MCP server with `RECLAIM_AGENT_TRACE=1`. Do not '
|
|
247
|
+
+ 'retry the same proof.',
|
|
248
|
+
}
|
|
249
|
+
}
|
|
@@ -18,9 +18,9 @@ export function proposeTool(resolveBackend: ResolveBackend): RegisteredTool {
|
|
|
18
18
|
name: 'propose_provider',
|
|
19
19
|
description:
|
|
20
20
|
'Drafts a provider from one authenticated captured request and '
|
|
21
|
-
+ 'keeps captured secrets server-side. Use `targets` for
|
|
22
|
-
+ '
|
|
23
|
-
+ 'and secret-free recipe. Set `shortenNames: false` to preserve an '
|
|
21
|
+
+ 'keeps captured secrets server-side. Use `targets` for the data '
|
|
22
|
+
+ 'points the claim needs from that response; returns a `draftId` '
|
|
23
|
+
+ 'and a secret-free recipe. Set `shortenNames: false` to preserve an '
|
|
24
24
|
+ 'explicit parameter name, including a hidden `REQ_` or `URL_` '
|
|
25
25
|
+ 'prefix. For matching and naming guidance, call '
|
|
26
26
|
+ 'how_it_works({ topic: "draft" }) and topic `params`.',
|
|
@@ -63,6 +63,7 @@ export function proposeTool(resolveBackend: ResolveBackend): RegisteredTool {
|
|
|
63
63
|
},
|
|
64
64
|
['requestId', 'name'],
|
|
65
65
|
),
|
|
66
|
+
annotations: { openWorldHint: false, destructiveHint: false },
|
|
66
67
|
},
|
|
67
68
|
async(args) => {
|
|
68
69
|
const backend = resolveBackend(args)
|
|
@@ -5,6 +5,7 @@ import type {
|
|
|
5
5
|
} from '@reclaimprotocol/client/openapi'
|
|
6
6
|
import assert from 'node:assert'
|
|
7
7
|
import type { JSONSchema } from 'zod/v4/core'
|
|
8
|
+
import type { KeptScriptRef } from '../../../authoring/index.ts'
|
|
8
9
|
import {
|
|
9
10
|
ALLOWED_REQUESTS_HINT,
|
|
10
11
|
COOKIE_ATTACHMENT_HINT,
|
|
@@ -232,13 +233,16 @@ interface PublishArgs {
|
|
|
232
233
|
export function createVersionFromCaptureTool(
|
|
233
234
|
client: ReclaimClient,
|
|
234
235
|
attachRef?: { current?: AttachState },
|
|
236
|
+
kept?: KeptScriptRef,
|
|
235
237
|
): RegisteredTool {
|
|
236
238
|
return defineTool<PublishArgs>(
|
|
237
239
|
{
|
|
238
240
|
name: 'create_provider_version_from_capture',
|
|
239
241
|
description:
|
|
240
242
|
'Creates a new immutable provider version after `run_proof` succeeds. '
|
|
241
|
-
+ '
|
|
243
|
+
+ 'Publishes the navigation script kept by `test_user_script` as '
|
|
244
|
+
+ '`jsUserScripts` unless `jsUserScripts` is passed. Pass exactly '
|
|
245
|
+
+ 'one `provider` or `providers` value returned by '
|
|
242
246
|
+ '`propose_provider`; do not rebuild the recipe. The default is a '
|
|
243
247
|
+ 'patch bump, or use `bump` or an explicitly requested higher '
|
|
244
248
|
+ '`version`. The new version remains a draft until published or '
|
|
@@ -306,6 +310,8 @@ export function createVersionFromCaptureTool(
|
|
|
306
310
|
jsUserScripts: {
|
|
307
311
|
type: 'string',
|
|
308
312
|
description:
|
|
313
|
+
'Defaults to the script kept by `test_user_script` with ' +
|
|
314
|
+
'`keep: true`; pass it only to override. ' +
|
|
309
315
|
USER_SCRIPT_HINT +
|
|
310
316
|
' Stored as webSettings.jsUserScripts; the Builder ' +
|
|
311
317
|
'verification client runs it before every page load ' +
|
|
@@ -369,8 +375,10 @@ export function createVersionFromCaptureTool(
|
|
|
369
375
|
},
|
|
370
376
|
required: ['providerId', 'initialUrl'],
|
|
371
377
|
},
|
|
378
|
+
annotations: { openWorldHint: true, destructiveHint: false },
|
|
372
379
|
},
|
|
373
380
|
async(args) => {
|
|
381
|
+
const jsUserScripts = args.jsUserScripts ?? kept?.keptScript?.script
|
|
374
382
|
// Exactly one of `provider` / `providers` — normalize to an array.
|
|
375
383
|
const hasMany = Array.isArray(args.providers) && args.providers.length > 0
|
|
376
384
|
const hasOne = !!args.provider
|
|
@@ -396,7 +404,7 @@ export function createVersionFromCaptureTool(
|
|
|
396
404
|
notes: args.notes,
|
|
397
405
|
version: ver,
|
|
398
406
|
requiredContext: args.requiredContext,
|
|
399
|
-
jsUserScripts
|
|
407
|
+
jsUserScripts,
|
|
400
408
|
allowedJsRequests: args.allowedJsRequests,
|
|
401
409
|
browserRequestActions: args.browserRequestActions,
|
|
402
410
|
interceptorOptions: args.interceptorOptions,
|
|
@@ -428,12 +436,12 @@ export function createVersionFromCaptureTool(
|
|
|
428
436
|
const attached = attachRef?.current
|
|
429
437
|
notes.push(...postPublishBrowserNotes(attached, {
|
|
430
438
|
initialUrl: args.initialUrl,
|
|
431
|
-
hasUserScript: !!
|
|
439
|
+
hasUserScript: !!jsUserScripts?.trim(),
|
|
432
440
|
hasInterceptor: !!args.interceptorOptions,
|
|
433
441
|
}))
|
|
434
442
|
|
|
435
443
|
// Let the developer know how and when the script runs.
|
|
436
|
-
if(
|
|
444
|
+
if(jsUserScripts?.trim()) {
|
|
437
445
|
notes.push(
|
|
438
446
|
'jsUserScripts will run in the Builder verification client before ' +
|
|
439
447
|
'every page load (before navigating to initialUrl), matching ' +
|
|
@@ -13,13 +13,15 @@ export function replayTool(resolveBackend: ResolveBackend): RegisteredTool {
|
|
|
13
13
|
{
|
|
14
14
|
name: 'replay_request',
|
|
15
15
|
description:
|
|
16
|
-
'
|
|
17
|
-
+ '
|
|
18
|
-
+ '
|
|
19
|
-
+ '
|
|
20
|
-
+ '
|
|
21
|
-
+ '
|
|
22
|
-
+ '
|
|
16
|
+
'Re-sends the drafted request from inside the attached authoring '
|
|
17
|
+
+ 'browser (not from this machine) and checks that its selectors '
|
|
18
|
+
+ 'extract the target data points. Captured secrets stay server-side '
|
|
19
|
+
+ 'and are not returned. Returns diagnostics. Run it before '
|
|
20
|
+
+ '`run_proof`. With `withoutSecrets: true`, sends the request with '
|
|
21
|
+
+ 'no cookies or auth headers, like an anonymous visitor. If the '
|
|
22
|
+
+ 'values still extract (`valid: true`), the endpoint is public and '
|
|
23
|
+
+ 'the proof would not bind to the user: choose an auth-bound '
|
|
24
|
+
+ 'request instead.',
|
|
23
25
|
inputSchema: objectSchema(
|
|
24
26
|
{
|
|
25
27
|
draftId: { type: 'string' },
|
|
@@ -27,6 +29,7 @@ export function replayTool(resolveBackend: ResolveBackend): RegisteredTool {
|
|
|
27
29
|
},
|
|
28
30
|
['draftId'],
|
|
29
31
|
),
|
|
32
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
30
33
|
},
|
|
31
34
|
async(args) => {
|
|
32
35
|
const backend = resolveBackend(args)
|
|
@@ -12,10 +12,13 @@ export function sessionTools(
|
|
|
12
12
|
{
|
|
13
13
|
name: 'get_session',
|
|
14
14
|
description:
|
|
15
|
-
'
|
|
16
|
-
+ 'persisted browser config
|
|
17
|
-
+ '
|
|
15
|
+
'Returns the current attach state, the live captureIds, the '
|
|
16
|
+
+ 'persisted browser config, and the navigation script kept by '
|
|
17
|
+
+ '`test_user_script`. Call it to check whether a browser is still '
|
|
18
|
+
+ 'attached before reattaching, or to recover a captureId or the '
|
|
19
|
+
+ 'kept script.',
|
|
18
20
|
inputSchema: { type: 'object', properties: {} },
|
|
21
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
19
22
|
},
|
|
20
23
|
async() => ({
|
|
21
24
|
config: attachRef.config.read()?.browser_agent,
|
|
@@ -27,16 +30,19 @@ export function sessionTools(
|
|
|
27
30
|
orgId: attachRef.current?.orgId,
|
|
28
31
|
captures: [...captures.sessions.keys()],
|
|
29
32
|
activeTab: captures.activeTab,
|
|
33
|
+
keptUserScript: captures.keptScript?.script,
|
|
30
34
|
}),
|
|
31
35
|
),
|
|
32
36
|
defineTool<Record<string, never>>(
|
|
33
37
|
{
|
|
34
38
|
name: 'dispose_browser',
|
|
35
39
|
description:
|
|
36
|
-
'
|
|
37
|
-
+ '
|
|
38
|
-
+ '
|
|
40
|
+
'Stops captures, disconnects the current browser, and deletes a '
|
|
41
|
+
+ 'Builder-hosted browser session at once. Clears the kept '
|
|
42
|
+
+ 'navigation script. Reclaim disconnects a custom CDP browser but '
|
|
43
|
+
+ 'never deletes it.',
|
|
39
44
|
inputSchema: { type: 'object', properties: {} },
|
|
45
|
+
annotations: { destructiveHint: true, openWorldHint: true },
|
|
40
46
|
},
|
|
41
47
|
async() => {
|
|
42
48
|
await resetAll(attachRef, captures)
|
|
@@ -47,9 +53,11 @@ export function sessionTools(
|
|
|
47
53
|
{
|
|
48
54
|
name: 'reset_session',
|
|
49
55
|
description:
|
|
50
|
-
'
|
|
51
|
-
+ '
|
|
56
|
+
'Stops all captures and clears capture state, dropping every '
|
|
57
|
+
+ 'draft and its in-memory secrets. Also disconnects the browser '
|
|
58
|
+
+ 'and clears the kept navigation script.',
|
|
52
59
|
inputSchema: { type: 'object', properties: {} },
|
|
60
|
+
annotations: { destructiveHint: true, openWorldHint: true },
|
|
53
61
|
},
|
|
54
62
|
async() => {
|
|
55
63
|
await resetAll(attachRef, captures)
|
|
@@ -12,13 +12,15 @@ import {
|
|
|
12
12
|
type AuthoringBackend,
|
|
13
13
|
authoringTools,
|
|
14
14
|
extendTool,
|
|
15
|
+
type KeptScriptRef,
|
|
15
16
|
} from '../../../authoring/index.ts'
|
|
16
17
|
import { runAttestorProof } from '../../../proof/attestor.ts'
|
|
18
|
+
import { checkAttestor } from '../../../proof/check-attestor.ts'
|
|
17
19
|
import { replayProviderInPage } from '../../../proof/page-replay.ts'
|
|
18
20
|
import type { OprfMode, ReclaimProvider } from '../../../provider/schema.ts'
|
|
19
21
|
import type { RegisteredTool } from '../../server.ts'
|
|
20
22
|
import type { CaptureEntry, CaptureStore } from './capture.ts'
|
|
21
|
-
import { shapeRichProofResult } from './proof.ts'
|
|
23
|
+
import { diagnoseNetworkFailure, shapeRichProofResult } from './proof.ts'
|
|
22
24
|
|
|
23
25
|
/**
|
|
24
26
|
* OPRF mode this LOCAL (non-TEE) attestor can actually run. Recipes carry
|
|
@@ -54,13 +56,18 @@ function withLocalOprfMode(provider: ReclaimProvider): ReclaimProvider {
|
|
|
54
56
|
/** The local backend for one capture: drive its tab over CDP, read its
|
|
55
57
|
* captured requests, prove via the in-process attestor, replay by re-fetching
|
|
56
58
|
* FROM INSIDE the tab (real cookies/session — see proof/page-replay.ts). */
|
|
57
|
-
function localBackend(
|
|
59
|
+
function localBackend(
|
|
60
|
+
entry: CaptureEntry,
|
|
61
|
+
kept: KeptScriptRef,
|
|
62
|
+
check: typeof checkAttestor,
|
|
63
|
+
): AuthoringBackend {
|
|
58
64
|
const { tabClient, session, drafts } = entry
|
|
59
65
|
const sendCdp = (method: string, params?: Record<string, unknown>) => {
|
|
60
66
|
return tabClient.send(method, params ?? {})
|
|
61
67
|
}
|
|
62
68
|
|
|
63
69
|
return {
|
|
70
|
+
kept,
|
|
64
71
|
capture: session,
|
|
65
72
|
drafts,
|
|
66
73
|
sendCdp,
|
|
@@ -107,11 +114,15 @@ function localBackend(entry: CaptureEntry): AuthoringBackend {
|
|
|
107
114
|
...(opts?.attestorUrl ? { attestorUrl: opts.attestorUrl } : {}),
|
|
108
115
|
},
|
|
109
116
|
)
|
|
110
|
-
|
|
117
|
+
const shaped = shapeRichProofResult(result, {
|
|
111
118
|
provider: draft.provider,
|
|
112
119
|
hasSecrets: Object.keys(draft.secrets).length > 0,
|
|
113
120
|
...(opts?.attestorUrl ? { attestorUrl: opts.attestorUrl } : {}),
|
|
114
121
|
})
|
|
122
|
+
const diagnosis = await diagnoseNetworkFailure(
|
|
123
|
+
result, check, opts?.attestorUrl,
|
|
124
|
+
)
|
|
125
|
+
return diagnosis ? { ...shaped, diagnosis } : shaped
|
|
115
126
|
},
|
|
116
127
|
}
|
|
117
128
|
}
|
|
@@ -121,25 +132,26 @@ function localBackend(entry: CaptureEntry): AuthoringBackend {
|
|
|
121
132
|
* of this). */
|
|
122
133
|
const RUN_PROOF_EXTRAS = {
|
|
123
134
|
description:
|
|
124
|
-
'
|
|
125
|
-
+ '
|
|
126
|
-
+ '
|
|
127
|
-
+ '
|
|
128
|
-
+ '
|
|
129
|
-
+ '
|
|
130
|
-
+ '
|
|
131
|
-
+ '
|
|
132
|
-
+ '`attestorUrl`
|
|
133
|
-
+ '
|
|
134
|
-
+ '
|
|
135
|
-
+ '
|
|
136
|
-
+ '
|
|
137
|
-
+ '
|
|
138
|
-
+ '
|
|
139
|
-
+ '
|
|
140
|
-
+ '
|
|
141
|
-
+
|
|
142
|
-
+ '
|
|
135
|
+
'Proves the drafted recipe with a full zkTLS proof through the attestor '
|
|
136
|
+
+ 'SDK. Call it after `replay_request` looks right; attestor calls are '
|
|
137
|
+
+ 'expensive. Call `resolve_owner_key` first and pass its address as '
|
|
138
|
+
+ '`ownerAddress`. Writes the full claim to '
|
|
139
|
+
+ '`~/.reclaim/claims/<timestamp>.json`. Returns `identifier`, '
|
|
140
|
+
+ '`extractedParameters`, `claimPath`, and a readable `claim` (request '
|
|
141
|
+
+ 'removed, byte arrays as base64). Set `RECLAIM_AGENT_TRACE=1` to also '
|
|
142
|
+
+ 'get `runLogPath`, a diagnostic dump with the attestor log stream. '
|
|
143
|
+
+ 'Set `attestorUrl` only if the developer supplied one; never invent '
|
|
144
|
+
+ 'backup URLs. If `errorKind` is `missing-owner-key`, the address has '
|
|
145
|
+
+ 'no known private key: run `resolve_owner_key` again, or issue or '
|
|
146
|
+
+ 'import a credential. Unlike `replay_request`, which re-sends from '
|
|
147
|
+
+ 'inside the attached browser tab, the attestor calls the target from '
|
|
148
|
+
+ 'its own network and IP. A request that replayed can still fail here '
|
|
149
|
+
+ 'for geo- or IP-bound content, such as country-locked pages or '
|
|
150
|
+
+ 'IP-allowlisted APIs. That is a property of the site, not a draft '
|
|
151
|
+
+ 'bug. After a network-like failure ("connection closed", '
|
|
152
|
+
+ '`ERROR_NETWORK_ERROR`, timeout), the result includes `diagnosis` '
|
|
153
|
+
+ 'with `attestor` (a `check_attestor` result), `likelyCause`, and '
|
|
154
|
+
+ '`next`. Follow `next`; do not retry the same proof.',
|
|
143
155
|
properties: {
|
|
144
156
|
ownerAddress: {
|
|
145
157
|
type: 'string',
|
|
@@ -163,7 +175,10 @@ const RUN_PROOF_EXTRAS = {
|
|
|
163
175
|
* takes `captureId`; `run_proof` also gets the credential-aware contract. The
|
|
164
176
|
* tools are authored plain in `../../../authoring`; this only wraps them with
|
|
165
177
|
* `extendTool` (handlers untouched — loose arg parse keeps `captureId`). */
|
|
166
|
-
export function sharedAuthoringTools(
|
|
178
|
+
export function sharedAuthoringTools(
|
|
179
|
+
captures: CaptureStore,
|
|
180
|
+
check: typeof checkAttestor = checkAttestor,
|
|
181
|
+
): RegisteredTool[] {
|
|
167
182
|
const resolveBackend = (args: Record<string, unknown>): AuthoringBackend => {
|
|
168
183
|
const captureId = String(args.captureId)
|
|
169
184
|
const entry = captures.sessions.get(captureId)
|
|
@@ -174,7 +189,7 @@ export function sharedAuthoringTools(captures: CaptureStore): RegisteredTool[] {
|
|
|
174
189
|
)
|
|
175
190
|
}
|
|
176
191
|
|
|
177
|
-
return localBackend(entry)
|
|
192
|
+
return localBackend(entry, captures, check)
|
|
178
193
|
}
|
|
179
194
|
|
|
180
195
|
return authoringTools(resolveBackend).map((tool) => {
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs'
|
|
2
|
+
import { fileURLToPath } from 'node:url'
|
|
3
|
+
import { defineTool, type RegisteredTool } from '../../server.ts'
|
|
4
|
+
|
|
5
|
+
interface StartIntegrationArgs {
|
|
6
|
+
goal: string
|
|
7
|
+
orgId?: string
|
|
8
|
+
providerIds?: string[]
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Returns the plan a coding agent follows to add Reclaim verification to the
|
|
13
|
+
* developer's app, from sign-in to a tested Verify button. The plan lives in
|
|
14
|
+
* `skill/start-integration.md`; this tool fills in the inputs.
|
|
15
|
+
*/
|
|
16
|
+
export function startIntegrationTool(): RegisteredTool {
|
|
17
|
+
return defineTool<StartIntegrationArgs>(
|
|
18
|
+
{
|
|
19
|
+
name: 'start_integration',
|
|
20
|
+
description:
|
|
21
|
+
'Returns a step-by-step plan for adding Reclaim verification to the '
|
|
22
|
+
+ 'current codebase: sign in, set up the app (session route, callback, '
|
|
23
|
+
+ 'Verify button, local event proxy) in parallel with building or '
|
|
24
|
+
+ 'choosing the provider, then wire the provider and test. Call it '
|
|
25
|
+
+ 'first when a developer wants their users to prove something, and '
|
|
26
|
+
+ 'follow the plan to the end. Changes nothing itself.',
|
|
27
|
+
inputSchema: {
|
|
28
|
+
type: 'object',
|
|
29
|
+
required: ['goal'],
|
|
30
|
+
properties: {
|
|
31
|
+
goal: {
|
|
32
|
+
type: 'string',
|
|
33
|
+
minLength: 1,
|
|
34
|
+
maxLength: 300,
|
|
35
|
+
description:
|
|
36
|
+
'What the developer\'s users prove, for example, "they have '
|
|
37
|
+
+ 'a GitHub account with 100 followers".',
|
|
38
|
+
},
|
|
39
|
+
orgId: {
|
|
40
|
+
type: 'string',
|
|
41
|
+
format: 'uuid',
|
|
42
|
+
description: 'Organization to use. Omit to choose from the list.',
|
|
43
|
+
},
|
|
44
|
+
providerIds: {
|
|
45
|
+
type: 'array',
|
|
46
|
+
maxItems: 10,
|
|
47
|
+
items: { type: 'string' },
|
|
48
|
+
description:
|
|
49
|
+
'Existing provider IDs to use. Omit to build a new provider.',
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
54
|
+
},
|
|
55
|
+
async(args) => renderStartIntegration(args),
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Fill the plan template. Exported for tests. */
|
|
60
|
+
export function renderStartIntegration(args: StartIntegrationArgs): string {
|
|
61
|
+
const providerIds = args.providerIds?.length
|
|
62
|
+
? args.providerIds.map((id) => `\`${id}\``).join(', ')
|
|
63
|
+
: undefined
|
|
64
|
+
const values: Record<string, string | undefined> = {
|
|
65
|
+
goal: args.goal,
|
|
66
|
+
orgId: args.orgId,
|
|
67
|
+
providerIds,
|
|
68
|
+
}
|
|
69
|
+
const template = readFileSync(
|
|
70
|
+
fileURLToPath(
|
|
71
|
+
new URL('../../../../skill/start-integration.md', import.meta.url),
|
|
72
|
+
),
|
|
73
|
+
'utf8',
|
|
74
|
+
)
|
|
75
|
+
return template
|
|
76
|
+
// Keep a conditional block only when its input was given (or not given).
|
|
77
|
+
.replace(
|
|
78
|
+
/<!-- (if|ifnot):(\w+) -->\n([\s\S]*?)<!-- endif -->\n/g,
|
|
79
|
+
(_, kind: string, name: string, body: string) => {
|
|
80
|
+
return (kind === 'if') === Boolean(values[name]) ? body : ''
|
|
81
|
+
},
|
|
82
|
+
)
|
|
83
|
+
.replace(/\{\{(\w+)\}\}/g, (_, name: string) => values[name] ?? '')
|
|
84
|
+
}
|
|
@@ -25,18 +25,16 @@ export function authenticateTool(
|
|
|
25
25
|
+ 'get_me_providers; those need the separate old-devtools login, '
|
|
26
26
|
+ 'reclaim_authenticate. Check both with get_devtools_mode. '
|
|
27
27
|
: '')
|
|
28
|
-
+ '
|
|
29
|
-
+ '
|
|
30
|
-
+ '
|
|
31
|
-
'
|
|
32
|
-
'
|
|
33
|
-
'
|
|
34
|
-
'
|
|
35
|
-
'
|
|
36
|
-
'issuing an org secret). Programmatic verification calls use a ' +
|
|
37
|
-
'separate org secret ' +
|
|
38
|
-
'(rorg_…, set as RECLAIM_ORG_SECRET).',
|
|
28
|
+
+ 'Signs the developer in to Builder. Opens the Builder sign-in page '
|
|
29
|
+
+ 'in their browser (OAuth 2.0 authorization code with PKCE, '
|
|
30
|
+
+ 'redirecting to a localhost port this server opens) and waits for '
|
|
31
|
+
+ 'them to approve. Saves the access token in ~/.reclaim/config.json '
|
|
32
|
+
+ 'with mode 0600 and uses it for this session; the token is never '
|
|
33
|
+
+ 'returned. The token authorizes organization-management tools such '
|
|
34
|
+
+ 'as `list_orgs`. Programmatic verification tools use a separate '
|
|
35
|
+
+ 'organization key; see `issue_org_token`. Call with no arguments.',
|
|
39
36
|
inputSchema: { type: 'object', properties: {} },
|
|
37
|
+
annotations: { openWorldHint: true, idempotentHint: true },
|
|
40
38
|
},
|
|
41
39
|
async() => runOAuthLoopback(client),
|
|
42
40
|
)
|
|
@@ -3,6 +3,7 @@ import type { RegisteredTool } from '../../server.ts'
|
|
|
3
3
|
import { authenticateTool } from './authenticate.ts'
|
|
4
4
|
import { importTool } from './import.ts'
|
|
5
5
|
import { issueTool } from './issue.ts'
|
|
6
|
+
import { issueOrgTokenTool } from './issue-org-token.ts'
|
|
6
7
|
import { resolveOwnerKeyTool } from './resolve-owner-key.ts'
|
|
7
8
|
|
|
8
9
|
/**
|
|
@@ -27,6 +28,7 @@ export function buildLocalCredentialTools(): RegisteredTool[] {
|
|
|
27
28
|
export function buildCredentialTools(client: ReclaimClient): RegisteredTool[] {
|
|
28
29
|
return [
|
|
29
30
|
authenticateTool(client),
|
|
31
|
+
issueOrgTokenTool(client),
|
|
30
32
|
...buildLocalCredentialTools(),
|
|
31
33
|
]
|
|
32
34
|
}
|
|
@@ -16,8 +16,20 @@
|
|
|
16
16
|
*/
|
|
17
17
|
import { deriveFromPrivateKey } from '@reclaimprotocol/client'
|
|
18
18
|
import assert from 'node:assert'
|
|
19
|
-
import {
|
|
20
|
-
|
|
19
|
+
import {
|
|
20
|
+
chmodSync,
|
|
21
|
+
existsSync,
|
|
22
|
+
mkdirSync,
|
|
23
|
+
readFileSync,
|
|
24
|
+
writeFileSync,
|
|
25
|
+
} from 'node:fs'
|
|
26
|
+
import {
|
|
27
|
+
dirname,
|
|
28
|
+
isAbsolute,
|
|
29
|
+
join,
|
|
30
|
+
relative,
|
|
31
|
+
resolve
|
|
32
|
+
} from 'node:path'
|
|
21
33
|
import { privateKey, privateKeyFile } from '../../../consts.ts'
|
|
22
34
|
import { findProjectRoot } from '../../../paths.ts'
|
|
23
35
|
|
|
@@ -124,7 +136,7 @@ export function resolveEthKey(
|
|
|
124
136
|
}
|
|
125
137
|
|
|
126
138
|
/** Append `entry` to the project `.gitignore` if not already present. */
|
|
127
|
-
function ensureGitignored(projectDir: string, entry: string) {
|
|
139
|
+
export function ensureGitignored(projectDir: string, entry: string) {
|
|
128
140
|
const path = join(projectDir, '.gitignore')
|
|
129
141
|
const current = existsSync(path) ? readFileSync(path, 'utf8') : ''
|
|
130
142
|
if(current.split('\n').some((l) => l.trim() === entry)) {
|
|
@@ -136,29 +148,47 @@ function ensureGitignored(projectDir: string, entry: string) {
|
|
|
136
148
|
}
|
|
137
149
|
|
|
138
150
|
/**
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
* hex). Returns the derived address and the `.env` path.
|
|
151
|
+
* Set `name=value` in a dotenv file, replacing an existing line or appending
|
|
152
|
+
* one. Writes with mode 0600 and adds the file to the project `.gitignore`.
|
|
153
|
+
* `envFile` is relative to `projectDir`.
|
|
143
154
|
*/
|
|
144
|
-
export function
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
const
|
|
155
|
+
export function upsertEnvVar(
|
|
156
|
+
projectDir: string,
|
|
157
|
+
envFile: string,
|
|
158
|
+
name: string,
|
|
159
|
+
value: string,
|
|
160
|
+
): string {
|
|
161
|
+
const envPath = resolve(projectDir, envFile)
|
|
162
|
+
const line = `${name}=${value}`
|
|
151
163
|
const existing = existsSync(envPath) ? readFileSync(envPath, 'utf8') : ''
|
|
152
|
-
const re = new RegExp(`^\\s*${
|
|
164
|
+
const re = new RegExp(`^\\s*${name}\\s*=.*$`, 'm')
|
|
153
165
|
let next: string
|
|
154
166
|
if(re.test(existing)) {
|
|
155
|
-
|
|
167
|
+
// A function replacer keeps `$` sequences in the value literal.
|
|
168
|
+
next = existing.replace(re, () => line)
|
|
156
169
|
} else {
|
|
157
170
|
const prefix = existing && !existing.endsWith('\n') ? '\n' : ''
|
|
158
171
|
next = existing + prefix + line + '\n'
|
|
159
172
|
}
|
|
160
173
|
|
|
174
|
+
mkdirSync(dirname(envPath), { recursive: true })
|
|
161
175
|
writeFileSync(envPath, next, { mode: 0o600 })
|
|
162
|
-
|
|
163
|
-
|
|
176
|
+
chmodSync(envPath, 0o600)
|
|
177
|
+
ensureGitignored(projectDir, relative(projectDir, envPath))
|
|
178
|
+
return envPath
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Persist a raw eth private key to the project `.env` as
|
|
183
|
+
* `RECLAIM_PRIVATE_KEY=0x…` (upsert; mode 0600; `.env` added to `.gitignore`).
|
|
184
|
+
* Used by `issue_credentials` (generate) and `import_credentials` (supplied
|
|
185
|
+
* hex). Returns the derived address and the `.env` path.
|
|
186
|
+
*/
|
|
187
|
+
export function writeEthKey(
|
|
188
|
+
privateKeyHex: string,
|
|
189
|
+
projectDir = findProjectRoot(process.cwd()),
|
|
190
|
+
): { path: string, address: string } {
|
|
191
|
+
const hex = normalizeEthHex(privateKeyHex)
|
|
192
|
+
const path = upsertEnvVar(projectDir, ENV_FILE, ENV_VAR, hex)
|
|
193
|
+
return { path, address: deriveFromPrivateKey(hex).address }
|
|
164
194
|
}
|