@reclaimprotocol/agent 0.1.0-dev.1

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 (191) hide show
  1. package/README.md +287 -0
  2. package/lib/cdp/capture.d.ts +22 -0
  3. package/lib/cdp/capture.js +159 -0
  4. package/lib/cdp/client.d.ts +9 -0
  5. package/lib/cdp/client.js +11 -0
  6. package/lib/cdp/eviction.d.ts +16 -0
  7. package/lib/cdp/eviction.js +38 -0
  8. package/lib/cdp/launcher.d.ts +14 -0
  9. package/lib/cdp/launcher.js +102 -0
  10. package/lib/cdp/tabs.d.ts +6 -0
  11. package/lib/cdp/tabs.js +10 -0
  12. package/lib/cli/args.d.ts +14 -0
  13. package/lib/cli/args.js +19 -0
  14. package/lib/cli/commands.d.ts +1 -0
  15. package/lib/cli/commands.js +19 -0
  16. package/lib/cli.d.ts +1 -0
  17. package/lib/cli.js +7 -0
  18. package/lib/config/store.d.ts +45 -0
  19. package/lib/config/store.js +46 -0
  20. package/lib/index.d.ts +3 -0
  21. package/lib/index.js +7 -0
  22. package/lib/logger.d.ts +2 -0
  23. package/lib/logger.js +7 -0
  24. package/lib/mcp/api/operations.gen.d.ts +152 -0
  25. package/lib/mcp/api/operations.gen.js +2300 -0
  26. package/lib/mcp/api/tools.d.ts +3 -0
  27. package/lib/mcp/api/tools.js +36 -0
  28. package/lib/mcp/remap_errors.d.ts +2 -0
  29. package/lib/mcp/remap_errors.js +27 -0
  30. package/lib/mcp/server.d.ts +38 -0
  31. package/lib/mcp/server.js +76 -0
  32. package/lib/mcp/start.d.ts +1 -0
  33. package/lib/mcp/start.js +63 -0
  34. package/lib/mcp/tools/agent/analyze.d.ts +3 -0
  35. package/lib/mcp/tools/agent/analyze.js +26 -0
  36. package/lib/mcp/tools/agent/attach.d.ts +11 -0
  37. package/lib/mcp/tools/agent/attach.js +72 -0
  38. package/lib/mcp/tools/agent/build.d.ts +33 -0
  39. package/lib/mcp/tools/agent/build.js +74 -0
  40. package/lib/mcp/tools/agent/capture.d.ts +10 -0
  41. package/lib/mcp/tools/agent/capture.js +142 -0
  42. package/lib/mcp/tools/agent/eval.d.ts +5 -0
  43. package/lib/mcp/tools/agent/eval.js +127 -0
  44. package/lib/mcp/tools/agent/find.d.ts +3 -0
  45. package/lib/mcp/tools/agent/find.js +60 -0
  46. package/lib/mcp/tools/agent/proof.d.ts +3 -0
  47. package/lib/mcp/tools/agent/proof.js +214 -0
  48. package/lib/mcp/tools/agent/propose.d.ts +9 -0
  49. package/lib/mcp/tools/agent/propose.js +75 -0
  50. package/lib/mcp/tools/agent/publish.d.ts +3 -0
  51. package/lib/mcp/tools/agent/publish.js +122 -0
  52. package/lib/mcp/tools/agent/replay.d.ts +3 -0
  53. package/lib/mcp/tools/agent/replay.js +46 -0
  54. package/lib/mcp/tools/agent/session.d.ts +10 -0
  55. package/lib/mcp/tools/agent/session.js +29 -0
  56. package/lib/mcp/tools/credentials/authenticate.d.ts +3 -0
  57. package/lib/mcp/tools/credentials/authenticate.js +108 -0
  58. package/lib/mcp/tools/credentials/build.d.ts +16 -0
  59. package/lib/mcp/tools/credentials/build.js +33 -0
  60. package/lib/mcp/tools/credentials/cache.d.ts +70 -0
  61. package/lib/mcp/tools/credentials/cache.js +114 -0
  62. package/lib/mcp/tools/credentials/env-file.d.ts +25 -0
  63. package/lib/mcp/tools/credentials/env-file.js +71 -0
  64. package/lib/mcp/tools/credentials/import.d.ts +2 -0
  65. package/lib/mcp/tools/credentials/import.js +52 -0
  66. package/lib/mcp/tools/credentials/issue.d.ts +2 -0
  67. package/lib/mcp/tools/credentials/issue.js +84 -0
  68. package/lib/mcp/tools/credentials/list-cached.d.ts +2 -0
  69. package/lib/mcp/tools/credentials/list-cached.js +55 -0
  70. package/lib/mcp/tools/credentials/register.d.ts +18 -0
  71. package/lib/mcp/tools/credentials/register.js +30 -0
  72. package/lib/mcp/tools/credentials/resolve-owner-key.d.ts +12 -0
  73. package/lib/mcp/tools/credentials/resolve-owner-key.js +68 -0
  74. package/lib/mcp/tools/credentials/session-cache.d.ts +14 -0
  75. package/lib/mcp/tools/credentials/session-cache.js +19 -0
  76. package/lib/mcp/tools/credentials/status.d.ts +3 -0
  77. package/lib/mcp/tools/credentials/status.js +62 -0
  78. package/lib/mcp-bin.d.ts +1 -0
  79. package/lib/mcp-bin.js +7 -0
  80. package/lib/old/client.d.ts +67 -0
  81. package/lib/old/client.js +146 -0
  82. package/lib/old/flag.d.ts +11 -0
  83. package/lib/old/flag.js +13 -0
  84. package/lib/old/identity.d.ts +22 -0
  85. package/lib/old/identity.js +47 -0
  86. package/lib/old/instructions.d.ts +7 -0
  87. package/lib/old/instructions.js +29 -0
  88. package/lib/old/login-cdp.d.ts +23 -0
  89. package/lib/old/login-cdp.js +125 -0
  90. package/lib/old/to-register.d.ts +47 -0
  91. package/lib/old/to-register.js +133 -0
  92. package/lib/old/tools/authenticate.d.ts +18 -0
  93. package/lib/old/tools/authenticate.js +127 -0
  94. package/lib/old/tools/build.d.ts +9 -0
  95. package/lib/old/tools/build.js +16 -0
  96. package/lib/old/tools/me-providers.d.ts +10 -0
  97. package/lib/old/tools/me-providers.js +32 -0
  98. package/lib/old/tools/publish.d.ts +16 -0
  99. package/lib/old/tools/publish.js +97 -0
  100. package/lib/paths.d.ts +11 -0
  101. package/lib/paths.js +32 -0
  102. package/lib/pkg.d.ts +3 -0
  103. package/lib/pkg.js +13 -0
  104. package/lib/proof/attestor.d.ts +39 -0
  105. package/lib/proof/attestor.js +207 -0
  106. package/lib/proof/replay.d.ts +8 -0
  107. package/lib/proof/replay.js +73 -0
  108. package/lib/provider/constraints.d.ts +2 -0
  109. package/lib/provider/constraints.js +122 -0
  110. package/lib/provider/draft.d.ts +6 -0
  111. package/lib/provider/draft.js +68 -0
  112. package/lib/provider/graphql.d.ts +2 -0
  113. package/lib/provider/graphql.js +35 -0
  114. package/lib/provider/matcher.d.ts +36 -0
  115. package/lib/provider/matcher.js +436 -0
  116. package/lib/provider/normalize.d.ts +4 -0
  117. package/lib/provider/normalize.js +51 -0
  118. package/lib/provider/schema.d.ts +76 -0
  119. package/lib/provider/schema.js +1 -0
  120. package/lib/provider/secrets.d.ts +5 -0
  121. package/lib/provider/secrets.js +22 -0
  122. package/lib/provider/to-version.d.ts +22 -0
  123. package/lib/provider/to-version.js +68 -0
  124. package/lib/types/openapi.d.ts +4 -0
  125. package/lib/types/openapi.js +1 -0
  126. package/package.json +88 -0
  127. package/scripts/download-zk-files.ts +42 -0
  128. package/scripts/prepend-shebang.ts +17 -0
  129. package/skill/SKILL.md +248 -0
  130. package/src/cdp/capture.ts +215 -0
  131. package/src/cdp/client.ts +20 -0
  132. package/src/cdp/eviction.ts +52 -0
  133. package/src/cdp/launcher.ts +133 -0
  134. package/src/cdp/tabs.ts +22 -0
  135. package/src/cli/args.ts +20 -0
  136. package/src/cli/commands.ts +27 -0
  137. package/src/cli.ts +7 -0
  138. package/src/config/store.ts +86 -0
  139. package/src/index.ts +12 -0
  140. package/src/logger.ts +8 -0
  141. package/src/mcp/api/operations.gen.ts +2397 -0
  142. package/src/mcp/api/tools.ts +45 -0
  143. package/src/mcp/remap_errors.ts +35 -0
  144. package/src/mcp/server.ts +119 -0
  145. package/src/mcp/start.ts +78 -0
  146. package/src/mcp/tools/agent/analyze.ts +34 -0
  147. package/src/mcp/tools/agent/attach.ts +95 -0
  148. package/src/mcp/tools/agent/build.ts +99 -0
  149. package/src/mcp/tools/agent/capture.ts +202 -0
  150. package/src/mcp/tools/agent/eval.ts +161 -0
  151. package/src/mcp/tools/agent/find.ts +73 -0
  152. package/src/mcp/tools/agent/proof.ts +272 -0
  153. package/src/mcp/tools/agent/propose.ts +111 -0
  154. package/src/mcp/tools/agent/publish.ts +155 -0
  155. package/src/mcp/tools/agent/replay.ts +58 -0
  156. package/src/mcp/tools/agent/session.ts +46 -0
  157. package/src/mcp/tools/credentials/authenticate.ts +135 -0
  158. package/src/mcp/tools/credentials/build.ts +37 -0
  159. package/src/mcp/tools/credentials/cache.ts +161 -0
  160. package/src/mcp/tools/credentials/env-file.ts +101 -0
  161. package/src/mcp/tools/credentials/import.ts +72 -0
  162. package/src/mcp/tools/credentials/issue.ts +107 -0
  163. package/src/mcp/tools/credentials/list-cached.ts +66 -0
  164. package/src/mcp/tools/credentials/register.ts +49 -0
  165. package/src/mcp/tools/credentials/resolve-owner-key.ts +78 -0
  166. package/src/mcp/tools/credentials/session-cache.ts +31 -0
  167. package/src/mcp/tools/credentials/status.ts +82 -0
  168. package/src/mcp-bin.ts +7 -0
  169. package/src/old/client.ts +236 -0
  170. package/src/old/flag.ts +13 -0
  171. package/src/old/identity.ts +56 -0
  172. package/src/old/instructions.ts +30 -0
  173. package/src/old/login-cdp.ts +153 -0
  174. package/src/old/to-register.ts +234 -0
  175. package/src/old/tools/authenticate.ts +152 -0
  176. package/src/old/tools/build.ts +21 -0
  177. package/src/old/tools/me-providers.ts +42 -0
  178. package/src/old/tools/publish.ts +135 -0
  179. package/src/paths.ts +37 -0
  180. package/src/pkg.ts +24 -0
  181. package/src/proof/attestor.ts +286 -0
  182. package/src/proof/replay.ts +102 -0
  183. package/src/provider/constraints.ts +150 -0
  184. package/src/provider/draft.ts +105 -0
  185. package/src/provider/graphql.ts +42 -0
  186. package/src/provider/matcher.ts +616 -0
  187. package/src/provider/normalize.ts +65 -0
  188. package/src/provider/schema.ts +92 -0
  189. package/src/provider/secrets.ts +29 -0
  190. package/src/provider/to-version.ts +90 -0
  191. package/src/types/openapi.ts +7 -0
@@ -0,0 +1,46 @@
1
+ import { replayProvider } from "../../../proof/replay.js";
2
+ import { defineTool } from "../../server.js";
3
+ export function replayTool(secrets) {
4
+ return defineTool({
5
+ name: 'replay_request',
6
+ description: 'Replay a drafted provider against the live endpoint ' +
7
+ 'using Node fetch (no attestor). ' +
8
+ 'Pre-flight gate before run_proof — ' +
9
+ 'diagnoses CSRF / token-rotation / matcher-too-tight ' +
10
+ 'issues with richer hints than the attestor would produce.',
11
+ inputSchema: {
12
+ type: 'object',
13
+ properties: {
14
+ // `type: 'object'` is required so MCP clients (the LLM)
15
+ // serialize the value as an object, not a JSON-stringified
16
+ // string. `properties: {}` is required so zod-from-json-schema
17
+ // compiles to a passthrough instead of strict-strip — leaving
18
+ // it off makes the server receive `{}`.
19
+ provider: { type: 'object', properties: {} },
20
+ secretParamsRef: { type: 'string' },
21
+ },
22
+ required: ['provider'],
23
+ },
24
+ }, async ({ provider, secretParamsRef }) => {
25
+ let resolved = {};
26
+ if (secretParamsRef !== undefined) {
27
+ const r = secrets.resolve(secretParamsRef);
28
+ if (!r) {
29
+ throw new Error(`unknown secretParamsRef "${secretParamsRef}" — ` +
30
+ 'the MCP server has restarted or reset_session was called. ' +
31
+ 'Re-run propose_provider to mint a fresh ref.');
32
+ }
33
+ resolved = r;
34
+ }
35
+ const result = await replayProvider(provider, resolved);
36
+ const out = {
37
+ status: result.status,
38
+ matchesOriginal: result.matchesOriginal,
39
+ bodySnippet: result.body.slice(0, 256),
40
+ };
41
+ if (result.hints !== undefined) {
42
+ out.hints = result.hints;
43
+ }
44
+ return out;
45
+ });
46
+ }
@@ -0,0 +1,10 @@
1
+ import type { ConfigStore } from '../../../config/store.ts';
2
+ import { type RegisteredTool } from '../../server.ts';
3
+ import type { AttachState } from './attach.ts';
4
+ import type { CaptureStore } from './capture.ts';
5
+ export declare function sessionTools(attachRef: {
6
+ current?: AttachState;
7
+ config: ConfigStore;
8
+ }, captures: CaptureStore, secrets: {
9
+ clear: () => void;
10
+ }): RegisteredTool[];
@@ -0,0 +1,29 @@
1
+ import { defineTool } from "../../server.js";
2
+ export function sessionTools(attachRef, captures, secrets) {
3
+ return [
4
+ defineTool({
5
+ name: 'get_session',
6
+ description: 'Return current attach state, active captures, and persisted config.',
7
+ inputSchema: { type: 'object', properties: {} },
8
+ }, async () => ({
9
+ config: attachRef.config.read()?.browser_agent,
10
+ attached: Boolean(attachRef.current),
11
+ port: attachRef.current?.port,
12
+ captures: [...captures.sessions.keys()],
13
+ activeTab: captures.activeTab,
14
+ })),
15
+ defineTool({
16
+ name: 'reset_session',
17
+ description: 'Stop all captures, drop in-memory secrets, and clear capture state.',
18
+ inputSchema: { type: 'object', properties: {} },
19
+ }, async () => {
20
+ for (const s of captures.sessions.values()) {
21
+ await s.stop().catch(() => { });
22
+ }
23
+ captures.sessions.clear();
24
+ delete captures.activeTab;
25
+ secrets.clear();
26
+ return { reset: true };
27
+ }),
28
+ ];
29
+ }
@@ -0,0 +1,3 @@
1
+ import type { ReclaimClient } from '@reclaimprotocol/client';
2
+ import { type RegisteredTool } from '../../server.ts';
3
+ export declare function authenticateTool(client: ReclaimClient): RegisteredTool;
@@ -0,0 +1,108 @@
1
+ import { defineTool } from "../../server.js";
2
+ import { writeSession } from "./session-cache.js";
3
+ export function authenticateTool(client) {
4
+ return defineTool({
5
+ name: 'reclaim_authenticate',
6
+ description: 'Authorize the CLI / MCP to make authenticated API calls by having ' +
7
+ 'the user sign in on the dashboard. Two steps:\n\n' +
8
+ 'Step 1 — call with no arguments. Returns a `code` and `attachUrl`. ' +
9
+ 'Tell the user to open the URL, sign in, and authorize.\n\n' +
10
+ 'Step 2 — once the user says they have authorized, call again ' +
11
+ 'with the same `code` from step 1 to check if confirmed. ' +
12
+ 'Returns `LINKED` with a 2-day bearer token when confirmed, ' +
13
+ '`PENDING` if not yet, or `EXPIRED` if too long has passed.\n\n' +
14
+ 'The token is cached in ~/.reclaim/config.json automatically.\n\n' +
15
+ 'To then link a credential to an org (needed only to use it ' +
16
+ 'for verification in production), use the token with the ' +
17
+ 'generated tools: `list_orgs` → `link_credential` with the ' +
18
+ "credential's `address` and the target `orgId`.",
19
+ inputSchema: {
20
+ type: 'object',
21
+ properties: {
22
+ code: {
23
+ type: 'string',
24
+ pattern: '^[A-HJ-NP-Z2-9]{5}$',
25
+ description: '5-character code from a previous step-1 call. ' +
26
+ 'When provided, checks whether the user has ' +
27
+ 'confirmed that session instead of opening a new one.',
28
+ },
29
+ },
30
+ },
31
+ }, async (args) => {
32
+ // Step 2: check an existing session
33
+ if (args.code) {
34
+ return checkSession(client, args.code);
35
+ }
36
+ // Step 1: open a new auth-only session
37
+ const sessionResult = (await client.call('CreateAttachSession'));
38
+ const session = sessionResult.data;
39
+ return {
40
+ status: 'PENDING',
41
+ code: session.code,
42
+ attachUrl: session.attachUrl,
43
+ expiresAt: session.expiresAt,
44
+ instructions: `Open ${session.attachUrl} in your browser, sign in, and click ` +
45
+ '"Authorize this device". ' +
46
+ 'Once done, call authenticate again with code: ' +
47
+ `"${session.code}" to confirm.`,
48
+ };
49
+ });
50
+ }
51
+ async function checkSession(client, code) {
52
+ let session;
53
+ try {
54
+ const sessionResult = (await client.call('GetAttachSession', {
55
+ params: { code }
56
+ }));
57
+ session = sessionResult.data;
58
+ }
59
+ catch (err) {
60
+ const status = err.status;
61
+ if (status === 404) {
62
+ return {
63
+ status: 'NOT_FOUND',
64
+ hint: 'Session not found — it may have expired (15-min window). ' +
65
+ 'Call authenticate without a code to start a new session.',
66
+ };
67
+ }
68
+ throw new Error(`Failed to check session: ${err.message}`);
69
+ }
70
+ if (session.status === 'LINKED') {
71
+ const token = session.token?.trim();
72
+ if (token) {
73
+ writeSession({
74
+ token,
75
+ expiresAt: new Date(Date.now() + 2 * 24 * 60 * 60 * 1000).toISOString(),
76
+ });
77
+ client.setToken(token);
78
+ return {
79
+ status: 'LINKED',
80
+ token,
81
+ note: 'Token cached in ~/.reclaim/config.json and active now.',
82
+ };
83
+ }
84
+ return {
85
+ status: 'LINKED_NO_TOKEN',
86
+ hint: 'Session confirmed but no token issued. ' +
87
+ 'Restart the backend (npm run dev) and try again.',
88
+ };
89
+ }
90
+ if (session.status === 'EXPIRED') {
91
+ return {
92
+ status: 'EXPIRED',
93
+ hint: 'Session expired (15-min window passed). ' +
94
+ 'Call authenticate without a code to start a new session.',
95
+ };
96
+ }
97
+ // Still PENDING
98
+ return {
99
+ status: 'PENDING',
100
+ code: session.code,
101
+ attachUrl: session.attachUrl,
102
+ hint: 'Session is still waiting. ' +
103
+ `Make sure you opened ${session.attachUrl}, ` +
104
+ 'signed in, and clicked "Authorize this device". ' +
105
+ `Call authenticate again with code: "${session.code}"` +
106
+ ' once you have done so.',
107
+ };
108
+ }
@@ -0,0 +1,16 @@
1
+ import type { ReclaimClient } from '@reclaimprotocol/client';
2
+ import type { RegisteredTool } from '../../server.ts';
3
+ /**
4
+ * Local-only credential tools — no backend client needed. These manage app
5
+ * key-pairs entirely on disk / in the env (mint, import, list, resolve which
6
+ * key to sign proofs with), so they are useful in BOTH builder and
7
+ * OLD-devtools mode.
8
+ */
9
+ export declare function buildLocalCredentialTools(): RegisteredTool[];
10
+ /**
11
+ * Full builder-mode credential set: the local tools plus the two that talk to
12
+ * the builder backend (`reclaim_authenticate` device-code flow,
13
+ * `reclaim_status`). Old mode does NOT use these — it has its own
14
+ * authenticate and no credential-status analog.
15
+ */
16
+ export declare function buildCredentialTools(client: ReclaimClient): RegisteredTool[];
@@ -0,0 +1,33 @@
1
+ import { authenticateTool } from "./authenticate.js";
2
+ import { importTool } from "./import.js";
3
+ import { issueTool } from "./issue.js";
4
+ import { listCachedTool } from "./list-cached.js";
5
+ import { resolveOwnerKeyTool } from "./resolve-owner-key.js";
6
+ import { statusTool } from "./status.js";
7
+ /**
8
+ * Local-only credential tools — no backend client needed. These manage app
9
+ * key-pairs entirely on disk / in the env (mint, import, list, resolve which
10
+ * key to sign proofs with), so they are useful in BOTH builder and
11
+ * OLD-devtools mode.
12
+ */
13
+ export function buildLocalCredentialTools() {
14
+ return [
15
+ listCachedTool(),
16
+ resolveOwnerKeyTool(),
17
+ issueTool(),
18
+ importTool(),
19
+ ];
20
+ }
21
+ /**
22
+ * Full builder-mode credential set: the local tools plus the two that talk to
23
+ * the builder backend (`reclaim_authenticate` device-code flow,
24
+ * `reclaim_status`). Old mode does NOT use these — it has its own
25
+ * authenticate and no credential-status analog.
26
+ */
27
+ export function buildCredentialTools(client) {
28
+ return [
29
+ authenticateTool(client),
30
+ statusTool(client),
31
+ ...buildLocalCredentialTools(),
32
+ ];
33
+ }
@@ -0,0 +1,70 @@
1
+ export type CachedCredential = {
2
+ address: string;
3
+ privateKey: string;
4
+ label?: string;
5
+ orgId?: string;
6
+ createdAt: string;
7
+ /** Project dir where this credential was first created. */
8
+ createdIn: string;
9
+ /** All project dirs this credential has been used in ("used by"). */
10
+ projectDirs: string[];
11
+ };
12
+ type CacheFile = {
13
+ version: 1;
14
+ credentials: CachedCredential[];
15
+ };
16
+ /**
17
+ * Read the `credentials` section of the unified config. Returns an empty
18
+ * doc if missing or malformed — we'd rather lose the cache than crash the
19
+ * CLI on a corrupted file the user can re-create.
20
+ */
21
+ export declare function readCredentialsFromCache(): CacheFile;
22
+ /**
23
+ * Persist the `credentials` section, merging into the unified config so
24
+ * other sections (browser_agent, session) survive untouched. We rewrite
25
+ * the whole list each save; it's tiny and the alternative (append-only)
26
+ * loses our ability to deduplicate `projectDirs`.
27
+ */
28
+ export declare function writeCache(doc: CacheFile): void;
29
+ /**
30
+ * Upsert a credential entry. If a row already exists for `address`,
31
+ * we merge the new `projectDirs` into the existing list, refresh
32
+ * mutable fields, and keep the original `createdAt` / `createdIn`
33
+ * (backfilling `createdIn` from the new entry when an old row lacks it).
34
+ */
35
+ export declare function upsertCredential(entry: CachedCredential): CachedCredential;
36
+ /** Look up a cached credential by address. Case-insensitive. */
37
+ export declare function findCredentialFromCachedBy(address: string): CachedCredential | undefined;
38
+ export type SuggestionReason = 'createdIn' | 'usedIn' | 'newest';
39
+ export type CredentialSuggestion = {
40
+ credential: CachedCredential;
41
+ reason: SuggestionReason;
42
+ };
43
+ /**
44
+ * Pick the best cached credential for `dir`. Ranking tiers:
45
+ * 1. created in `dir` (reason: 'createdIn')
46
+ * 2. previously used in `dir` (reason: 'usedIn')
47
+ * 3. most recently created (reason: 'newest')
48
+ * Within every tier, newest `createdAt` wins; address ascending is the
49
+ * final deterministic tie-break. Returns undefined only on an empty cache.
50
+ *
51
+ * Pass a pre-read `creds` array to avoid a redundant `readCache()` (and to
52
+ * rank against the same snapshot the caller already holds).
53
+ */
54
+ export declare function suggestCredentialForDir(dir: string, creds?: CachedCredential[]): CredentialSuggestion | undefined;
55
+ /**
56
+ * Resolve the private key to sign attestor proofs with (the claim
57
+ * "owner"). Reuses the app credential rather than a dedicated key — for
58
+ * build-time test proofs the developer is both the app and the proof
59
+ * owner, and the owner identity is immaterial to whether the provider's
60
+ * matching works.
61
+ *
62
+ * The owner is chosen up front (see the `resolve_owner_key` tool) and
63
+ * passed by `address`; this just fetches the matching private key:
64
+ * - the `RECLAIM_PRIVATE_KEY` env / project `.env` key, if it derives
65
+ * to `address` (the dev pinned it), else
66
+ * - the cached credential with that address.
67
+ * Returns undefined when no key for `address` is known.
68
+ */
69
+ export declare function resolveOwnerPrivateKey(address: string): string | undefined;
70
+ export {};
@@ -0,0 +1,114 @@
1
+ import { deriveFromPrivateKey } from '@reclaimprotocol/client';
2
+ import { ConfigStore } from "../../../config/store.js";
3
+ import { findProjectRoot } from "../../../paths.js";
4
+ import { readPrivateKeyFromEnvFile } from "./env-file.js";
5
+ const EMPTY = { version: 1, credentials: [] };
6
+ /**
7
+ * Read the `credentials` section of the unified config. Returns an empty
8
+ * doc if missing or malformed — we'd rather lose the cache than crash the
9
+ * CLI on a corrupted file the user can re-create.
10
+ */
11
+ export function readCredentialsFromCache() {
12
+ const creds = new ConfigStore().read()?.credentials;
13
+ if (!Array.isArray(creds)) {
14
+ return { ...EMPTY };
15
+ }
16
+ return { version: 1, credentials: creds };
17
+ }
18
+ /**
19
+ * Persist the `credentials` section, merging into the unified config so
20
+ * other sections (browser_agent, session) survive untouched. We rewrite
21
+ * the whole list each save; it's tiny and the alternative (append-only)
22
+ * loses our ability to deduplicate `projectDirs`.
23
+ */
24
+ export function writeCache(doc) {
25
+ new ConfigStore().write({ credentials: doc.credentials });
26
+ }
27
+ /**
28
+ * Upsert a credential entry. If a row already exists for `address`,
29
+ * we merge the new `projectDirs` into the existing list, refresh
30
+ * mutable fields, and keep the original `createdAt` / `createdIn`
31
+ * (backfilling `createdIn` from the new entry when an old row lacks it).
32
+ */
33
+ export function upsertCredential(entry) {
34
+ const doc = readCredentialsFromCache();
35
+ const idx = doc.credentials.findIndex((c) => c.address === entry.address.toLowerCase());
36
+ const merged = {
37
+ ...entry,
38
+ address: entry.address.toLowerCase(),
39
+ projectDirs: Array.from(new Set([
40
+ ...(doc.credentials[idx]?.projectDirs ?? []),
41
+ ...entry.projectDirs,
42
+ ])),
43
+ createdAt: doc.credentials[idx]?.createdAt ?? entry.createdAt,
44
+ createdIn: doc.credentials[idx]?.createdIn ?? entry.createdIn,
45
+ };
46
+ if (idx >= 0) {
47
+ doc.credentials[idx] = merged;
48
+ }
49
+ else {
50
+ doc.credentials.unshift(merged);
51
+ }
52
+ writeCache(doc);
53
+ return merged;
54
+ }
55
+ /** Look up a cached credential by address. Case-insensitive. */
56
+ export function findCredentialFromCachedBy(address) {
57
+ const doc = readCredentialsFromCache();
58
+ const lc = address.toLowerCase();
59
+ return doc.credentials.find((c) => c.address === lc);
60
+ }
61
+ /**
62
+ * Pick the best cached credential for `dir`. Ranking tiers:
63
+ * 1. created in `dir` (reason: 'createdIn')
64
+ * 2. previously used in `dir` (reason: 'usedIn')
65
+ * 3. most recently created (reason: 'newest')
66
+ * Within every tier, newest `createdAt` wins; address ascending is the
67
+ * final deterministic tie-break. Returns undefined only on an empty cache.
68
+ *
69
+ * Pass a pre-read `creds` array to avoid a redundant `readCache()` (and to
70
+ * rank against the same snapshot the caller already holds).
71
+ */
72
+ export function suggestCredentialForDir(dir, creds = readCredentialsFromCache().credentials) {
73
+ if (creds.length === 0) {
74
+ return undefined;
75
+ }
76
+ const byNewest = (a, b) => {
77
+ return (b.createdAt ?? '').localeCompare(a.createdAt ?? '') ||
78
+ a.address.localeCompare(b.address);
79
+ };
80
+ const createdHere = creds.filter((c) => c.createdIn === dir).sort(byNewest);
81
+ if (createdHere[0]) {
82
+ return { credential: createdHere[0], reason: 'createdIn' };
83
+ }
84
+ const usedHere = creds
85
+ .filter((c) => c.projectDirs.includes(dir))
86
+ .sort(byNewest);
87
+ if (usedHere[0]) {
88
+ return { credential: usedHere[0], reason: 'usedIn' };
89
+ }
90
+ return { credential: [...creds].sort(byNewest)[0], reason: 'newest' };
91
+ }
92
+ /**
93
+ * Resolve the private key to sign attestor proofs with (the claim
94
+ * "owner"). Reuses the app credential rather than a dedicated key — for
95
+ * build-time test proofs the developer is both the app and the proof
96
+ * owner, and the owner identity is immaterial to whether the provider's
97
+ * matching works.
98
+ *
99
+ * The owner is chosen up front (see the `resolve_owner_key` tool) and
100
+ * passed by `address`; this just fetches the matching private key:
101
+ * - the `RECLAIM_PRIVATE_KEY` env / project `.env` key, if it derives
102
+ * to `address` (the dev pinned it), else
103
+ * - the cached credential with that address.
104
+ * Returns undefined when no key for `address` is known.
105
+ */
106
+ export function resolveOwnerPrivateKey(address) {
107
+ const target = address.toLowerCase();
108
+ const envKey = process.env.RECLAIM_PRIVATE_KEY ??
109
+ readPrivateKeyFromEnvFile(findProjectRoot(process.cwd()));
110
+ if (envKey && deriveFromPrivateKey(envKey).address === target) {
111
+ return envKey;
112
+ }
113
+ return findCredentialFromCachedBy(target)?.privateKey;
114
+ }
@@ -0,0 +1,25 @@
1
+ export type EnvWriteResult = {
2
+ envPath: string;
3
+ gitignorePath: string;
4
+ gitignoreAppended: boolean;
5
+ };
6
+ /**
7
+ * Idempotently set `RECLAIM_PRIVATE_KEY` in the project's `.env`, then
8
+ * `chmod 0600` it. The private key is the only secret we write — the
9
+ * ETH address (and public key) are derived from it on demand, so there
10
+ * is no separate address variable to keep in sync. Existing variables
11
+ * are replaced in place; other lines are preserved verbatim so we
12
+ * don't trample on unrelated config the user wrote by hand.
13
+ *
14
+ * Also appends `.env` to the project's `.gitignore` if it isn't
15
+ * already listed.
16
+ */
17
+ export declare function writeAppCredentialsToEnv(projectDir: string, privateKey: string): EnvWriteResult;
18
+ /**
19
+ * Read `RECLAIM_PRIVATE_KEY` back out of the project's `.env`, if set.
20
+ * Read live (not from `process.env`) so a credential written by
21
+ * `issue_credentials` / `import_credentials` is picked up immediately,
22
+ * without a Claude Code restart. Returns undefined when the file or the
23
+ * variable is absent.
24
+ */
25
+ export declare function readPrivateKeyFromEnvFile(projectDir: string): string | undefined;
@@ -0,0 +1,71 @@
1
+ import { appendFileSync, chmodSync, existsSync, readFileSync, writeFileSync, } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ const PRIVATE_KEY = 'RECLAIM_PRIVATE_KEY';
4
+ const GITIGNORE_LINE = '.env';
5
+ /**
6
+ * Idempotently set `RECLAIM_PRIVATE_KEY` in the project's `.env`, then
7
+ * `chmod 0600` it. The private key is the only secret we write — the
8
+ * ETH address (and public key) are derived from it on demand, so there
9
+ * is no separate address variable to keep in sync. Existing variables
10
+ * are replaced in place; other lines are preserved verbatim so we
11
+ * don't trample on unrelated config the user wrote by hand.
12
+ *
13
+ * Also appends `.env` to the project's `.gitignore` if it isn't
14
+ * already listed.
15
+ */
16
+ export function writeAppCredentialsToEnv(projectDir, privateKey) {
17
+ const envPath = join(projectDir, '.env');
18
+ const lines = existsSync(envPath)
19
+ ? readFileSync(envPath, 'utf8').split('\n')
20
+ : [];
21
+ setOrInsert(lines, PRIVATE_KEY, privateKey);
22
+ const body = lines.join('\n').replace(/\n+$/, '') + '\n';
23
+ writeFileSync(envPath, body, { mode: 0o600 });
24
+ chmodSync(envPath, 0o600);
25
+ const gitignorePath = join(projectDir, '.gitignore');
26
+ const gitignoreAppended = ensureLine(gitignorePath, GITIGNORE_LINE);
27
+ return { envPath, gitignorePath, gitignoreAppended };
28
+ }
29
+ /**
30
+ * Read `RECLAIM_PRIVATE_KEY` back out of the project's `.env`, if set.
31
+ * Read live (not from `process.env`) so a credential written by
32
+ * `issue_credentials` / `import_credentials` is picked up immediately,
33
+ * without a Claude Code restart. Returns undefined when the file or the
34
+ * variable is absent.
35
+ */
36
+ export function readPrivateKeyFromEnvFile(projectDir) {
37
+ const envPath = join(projectDir, '.env');
38
+ if (!existsSync(envPath)) {
39
+ return undefined;
40
+ }
41
+ const prefix = `${PRIVATE_KEY}=`;
42
+ const line = readFileSync(envPath, 'utf8')
43
+ .split('\n')
44
+ .find((l) => l.startsWith(prefix));
45
+ const value = line?.slice(prefix.length).trim();
46
+ return value || undefined;
47
+ }
48
+ function setOrInsert(lines, key, value) {
49
+ const prefix = `${key}=`;
50
+ const idx = lines.findIndex((l) => l.startsWith(prefix));
51
+ const next = `${key}=${value}`;
52
+ if (idx >= 0) {
53
+ lines[idx] = next;
54
+ return;
55
+ }
56
+ lines.push(next);
57
+ }
58
+ function ensureLine(path, line) {
59
+ if (!existsSync(path)) {
60
+ writeFileSync(path, line + '\n', { mode: 0o644 });
61
+ return true;
62
+ }
63
+ const body = readFileSync(path, 'utf8');
64
+ const present = body.split('\n').some((l) => l.trim() === line);
65
+ if (present) {
66
+ return false;
67
+ }
68
+ const suffix = body.endsWith('\n') ? '' : '\n';
69
+ appendFileSync(path, suffix + line + '\n');
70
+ return true;
71
+ }
@@ -0,0 +1,2 @@
1
+ import { type RegisteredTool } from '../../server.ts';
2
+ export declare function importTool(): RegisteredTool;
@@ -0,0 +1,52 @@
1
+ import { deriveFromPrivateKey } from '@reclaimprotocol/client';
2
+ import { findProjectRoot } from "../../../paths.js";
3
+ import { defineTool } from "../../server.js";
4
+ import { persistCredentialLocally } from "./register.js";
5
+ export function importTool() {
6
+ return defineTool({
7
+ name: 'import_credentials',
8
+ description: 'Import an existing Reclaim app credential the user ' +
9
+ 'already holds elsewhere. Provide the private key ' +
10
+ '(0x-prefixed hex, 64 chars); we derive the address ' +
11
+ 'locally and cache it. By default also writes ' +
12
+ 'RECLAIM_PRIVATE_KEY to the project .env (0600) and appends ' +
13
+ '.env to .gitignore; pass `writeEnv: false` to keep it in ' +
14
+ 'the local cache (memory) only and leave .env untouched. ' +
15
+ 'ALWAYS ask the dev whether to save to .env before writing. ' +
16
+ 'The credential stays local-only (SANDBOX); link it to an ' +
17
+ 'org to use it in production.',
18
+ inputSchema: {
19
+ type: 'object',
20
+ properties: {
21
+ privateKey: {
22
+ type: 'string',
23
+ pattern: '^0x[0-9a-fA-F]{64}$',
24
+ description: '0x-prefixed hex private key (64 hex chars / 32 bytes).',
25
+ },
26
+ label: {
27
+ type: 'string',
28
+ minLength: 1,
29
+ maxLength: 100,
30
+ },
31
+ projectDir: {
32
+ type: 'string',
33
+ description: 'Project directory to write .env into. Defaults ' +
34
+ 'to the closest ancestor of cwd containing ' +
35
+ '.git or package.json.',
36
+ },
37
+ writeEnv: {
38
+ type: 'boolean',
39
+ description: 'Write RECLAIM_PRIVATE_KEY to the project .env ' +
40
+ '(default true). Set false to cache in memory only, ' +
41
+ 'without touching .env.',
42
+ },
43
+ },
44
+ required: ['privateKey'],
45
+ },
46
+ }, async (args) => {
47
+ const keypair = deriveFromPrivateKey(args.privateKey);
48
+ const projectDir = findProjectRoot(args.projectDir || process.cwd());
49
+ const persisted = persistCredentialLocally(keypair, args.label, projectDir, args.writeEnv ?? true);
50
+ return { ...persisted };
51
+ });
52
+ }
@@ -0,0 +1,2 @@
1
+ import { type RegisteredTool } from '../../server.ts';
2
+ export declare function issueTool(): RegisteredTool;