@reclaimprotocol/agent 0.1.26 → 0.1.28

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.
Files changed (92) hide show
  1. package/README.md +5 -5
  2. package/lib/authoring/tools.js +2 -1
  3. package/lib/consts.d.ts +1 -1
  4. package/lib/consts.js +1 -1
  5. package/lib/live/tunnel.d.ts +10 -7
  6. package/lib/live/tunnel.js +78 -56
  7. package/lib/mcp/api/operations.gen.d.ts +12 -8
  8. package/lib/mcp/api/operations.gen.js +378 -580
  9. package/lib/mcp/api/tools.d.ts +1 -1
  10. package/lib/mcp/instructions.js +7 -3
  11. package/lib/mcp/remap_errors.js +9 -5
  12. package/lib/mcp/start.js +1 -1
  13. package/lib/mcp/tools/agent/attach.d.ts +2 -2
  14. package/lib/mcp/tools/agent/attach.js +4 -0
  15. package/lib/mcp/tools/agent/build.d.ts +1 -1
  16. package/lib/mcp/tools/agent/live-view-url.d.ts +2 -0
  17. package/lib/mcp/tools/agent/live-view-url.js +11 -0
  18. package/lib/mcp/tools/agent/live-view.d.ts +4 -2
  19. package/lib/mcp/tools/agent/live-view.js +40 -54
  20. package/lib/mcp/tools/agent/propose.js +10 -4
  21. package/lib/mcp/tools/agent/publish.d.ts +1 -1
  22. package/lib/mcp/tools/agent/publish.js +49 -26
  23. package/lib/mcp/tools/agent/shared.js +0 -9
  24. package/lib/mcp/tools/authenticate/authenticate.d.ts +1 -1
  25. package/lib/mcp/tools/authenticate/build.d.ts +1 -1
  26. package/lib/mcp/tools/authenticate/builder-access.d.ts +1 -1
  27. package/lib/mcp/tools/verification/result.js +12 -13
  28. package/lib/old/login-cdp.d.ts +17 -6
  29. package/lib/old/login-cdp.js +28 -11
  30. package/lib/old/to-register.d.ts +6 -0
  31. package/lib/old/to-register.js +35 -5
  32. package/lib/old/tools/authenticate.d.ts +5 -4
  33. package/lib/old/tools/authenticate.js +9 -5
  34. package/lib/old/tools/publish.d.ts +2 -2
  35. package/lib/old/tools/publish.js +47 -21
  36. package/lib/proof/attestor-auth.d.ts +13 -0
  37. package/lib/proof/attestor-auth.js +16 -0
  38. package/lib/proof/attestor.d.ts +7 -4
  39. package/lib/proof/attestor.js +7 -4
  40. package/lib/proof/index.d.ts +1 -0
  41. package/lib/proof/index.js +1 -0
  42. package/lib/proof/pod-prover.d.ts +2 -0
  43. package/lib/proof/pod-prover.js +5 -5
  44. package/lib/provider/injection-doc.d.ts +2 -0
  45. package/lib/provider/injection-doc.js +7 -1
  46. package/lib/provider/post-publish-browser.d.ts +12 -0
  47. package/lib/provider/post-publish-browser.js +40 -0
  48. package/lib/provider/to-version.d.ts +19 -7
  49. package/lib/provider/to-version.js +67 -25
  50. package/lib/tools/registry.d.ts +1 -1
  51. package/lib/tools/registry.js +7 -4
  52. package/package.json +2 -2
  53. package/skill/SKILL.builder.md +134 -13
  54. package/skill/SKILL.common.md +30 -19
  55. package/skill/SKILL.old.md +22 -12
  56. package/skill/builder-mode.md +19 -9
  57. package/skill/community-contributions.md +108 -0
  58. package/skill/hash-validation.md +8 -5
  59. package/skill/interception.md +35 -22
  60. package/skill/old-mode.md +11 -5
  61. package/skill/user-script.md +41 -10
  62. package/src/authoring/tools.ts +2 -1
  63. package/src/consts.ts +1 -2
  64. package/src/live/tunnel.ts +112 -66
  65. package/src/mcp/api/operations.gen.ts +384 -584
  66. package/src/mcp/api/tools.ts +1 -1
  67. package/src/mcp/instructions.ts +7 -3
  68. package/src/mcp/remap_errors.ts +9 -7
  69. package/src/mcp/start.ts +1 -1
  70. package/src/mcp/tools/agent/attach.ts +6 -2
  71. package/src/mcp/tools/agent/build.ts +1 -1
  72. package/src/mcp/tools/agent/live-view-url.ts +13 -0
  73. package/src/mcp/tools/agent/live-view.ts +42 -54
  74. package/src/mcp/tools/agent/propose.ts +10 -4
  75. package/src/mcp/tools/agent/publish.ts +63 -28
  76. package/src/mcp/tools/agent/shared.ts +5 -0
  77. package/src/mcp/tools/authenticate/authenticate.ts +1 -1
  78. package/src/mcp/tools/authenticate/build.ts +1 -1
  79. package/src/mcp/tools/authenticate/builder-access.ts +1 -1
  80. package/src/mcp/tools/verification/result.ts +11 -13
  81. package/src/old/login-cdp.ts +38 -11
  82. package/src/old/to-register.ts +56 -7
  83. package/src/old/tools/authenticate.ts +9 -5
  84. package/src/old/tools/publish.ts +64 -22
  85. package/src/proof/attestor-auth.ts +33 -0
  86. package/src/proof/attestor.ts +14 -8
  87. package/src/proof/index.ts +4 -0
  88. package/src/proof/pod-prover.ts +7 -5
  89. package/src/provider/injection-doc.ts +9 -1
  90. package/src/provider/post-publish-browser.ts +56 -0
  91. package/src/provider/to-version.ts +104 -27
  92. package/src/tools/registry.ts +12 -9
@@ -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 the developer to
97
- * finish signing in, read the resulting Firebase ID token from page storage,
98
- * then clean up. If we had to launch a dedicated Chrome for this, we close the
99
- * whole browser afterwards; if we attached to a Chrome that was already up
100
- * (for example, the capture flow's), we only close the tab we opened so we
101
- * don't disrupt it.
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 cfg = new ConfigStore().read()?.browser_agent
120
- const mode = cfg?.attachMode ?? 'dedicated'
121
- const port = cfg?.port ?? 9222
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}. Launch Chrome ` +
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 switch to dedicated mode (attach_browser).',
159
+ '--user-data-dir, or use the dedicated local login mode.',
133
160
  )
134
161
  }
135
162
 
@@ -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
- if(versions.length === 0) {
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 `${hi.major}.${hi.minor}.${hi.patch + 1}`
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 Chrome via CDP:
9
- * it opens the devtools dashboard, waits for the developer to sign in, reads
10
- * the Firebase ID token straight from page storage, then closes the tab (or
11
- * the browser, if it launched one just for this). Two manual overrides remain:
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 Chrome via CDP, ' +
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. ' +
@@ -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 (auto-bumps the patch above the
74
- * highest existing version).
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 to it; '
123
- + 'the patch auto-bumps. You can only version a provider YOU own — '
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). What each value actually does: ' +
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
- if(attached) {
518
- notes.push(
519
- `A "${attached.mode}" browser session is still attached` +
520
- (attached.browserSessionId
521
- ? ` (browserSessionId: ${attached.browserSessionId})`
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 = bumpPatchString(
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 — pick the next patch above
635
- // the highest existing version. A provider you don't own (for example, a
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 = bumpPatchString(extractVersions(versionsResp))
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
+ }
@@ -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 nested proof
37
- * JWS a Consumer verifies against the attestor's key. Populated when
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 nested-proof construction. */
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 nested-proof verification. Set only when
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: { url: resolveAttestorUrl(opts.attestorUrl) },
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 a nested,
315
- // attestor-signed proof the Consumer verifies independently.
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 }
@@ -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,
@@ -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` only (see `@reclaimprotocol/client`'s `verifyNestedProof`),
30
- * so this hands back a {@link AttestorClaim} whose hex `claimSignature` the
31
- * shared `wrapProofAsJws` (app-side) turns into the nested result proof.
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. Full guide: how_it_works({ topic: "user-script" }).'
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
+ }