@reclaimprotocol/agent 0.1.1-dev.1 → 0.1.2
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 +1 -1
- package/lib/cdp/capture-engine.d.ts +66 -0
- package/lib/cdp/capture-engine.js +153 -0
- package/lib/cdp/capture.d.ts +9 -20
- package/lib/cdp/capture.js +14 -138
- package/lib/cdp/index.d.ts +3 -0
- package/lib/cdp/index.js +6 -0
- package/lib/cdp/launcher.js +4 -3
- package/lib/config/store.d.ts +7 -6
- package/lib/config/store.js +2 -2
- package/lib/consts.d.ts +68 -0
- package/lib/consts.js +134 -0
- package/lib/index.d.ts +2 -2
- package/lib/index.js +1 -1
- package/lib/logger.js +2 -1
- package/lib/mcp/api/operations.gen.d.ts +100 -48
- package/lib/mcp/api/operations.gen.js +1129 -194
- package/lib/mcp/api/to-zod.d.ts +24 -0
- package/lib/mcp/api/to-zod.js +94 -0
- package/lib/mcp/api/tools.js +1 -1
- package/lib/mcp/credentials/authenticate.d.ts +3 -0
- package/lib/mcp/{tools/credentials → credentials}/authenticate.js +3 -3
- package/lib/mcp/credentials/build.d.ts +5 -0
- package/lib/mcp/credentials/build.js +18 -0
- package/lib/mcp/{tools/credentials → credentials}/cache.d.ts +9 -9
- package/lib/mcp/{tools/credentials → credentials}/cache.js +36 -19
- package/lib/mcp/credentials/import.d.ts +2 -0
- package/lib/mcp/{tools/credentials → credentials}/import.js +2 -2
- package/lib/mcp/credentials/issue.d.ts +2 -0
- package/lib/mcp/{tools/credentials → credentials}/issue.js +4 -4
- package/lib/mcp/credentials/keypair.d.ts +17 -0
- package/lib/mcp/credentials/keypair.js +48 -0
- package/lib/mcp/credentials/link.d.ts +3 -0
- package/lib/mcp/credentials/link.js +107 -0
- package/lib/mcp/{tools/credentials → credentials}/list-cached.d.ts +1 -1
- package/lib/mcp/{tools/credentials → credentials}/list-cached.js +5 -5
- package/lib/mcp/credentials/owner-key.d.ts +23 -0
- package/lib/mcp/credentials/owner-key.js +58 -0
- package/lib/mcp/credentials/paths.d.ts +12 -0
- package/lib/mcp/credentials/paths.js +35 -0
- package/lib/mcp/credentials/poll.d.ts +27 -0
- package/lib/mcp/credentials/poll.js +61 -0
- package/lib/mcp/{tools/credentials → credentials}/resolve-owner-key.d.ts +1 -1
- package/lib/mcp/{tools/credentials → credentials}/resolve-owner-key.js +2 -2
- package/lib/mcp/credentials/session-cache.d.ts +14 -0
- package/lib/mcp/credentials/session-cache.js +42 -0
- package/lib/mcp/{tools/credentials → credentials}/status.d.ts +1 -1
- package/lib/mcp/credentials/status.js +76 -0
- package/lib/mcp/instructions.d.ts +1 -0
- package/lib/mcp/instructions.js +11 -0
- package/lib/mcp/register.d.ts +38 -0
- package/lib/mcp/register.js +80 -0
- package/lib/mcp/server.d.ts +2 -8
- package/lib/mcp/server.js +3 -33
- package/lib/mcp/start.js +17 -11
- package/lib/mcp/tools/agent/build.d.ts +5 -9
- package/lib/mcp/tools/agent/build.js +7 -24
- package/lib/mcp/tools/agent/index.d.ts +2 -0
- package/lib/mcp/tools/agent/index.js +3 -0
- package/lib/mcp/tools/agent/proof.js +3 -7
- package/lib/mcp/tools/agent/propose.js +13 -2
- package/lib/mcp/tools/agent/publish.js +172 -75
- package/lib/mcp/tools/agent/replay.js +0 -5
- package/lib/mcp/tools/analyze.d.ts +3 -0
- package/lib/mcp/tools/analyze.js +26 -0
- package/lib/mcp/tools/attach.d.ts +11 -0
- package/lib/mcp/tools/attach.js +72 -0
- package/lib/mcp/tools/authenticate/authenticate.js +107 -0
- package/lib/mcp/tools/authenticate/build.d.ts +15 -0
- package/lib/mcp/tools/authenticate/build.js +28 -0
- package/lib/mcp/tools/authenticate/decrypt-key.d.ts +19 -0
- package/lib/mcp/tools/authenticate/decrypt-key.js +46 -0
- package/lib/mcp/tools/authenticate/eth-key.d.ts +31 -0
- package/lib/mcp/tools/authenticate/eth-key.js +116 -0
- package/lib/mcp/tools/authenticate/import.js +57 -0
- package/lib/mcp/tools/authenticate/index.d.ts +2 -0
- package/lib/mcp/tools/authenticate/index.js +4 -0
- package/lib/mcp/tools/authenticate/issue.js +42 -0
- package/lib/mcp/tools/authenticate/resolve-owner-key.d.ts +13 -0
- package/lib/mcp/tools/authenticate/resolve-owner-key.js +53 -0
- package/lib/mcp/tools/capture.d.ts +10 -0
- package/lib/mcp/tools/capture.js +142 -0
- package/lib/mcp/tools/eval.d.ts +5 -0
- package/lib/mcp/tools/eval.js +127 -0
- package/lib/mcp/tools/find.d.ts +3 -0
- package/lib/mcp/tools/find.js +60 -0
- package/lib/mcp/tools/index.d.ts +3 -0
- package/lib/mcp/tools/index.js +6 -0
- package/lib/mcp/tools/owner-key.d.ts +2 -0
- package/lib/mcp/tools/owner-key.js +63 -0
- package/lib/mcp/tools/proof.d.ts +3 -0
- package/lib/mcp/tools/proof.js +214 -0
- package/lib/mcp/tools/propose.d.ts +9 -0
- package/lib/mcp/tools/propose.js +75 -0
- package/lib/mcp/tools/publish.d.ts +3 -0
- package/lib/mcp/tools/publish.js +59 -0
- package/lib/mcp/tools/replay.d.ts +3 -0
- package/lib/mcp/tools/replay.js +46 -0
- package/lib/mcp/tools/session.d.ts +10 -0
- package/lib/mcp/tools/session.js +29 -0
- package/lib/mcp/tools/verification/build.d.ts +12 -0
- package/lib/mcp/tools/verification/build.js +14 -0
- package/lib/mcp/tools/verification/diagnose.d.ts +11 -0
- package/lib/mcp/tools/verification/diagnose.js +132 -0
- package/lib/mcp/tools/verification/index.d.ts +1 -0
- package/lib/mcp/tools/verification/index.js +2 -0
- package/lib/mcp/tools/verification/result.d.ts +11 -0
- package/lib/mcp/tools/verification/result.js +126 -0
- package/lib/mcp/verifications/build.d.ts +5 -0
- package/lib/mcp/verifications/build.js +14 -0
- package/lib/mcp/verifications/create.d.ts +3 -0
- package/lib/mcp/verifications/create.js +57 -0
- package/lib/mcp/verifications/find.d.ts +3 -0
- package/lib/mcp/verifications/find.js +37 -0
- package/lib/mcp/verifications/get.d.ts +3 -0
- package/lib/mcp/verifications/get.js +26 -0
- package/lib/mcp/verifications/update.d.ts +3 -0
- package/lib/mcp/verifications/update.js +78 -0
- package/lib/old/client.d.ts +3 -0
- package/lib/old/flag.d.ts +4 -1
- package/lib/old/flag.js +4 -3
- package/lib/old/identity.d.ts +0 -10
- package/lib/old/identity.js +8 -7
- package/lib/old/instructions.d.ts +0 -6
- package/lib/old/instructions.js +6 -24
- package/lib/old/login-cdp.d.ts +0 -3
- package/lib/old/login-cdp.js +2 -4
- package/lib/old/to-register.d.ts +3 -0
- package/lib/old/to-register.js +32 -7
- package/lib/old/tools/authenticate.js +2 -1
- package/lib/old/tools/publish.js +71 -10
- package/lib/paths.js +2 -2
- package/lib/proof/attestor.d.ts +33 -1
- package/lib/proof/attestor.js +67 -64
- package/lib/proof/index.d.ts +5 -0
- package/lib/proof/index.js +6 -0
- package/lib/proof/proof-core.d.ts +35 -0
- package/lib/proof/proof-core.js +84 -0
- package/lib/provider/index.d.ts +5 -0
- package/lib/provider/index.js +9 -0
- package/lib/provider/injection-doc.d.ts +1 -0
- package/lib/provider/injection-doc.js +17 -0
- package/lib/provider/match-redaction-warning.d.ts +25 -0
- package/lib/provider/match-redaction-warning.js +35 -0
- package/lib/provider/schema.d.ts +16 -0
- package/lib/provider/schema.js +11 -1
- package/lib/provider/to-version.d.ts +61 -12
- package/lib/provider/to-version.js +109 -24
- package/lib/provider/verification-checks.d.ts +12 -0
- package/lib/provider/verification-checks.js +30 -0
- package/lib/routes.gen.d.ts +271 -0
- package/lib/routes.gen.js +73 -0
- package/lib/types/openapi.gen.d.ts +3967 -0
- package/lib/types/openapi.gen.js +5 -0
- package/package.json +16 -7
- package/skill/SKILL.md +35 -14
- package/skill/builder-mode.md +13 -0
- package/skill/injection-api.md +42 -0
- package/skill/old-mode.md +15 -0
- package/src/cdp/capture-engine.ts +256 -0
- package/src/cdp/capture.ts +25 -185
- package/src/cdp/index.ts +6 -0
- package/src/cdp/launcher.ts +5 -4
- package/src/config/store.ts +7 -6
- package/src/consts.ts +164 -0
- package/src/index.ts +2 -4
- package/src/logger.ts +2 -1
- package/src/mcp/api/operations.gen.ts +1155 -194
- package/src/mcp/api/to-zod.ts +115 -0
- package/src/mcp/api/tools.ts +9 -3
- package/src/mcp/instructions.ts +15 -0
- package/src/mcp/server.ts +5 -50
- package/src/mcp/start.ts +27 -11
- package/src/mcp/tools/agent/build.ts +7 -27
- package/src/mcp/tools/agent/index.ts +4 -0
- package/src/mcp/tools/agent/proof.ts +3 -8
- package/src/mcp/tools/agent/propose.ts +13 -2
- package/src/mcp/tools/agent/publish.ts +218 -92
- package/src/mcp/tools/agent/replay.ts +0 -5
- package/src/mcp/tools/{credentials → authenticate}/authenticate.ts +9 -8
- package/src/mcp/tools/authenticate/build.ts +32 -0
- package/src/mcp/tools/authenticate/decrypt-key.ts +59 -0
- package/src/mcp/tools/authenticate/eth-key.ts +162 -0
- package/src/mcp/tools/authenticate/import.ts +73 -0
- package/src/mcp/tools/authenticate/index.ts +4 -0
- package/src/mcp/tools/authenticate/issue.ts +50 -0
- package/src/mcp/tools/authenticate/resolve-owner-key.ts +60 -0
- package/src/mcp/tools/index.ts +6 -0
- package/src/mcp/tools/verification/build.ts +16 -0
- package/src/mcp/tools/verification/index.ts +2 -0
- package/src/mcp/tools/verification/result.ts +154 -0
- package/src/old/client.ts +3 -0
- package/src/old/flag.ts +4 -3
- package/src/old/identity.ts +8 -7
- package/src/old/instructions.ts +10 -25
- package/src/old/login-cdp.ts +2 -5
- package/src/old/to-register.ts +58 -26
- package/src/old/tools/authenticate.ts +2 -1
- package/src/old/tools/publish.ts +92 -16
- package/src/paths.ts +2 -2
- package/src/proof/attestor.ts +114 -72
- package/src/proof/index.ts +21 -0
- package/src/proof/proof-core.ts +119 -0
- package/src/provider/index.ts +20 -0
- package/src/provider/injection-doc.ts +21 -0
- package/src/provider/match-redaction-warning.ts +53 -0
- package/src/provider/schema.ts +23 -0
- package/src/provider/to-version.ts +180 -33
- package/src/provider/verification-checks.ts +36 -0
- package/lib/mcp/tools/credentials/build.d.ts +0 -16
- package/lib/mcp/tools/credentials/build.js +0 -33
- package/lib/mcp/tools/credentials/status.js +0 -62
- package/src/mcp/tools/credentials/build.ts +0 -37
- package/src/mcp/tools/credentials/cache.ts +0 -161
- package/src/mcp/tools/credentials/env-file.ts +0 -101
- package/src/mcp/tools/credentials/import.ts +0 -72
- package/src/mcp/tools/credentials/issue.ts +0 -107
- package/src/mcp/tools/credentials/list-cached.ts +0 -66
- package/src/mcp/tools/credentials/register.ts +0 -49
- package/src/mcp/tools/credentials/resolve-owner-key.ts +0 -78
- package/src/mcp/tools/credentials/status.ts +0 -82
- package/src/types/openapi.ts +0 -7
- /package/lib/mcp/{tools/credentials → credentials}/env-file.d.ts +0 -0
- /package/lib/mcp/{tools/credentials → credentials}/env-file.js +0 -0
- /package/lib/mcp/{tools/credentials → credentials}/register.d.ts +0 -0
- /package/lib/mcp/{tools/credentials → credentials}/register.js +0 -0
- /package/lib/mcp/tools/{credentials → authenticate}/authenticate.d.ts +0 -0
- /package/lib/mcp/tools/{credentials → authenticate}/import.d.ts +0 -0
- /package/lib/mcp/tools/{credentials → authenticate}/issue.d.ts +0 -0
- /package/lib/mcp/tools/{credentials → authenticate}/session-cache.d.ts +0 -0
- /package/lib/mcp/tools/{credentials → authenticate}/session-cache.js +0 -0
- /package/src/mcp/tools/{credentials → authenticate}/session-cache.ts +0 -0
|
@@ -1,33 +1,160 @@
|
|
|
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
|
|
23
|
-
'Call this after run_proof succeeds, passing the `provider`
|
|
24
|
-
'from propose_provider VERBATIM. This tool builds the
|
|
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
|
-
'
|
|
30
|
-
'
|
|
147
|
+
'The version lands on the draft branch — to go live, call ' +
|
|
148
|
+
'`publish_provider_version` (PRIVATE) or ' +
|
|
149
|
+
'`submit_provider_version_for_review` (PUBLIC) afterwards. ' +
|
|
150
|
+
'`requiredContext` is derived from any {{context.<name>}} ' +
|
|
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). ' +
|
|
156
|
+
'Refine types/descriptions via `update_provider_version` while ' +
|
|
157
|
+
'still on draft.',
|
|
31
158
|
inputSchema: {
|
|
32
159
|
type: 'object',
|
|
33
160
|
properties: {
|
|
@@ -35,77 +162,15 @@ export function createVersionFromCaptureTool(
|
|
|
35
162
|
type: 'string',
|
|
36
163
|
description: 'UUID of the verification to publish the version on.',
|
|
37
164
|
},
|
|
38
|
-
provider:
|
|
39
|
-
|
|
165
|
+
provider: PROVIDER_SCHEMA,
|
|
166
|
+
providers: {
|
|
167
|
+
type: 'array',
|
|
40
168
|
description:
|
|
41
|
-
'
|
|
42
|
-
'
|
|
43
|
-
'
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
url: {
|
|
47
|
-
type: 'string',
|
|
48
|
-
description:
|
|
49
|
-
'Exact API URL the attestor replays, with {{param}} ' +
|
|
50
|
-
'placeholders for any templated values.',
|
|
51
|
-
},
|
|
52
|
-
method: {
|
|
53
|
-
type: 'string',
|
|
54
|
-
enum: ['GET', 'POST', 'PUT', 'PATCH'],
|
|
55
|
-
},
|
|
56
|
-
headers: {
|
|
57
|
-
type: 'object',
|
|
58
|
-
description:
|
|
59
|
-
'Request headers from the capture. Secret headers are ' +
|
|
60
|
-
'stripped before persistence; the client re-supplies them ' +
|
|
61
|
-
'at runtime.',
|
|
62
|
-
},
|
|
63
|
-
body: {
|
|
64
|
-
type: 'string',
|
|
65
|
-
description: 'Request body template, if any.',
|
|
66
|
-
},
|
|
67
|
-
responseMatches: {
|
|
68
|
-
type: 'array',
|
|
69
|
-
description:
|
|
70
|
-
'What the attestor checks on the revealed response slice.',
|
|
71
|
-
items: {
|
|
72
|
-
type: 'object',
|
|
73
|
-
properties: {
|
|
74
|
-
type: { type: 'string', enum: ['regex', 'contains'] },
|
|
75
|
-
value: { type: 'string' },
|
|
76
|
-
},
|
|
77
|
-
required: ['type', 'value'],
|
|
78
|
-
},
|
|
79
|
-
},
|
|
80
|
-
responseRedactions: {
|
|
81
|
-
type: 'array',
|
|
82
|
-
description:
|
|
83
|
-
'Which portions of the response the prover reveals ' +
|
|
84
|
-
'(xPath / jsonPath / regex; hash to OPRF the value). ' +
|
|
85
|
-
'Paired with responseMatches by index — the tool folds ' +
|
|
86
|
-
'each one into the matching entry\'s `extract` in the ' +
|
|
87
|
-
'persisted version, so keep them aligned 1-to-1.',
|
|
88
|
-
items: {
|
|
89
|
-
type: 'object',
|
|
90
|
-
properties: {
|
|
91
|
-
xPath: { type: 'string' },
|
|
92
|
-
jsonPath: { type: 'string' },
|
|
93
|
-
regex: { type: 'string' },
|
|
94
|
-
hash: {
|
|
95
|
-
type: 'string',
|
|
96
|
-
enum: ['oprf', 'oprf-mpc', 'oprf-raw'],
|
|
97
|
-
},
|
|
98
|
-
},
|
|
99
|
-
},
|
|
100
|
-
},
|
|
101
|
-
paramValues: {
|
|
102
|
-
type: 'object',
|
|
103
|
-
description:
|
|
104
|
-
'Real values for the {{param}} placeholders, ' +
|
|
105
|
-
'e.g. { "username": "Syed" }.',
|
|
106
|
-
},
|
|
107
|
-
},
|
|
108
|
-
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,
|
|
109
174
|
},
|
|
110
175
|
initialUrl: {
|
|
111
176
|
type: 'string',
|
|
@@ -131,25 +196,86 @@ export function createVersionFromCaptureTool(
|
|
|
131
196
|
},
|
|
132
197
|
required: ['major', 'minor', 'patch'],
|
|
133
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
|
+
},
|
|
134
222
|
},
|
|
135
|
-
required: ['providerId', '
|
|
223
|
+
required: ['providerId', 'initialUrl'],
|
|
136
224
|
},
|
|
137
225
|
},
|
|
138
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
|
+
|
|
139
239
|
// Auto-pick the next version if not explicit
|
|
140
240
|
const ver = args.version ?? (await nextVersion(client, args.providerId))
|
|
141
241
|
|
|
142
|
-
const body = providerToCreateVersionRequest(
|
|
143
|
-
args.
|
|
144
|
-
|
|
145
|
-
args.
|
|
146
|
-
|
|
147
|
-
)
|
|
148
|
-
const result =
|
|
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', {
|
|
149
249
|
params: { providerId: args.providerId },
|
|
150
250
|
body,
|
|
151
|
-
})
|
|
152
|
-
|
|
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
|
|
153
279
|
},
|
|
154
280
|
)
|
|
155
281
|
}
|
|
@@ -16,11 +16,6 @@ export function replayTool(secrets: SecretStore): RegisteredTool {
|
|
|
16
16
|
inputSchema: {
|
|
17
17
|
type: 'object',
|
|
18
18
|
properties: {
|
|
19
|
-
// `type: 'object'` is required so MCP clients (the LLM)
|
|
20
|
-
// serialize the value as an object, not a JSON-stringified
|
|
21
|
-
// string. `properties: {}` is required so zod-from-json-schema
|
|
22
|
-
// compiles to a passthrough instead of strict-strip — leaving
|
|
23
|
-
// it off makes the server receive `{}`.
|
|
24
19
|
provider: { type: 'object', properties: {} },
|
|
25
20
|
secretParamsRef: { type: 'string' },
|
|
26
21
|
},
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ReclaimClient } from '@reclaimprotocol/client'
|
|
2
2
|
import type {
|
|
3
|
-
CredentialAttachSession
|
|
4
|
-
} from '
|
|
3
|
+
CredentialAttachSession
|
|
4
|
+
} from '@reclaimprotocol/client/openapi'
|
|
5
5
|
import { defineTool, type RegisteredTool } from '../../server.ts'
|
|
6
6
|
import { writeSession } from './session-cache.ts'
|
|
7
7
|
|
|
@@ -21,10 +21,9 @@ export function authenticateTool(client: ReclaimClient): RegisteredTool {
|
|
|
21
21
|
'Returns `LINKED` with a 2-day bearer token when confirmed, ' +
|
|
22
22
|
'`PENDING` if not yet, or `EXPIRED` if too long has passed.\n\n' +
|
|
23
23
|
'The token is cached in ~/.reclaim/config.json automatically.\n\n' +
|
|
24
|
-
'
|
|
25
|
-
'
|
|
26
|
-
'
|
|
27
|
-
"credential's `address` and the target `orgId`.",
|
|
24
|
+
'This bearer token authenticates dashboard/org-management calls ' +
|
|
25
|
+
'(e.g. list_orgs, issuing an org secret). Programmatic verification ' +
|
|
26
|
+
'calls use a separate org secret (rorg_…, set as RECLAIM_ORG_SECRET).',
|
|
28
27
|
inputSchema: {
|
|
29
28
|
type: 'object',
|
|
30
29
|
properties: {
|
|
@@ -68,7 +67,7 @@ async function checkSession(
|
|
|
68
67
|
client: ReclaimClient,
|
|
69
68
|
code: string,
|
|
70
69
|
): Promise<unknown> {
|
|
71
|
-
let session:
|
|
70
|
+
let session: CredentialAttachSession
|
|
72
71
|
try {
|
|
73
72
|
const sessionResult = (await client.call('GetAttachSession', {
|
|
74
73
|
params: { code }
|
|
@@ -93,7 +92,9 @@ async function checkSession(
|
|
|
93
92
|
if(token) {
|
|
94
93
|
writeSession({
|
|
95
94
|
token,
|
|
96
|
-
expiresAt: new Date(
|
|
95
|
+
expiresAt: new Date(
|
|
96
|
+
Date.now() + 2 * 24 * 60 * 60 * 1000
|
|
97
|
+
).toISOString(),
|
|
97
98
|
})
|
|
98
99
|
client.setToken(token)
|
|
99
100
|
return {
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { ReclaimClient } from '@reclaimprotocol/client'
|
|
2
|
+
import type { RegisteredTool } from '../../server.ts'
|
|
3
|
+
import { authenticateTool } from './authenticate.ts'
|
|
4
|
+
import { importTool } from './import.ts'
|
|
5
|
+
import { issueTool } from './issue.ts'
|
|
6
|
+
import { resolveOwnerKeyTool } from './resolve-owner-key.ts'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Local-only key tools — no backend client needed. These manage the eth
|
|
10
|
+
* proof-owner key entirely on disk (as a secp256k1 PEM) or via env: generate,
|
|
11
|
+
* import, and resolve which key to sign proofs with. No `~/.reclaim` caching;
|
|
12
|
+
* useful in BOTH builder and OLD-devtools mode.
|
|
13
|
+
*/
|
|
14
|
+
export function buildLocalCredentialTools(): RegisteredTool[] {
|
|
15
|
+
return [
|
|
16
|
+
resolveOwnerKeyTool(),
|
|
17
|
+
issueTool(),
|
|
18
|
+
importTool(),
|
|
19
|
+
]
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Full builder-mode credential set: the local key tools plus the backend
|
|
24
|
+
* `reclaim_authenticate` device-code flow. Old mode does NOT use the
|
|
25
|
+
* authenticate tool — it has its own.
|
|
26
|
+
*/
|
|
27
|
+
export function buildCredentialTools(client: ReclaimClient): RegisteredTool[] {
|
|
28
|
+
return [
|
|
29
|
+
authenticateTool(client),
|
|
30
|
+
...buildLocalCredentialTools(),
|
|
31
|
+
]
|
|
32
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
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
|
+
*
|
|
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
|
|
9
|
+
* deliveries surface a clear error).
|
|
10
|
+
*
|
|
11
|
+
* The hex is loaded via the client's `loadDecryptionKey` and passed straight
|
|
12
|
+
* through as `ReclaimConfig.credential` / to `decryptCallback`.
|
|
13
|
+
*/
|
|
14
|
+
import {
|
|
15
|
+
type DecryptionKey,
|
|
16
|
+
loadDecryptionKey,
|
|
17
|
+
} from '@reclaimprotocol/client'
|
|
18
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
19
|
+
import { join } from 'node:path'
|
|
20
|
+
import { findProjectRoot } from '../../../paths.ts'
|
|
21
|
+
|
|
22
|
+
/** Default eth decrypt-key filename the agent looks for in the project root. */
|
|
23
|
+
const DEFAULT_DECRYPT_KEY_FILE = 'reclaim-decrypt.key'
|
|
24
|
+
|
|
25
|
+
/**
|
|
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).
|
|
29
|
+
*/
|
|
30
|
+
function resolveDecryptKeyInput(
|
|
31
|
+
projectDir = findProjectRoot(process.cwd()),
|
|
32
|
+
): string | undefined {
|
|
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()
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return undefined
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Load the optional decryption key. Returns `undefined` when no key is
|
|
48
|
+
* configured (the consumer then handles only plaintext deliveries).
|
|
49
|
+
*/
|
|
50
|
+
export async function resolveDecryptionKey(
|
|
51
|
+
projectDir = findProjectRoot(process.cwd()),
|
|
52
|
+
): Promise<DecryptionKey | undefined> {
|
|
53
|
+
const input = resolveDecryptKeyInput(projectDir)
|
|
54
|
+
if(input === undefined) {
|
|
55
|
+
return undefined
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return loadDecryptionKey(input)
|
|
59
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Eth proof-owner key resolution — the raw secp256k1 private key passed to
|
|
3
|
+
* attestor-core when the agent creates a local claim. It's a plain 0x-hex
|
|
4
|
+
* Ethereum private key (no PEM), resolved from, in order:
|
|
5
|
+
*
|
|
6
|
+
* 1. `process.env.RECLAIM_PRIVATE_KEY` — a 0x-hex 32-byte scalar.
|
|
7
|
+
* 2. A `.env` file in the project root — its `RECLAIM_PRIVATE_KEY=0x…` line.
|
|
8
|
+
* 3. A key file holding the raw 0x-hex scalar, at `RECLAIM_PRIVATE_KEY_FILE`
|
|
9
|
+
* (absolute or project-relative).
|
|
10
|
+
* 4. Absent — the caller offers to generate one or asks the dev for a key.
|
|
11
|
+
*
|
|
12
|
+
* Generated/imported keys are written to the project `.env` (mode 0600, and the
|
|
13
|
+
* file is added to `.gitignore`). NO `~/.reclaim` caching. This is a LOCAL
|
|
14
|
+
* signing key only — unrelated to org auth (the org secret) or encryption
|
|
15
|
+
* (the eth decrypt key).
|
|
16
|
+
*/
|
|
17
|
+
import { deriveFromPrivateKey } from '@reclaimprotocol/client'
|
|
18
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs'
|
|
19
|
+
import { isAbsolute, join } from 'node:path'
|
|
20
|
+
import { privateKey, privateKeyFile } from '../../../consts.ts'
|
|
21
|
+
import { findProjectRoot } from '../../../paths.ts'
|
|
22
|
+
|
|
23
|
+
const ENV_VAR = 'RECLAIM_PRIVATE_KEY'
|
|
24
|
+
/** The dotenv file the agent reads from / writes the key to. */
|
|
25
|
+
export const ENV_FILE = '.env'
|
|
26
|
+
|
|
27
|
+
/** Where the resolved eth key came from. */
|
|
28
|
+
export type EthKeySource = 'env' | 'dotenv' | 'file'
|
|
29
|
+
|
|
30
|
+
export type ResolvedEthKey = {
|
|
31
|
+
/** Raw 0x-hex 32-byte scalar. */
|
|
32
|
+
privateKey: string
|
|
33
|
+
/** Lowercase eth address derived from the scalar. */
|
|
34
|
+
address: string
|
|
35
|
+
source: EthKeySource
|
|
36
|
+
/** Absolute path the key was read from (for `dotenv` / `file` sources). */
|
|
37
|
+
path?: string
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Validate + normalize a raw Ethereum private key to lowercase 0x-hex. */
|
|
41
|
+
export function normalizeEthHex(value: string): string {
|
|
42
|
+
const v = value.trim().replace(/^['"]|['"]$/g, '')
|
|
43
|
+
if(!/^(0x)?[0-9a-fA-F]{64}$/.test(v)) {
|
|
44
|
+
throw new Error(
|
|
45
|
+
'not a valid Ethereum private key (expected a 32-byte / 64-hex scalar)',
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return '0x' + v.replace(/^0x/, '').toLowerCase()
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Read `RECLAIM_PRIVATE_KEY` from a dotenv file (simple KEY=VALUE parse). */
|
|
53
|
+
function readDotenvKey(path: string): string | undefined {
|
|
54
|
+
if(!existsSync(path)) {
|
|
55
|
+
return undefined
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
for(const line of readFileSync(path, 'utf8').split('\n')) {
|
|
59
|
+
const m = line.match(/^\s*RECLAIM_PRIVATE_KEY\s*=\s*(.+?)\s*$/)
|
|
60
|
+
if(m) {
|
|
61
|
+
return m[1]
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return undefined
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** The explicit raw-hex key-file path, if `RECLAIM_PRIVATE_KEY_FILE` is set. */
|
|
69
|
+
function ethKeyFilePath(
|
|
70
|
+
projectDir = findProjectRoot(process.cwd()),
|
|
71
|
+
): string | undefined {
|
|
72
|
+
const p = privateKeyFile()
|
|
73
|
+
if(!p) {
|
|
74
|
+
return undefined
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return isAbsolute(p) ? p : join(projectDir, p)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function resolved(
|
|
81
|
+
privateKey: string,
|
|
82
|
+
source: EthKeySource,
|
|
83
|
+
path?: string,
|
|
84
|
+
): ResolvedEthKey {
|
|
85
|
+
return {
|
|
86
|
+
privateKey,
|
|
87
|
+
address: deriveFromPrivateKey(privateKey).address,
|
|
88
|
+
source,
|
|
89
|
+
...(path ? { path } : {}),
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Resolve the eth proof-owner key without prompting. Returns `undefined` when
|
|
95
|
+
* no key is configured anywhere — the caller decides whether to generate or
|
|
96
|
+
* ask for one (see `resolve_owner_key`).
|
|
97
|
+
*/
|
|
98
|
+
export function resolveEthKey(
|
|
99
|
+
projectDir = findProjectRoot(process.cwd()),
|
|
100
|
+
): ResolvedEthKey | undefined {
|
|
101
|
+
const env = privateKey()
|
|
102
|
+
if(env?.trim()) {
|
|
103
|
+
return resolved(normalizeEthHex(env), 'env')
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const envPath = join(projectDir, ENV_FILE)
|
|
107
|
+
const fromDotenv = readDotenvKey(envPath)
|
|
108
|
+
if(fromDotenv) {
|
|
109
|
+
return resolved(normalizeEthHex(fromDotenv), 'dotenv', envPath)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const keyFile = ethKeyFilePath(projectDir)
|
|
113
|
+
if(keyFile && existsSync(keyFile)) {
|
|
114
|
+
return resolved(
|
|
115
|
+
normalizeEthHex(readFileSync(keyFile, 'utf8')),
|
|
116
|
+
'file',
|
|
117
|
+
keyFile,
|
|
118
|
+
)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return undefined
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Append `entry` to the project `.gitignore` if not already present. */
|
|
125
|
+
function ensureGitignored(projectDir: string, entry: string): void {
|
|
126
|
+
const path = join(projectDir, '.gitignore')
|
|
127
|
+
const current = existsSync(path) ? readFileSync(path, 'utf8') : ''
|
|
128
|
+
if(current.split('\n').some((l) => l.trim() === entry)) {
|
|
129
|
+
return
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const prefix = current && !current.endsWith('\n') ? '\n' : ''
|
|
133
|
+
writeFileSync(path, current + prefix + entry + '\n')
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Persist a raw eth private key to the project `.env` as
|
|
138
|
+
* `RECLAIM_PRIVATE_KEY=0x…` (upsert; mode 0600; `.env` added to `.gitignore`).
|
|
139
|
+
* Used by `issue_credentials` (generate) and `import_credentials` (supplied
|
|
140
|
+
* hex). Returns the derived address and the `.env` path.
|
|
141
|
+
*/
|
|
142
|
+
export function writeEthKey(
|
|
143
|
+
privateKeyHex: string,
|
|
144
|
+
projectDir = findProjectRoot(process.cwd()),
|
|
145
|
+
): { path: string, address: string } {
|
|
146
|
+
const hex = normalizeEthHex(privateKeyHex)
|
|
147
|
+
const envPath = join(projectDir, ENV_FILE)
|
|
148
|
+
const line = `${ENV_VAR}=${hex}`
|
|
149
|
+
const existing = existsSync(envPath) ? readFileSync(envPath, 'utf8') : ''
|
|
150
|
+
const re = new RegExp(`^\\s*${ENV_VAR}\\s*=.*$`, 'm')
|
|
151
|
+
let next: string
|
|
152
|
+
if(re.test(existing)) {
|
|
153
|
+
next = existing.replace(re, line)
|
|
154
|
+
} else {
|
|
155
|
+
const prefix = existing && !existing.endsWith('\n') ? '\n' : ''
|
|
156
|
+
next = existing + prefix + line + '\n'
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
writeFileSync(envPath, next, { mode: 0o600 })
|
|
160
|
+
ensureGitignored(projectDir, ENV_FILE)
|
|
161
|
+
return { path: envPath, address: deriveFromPrivateKey(hex).address }
|
|
162
|
+
}
|