@reclaimprotocol/agent 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/lib/cdp/launcher.js +4 -3
  2. package/lib/consts.d.ts +68 -0
  3. package/lib/consts.js +134 -0
  4. package/lib/logger.js +2 -1
  5. package/lib/mcp/api/operations.gen.d.ts +11 -7
  6. package/lib/mcp/api/operations.gen.js +356 -199
  7. package/lib/mcp/server.d.ts +0 -6
  8. package/lib/mcp/server.js +0 -30
  9. package/lib/mcp/start.js +7 -7
  10. package/lib/mcp/tools/agent/build.d.ts +5 -9
  11. package/lib/mcp/tools/agent/build.js +5 -9
  12. package/lib/mcp/tools/agent/proof.js +2 -1
  13. package/lib/mcp/tools/agent/publish.js +166 -77
  14. package/lib/mcp/tools/authenticate/decrypt-key.d.ts +10 -23
  15. package/lib/mcp/tools/authenticate/decrypt-key.js +23 -26
  16. package/lib/mcp/tools/authenticate/eth-key.d.ts +0 -7
  17. package/lib/mcp/tools/authenticate/eth-key.js +5 -19
  18. package/lib/mcp/tools/authenticate/issue.js +1 -1
  19. package/lib/mcp/tools/verification/build.d.ts +1 -1
  20. package/lib/mcp/tools/verification/build.js +1 -1
  21. package/lib/mcp/tools/verification/result.d.ts +4 -5
  22. package/lib/mcp/tools/verification/result.js +17 -17
  23. package/lib/old/flag.d.ts +4 -1
  24. package/lib/old/flag.js +4 -3
  25. package/lib/old/identity.d.ts +0 -10
  26. package/lib/old/identity.js +8 -7
  27. package/lib/old/login-cdp.d.ts +0 -3
  28. package/lib/old/login-cdp.js +2 -4
  29. package/lib/old/to-register.d.ts +5 -3
  30. package/lib/old/to-register.js +20 -15
  31. package/lib/old/tools/authenticate.js +2 -1
  32. package/lib/old/tools/publish.js +97 -13
  33. package/lib/paths.js +2 -2
  34. package/lib/proof/attestor.js +2 -1
  35. package/lib/proof/proof-core.js +13 -2
  36. package/lib/provider/injection-doc.d.ts +1 -0
  37. package/lib/provider/injection-doc.js +17 -0
  38. package/lib/provider/match-redaction-warning.d.ts +25 -0
  39. package/lib/provider/match-redaction-warning.js +35 -0
  40. package/lib/provider/schema.d.ts +16 -0
  41. package/lib/provider/schema.js +11 -1
  42. package/lib/provider/to-version.d.ts +59 -7
  43. package/lib/provider/to-version.js +79 -28
  44. package/package.json +1 -1
  45. package/skill/builder-mode.md +8 -2
  46. package/skill/injection-api.md +42 -0
  47. package/skill/old-mode.md +4 -2
  48. package/src/cdp/launcher.ts +5 -4
  49. package/src/consts.ts +164 -0
  50. package/src/logger.ts +2 -1
  51. package/src/mcp/api/operations.gen.ts +358 -203
  52. package/src/mcp/server.ts +0 -46
  53. package/src/mcp/start.ts +13 -7
  54. package/src/mcp/tools/agent/build.ts +5 -9
  55. package/src/mcp/tools/agent/proof.ts +2 -1
  56. package/src/mcp/tools/agent/publish.ts +212 -94
  57. package/src/mcp/tools/authenticate/decrypt-key.ts +24 -29
  58. package/src/mcp/tools/authenticate/eth-key.ts +5 -25
  59. package/src/mcp/tools/authenticate/issue.ts +1 -1
  60. package/src/mcp/tools/verification/build.ts +1 -1
  61. package/src/mcp/tools/verification/result.ts +16 -17
  62. package/src/old/flag.ts +4 -3
  63. package/src/old/identity.ts +8 -7
  64. package/src/old/login-cdp.ts +2 -5
  65. package/src/old/to-register.ts +22 -16
  66. package/src/old/tools/authenticate.ts +2 -1
  67. package/src/old/tools/publish.ts +124 -20
  68. package/src/paths.ts +2 -2
  69. package/src/proof/attestor.ts +2 -1
  70. package/src/proof/proof-core.ts +13 -3
  71. package/src/provider/injection-doc.ts +21 -0
  72. package/src/provider/match-redaction-warning.ts +53 -0
  73. package/src/provider/schema.ts +23 -0
  74. package/src/provider/to-version.ts +137 -31
package/src/mcp/server.ts CHANGED
@@ -1,11 +1,4 @@
1
- import { Server } from '@modelcontextprotocol/sdk/server/index.js'
2
1
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
3
- import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
4
- import type { Tool } from '@modelcontextprotocol/sdk/types.js'
5
- import {
6
- CallToolRequestSchema,
7
- ListToolsRequestSchema,
8
- } from '@modelcontextprotocol/sdk/types.js'
9
2
  import type { JSONSchema } from 'zod/v4/core'
10
3
  import { toZod } from './api/to-zod.ts'
11
4
  import { remapErrorAsResponse } from './remap_errors.ts'
@@ -79,42 +72,3 @@ export function registerToolList(server: McpServer, tools: RegisteredTool[]) {
79
72
  )
80
73
  }
81
74
  }
82
-
83
- export class ReclaimMcpServer {
84
- #tools = new Map<string, RegisteredTool>()
85
- #server: Server
86
-
87
- constructor() {
88
- this.#server = new Server(
89
- { name: 'reclaim-agent', version: '0.1.0' },
90
- { capabilities: { tools: {} } },
91
- )
92
-
93
- this.#server.setRequestHandler(ListToolsRequestSchema, async() => ({
94
- // Double-cast: our `ToolDefinition` carries the same fields as the
95
- // SDK's `Tool` but the SDK's type is strict-narrowed and won't
96
- // accept ours without going through `unknown`.
97
- tools: [...this.#tools.values()].map(
98
- (t) => t.definition as unknown as Tool,
99
- ),
100
- }))
101
-
102
- this.#server.setRequestHandler(CallToolRequestSchema, async(req) => {
103
- const tool = this.#tools.get(req.params.name)
104
- if(!tool) {
105
- throw new Error(`unknown tool: ${req.params.name}`)
106
- }
107
-
108
- const result = await tool.handler(req.params.arguments ?? {})
109
- return { content: [{ type: 'text', text: JSON.stringify(result) }] }
110
- })
111
- }
112
-
113
- register(tool: RegisteredTool) {
114
- this.#tools.set(tool.definition.name, tool)
115
- }
116
-
117
- async listen(): Promise<void> {
118
- await this.#server.connect(new StdioServerTransport())
119
- }
120
- }
package/src/mcp/start.ts CHANGED
@@ -1,8 +1,14 @@
1
1
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
2
2
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
3
- import { DEFAULT_BASE_URL, ReclaimClient } from '@reclaimprotocol/client'
4
- import { DEFAULT_OLD_API_URL, ReclaimOldClient } from '../old/client.ts'
5
- import { useOldDevtools } from '../old/flag.ts'
3
+ import { ReclaimClient } from '@reclaimprotocol/client'
4
+ import {
5
+ agentDisabled,
6
+ apiToken,
7
+ apiUrl,
8
+ oldApiUrl,
9
+ useOldDevtools,
10
+ } from '../consts.ts'
11
+ import { ReclaimOldClient } from '../old/client.ts'
6
12
  import { resolveInitialIdentity } from '../old/identity.ts'
7
13
  import { OLD_MODE_INSTRUCTIONS } from '../old/instructions.ts'
8
14
  import { buildOldBackendTools } from '../old/tools/build.ts'
@@ -34,7 +40,7 @@ export async function startMcpServer() {
34
40
  // backend-independent and registered in both modes. Skipped in hosted
35
41
  // deployments — set RECLAIM_AGENT_DISABLED=1 to register only the API
36
42
  // surface.
37
- const agentToolsEnabled = process.env.RECLAIM_AGENT_DISABLED !== '1'
43
+ const agentToolsEnabled = !agentDisabled()
38
44
 
39
45
  if(oldMode) {
40
46
  // OLD-devtools mode (the default): the legacy backend is still in
@@ -42,7 +48,7 @@ export async function startMcpServer() {
42
48
  // are disabled; a small ReclaimOldClient serves authenticate / publish /
43
49
  // get_me_providers instead.
44
50
  const oldClient = new ReclaimOldClient({
45
- baseUrl: process.env.RECLAIM_OLD_API_URL || DEFAULT_OLD_API_URL,
51
+ baseUrl: oldApiUrl(),
46
52
  identity: resolveInitialIdentity(),
47
53
  })
48
54
 
@@ -55,9 +61,9 @@ export async function startMcpServer() {
55
61
  // NOTE: registerTools() is intentionally NOT called — generated tools off.
56
62
  } else {
57
63
  const client = new ReclaimClient({
58
- baseUrl: process.env.RECLAIM_API_URL || DEFAULT_BASE_URL,
64
+ baseUrl: apiUrl(),
59
65
  // Priority: explicit env var → cached session token → unauthenticated
60
- token: process.env.RECLAIM_API_TOKEN || readSession()?.token,
66
+ token: apiToken() || readSession()?.token,
61
67
  headers: { 'User-Agent': `${NAME}/${VERSION}` },
62
68
  })
63
69
 
@@ -1,12 +1,8 @@
1
1
  /**
2
2
  * Factory entry-point for embedding the reclaim-agent's MCP tools into
3
- * a host MCP server. Two consumers:
4
- *
5
- * - The agent's own standalone `src/index.ts` (uses `ReclaimMcpServer`,
6
- * the low-level wrapper around `@modelcontextprotocol/sdk`'s Server).
7
- * - `@reclaimprotocol/client`'s MCP server (uses `McpServer`, the
8
- * high-level SDK wrapper). Calls `registerAgentTools` to fold the
9
- * agent's 16 tools alongside its auto-generated API tools.
3
+ * a host MCP server via `registerToolList` (server.ts) on the high-level
4
+ * `@modelcontextprotocol/sdk` `McpServer`. Folds the agent's tools
5
+ * alongside the auto-generated API tools.
10
6
  *
11
7
  * Shared state — Chrome attach state, capture store, secret store — is
12
8
  * built once per server and threaded through every tool factory.
@@ -45,8 +41,8 @@ function buildAgentState(): AgentState {
45
41
  }
46
42
  }
47
43
 
48
- /** Build the flat list of `RegisteredTool` entries. Useful when the host
49
- * uses our internal `ReclaimMcpServer` interface (`.register(tool)`).
44
+ /** Build the flat list of `RegisteredTool` entries, ready for
45
+ * `registerToolList`.
50
46
  * Pass `client` to include the builder `create_provider_version_from_capture`;
51
47
  * omit it (e.g. OLD-devtools mode) to register only the backend-independent
52
48
  * capture/proof tools — old mode supplies its own publish tool. */
@@ -1,5 +1,6 @@
1
1
  import { mkdirSync, writeFileSync } from 'node:fs'
2
2
  import { join } from 'node:path'
3
+ import { agentTraceEnabled } from '../../../consts.ts'
3
4
  import { reclaimHomeDirPath } from '../../../paths.ts'
4
5
  import {
5
6
  runAttestorProof,
@@ -237,7 +238,7 @@ export function proofTool(secrets: SecretStore): RegisteredTool {
237
238
  // when investigating proof failures and the file at runLogPath
238
239
  // will carry the input provider, sentParams, full attestor log
239
240
  // stream, and result.
240
- if(process.env['RECLAIM_AGENT_TRACE'] === '1') {
241
+ if(agentTraceEnabled()) {
241
242
  const runLogPath = writeProofRunDump({
242
243
  provider,
243
244
  hasSecretParamsRef: secretParamsRef !== undefined,
@@ -1,39 +1,158 @@
1
1
  import type { ReclaimClient } from '@reclaimprotocol/client'
2
+ import type { JSONSchema } from 'zod/v4/core'
3
+ import { INJECTION_GUIDE } from '../../../provider/injection-doc.ts'
4
+ import {
5
+ countsFor,
6
+ matchRedactionWarnings,
7
+ } from '../../../provider/match-redaction-warning.ts'
2
8
  import type { ReclaimProvider } from '../../../provider/schema.ts'
9
+ import type { AllowedJsRequest } from '../../../provider/to-version.ts'
3
10
  import {
4
11
  nextVersion,
5
12
  providerToCreateVersionRequest,
6
13
  } from '../../../provider/to-version.ts'
7
14
  import { defineTool, type RegisteredTool } from '../../server.ts'
8
15
 
16
+ /** Schemas shared by the top-level `provider`/`providers` and the lighter
17
+ * `allowedJsRequests` drafts — keeps the request shape from drifting between
18
+ * them. */
19
+ const RESPONSE_MATCHES_SCHEMA: JSONSchema.Schema = {
20
+ type: 'array',
21
+ description: 'What the attestor checks on the revealed response slice.',
22
+ items: {
23
+ type: 'object',
24
+ properties: {
25
+ type: { type: 'string', enum: ['regex', 'contains'] },
26
+ value: { type: 'string' },
27
+ },
28
+ required: ['type', 'value'],
29
+ },
30
+ }
31
+
32
+ const RESPONSE_REDACTIONS_SCHEMA: JSONSchema.Schema = {
33
+ type: 'array',
34
+ description:
35
+ 'Which portions of the response the prover reveals ' +
36
+ '(xPath / jsonPath / regex; hash to OPRF the value). Paired with ' +
37
+ 'responseMatches by index — keep them aligned 1-to-1. If you list more ' +
38
+ 'redactions than matches, the tool pads matches with no-op optional ' +
39
+ 'entries so older index-paired claim creators stay happy.',
40
+ items: {
41
+ type: 'object',
42
+ properties: {
43
+ xPath: { type: 'string' },
44
+ jsonPath: { type: 'string' },
45
+ regex: { type: 'string' },
46
+ hash: { type: 'string', enum: ['oprf', 'oprf-mpc', 'oprf-raw'] },
47
+ },
48
+ },
49
+ }
50
+
51
+ /** Properties every drafted request shares (top-level providers and the
52
+ * injected `allowedJsRequests`). */
53
+ const REQUEST_DRAFT_PROPERTIES: Record<string, JSONSchema.Schema> = {
54
+ url: {
55
+ type: 'string',
56
+ description:
57
+ 'Exact API URL the attestor replays, with {{param}} placeholders ' +
58
+ 'for any templated values.',
59
+ },
60
+ method: { type: 'string', enum: ['GET', 'POST', 'PUT', 'PATCH'] },
61
+ body: { type: 'string', description: 'Request body template, if any.' },
62
+ responseMatches: RESPONSE_MATCHES_SCHEMA,
63
+ responseRedactions: RESPONSE_REDACTIONS_SCHEMA,
64
+ }
65
+
66
+ /** A full provider draft (from propose_provider), passed through verbatim. */
67
+ const PROVIDER_SCHEMA: JSONSchema.Schema = {
68
+ type: 'object',
69
+ description:
70
+ 'A `provider` object from propose_provider, passed through verbatim — ' +
71
+ 'the tool builds the version body (the requests[] entry etc.) for you.',
72
+ properties: {
73
+ name: { type: 'string', description: 'Verification name.' },
74
+ ...REQUEST_DRAFT_PROPERTIES,
75
+ headers: {
76
+ type: 'object',
77
+ description:
78
+ 'Request headers from the capture. Secret headers are stripped ' +
79
+ 'before persistence; the client re-supplies them at runtime.',
80
+ },
81
+ paramValues: {
82
+ type: 'object',
83
+ description:
84
+ 'Real values for the {{param}} placeholders, e.g. ' +
85
+ '{ "username": "Syed" }.',
86
+ },
87
+ },
88
+ required: ['name', 'url', 'method', 'responseMatches'],
89
+ }
90
+
91
+ /** One `allowedJsRequests` template: a request the injection script may fire,
92
+ * plus pagination/optionality flags. */
93
+ const ALLOWED_JS_REQUEST_SCHEMA: JSONSchema.Schema = {
94
+ type: 'object',
95
+ properties: {
96
+ ...REQUEST_DRAFT_PROPERTIES,
97
+ multiple: {
98
+ type: 'boolean',
99
+ description:
100
+ 'Allow the template to match many requests (e.g. pagination). ' +
101
+ 'Default true.',
102
+ },
103
+ required: {
104
+ type: 'boolean',
105
+ description:
106
+ 'Fail validation if no request matches this template. Default true.',
107
+ },
108
+ },
109
+ required: ['url', 'method'],
110
+ }
111
+
112
+ interface PublishArgs {
113
+ providerId: string
114
+ /** Single-provider back-compat input; normalized to `[provider]`. */
115
+ provider?: ReclaimProvider
116
+ /** Multi-request input — one version carrying every request. */
117
+ providers?: ReclaimProvider[]
118
+ initialUrl: string
119
+ notes?: string
120
+ version?: { major: number, minor: number, patch: number }
121
+ jsUserScripts?: string
122
+ allowedJsRequests?: AllowedJsRequest[]
123
+ }
124
+
9
125
  export function createVersionFromCaptureTool(
10
126
  client: ReclaimClient,
11
127
  ): RegisteredTool {
12
- return defineTool<{
13
- providerId: string
14
- provider: ReclaimProvider
15
- initialUrl: string
16
- notes?: string
17
- version?: { major: number, minor: number, patch: number }
18
- }>(
128
+ return defineTool<PublishArgs>(
19
129
  {
20
130
  name: 'create_provider_version_from_capture',
21
131
  description:
22
- 'Persist a drafted ReclaimProvider as a verification version. ' +
23
- 'Call this after run_proof succeeds, passing the `provider` object ' +
24
- 'from propose_provider VERBATIM. This tool builds the full version ' +
132
+ 'Persist drafted ReclaimProvider(s) as a verification version. ' +
133
+ 'Call this after run_proof succeeds, passing the `provider` ' +
134
+ 'object(s) from propose_provider VERBATIM. This tool builds the ' +
25
135
  'body (the requests[] array, webSettings, etc.) for you — do NOT ' +
26
136
  'hand-construct that shape or call create_provider_version directly. ' +
137
+ 'For a MULTI-REQUEST provider, pass `providers: [...]` (each entry a ' +
138
+ 'proven draft) — every entry becomes one request in the version, and ' +
139
+ 'all are required at verification time. Pass a single `provider` ' +
140
+ 'for the one-request case (exactly one of `provider`/`providers`). ' +
141
+ 'For data gated behind login/navigation, supply `jsUserScripts` (a ' +
142
+ 'page-injection script that drives the page to where the request ' +
143
+ 'fires) and/or `allowedJsRequests` (extra requests the script may ' +
144
+ 'fire, e.g. pagination). ' +
27
145
  'Omit `version` to have the tool auto-select the next available ' +
28
146
  'version (it lists existing versions and bumps past the highest). ' +
29
147
  'The version lands on the draft branch — to go live, call ' +
30
148
  '`publish_provider_version` (PRIVATE) or ' +
31
149
  '`submit_provider_version_for_review` (PUBLIC) afterwards. ' +
32
150
  '`requiredContext` is derived from any {{context.<name>}} ' +
33
- 'placeholders and returned on the created version — when ' +
34
- 'present, tell the dev verbatim which context fields consumers ' +
35
- 'MUST send when creating sessions (verification matches those ' +
36
- 'values exactly; a missing field is a 400 at session create). ' +
151
+ 'placeholders across ALL requests (plus injected requests / script) ' +
152
+ 'and returned on the created version — when present, tell the dev ' +
153
+ 'verbatim which context fields consumers MUST send when creating ' +
154
+ 'sessions (verification matches those values exactly; a missing ' +
155
+ 'field is a 400 at session create). ' +
37
156
  'Refine types/descriptions via `update_provider_version` while ' +
38
157
  'still on draft.',
39
158
  inputSchema: {
@@ -43,77 +162,15 @@ export function createVersionFromCaptureTool(
43
162
  type: 'string',
44
163
  description: 'UUID of the verification to publish the version on.',
45
164
  },
46
- provider: {
47
- type: 'object',
165
+ provider: PROVIDER_SCHEMA,
166
+ providers: {
167
+ type: 'array',
48
168
  description:
49
- 'The `provider` object from propose_provider, passed ' +
50
- 'through verbatim — the tool builds the version body ' +
51
- '(the requests[] array etc.) for you.',
52
- properties: {
53
- name: { type: 'string', description: 'Verification name.' },
54
- url: {
55
- type: 'string',
56
- description:
57
- 'Exact API URL the attestor replays, with {{param}} ' +
58
- 'placeholders for any templated values.',
59
- },
60
- method: {
61
- type: 'string',
62
- enum: ['GET', 'POST', 'PUT', 'PATCH'],
63
- },
64
- headers: {
65
- type: 'object',
66
- description:
67
- 'Request headers from the capture. Secret headers are ' +
68
- 'stripped before persistence; the client re-supplies them ' +
69
- 'at runtime.',
70
- },
71
- body: {
72
- type: 'string',
73
- description: 'Request body template, if any.',
74
- },
75
- responseMatches: {
76
- type: 'array',
77
- description:
78
- 'What the attestor checks on the revealed response slice.',
79
- items: {
80
- type: 'object',
81
- properties: {
82
- type: { type: 'string', enum: ['regex', 'contains'] },
83
- value: { type: 'string' },
84
- },
85
- required: ['type', 'value'],
86
- },
87
- },
88
- responseRedactions: {
89
- type: 'array',
90
- description:
91
- 'Which portions of the response the prover reveals ' +
92
- '(xPath / jsonPath / regex; hash to OPRF the value). ' +
93
- 'Paired with responseMatches by index — the tool folds ' +
94
- 'each one into the matching entry\'s `extract` in the ' +
95
- 'persisted version, so keep them aligned 1-to-1.',
96
- items: {
97
- type: 'object',
98
- properties: {
99
- xPath: { type: 'string' },
100
- jsonPath: { type: 'string' },
101
- regex: { type: 'string' },
102
- hash: {
103
- type: 'string',
104
- enum: ['oprf', 'oprf-mpc', 'oprf-raw'],
105
- },
106
- },
107
- },
108
- },
109
- paramValues: {
110
- type: 'object',
111
- description:
112
- 'Real values for the {{param}} placeholders, ' +
113
- 'e.g. { "username": "Syed" }.',
114
- },
115
- },
116
- required: ['name', 'url', 'method', 'responseMatches'],
169
+ 'Multiple proven drafts → one multi-request version. Each ' +
170
+ 'entry has the same shape as `provider`. Pass this OR ' +
171
+ '`provider`, not both.',
172
+ items: PROVIDER_SCHEMA,
173
+ minItems: 1,
117
174
  },
118
175
  initialUrl: {
119
176
  type: 'string',
@@ -139,25 +196,86 @@ export function createVersionFromCaptureTool(
139
196
  },
140
197
  required: ['major', 'minor', 'patch'],
141
198
  },
199
+ jsUserScripts: {
200
+ type: 'string',
201
+ description:
202
+ 'Plain JavaScript injected into the provider page before every ' +
203
+ 'page load (stored as webSettings.jsUserScripts). Use standard ' +
204
+ 'browser APIs (window.location, element.click(), forms) to ' +
205
+ 'drive login/navigation so the target request fires — the ' +
206
+ 'client already intercepts page requests, and window.Reclaim ' +
207
+ 'is only for rare edge cases, not navigation. The Builder ' +
208
+ 'verification client installs this to run before every page ' +
209
+ 'load (before navigating to initialUrl), matching old-devtool ' +
210
+ 'customInjection; window.Reclaim itself is not yet exposed ' +
211
+ 'there, so prefer plain browser JS. Guide:\n\n' +
212
+ INJECTION_GUIDE,
213
+ },
214
+ allowedJsRequests: {
215
+ type: 'array',
216
+ description:
217
+ 'Extra requests the injection script is allowed to fire ' +
218
+ '(e.g. paginated API calls), as RequestSelectionTemplates. ' +
219
+ '`multiple`/`required` default to true.',
220
+ items: ALLOWED_JS_REQUEST_SCHEMA,
221
+ },
142
222
  },
143
- required: ['providerId', 'provider', 'initialUrl'],
223
+ required: ['providerId', 'initialUrl'],
144
224
  },
145
225
  },
146
226
  async(args) => {
227
+ // Exactly one of `provider` / `providers` — normalize to an array.
228
+ const hasMany = Array.isArray(args.providers) && args.providers.length > 0
229
+ const hasOne = !!args.provider
230
+ if(hasMany === hasOne) {
231
+ throw new Error(
232
+ 'Pass exactly one of `provider` (a single draft) or ' +
233
+ '`providers` (an array of 1+ drafts).',
234
+ )
235
+ }
236
+
237
+ const providers = hasMany ? args.providers! : [args.provider!]
238
+
147
239
  // Auto-pick the next version if not explicit
148
240
  const ver = args.version ?? (await nextVersion(client, args.providerId))
149
241
 
150
- const body = providerToCreateVersionRequest(
151
- args.provider,
152
- args.initialUrl,
153
- args.notes,
154
- ver,
155
- )
156
- const result = (await client.call('CreateProviderVersion', {
242
+ const body = providerToCreateVersionRequest(providers, args.initialUrl, {
243
+ notes: args.notes,
244
+ version: ver,
245
+ jsUserScripts: args.jsUserScripts,
246
+ allowedJsRequests: args.allowedJsRequests,
247
+ })
248
+ const result = await client.call('CreateProviderVersion', {
157
249
  params: { providerId: args.providerId },
158
250
  body,
159
- }))
160
- return result.data
251
+ })
252
+
253
+ const created = result.data
254
+
255
+ // Aggregate non-blocking advisories for the dev — nothing here blocks
256
+ // or alters what was published; the author keeps full control.
257
+ const notes: string[] = []
258
+
259
+ // Match/redaction advisory across every request (+ injected requests).
260
+ const requestCounts = [
261
+ ...providers.map((p, i) => countsFor(`Request ${i + 1}`, p)),
262
+ ...(args.allowedJsRequests ?? []).map(
263
+ (r, i) => countsFor(`Injected request ${i + 1}`, r),
264
+ ),
265
+ ]
266
+ notes.push(...matchRedactionWarnings(requestCounts))
267
+
268
+ // Let the dev know how/when the script runs (and the window.Reclaim gap).
269
+ if(args.jsUserScripts?.trim()) {
270
+ notes.push(
271
+ 'jsUserScripts will run in the Builder verification client before ' +
272
+ 'every page load (before navigating to initialUrl), matching ' +
273
+ 'old-devtool customInjection. window.Reclaim is not yet exposed ' +
274
+ 'there, so prefer plain browser JS for navigation/interaction.',
275
+ )
276
+ }
277
+
278
+ return notes.length ? { ...created, _notes: notes } : created
161
279
  },
162
280
  )
163
281
  }
@@ -1,19 +1,15 @@
1
1
  /**
2
- * Optional X25519 decryption key — only needed when testing ENCRYPTED callback
3
- * payloads in the consumer role. Resolution is file-only (no env var, no path
4
- * override, no `~/.reclaim` caching):
2
+ * Optional eth decryption key — only needed when testing ENCRYPTED callback
3
+ * payloads in the consumer role. The org's results are ECIES-encrypted to its
4
+ * eth public key; this is the matching eth PRIVATE key (`0x`-hex). Resolution:
5
5
  *
6
- * 1. An X25519 PKCS#8 PEM file named `reclaim-decrypt.pem` in the project
7
- * root. Mint it with openssl:
8
- *
9
- * openssl genpkey -algorithm X25519 -out reclaim-decrypt.pem
10
- *
11
- * 2. Absent — plaintext-only testing (no decryption key; encrypted
6
+ * 1. The `RECLAIM_DECRYPT_KEY` env var (a `0x`-prefixed 32-byte hex key), or
7
+ * 2. A file named `reclaim-decrypt.key` in the project root holding that hex.
8
+ * 3. Absent — plaintext-only testing (no decryption key; encrypted
12
9
  * deliveries surface a clear error).
13
10
  *
14
- * The PEM *content* is loaded via the client's `loadDecryptionKey` (jose
15
- * `ECDH-ES`) and passed straight through as `ReclaimConfig.credential` / to
16
- * `decryptCallback`.
11
+ * The hex is loaded via the client's `loadDecryptionKey` and passed straight
12
+ * through as `ReclaimConfig.credential` / to `decryptCallback`.
17
13
  */
18
14
  import {
19
15
  type DecryptionKey,
@@ -23,34 +19,33 @@ import { existsSync, readFileSync } from 'node:fs'
23
19
  import { join } from 'node:path'
24
20
  import { findProjectRoot } from '../../../paths.ts'
25
21
 
26
- /** Default X25519 PEM filename the agent looks for in the project root. */
27
- export const DEFAULT_DECRYPT_PEM = 'reclaim-decrypt.pem'
28
-
29
- /** Resolve the decrypt-PEM path (the default file in the project root). */
30
- export function decryptPemPath(
31
- projectDir = findProjectRoot(process.cwd()),
32
- ): string {
33
- return join(projectDir, DEFAULT_DECRYPT_PEM)
34
- }
22
+ /** Default eth decrypt-key filename the agent looks for in the project root. */
23
+ const DEFAULT_DECRYPT_KEY_FILE = 'reclaim-decrypt.key'
35
24
 
36
25
  /**
37
- * Read the raw decryption-key PEM string from the default file, without loading
38
- * it. Returns `undefined` when the file is absent (plaintext-only).
26
+ * Read the raw eth decryption-key hex — from `RECLAIM_DECRYPT_KEY` or the
27
+ * default file — without loading it. Returns `undefined` when neither is
28
+ * present (plaintext-only).
39
29
  */
40
- export function resolveDecryptKeyInput(
30
+ function resolveDecryptKeyInput(
41
31
  projectDir = findProjectRoot(process.cwd()),
42
32
  ): string | undefined {
43
- const pemPath = decryptPemPath(projectDir)
44
- if(existsSync(pemPath)) {
45
- return readFileSync(pemPath, 'utf8')
33
+ const fromEnv = process.env.RECLAIM_DECRYPT_KEY?.trim()
34
+ if(fromEnv) {
35
+ return fromEnv
36
+ }
37
+
38
+ const keyPath = join(projectDir, DEFAULT_DECRYPT_KEY_FILE)
39
+ if(existsSync(keyPath)) {
40
+ return readFileSync(keyPath, 'utf8').trim()
46
41
  }
47
42
 
48
43
  return undefined
49
44
  }
50
45
 
51
46
  /**
52
- * Load the optional decryption key. Returns `undefined` when the PEM file is
53
- * absent (the consumer then handles only plaintext deliveries).
47
+ * Load the optional decryption key. Returns `undefined` when no key is
48
+ * configured (the consumer then handles only plaintext deliveries).
54
49
  */
55
50
  export async function resolveDecryptionKey(
56
51
  projectDir = findProjectRoot(process.cwd()),
@@ -12,11 +12,12 @@
12
12
  * Generated/imported keys are written to the project `.env` (mode 0600, and the
13
13
  * file is added to `.gitignore`). NO `~/.reclaim` caching. This is a LOCAL
14
14
  * signing key only — unrelated to org auth (the org secret) or encryption
15
- * (the X25519 decrypt key).
15
+ * (the eth decrypt key).
16
16
  */
17
17
  import { deriveFromPrivateKey } from '@reclaimprotocol/client'
18
18
  import { existsSync, readFileSync, writeFileSync } from 'node:fs'
19
19
  import { isAbsolute, join } from 'node:path'
20
+ import { privateKey, privateKeyFile } from '../../../consts.ts'
20
21
  import { findProjectRoot } from '../../../paths.ts'
21
22
 
22
23
  const ENV_VAR = 'RECLAIM_PRIVATE_KEY'
@@ -65,10 +66,10 @@ function readDotenvKey(path: string): string | undefined {
65
66
  }
66
67
 
67
68
  /** The explicit raw-hex key-file path, if `RECLAIM_PRIVATE_KEY_FILE` is set. */
68
- export function ethKeyFilePath(
69
+ function ethKeyFilePath(
69
70
  projectDir = findProjectRoot(process.cwd()),
70
71
  ): string | undefined {
71
- const p = process.env.RECLAIM_PRIVATE_KEY_FILE
72
+ const p = privateKeyFile()
72
73
  if(!p) {
73
74
  return undefined
74
75
  }
@@ -97,7 +98,7 @@ function resolved(
97
98
  export function resolveEthKey(
98
99
  projectDir = findProjectRoot(process.cwd()),
99
100
  ): ResolvedEthKey | undefined {
100
- const env = process.env.RECLAIM_PRIVATE_KEY
101
+ const env = privateKey()
101
102
  if(env?.trim()) {
102
103
  return resolved(normalizeEthHex(env), 'env')
103
104
  }
@@ -120,27 +121,6 @@ export function resolveEthKey(
120
121
  return undefined
121
122
  }
122
123
 
123
- /**
124
- * Resolve the eth proof-owner private key (0x-hex) or throw a clear, actionable
125
- * error. Used by the proof flow that can't proceed without a signing key.
126
- */
127
- export function requireEthPrivateKey(
128
- projectDir = findProjectRoot(process.cwd()),
129
- ): string {
130
- const r = resolveEthKey(projectDir)
131
- if(!r) {
132
- throw new Error(
133
- 'No eth proof-owner key found. Set RECLAIM_PRIVATE_KEY (0x-hex), add a '
134
- + `RECLAIM_PRIVATE_KEY=0x… line to ${join(projectDir, ENV_FILE)}, or `
135
- + 'point RECLAIM_PRIVATE_KEY_FILE at a file holding the raw hex. Run '
136
- + 'issue_credentials to generate one, or import_credentials to use a '
137
- + 'key you already hold.',
138
- )
139
- }
140
-
141
- return r.privateKey
142
- }
143
-
144
124
  /** Append `entry` to the project `.gitignore` if not already present. */
145
125
  function ensureGitignored(projectDir: string, entry: string): void {
146
126
  const path = join(projectDir, '.gitignore')
@@ -13,7 +13,7 @@ export function issueTool(): RegisteredTool {
13
13
  'the project `.env` as RECLAIM_PRIVATE_KEY=0x… (mode 0600; `.env` ' +
14
14
  'added to .gitignore). This is a LOCAL signing key only — it is NOT ' +
15
15
  'registered with any server and is unrelated to org auth (the org ' +
16
- 'secret) or encryption (the X25519 decrypt key). ALWAYS confirm with ' +
16
+ 'secret) or encryption (the eth decrypt key). ALWAYS confirm with ' +
17
17
  'the dev before generating a new key. After writing, run_proof picks ' +
18
18
  'it up automatically via resolve_owner_key. The private key is never ' +
19
19
  'returned in the response.',
@@ -5,7 +5,7 @@ import { verificationResultTool } from './result.ts'
5
5
  * Builder-mode verification tools: higher-level orchestrations over the
6
6
  * generated API surface. `get_verification_result` authenticates with the org
7
7
  * token, checks status, pulls the stored payload, and decrypts it locally with
8
- * the optional X25519 key — a flow the generated raw-API tools can't express
8
+ * the optional eth key — a flow the generated raw-API tools can't express
9
9
  * (the decryption key never leaves the consumer). Diagnosis is left to the
10
10
  * agent over the generated tools (`get_verification_session`,
11
11
  * `list_verification_events`, `list_verification_deliveries`), whose