@reclaimprotocol/agent 0.1.27 → 0.1.29
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 +5 -5
- package/lib/authoring/tools.js +2 -1
- package/lib/consts.d.ts +1 -1
- package/lib/consts.js +1 -1
- package/lib/live/tunnel.d.ts +4 -0
- package/lib/live/tunnel.js +21 -12
- package/lib/mcp/api/operations.gen.d.ts +8 -8
- package/lib/mcp/api/operations.gen.js +339 -581
- package/lib/mcp/api/tools.d.ts +1 -1
- package/lib/mcp/instructions.js +7 -3
- package/lib/mcp/remap_errors.js +9 -5
- package/lib/mcp/start.js +1 -1
- package/lib/mcp/tools/agent/attach.d.ts +2 -2
- package/lib/mcp/tools/agent/attach.js +4 -0
- package/lib/mcp/tools/agent/build.d.ts +1 -1
- package/lib/mcp/tools/agent/live-view-url.d.ts +2 -0
- package/lib/mcp/tools/agent/live-view-url.js +11 -0
- package/lib/mcp/tools/agent/live-view.d.ts +4 -2
- package/lib/mcp/tools/agent/live-view.js +42 -39
- package/lib/mcp/tools/agent/propose.js +10 -4
- package/lib/mcp/tools/agent/publish.d.ts +1 -1
- package/lib/mcp/tools/agent/publish.js +49 -26
- package/lib/mcp/tools/agent/shared.js +0 -9
- package/lib/mcp/tools/authenticate/authenticate.d.ts +1 -1
- package/lib/mcp/tools/authenticate/build.d.ts +1 -1
- package/lib/mcp/tools/authenticate/builder-access.d.ts +1 -1
- package/lib/mcp/tools/verification/result.js +12 -13
- package/lib/old/login-cdp.d.ts +17 -6
- package/lib/old/login-cdp.js +28 -11
- package/lib/old/to-register.d.ts +6 -0
- package/lib/old/to-register.js +35 -5
- package/lib/old/tools/authenticate.d.ts +5 -4
- package/lib/old/tools/authenticate.js +9 -5
- package/lib/old/tools/publish.d.ts +2 -2
- package/lib/old/tools/publish.js +47 -21
- package/lib/proof/attestor-auth.d.ts +13 -0
- package/lib/proof/attestor-auth.js +16 -0
- package/lib/proof/attestor.d.ts +7 -4
- package/lib/proof/attestor.js +7 -4
- package/lib/proof/index.d.ts +1 -0
- package/lib/proof/index.js +1 -0
- package/lib/proof/pod-prover.d.ts +2 -0
- package/lib/proof/pod-prover.js +5 -5
- package/lib/provider/injection-doc.d.ts +2 -0
- package/lib/provider/injection-doc.js +7 -1
- package/lib/provider/post-publish-browser.d.ts +12 -0
- package/lib/provider/post-publish-browser.js +40 -0
- package/lib/provider/to-version.d.ts +19 -7
- package/lib/provider/to-version.js +67 -25
- package/lib/tools/registry.d.ts +1 -1
- package/lib/tools/registry.js +7 -4
- package/package.json +2 -2
- package/skill/SKILL.builder.md +118 -13
- package/skill/SKILL.common.md +30 -19
- package/skill/SKILL.old.md +22 -12
- package/skill/builder-mode.md +19 -9
- package/skill/community-contributions.md +108 -0
- package/skill/hash-validation.md +8 -5
- package/skill/interception.md +35 -22
- package/skill/old-mode.md +11 -5
- package/skill/user-script.md +41 -10
- package/src/authoring/tools.ts +2 -1
- package/src/consts.ts +1 -2
- package/src/live/tunnel.ts +28 -14
- package/src/mcp/api/operations.gen.ts +341 -585
- package/src/mcp/api/tools.ts +1 -1
- package/src/mcp/instructions.ts +7 -3
- package/src/mcp/remap_errors.ts +9 -7
- package/src/mcp/start.ts +1 -1
- package/src/mcp/tools/agent/attach.ts +6 -2
- package/src/mcp/tools/agent/build.ts +1 -1
- package/src/mcp/tools/agent/live-view-url.ts +13 -0
- package/src/mcp/tools/agent/live-view.ts +44 -39
- package/src/mcp/tools/agent/propose.ts +10 -4
- package/src/mcp/tools/agent/publish.ts +63 -28
- package/src/mcp/tools/agent/shared.ts +5 -0
- package/src/mcp/tools/authenticate/authenticate.ts +1 -1
- package/src/mcp/tools/authenticate/build.ts +1 -1
- package/src/mcp/tools/authenticate/builder-access.ts +1 -1
- package/src/mcp/tools/verification/result.ts +11 -13
- package/src/old/login-cdp.ts +38 -11
- package/src/old/to-register.ts +56 -7
- package/src/old/tools/authenticate.ts +9 -5
- package/src/old/tools/publish.ts +64 -22
- package/src/proof/attestor-auth.ts +33 -0
- package/src/proof/attestor.ts +14 -8
- package/src/proof/index.ts +4 -0
- package/src/proof/pod-prover.ts +7 -5
- package/src/provider/injection-doc.ts +9 -1
- package/src/provider/post-publish-browser.ts +56 -0
- package/src/provider/to-version.ts +104 -27
- package/src/tools/registry.ts +12 -9
package/src/old/login-cdp.ts
CHANGED
|
@@ -92,13 +92,39 @@ export interface CdpLoginResult {
|
|
|
92
92
|
closedBrowser: boolean
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
+
export interface LocalDashboardBrowser {
|
|
96
|
+
mode: 'dedicated' | 'attach'
|
|
97
|
+
port: number
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Accept only the two loopback Chrome modes used for legacy dashboard login.
|
|
102
|
+
* Config files are user-editable, so validate at runtime rather than trusting
|
|
103
|
+
* the TypeScript shape. Remote Builder, container, custom-CDP, and URL inputs
|
|
104
|
+
* are deliberately unsupported here.
|
|
105
|
+
*/
|
|
106
|
+
export function localDashboardBrowser(value: unknown): LocalDashboardBrowser {
|
|
107
|
+
if(!value || typeof value !== 'object') {
|
|
108
|
+
return { mode: 'dedicated', port: 9222 }
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const mode = Reflect.get(value, 'attachMode')
|
|
112
|
+
const port = Reflect.get(value, 'port')
|
|
113
|
+
if((mode === 'dedicated' || mode === 'attach')
|
|
114
|
+
&& Number.isInteger(port) && Number(port) > 0 && Number(port) <= 65_535) {
|
|
115
|
+
return { mode, port: Number(port) }
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return { mode: 'dedicated', port: 9222 }
|
|
119
|
+
}
|
|
120
|
+
|
|
95
121
|
/**
|
|
96
|
-
* Open the dashboard login page in Chrome via CDP, wait for
|
|
97
|
-
* finish signing in, read the resulting Firebase ID token
|
|
98
|
-
* then clean up.
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
122
|
+
* Open the dashboard login page in a local Chrome via loopback CDP, wait for
|
|
123
|
+
* the developer to finish signing in, read the resulting Firebase ID token
|
|
124
|
+
* from page storage, then clean up. This never uses a Builder, container,
|
|
125
|
+
* custom-CDP, or shared browser. If we launch a dedicated Chrome, we close the
|
|
126
|
+
* whole browser afterward; if we attach to an existing local Chrome, we close
|
|
127
|
+
* only the tab we opened.
|
|
102
128
|
*/
|
|
103
129
|
export async function captureFirebaseTokenViaCdp(opts: {
|
|
104
130
|
loginUrl?: string
|
|
@@ -116,9 +142,9 @@ export async function captureFirebaseTokenViaCdp(opts: {
|
|
|
116
142
|
const timeoutMs = opts.timeoutMs ?? 360_000
|
|
117
143
|
const pollMs = opts.pollMs ?? 1000
|
|
118
144
|
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
|
|
145
|
+
const { mode, port } = localDashboardBrowser(
|
|
146
|
+
new ConfigStore().read()?.browser_agent,
|
|
147
|
+
)
|
|
122
148
|
|
|
123
149
|
// Ensure something is answering CDP. In dedicated mode we may launch it.
|
|
124
150
|
let launchedByUs = false
|
|
@@ -127,9 +153,10 @@ export async function captureFirebaseTokenViaCdp(opts: {
|
|
|
127
153
|
} catch{
|
|
128
154
|
if(mode !== 'dedicated') {
|
|
129
155
|
throw new Error(
|
|
130
|
-
`attach mode: nothing answering CDP on port ${port}.
|
|
156
|
+
`local attach mode: nothing answering CDP on port ${port}. ` +
|
|
157
|
+
'Launch Chrome ' +
|
|
131
158
|
`with --remote-debugging-port=${port} AND a non-default ` +
|
|
132
|
-
'--user-data-dir, or
|
|
159
|
+
'--user-data-dir, or use the dedicated local login mode.',
|
|
133
160
|
)
|
|
134
161
|
}
|
|
135
162
|
|
package/src/old/to-register.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { badRequest, conflict } from '@hapi/boom'
|
|
1
2
|
import assert from 'node:assert'
|
|
2
3
|
import type { ReclaimProvider } from '../provider/schema.ts'
|
|
3
4
|
import type { AllowedJsRequest, DraftRequest } from '../provider/to-version.ts'
|
|
@@ -353,6 +354,8 @@ export interface SemverTriplet {
|
|
|
353
354
|
patch: number
|
|
354
355
|
}
|
|
355
356
|
|
|
357
|
+
export type SemverBump = 'major' | 'minor' | 'patch'
|
|
358
|
+
|
|
356
359
|
/**
|
|
357
360
|
* Pull `{major,minor,patch}` triplets out of a `/versions` response, tolerant
|
|
358
361
|
* of the exact envelope (array, `{ providerVersions: [...] }`, `{ items }`,
|
|
@@ -384,22 +387,68 @@ export function extractVersions(resp: unknown): SemverTriplet[] {
|
|
|
384
387
|
* rather than collide with the guaranteed 1.0.0).
|
|
385
388
|
*/
|
|
386
389
|
export function bumpPatchString(versions: SemverTriplet[]): string {
|
|
387
|
-
|
|
390
|
+
return resolveNewVersionString(versions)
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/** Resolve a strictly newer immutable version for old-devtools publishing. */
|
|
394
|
+
export function resolveNewVersionString(
|
|
395
|
+
versions: SemverTriplet[],
|
|
396
|
+
input: { version?: SemverTriplet, bump?: SemverBump } = {},
|
|
397
|
+
): string {
|
|
398
|
+
assert(
|
|
399
|
+
!(input.version && input.bump),
|
|
400
|
+
badRequest('Pass either `version` or `bump`, not both.'),
|
|
401
|
+
)
|
|
402
|
+
const highest = highestVersion(versions)
|
|
403
|
+
if(input.version) {
|
|
404
|
+
assert(
|
|
405
|
+
!highest || compareVersions(input.version, highest) > 0,
|
|
406
|
+
conflict(
|
|
407
|
+
`Version ${formatVersion(input.version)} must be higher than the ` +
|
|
408
|
+
`current highest version ${highest ? formatVersion(highest) : 'none'}.`,
|
|
409
|
+
),
|
|
410
|
+
)
|
|
411
|
+
return formatVersion(input.version)
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
if(!highest) {
|
|
388
415
|
return '1.0.1'
|
|
389
416
|
}
|
|
390
417
|
|
|
418
|
+
const bump = input.bump ?? 'patch'
|
|
419
|
+
switch (bump) {
|
|
420
|
+
case 'major':
|
|
421
|
+
return `${highest.major + 1}.0.0`
|
|
422
|
+
case 'minor':
|
|
423
|
+
return `${highest.major}.${highest.minor + 1}.0`
|
|
424
|
+
case 'patch':
|
|
425
|
+
return `${highest.major}.${highest.minor}.${highest.patch + 1}`
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
function highestVersion(
|
|
430
|
+
versions: SemverTriplet[],
|
|
431
|
+
): SemverTriplet | undefined {
|
|
432
|
+
if(versions.length === 0) {
|
|
433
|
+
return undefined
|
|
434
|
+
}
|
|
435
|
+
|
|
391
436
|
let hi = versions[0]
|
|
392
437
|
for(const v of versions) {
|
|
393
|
-
if(
|
|
394
|
-
v.major > hi.major ||
|
|
395
|
-
(v.major === hi.major && v.minor > hi.minor) ||
|
|
396
|
-
(v.major === hi.major && v.minor === hi.minor && v.patch > hi.patch)
|
|
397
|
-
) {
|
|
438
|
+
if(compareVersions(v, hi) > 0) {
|
|
398
439
|
hi = v
|
|
399
440
|
}
|
|
400
441
|
}
|
|
401
442
|
|
|
402
|
-
return
|
|
443
|
+
return hi
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
function compareVersions(a: SemverTriplet, b: SemverTriplet) {
|
|
447
|
+
return a.major - b.major || a.minor - b.minor || a.patch - b.patch
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
function formatVersion(version: SemverTriplet) {
|
|
451
|
+
return `${version.major}.${version.minor}.${version.patch}`
|
|
403
452
|
}
|
|
404
453
|
|
|
405
454
|
function pickArray(resp: unknown): unknown[] {
|
|
@@ -5,10 +5,11 @@ import { persistIdentity, toEthUid } from '../identity.ts'
|
|
|
5
5
|
import { captureFirebaseTokenViaCdp } from '../login-cdp.ts'
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
* OLD-devtools authenticate. The default, zero-arg flow drives
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
8
|
+
* OLD-devtools authenticate. The default, zero-arg flow drives only a local
|
|
9
|
+
* Chrome through loopback CDP. It never uses a Builder, container, custom-CDP,
|
|
10
|
+
* or shared browser. It opens the devtools dashboard, waits for the developer
|
|
11
|
+
* to sign in, reads the Firebase ID token from page storage, then closes the
|
|
12
|
+
* tab or the dedicated browser it launched. Two manual overrides remain:
|
|
12
13
|
*
|
|
13
14
|
* - `token`: paste a Firebase ID token yourself (skips the browser).
|
|
14
15
|
* - `ethAddress`: an `eth:<addr>`/`0x…` uid for an already-provisioned eth
|
|
@@ -41,7 +42,9 @@ export function authenticateTool(client: ReclaimOldClient): RegisteredTool {
|
|
|
41
42
|
name: 'reclaim_authenticate',
|
|
42
43
|
description:
|
|
43
44
|
'Authenticate against the OLD Reclaim devtools backend. \n\n' +
|
|
44
|
-
'Default (no args): opens the devtools dashboard in
|
|
45
|
+
'Default (no args): opens the devtools dashboard in a LOCAL Chrome ' +
|
|
46
|
+
'over loopback CDP. It never uses a Builder, container, custom-CDP, ' +
|
|
47
|
+
'or shared browser. It ' +
|
|
45
48
|
'waits for you to sign in, grabs the Firebase token automatically, ' +
|
|
46
49
|
'and closes the tab. A previously stored identity that is still ' +
|
|
47
50
|
'valid is reused first (pass `useBrowser: true` to force a fresh ' +
|
|
@@ -151,6 +154,7 @@ export function authenticateTool(client: ReclaimOldClient): RegisteredTool {
|
|
|
151
154
|
return {
|
|
152
155
|
authenticated: true,
|
|
153
156
|
method: 'browser',
|
|
157
|
+
browser: 'local-chrome',
|
|
154
158
|
closedBrowser: result.closedBrowser,
|
|
155
159
|
note:
|
|
156
160
|
'Signed in via the dashboard; Firebase token captured and cached. ' +
|
package/src/old/tools/publish.ts
CHANGED
|
@@ -1,24 +1,29 @@
|
|
|
1
|
-
import { Boom, isBoom } from '@hapi/boom'
|
|
1
|
+
import { badRequest, Boom, isBoom } from '@hapi/boom'
|
|
2
|
+
import assert from 'node:assert'
|
|
2
3
|
import { defineTool, type RegisteredTool } from '../../mcp/server.ts'
|
|
3
4
|
import type { AttachState } from '../../mcp/tools/agent/attach.ts'
|
|
4
5
|
import {
|
|
5
6
|
ALLOWED_REQUESTS_HINT,
|
|
7
|
+
COOKIE_ATTACHMENT_HINT,
|
|
6
8
|
USER_SCRIPT_HINT,
|
|
7
9
|
} from '../../provider/injection-doc.ts'
|
|
8
10
|
import {
|
|
9
11
|
countsFor,
|
|
10
12
|
matchRedactionWarnings,
|
|
11
13
|
} from '../../provider/match-redaction-warning.ts'
|
|
14
|
+
import { postPublishBrowserNotes } from '../../provider/post-publish-browser.ts'
|
|
12
15
|
import type { ReclaimProvider } from '../../provider/schema.ts'
|
|
13
16
|
import type { AllowedJsRequest } from '../../provider/to-version.ts'
|
|
14
17
|
import type { ReclaimOldClient } from '../client.ts'
|
|
15
18
|
import {
|
|
16
|
-
bumpPatchString,
|
|
17
19
|
extractVersions,
|
|
18
20
|
injectedRequestDataFrom,
|
|
19
21
|
type OldProviderOpts,
|
|
20
22
|
providerToConfigBody,
|
|
21
23
|
providerToRegisterBody,
|
|
24
|
+
resolveNewVersionString,
|
|
25
|
+
type SemverBump,
|
|
26
|
+
type SemverTriplet,
|
|
22
27
|
} from '../to-register.ts'
|
|
23
28
|
|
|
24
29
|
const DEFAULT_VERSION_INFO = 'Updated from MCP browser capture'
|
|
@@ -70,8 +75,8 @@ function rethrowOwnershipError(err: unknown, providerId: string): never {
|
|
|
70
75
|
* - WITHOUT `providerId` → `POST /api/providers/register`: creates a
|
|
71
76
|
* brand-new provider + version 1.0.0. Re-running creates a DUPLICATE.
|
|
72
77
|
* - WITH `providerId` → `POST /api/providers/:providerId/config`: adds a
|
|
73
|
-
* new version to an existing provider (
|
|
74
|
-
*
|
|
78
|
+
* new immutable version to an existing provider (patch by default;
|
|
79
|
+
* major/minor/patch or an exact higher version when requested).
|
|
75
80
|
*
|
|
76
81
|
* Get a `providerId` from `get_me_providers`.
|
|
77
82
|
*
|
|
@@ -90,6 +95,8 @@ export function createVersionFromCaptureTool(
|
|
|
90
95
|
providers?: ReclaimProvider[]
|
|
91
96
|
initialUrl: string
|
|
92
97
|
providerId?: string
|
|
98
|
+
version?: SemverTriplet
|
|
99
|
+
bump?: SemverBump
|
|
93
100
|
description?: string
|
|
94
101
|
geoLocation?: string
|
|
95
102
|
useProxy?: boolean
|
|
@@ -119,8 +126,11 @@ export function createVersionFromCaptureTool(
|
|
|
119
126
|
+ 'redactions are supported.\n\n'
|
|
120
127
|
+ 'Omit `providerId` to REGISTER A NEW provider (creates it + '
|
|
121
128
|
+ 'version 1.0.0 — re-running creates a DUPLICATE). Pass '
|
|
122
|
-
+ '`providerId` (from get_me_providers) to add a NEW VERSION
|
|
123
|
-
+ '
|
|
129
|
+
+ '`providerId` (from get_me_providers) to add a NEW IMMUTABLE VERSION '
|
|
130
|
+
+ 'to it. Omit `version` and `bump` for a patch bump, pass `bump` for '
|
|
131
|
+
+ 'a major/minor/patch bump, or pass an exact higher `version` when '
|
|
132
|
+
+ 'the user directs it. Never edit an existing version. You can only '
|
|
133
|
+
+ 'version a provider YOU own — '
|
|
124
134
|
+ 'to publish your own take on one you don\'t own, omit '
|
|
125
135
|
+ '`providerId` to register your own copy.\n\n'
|
|
126
136
|
+ 'THIS BACKEND ONLY EVER CREATES A NEW VERSION — there is no '
|
|
@@ -225,6 +235,27 @@ export function createVersionFromCaptureTool(
|
|
|
225
235
|
'Existing provider to add a new version to. Omit to register ' +
|
|
226
236
|
'a brand-new provider.',
|
|
227
237
|
},
|
|
238
|
+
version: {
|
|
239
|
+
type: 'object',
|
|
240
|
+
description:
|
|
241
|
+
'Exact new semver requested by the user. Existing-provider ' +
|
|
242
|
+
'publishing only; it must be higher than the current highest ' +
|
|
243
|
+
'version. Do not pass with `bump`.',
|
|
244
|
+
properties: {
|
|
245
|
+
major: { type: 'integer', minimum: 0 },
|
|
246
|
+
minor: { type: 'integer', minimum: 0 },
|
|
247
|
+
patch: { type: 'integer', minimum: 0 },
|
|
248
|
+
},
|
|
249
|
+
required: ['major', 'minor', 'patch'],
|
|
250
|
+
},
|
|
251
|
+
bump: {
|
|
252
|
+
type: 'string',
|
|
253
|
+
enum: ['major', 'minor', 'patch'],
|
|
254
|
+
description:
|
|
255
|
+
'Semantic increment from the highest version. Defaults to ' +
|
|
256
|
+
'`patch`. Existing-provider publishing only; do not pass with ' +
|
|
257
|
+
'an exact `version`.',
|
|
258
|
+
},
|
|
228
259
|
description: {
|
|
229
260
|
type: 'string',
|
|
230
261
|
description: 'Provider description (new-provider register only).',
|
|
@@ -435,7 +466,8 @@ export function createVersionFromCaptureTool(
|
|
|
435
466
|
'certain none is configured. Builder calls the equivalent ' +
|
|
436
467
|
'concept `interceptorType` (no `NONE` there — omitting the ' +
|
|
437
468
|
'whole `interceptorOptions` object is its no-interception ' +
|
|
438
|
-
'signal instead).
|
|
469
|
+
'signal instead). ' + COOKIE_ATTACHMENT_HINT +
|
|
470
|
+
'What each value actually does: ' +
|
|
439
471
|
'how_it_works({ topic: "interception" }).',
|
|
440
472
|
},
|
|
441
473
|
disableRequestReplay: {
|
|
@@ -456,6 +488,17 @@ export function createVersionFromCaptureTool(
|
|
|
456
488
|
},
|
|
457
489
|
},
|
|
458
490
|
async(args) => {
|
|
491
|
+
assert(
|
|
492
|
+
!(args.version && args.bump),
|
|
493
|
+
badRequest('Pass either `version` or `bump`, not both.'),
|
|
494
|
+
)
|
|
495
|
+
assert(
|
|
496
|
+
args.providerId || (!args.version && !args.bump),
|
|
497
|
+
badRequest(
|
|
498
|
+
'`version` and `bump` require `providerId`; new-provider ' +
|
|
499
|
+
'registration starts at the backend-defined version.',
|
|
500
|
+
),
|
|
501
|
+
)
|
|
459
502
|
// `provider` / `providers` are just two spellings of the same input —
|
|
460
503
|
// fold them into one array. Zero drafts is allowed ONLY when adding a
|
|
461
504
|
// version to an EXISTING provider (`providerId` set): that publishes
|
|
@@ -514,17 +557,12 @@ export function createVersionFromCaptureTool(
|
|
|
514
557
|
// developer before closing it (a builder-mode session is
|
|
515
558
|
// quota-accounted and otherwise just sits until its TTL expires).
|
|
516
559
|
const attached = attachRef?.current
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
'. Ask the developer whether they want to keep it open for ' +
|
|
524
|
-
'further edits — only call dispose_browser once they confirm ' +
|
|
525
|
-
'they\'re done with it.',
|
|
526
|
-
)
|
|
527
|
-
}
|
|
560
|
+
notes.push(...postPublishBrowserNotes(attached, {
|
|
561
|
+
initialUrl: args.initialUrl,
|
|
562
|
+
hasUserScript: !!args.customInjection?.trim(),
|
|
563
|
+
hasInterceptor: args.injectionType !== 'NONE'
|
|
564
|
+
&& !!args.customInjection?.trim(),
|
|
565
|
+
}))
|
|
528
566
|
|
|
529
567
|
const withNotes = (result: unknown) => {
|
|
530
568
|
if(notes.length && result && typeof result === 'object') {
|
|
@@ -585,7 +623,7 @@ export function createVersionFromCaptureTool(
|
|
|
585
623
|
// providerId.
|
|
586
624
|
try {
|
|
587
625
|
const versionsResp = await client.listVersions(newProviderId)
|
|
588
|
-
const followVersion =
|
|
626
|
+
const followVersion = resolveNewVersionString(
|
|
589
627
|
extractVersions(versionsResp),
|
|
590
628
|
)
|
|
591
629
|
const configBody = providerToConfigBody(
|
|
@@ -631,8 +669,9 @@ export function createVersionFromCaptureTool(
|
|
|
631
669
|
return withNotes(registered)
|
|
632
670
|
}
|
|
633
671
|
|
|
634
|
-
// New version of an existing provider —
|
|
635
|
-
// the highest existing version. A provider you don't own
|
|
672
|
+
// New version of an existing provider — resolve the requested semantic
|
|
673
|
+
// increment above the highest existing version. A provider you don't own
|
|
674
|
+
// (for example, a
|
|
636
675
|
// public one) fails here with 401/403/404; surface the register-a-copy
|
|
637
676
|
// workaround instead of a bare permission error.
|
|
638
677
|
const providerId = args.providerId
|
|
@@ -661,7 +700,10 @@ export function createVersionFromCaptureTool(
|
|
|
661
700
|
)
|
|
662
701
|
|
|
663
702
|
const versionsResp = await client.listVersions(providerId)
|
|
664
|
-
const version =
|
|
703
|
+
const version = resolveNewVersionString(
|
|
704
|
+
extractVersions(versionsResp),
|
|
705
|
+
{ version: args.version, bump: args.bump },
|
|
706
|
+
)
|
|
665
707
|
const versionInfo =
|
|
666
708
|
args.notes && args.notes.trim().length >= 10
|
|
667
709
|
? args.notes.trim()
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { createAuthRequest, type proto } from '@reclaimprotocol/attestor-core'
|
|
2
|
+
|
|
3
|
+
/** Session-bound authorization in both forms used by Reclaim proof clients. */
|
|
4
|
+
export interface AttestorAuthentication {
|
|
5
|
+
/** Native object consumed by the JavaScript attestor client. */
|
|
6
|
+
request: proto.AuthenticationRequest
|
|
7
|
+
/** Legacy base64 JSON token consumed by native TEE clients. */
|
|
8
|
+
token: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Create legacy-compatible attestor authorization when the deployment has an
|
|
13
|
+
* auth private key. An absent key intentionally means authentication is off.
|
|
14
|
+
*/
|
|
15
|
+
export async function createAttestorAuthentication(
|
|
16
|
+
sessionId: string,
|
|
17
|
+
privateKey: string | undefined,
|
|
18
|
+
): Promise<AttestorAuthentication | undefined> {
|
|
19
|
+
if(!privateKey?.trim()) {
|
|
20
|
+
return undefined
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const request = await createAuthRequest({ id: sessionId }, privateKey)
|
|
24
|
+
const token = Buffer.from(
|
|
25
|
+
JSON.stringify({
|
|
26
|
+
...request,
|
|
27
|
+
signature: Buffer.from(request.signature).toString('base64'),
|
|
28
|
+
}),
|
|
29
|
+
'utf8',
|
|
30
|
+
).toString('base64')
|
|
31
|
+
|
|
32
|
+
return { request, token }
|
|
33
|
+
}
|
package/src/proof/attestor.ts
CHANGED
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
import { bytesToHex } from '@noble/hashes/utils.js'
|
|
10
10
|
import {
|
|
11
11
|
createClaimOnAttestor,
|
|
12
|
-
type Logger
|
|
12
|
+
type Logger,
|
|
13
|
+
type proto,
|
|
13
14
|
} from '@reclaimprotocol/attestor-core'
|
|
14
15
|
// Side-effect: make external domhandler nodes DOM-like so attestor-core's
|
|
15
16
|
// bundled `xpath` accepts them (see patch-domhandler.ts). Must load before any
|
|
@@ -33,8 +34,8 @@ export interface AttestorLogEntry {
|
|
|
33
34
|
ctx?: Record<string, unknown>
|
|
34
35
|
}
|
|
35
36
|
|
|
36
|
-
/** The attestor's signed claim data + signature, for building the
|
|
37
|
-
*
|
|
37
|
+
/** The attestor's signed claim data + signature, for building the exact legacy
|
|
38
|
+
* Proof a Consumer verifies against the attestor's key. Populated when
|
|
38
39
|
* `includeAttestorClaim` is set (the app's verification runner needs it; the
|
|
39
40
|
* MCP `run_proof` tool ignores it). */
|
|
40
41
|
export interface AttestorClaim {
|
|
@@ -50,6 +51,8 @@ export interface AttestorClaim {
|
|
|
50
51
|
/** Options for {@link runAttestorProof}. */
|
|
51
52
|
export interface RunAttestorProofOptions {
|
|
52
53
|
attestorUrl?: string
|
|
54
|
+
/** Optional session-bound authorization generated by the trusted backend. */
|
|
55
|
+
authRequest?: proto.AuthenticationRequest
|
|
53
56
|
/** When resolving the key from env/.env/file, the address the caller expects
|
|
54
57
|
* to sign with (sanity check). Ignored when `ownerPrivateKey` is given. */
|
|
55
58
|
ownerAddress?: string
|
|
@@ -62,7 +65,7 @@ export interface RunAttestorProofOptions {
|
|
|
62
65
|
* want gnark's speed pin it explicitly (for example, the verify runner). */
|
|
63
66
|
zkEngine?: 'gnark' | 'snarkjs' | 'stwo'
|
|
64
67
|
/** Extract the structured {@link AttestorClaim} (claim + attestor signature)
|
|
65
|
-
* onto the result for
|
|
68
|
+
* onto the result for legacy Proof construction. */
|
|
66
69
|
includeAttestorClaim?: boolean
|
|
67
70
|
}
|
|
68
71
|
|
|
@@ -74,7 +77,7 @@ export interface ProofResult {
|
|
|
74
77
|
owner?: string
|
|
75
78
|
verified: boolean
|
|
76
79
|
error?: string
|
|
77
|
-
/** Structured claim + signature for
|
|
80
|
+
/** Structured claim + signature for legacy Proof verification. Set only when
|
|
78
81
|
* `includeAttestorClaim` was requested and the claim carries signatures. */
|
|
79
82
|
attestorClaim?: AttestorClaim
|
|
80
83
|
/** Set to `'missing-owner-key'` when no credential was available to
|
|
@@ -274,7 +277,10 @@ export async function runAttestorProof(
|
|
|
274
277
|
headers: secrets,
|
|
275
278
|
},
|
|
276
279
|
ownerPrivateKey,
|
|
277
|
-
client: {
|
|
280
|
+
client: {
|
|
281
|
+
url: resolveAttestorUrl(opts.attestorUrl),
|
|
282
|
+
...(opts.authRequest ? { authRequest: opts.authRequest } : {}),
|
|
283
|
+
},
|
|
278
284
|
zkEngine: opts.zkEngine ?? 'stwo',
|
|
279
285
|
...(capture !== undefined && { logger: capture.logger as Logger }),
|
|
280
286
|
})
|
|
@@ -311,8 +317,8 @@ export async function runAttestorProof(
|
|
|
311
317
|
result.owner = owner
|
|
312
318
|
}
|
|
313
319
|
|
|
314
|
-
// Structured claim + signature so the result JWS can carry
|
|
315
|
-
//
|
|
320
|
+
// Structured claim + signature so the result JWS can carry the exact
|
|
321
|
+
// legacy Proof the Consumer verifies independently.
|
|
316
322
|
if(opts.includeAttestorClaim) {
|
|
317
323
|
const sigs = (claim as {
|
|
318
324
|
signatures?: { attestorAddress?: unknown, claimSignature?: unknown }
|
package/src/proof/index.ts
CHANGED
|
@@ -8,6 +8,10 @@ export type {
|
|
|
8
8
|
RunAttestorProofOptions,
|
|
9
9
|
} from './attestor.ts'
|
|
10
10
|
export { runAttestorProof, tailLogs, warnsAndErrors } from './attestor.ts'
|
|
11
|
+
export {
|
|
12
|
+
createAttestorAuthentication,
|
|
13
|
+
type AttestorAuthentication,
|
|
14
|
+
} from './attestor-auth.ts'
|
|
11
15
|
export {
|
|
12
16
|
buildHttpParams,
|
|
13
17
|
ensureAttestorCrypto,
|
package/src/proof/pod-prover.ts
CHANGED
|
@@ -26,10 +26,9 @@
|
|
|
26
26
|
*
|
|
27
27
|
* The pod API is unauthenticated (gateway/session is the trust boundary); call
|
|
28
28
|
* it over the same internal host used for CDP. Consumers verify
|
|
29
|
-
* `claim_signature`
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* `result_signature` is unused by any consumer.
|
|
29
|
+
* `claim_signature` with the legacy SDK's `verifyProof`, so this hands back an
|
|
30
|
+
* {@link AttestorClaim} that the app places in the exact legacy `Proof` shape.
|
|
31
|
+
* `result_signature` is unused by consumers.
|
|
33
32
|
*/
|
|
34
33
|
import { bytesToHex } from '@noble/hashes/utils.js'
|
|
35
34
|
import type { ProviderParams } from '@reclaimprotocol/attestor-core'
|
|
@@ -77,6 +76,8 @@ export interface PopcornProveOptions {
|
|
|
77
76
|
secretParams: HttpSecretParams
|
|
78
77
|
/** Session context, stringified into the claim context if given. */
|
|
79
78
|
context?: Record<string, unknown>
|
|
79
|
+
/** Base64 JSON authorization forwarded by reclaim-tee to the attestor. */
|
|
80
|
+
authRequest?: string
|
|
80
81
|
fetchImpl?: typeof fetch
|
|
81
82
|
}
|
|
82
83
|
|
|
@@ -142,7 +143,7 @@ function b64ToBytes(b64: string): Uint8Array {
|
|
|
142
143
|
export async function popcornProve(
|
|
143
144
|
opts: PopcornProveOptions,
|
|
144
145
|
): Promise<ProofResult> {
|
|
145
|
-
const { apiUrl, params, secretParams, context } = opts
|
|
146
|
+
const { apiUrl, params, secretParams, context, authRequest } = opts
|
|
146
147
|
const doFetch = opts.fetchImpl ?? fetch
|
|
147
148
|
const base = apiUrl.replace(/\/$/, '')
|
|
148
149
|
|
|
@@ -151,6 +152,7 @@ export async function popcornProve(
|
|
|
151
152
|
params,
|
|
152
153
|
secretParams,
|
|
153
154
|
...(context ? { context: JSON.stringify(context) } : {}),
|
|
155
|
+
...(authRequest ? { authRequest } : {}),
|
|
154
156
|
})
|
|
155
157
|
const fail = (error: string): ProofResult => ({
|
|
156
158
|
proof: undefined,
|
|
@@ -15,13 +15,21 @@
|
|
|
15
15
|
* Shared by both publish tools so the two can't drift apart.
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
+
/** Auth behavior shared by intercepted and script-requested claims. */
|
|
19
|
+
export const COOKIE_ATTACHMENT_HINT =
|
|
20
|
+
'For an intercepted request or window.Reclaim.requestClaim, the verification '
|
|
21
|
+
+ 'client automatically attaches cookies applicable to the request URL to '
|
|
22
|
+
+ 'the claim sent to the attestor. Do not read, hardcode, or copy Cookie '
|
|
23
|
+
+ 'values into the script or public params. '
|
|
24
|
+
|
|
18
25
|
/** What the script is and where the real guidance lives. */
|
|
19
26
|
export const USER_SCRIPT_HINT =
|
|
20
27
|
'Plain JavaScript injected into the provider page before every page load. '
|
|
21
28
|
+ 'Use standard browser APIs (window.location, element.click(), forms) to '
|
|
22
29
|
+ 'drive login/navigation so the target request fires — the client already '
|
|
23
30
|
+ 'intercepts page requests, and window.Reclaim is only for rare edge cases, '
|
|
24
|
-
+ 'not navigation.
|
|
31
|
+
+ 'not navigation. ' + COOKIE_ATTACHMENT_HINT
|
|
32
|
+
+ 'Full guide: how_it_works({ topic: "user-script" }).'
|
|
25
33
|
|
|
26
34
|
/** Prefixes the `allowedJsRequests` / `allowedInjectedRequestData` field. The
|
|
27
35
|
* trap it describes is silent and total — a mis-declared entry makes
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
interface AttachedBrowser {
|
|
2
|
+
mode: string
|
|
3
|
+
browserSessionId?: string
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
interface BrowserRetestOptions {
|
|
7
|
+
initialUrl: string
|
|
8
|
+
hasUserScript: boolean
|
|
9
|
+
hasInterceptor: boolean
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** Advisory follow-up when a browser remains after provider creation. */
|
|
13
|
+
export function postPublishBrowserNotes(
|
|
14
|
+
attached: AttachedBrowser | undefined,
|
|
15
|
+
options: BrowserRetestOptions,
|
|
16
|
+
): string[] {
|
|
17
|
+
if(!attached) {
|
|
18
|
+
return []
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const identity = `A "${attached.mode}" browser session is still attached`
|
|
22
|
+
+ (attached.browserSessionId
|
|
23
|
+
? ` (browserSessionId: ${attached.browserSessionId})`
|
|
24
|
+
: '')
|
|
25
|
+
const retest = 'Before disposing it, best-effort test the '
|
|
26
|
+
+ 'already-authenticated path only when this profile is still signed in: '
|
|
27
|
+
+ `navigate to initialUrl (${options.initialUrl}) without clearing cookies `
|
|
28
|
+
+ 'or forcing another login. '
|
|
29
|
+
+ (options.hasUserScript
|
|
30
|
+
? 'Use test_user_script with this version\'s script and initialUrl; '
|
|
31
|
+
: 'Navigate to initialUrl normally; ')
|
|
32
|
+
+ 'confirm every required target request still fires, then replay and '
|
|
33
|
+
+ 'prove each one when the existing capture has enough data. Check that '
|
|
34
|
+
+ 'the flow handles '
|
|
35
|
+
+ 'both a fresh sign-in and an already-signed-in landing from current page '
|
|
36
|
+
+ 'state, not only a login transition. This is advisory: skip it when no '
|
|
37
|
+
+ 'authenticated state is available. An inconclusive result must not block '
|
|
38
|
+
+ 'disposal or trigger a republish.'
|
|
39
|
+
const interceptor = options.hasInterceptor
|
|
40
|
+
? 'The attached authoring browser validates page behavior and the user '
|
|
41
|
+
+ 'script, but it does not prove compatibility with an in-page '
|
|
42
|
+
+ 'production interceptor. '
|
|
43
|
+
+ 'For the InApp SDK, verifier app, or browser extension, keep any '
|
|
44
|
+
+ 'client-specific interceptor verification as a separate follow-up.'
|
|
45
|
+
: undefined
|
|
46
|
+
const dispose = 'Ask the developer whether they want to keep the browser '
|
|
47
|
+
+ 'open '
|
|
48
|
+
+ 'for this retest or further edits. Only call dispose_browser after they '
|
|
49
|
+
+ 'confirm they are done.'
|
|
50
|
+
|
|
51
|
+
return [
|
|
52
|
+
identity + '. ' + retest,
|
|
53
|
+
...(interceptor ? [interceptor] : []),
|
|
54
|
+
dispose,
|
|
55
|
+
]
|
|
56
|
+
}
|