@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.
Files changed (232) hide show
  1. package/README.md +1 -1
  2. package/lib/cdp/capture-engine.d.ts +66 -0
  3. package/lib/cdp/capture-engine.js +153 -0
  4. package/lib/cdp/capture.d.ts +9 -20
  5. package/lib/cdp/capture.js +14 -138
  6. package/lib/cdp/index.d.ts +3 -0
  7. package/lib/cdp/index.js +6 -0
  8. package/lib/cdp/launcher.js +4 -3
  9. package/lib/config/store.d.ts +7 -6
  10. package/lib/config/store.js +2 -2
  11. package/lib/consts.d.ts +68 -0
  12. package/lib/consts.js +134 -0
  13. package/lib/index.d.ts +2 -2
  14. package/lib/index.js +1 -1
  15. package/lib/logger.js +2 -1
  16. package/lib/mcp/api/operations.gen.d.ts +100 -48
  17. package/lib/mcp/api/operations.gen.js +1129 -194
  18. package/lib/mcp/api/to-zod.d.ts +24 -0
  19. package/lib/mcp/api/to-zod.js +94 -0
  20. package/lib/mcp/api/tools.js +1 -1
  21. package/lib/mcp/credentials/authenticate.d.ts +3 -0
  22. package/lib/mcp/{tools/credentials → credentials}/authenticate.js +3 -3
  23. package/lib/mcp/credentials/build.d.ts +5 -0
  24. package/lib/mcp/credentials/build.js +18 -0
  25. package/lib/mcp/{tools/credentials → credentials}/cache.d.ts +9 -9
  26. package/lib/mcp/{tools/credentials → credentials}/cache.js +36 -19
  27. package/lib/mcp/credentials/import.d.ts +2 -0
  28. package/lib/mcp/{tools/credentials → credentials}/import.js +2 -2
  29. package/lib/mcp/credentials/issue.d.ts +2 -0
  30. package/lib/mcp/{tools/credentials → credentials}/issue.js +4 -4
  31. package/lib/mcp/credentials/keypair.d.ts +17 -0
  32. package/lib/mcp/credentials/keypair.js +48 -0
  33. package/lib/mcp/credentials/link.d.ts +3 -0
  34. package/lib/mcp/credentials/link.js +107 -0
  35. package/lib/mcp/{tools/credentials → credentials}/list-cached.d.ts +1 -1
  36. package/lib/mcp/{tools/credentials → credentials}/list-cached.js +5 -5
  37. package/lib/mcp/credentials/owner-key.d.ts +23 -0
  38. package/lib/mcp/credentials/owner-key.js +58 -0
  39. package/lib/mcp/credentials/paths.d.ts +12 -0
  40. package/lib/mcp/credentials/paths.js +35 -0
  41. package/lib/mcp/credentials/poll.d.ts +27 -0
  42. package/lib/mcp/credentials/poll.js +61 -0
  43. package/lib/mcp/{tools/credentials → credentials}/resolve-owner-key.d.ts +1 -1
  44. package/lib/mcp/{tools/credentials → credentials}/resolve-owner-key.js +2 -2
  45. package/lib/mcp/credentials/session-cache.d.ts +14 -0
  46. package/lib/mcp/credentials/session-cache.js +42 -0
  47. package/lib/mcp/{tools/credentials → credentials}/status.d.ts +1 -1
  48. package/lib/mcp/credentials/status.js +76 -0
  49. package/lib/mcp/instructions.d.ts +1 -0
  50. package/lib/mcp/instructions.js +11 -0
  51. package/lib/mcp/register.d.ts +38 -0
  52. package/lib/mcp/register.js +80 -0
  53. package/lib/mcp/server.d.ts +2 -8
  54. package/lib/mcp/server.js +3 -33
  55. package/lib/mcp/start.js +17 -11
  56. package/lib/mcp/tools/agent/build.d.ts +5 -9
  57. package/lib/mcp/tools/agent/build.js +7 -24
  58. package/lib/mcp/tools/agent/index.d.ts +2 -0
  59. package/lib/mcp/tools/agent/index.js +3 -0
  60. package/lib/mcp/tools/agent/proof.js +3 -7
  61. package/lib/mcp/tools/agent/propose.js +13 -2
  62. package/lib/mcp/tools/agent/publish.js +172 -75
  63. package/lib/mcp/tools/agent/replay.js +0 -5
  64. package/lib/mcp/tools/analyze.d.ts +3 -0
  65. package/lib/mcp/tools/analyze.js +26 -0
  66. package/lib/mcp/tools/attach.d.ts +11 -0
  67. package/lib/mcp/tools/attach.js +72 -0
  68. package/lib/mcp/tools/authenticate/authenticate.js +107 -0
  69. package/lib/mcp/tools/authenticate/build.d.ts +15 -0
  70. package/lib/mcp/tools/authenticate/build.js +28 -0
  71. package/lib/mcp/tools/authenticate/decrypt-key.d.ts +19 -0
  72. package/lib/mcp/tools/authenticate/decrypt-key.js +46 -0
  73. package/lib/mcp/tools/authenticate/eth-key.d.ts +31 -0
  74. package/lib/mcp/tools/authenticate/eth-key.js +116 -0
  75. package/lib/mcp/tools/authenticate/import.js +57 -0
  76. package/lib/mcp/tools/authenticate/index.d.ts +2 -0
  77. package/lib/mcp/tools/authenticate/index.js +4 -0
  78. package/lib/mcp/tools/authenticate/issue.js +42 -0
  79. package/lib/mcp/tools/authenticate/resolve-owner-key.d.ts +13 -0
  80. package/lib/mcp/tools/authenticate/resolve-owner-key.js +53 -0
  81. package/lib/mcp/tools/capture.d.ts +10 -0
  82. package/lib/mcp/tools/capture.js +142 -0
  83. package/lib/mcp/tools/eval.d.ts +5 -0
  84. package/lib/mcp/tools/eval.js +127 -0
  85. package/lib/mcp/tools/find.d.ts +3 -0
  86. package/lib/mcp/tools/find.js +60 -0
  87. package/lib/mcp/tools/index.d.ts +3 -0
  88. package/lib/mcp/tools/index.js +6 -0
  89. package/lib/mcp/tools/owner-key.d.ts +2 -0
  90. package/lib/mcp/tools/owner-key.js +63 -0
  91. package/lib/mcp/tools/proof.d.ts +3 -0
  92. package/lib/mcp/tools/proof.js +214 -0
  93. package/lib/mcp/tools/propose.d.ts +9 -0
  94. package/lib/mcp/tools/propose.js +75 -0
  95. package/lib/mcp/tools/publish.d.ts +3 -0
  96. package/lib/mcp/tools/publish.js +59 -0
  97. package/lib/mcp/tools/replay.d.ts +3 -0
  98. package/lib/mcp/tools/replay.js +46 -0
  99. package/lib/mcp/tools/session.d.ts +10 -0
  100. package/lib/mcp/tools/session.js +29 -0
  101. package/lib/mcp/tools/verification/build.d.ts +12 -0
  102. package/lib/mcp/tools/verification/build.js +14 -0
  103. package/lib/mcp/tools/verification/diagnose.d.ts +11 -0
  104. package/lib/mcp/tools/verification/diagnose.js +132 -0
  105. package/lib/mcp/tools/verification/index.d.ts +1 -0
  106. package/lib/mcp/tools/verification/index.js +2 -0
  107. package/lib/mcp/tools/verification/result.d.ts +11 -0
  108. package/lib/mcp/tools/verification/result.js +126 -0
  109. package/lib/mcp/verifications/build.d.ts +5 -0
  110. package/lib/mcp/verifications/build.js +14 -0
  111. package/lib/mcp/verifications/create.d.ts +3 -0
  112. package/lib/mcp/verifications/create.js +57 -0
  113. package/lib/mcp/verifications/find.d.ts +3 -0
  114. package/lib/mcp/verifications/find.js +37 -0
  115. package/lib/mcp/verifications/get.d.ts +3 -0
  116. package/lib/mcp/verifications/get.js +26 -0
  117. package/lib/mcp/verifications/update.d.ts +3 -0
  118. package/lib/mcp/verifications/update.js +78 -0
  119. package/lib/old/client.d.ts +3 -0
  120. package/lib/old/flag.d.ts +4 -1
  121. package/lib/old/flag.js +4 -3
  122. package/lib/old/identity.d.ts +0 -10
  123. package/lib/old/identity.js +8 -7
  124. package/lib/old/instructions.d.ts +0 -6
  125. package/lib/old/instructions.js +6 -24
  126. package/lib/old/login-cdp.d.ts +0 -3
  127. package/lib/old/login-cdp.js +2 -4
  128. package/lib/old/to-register.d.ts +3 -0
  129. package/lib/old/to-register.js +32 -7
  130. package/lib/old/tools/authenticate.js +2 -1
  131. package/lib/old/tools/publish.js +71 -10
  132. package/lib/paths.js +2 -2
  133. package/lib/proof/attestor.d.ts +33 -1
  134. package/lib/proof/attestor.js +67 -64
  135. package/lib/proof/index.d.ts +5 -0
  136. package/lib/proof/index.js +6 -0
  137. package/lib/proof/proof-core.d.ts +35 -0
  138. package/lib/proof/proof-core.js +84 -0
  139. package/lib/provider/index.d.ts +5 -0
  140. package/lib/provider/index.js +9 -0
  141. package/lib/provider/injection-doc.d.ts +1 -0
  142. package/lib/provider/injection-doc.js +17 -0
  143. package/lib/provider/match-redaction-warning.d.ts +25 -0
  144. package/lib/provider/match-redaction-warning.js +35 -0
  145. package/lib/provider/schema.d.ts +16 -0
  146. package/lib/provider/schema.js +11 -1
  147. package/lib/provider/to-version.d.ts +61 -12
  148. package/lib/provider/to-version.js +109 -24
  149. package/lib/provider/verification-checks.d.ts +12 -0
  150. package/lib/provider/verification-checks.js +30 -0
  151. package/lib/routes.gen.d.ts +271 -0
  152. package/lib/routes.gen.js +73 -0
  153. package/lib/types/openapi.gen.d.ts +3967 -0
  154. package/lib/types/openapi.gen.js +5 -0
  155. package/package.json +16 -7
  156. package/skill/SKILL.md +35 -14
  157. package/skill/builder-mode.md +13 -0
  158. package/skill/injection-api.md +42 -0
  159. package/skill/old-mode.md +15 -0
  160. package/src/cdp/capture-engine.ts +256 -0
  161. package/src/cdp/capture.ts +25 -185
  162. package/src/cdp/index.ts +6 -0
  163. package/src/cdp/launcher.ts +5 -4
  164. package/src/config/store.ts +7 -6
  165. package/src/consts.ts +164 -0
  166. package/src/index.ts +2 -4
  167. package/src/logger.ts +2 -1
  168. package/src/mcp/api/operations.gen.ts +1155 -194
  169. package/src/mcp/api/to-zod.ts +115 -0
  170. package/src/mcp/api/tools.ts +9 -3
  171. package/src/mcp/instructions.ts +15 -0
  172. package/src/mcp/server.ts +5 -50
  173. package/src/mcp/start.ts +27 -11
  174. package/src/mcp/tools/agent/build.ts +7 -27
  175. package/src/mcp/tools/agent/index.ts +4 -0
  176. package/src/mcp/tools/agent/proof.ts +3 -8
  177. package/src/mcp/tools/agent/propose.ts +13 -2
  178. package/src/mcp/tools/agent/publish.ts +218 -92
  179. package/src/mcp/tools/agent/replay.ts +0 -5
  180. package/src/mcp/tools/{credentials → authenticate}/authenticate.ts +9 -8
  181. package/src/mcp/tools/authenticate/build.ts +32 -0
  182. package/src/mcp/tools/authenticate/decrypt-key.ts +59 -0
  183. package/src/mcp/tools/authenticate/eth-key.ts +162 -0
  184. package/src/mcp/tools/authenticate/import.ts +73 -0
  185. package/src/mcp/tools/authenticate/index.ts +4 -0
  186. package/src/mcp/tools/authenticate/issue.ts +50 -0
  187. package/src/mcp/tools/authenticate/resolve-owner-key.ts +60 -0
  188. package/src/mcp/tools/index.ts +6 -0
  189. package/src/mcp/tools/verification/build.ts +16 -0
  190. package/src/mcp/tools/verification/index.ts +2 -0
  191. package/src/mcp/tools/verification/result.ts +154 -0
  192. package/src/old/client.ts +3 -0
  193. package/src/old/flag.ts +4 -3
  194. package/src/old/identity.ts +8 -7
  195. package/src/old/instructions.ts +10 -25
  196. package/src/old/login-cdp.ts +2 -5
  197. package/src/old/to-register.ts +58 -26
  198. package/src/old/tools/authenticate.ts +2 -1
  199. package/src/old/tools/publish.ts +92 -16
  200. package/src/paths.ts +2 -2
  201. package/src/proof/attestor.ts +114 -72
  202. package/src/proof/index.ts +21 -0
  203. package/src/proof/proof-core.ts +119 -0
  204. package/src/provider/index.ts +20 -0
  205. package/src/provider/injection-doc.ts +21 -0
  206. package/src/provider/match-redaction-warning.ts +53 -0
  207. package/src/provider/schema.ts +23 -0
  208. package/src/provider/to-version.ts +180 -33
  209. package/src/provider/verification-checks.ts +36 -0
  210. package/lib/mcp/tools/credentials/build.d.ts +0 -16
  211. package/lib/mcp/tools/credentials/build.js +0 -33
  212. package/lib/mcp/tools/credentials/status.js +0 -62
  213. package/src/mcp/tools/credentials/build.ts +0 -37
  214. package/src/mcp/tools/credentials/cache.ts +0 -161
  215. package/src/mcp/tools/credentials/env-file.ts +0 -101
  216. package/src/mcp/tools/credentials/import.ts +0 -72
  217. package/src/mcp/tools/credentials/issue.ts +0 -107
  218. package/src/mcp/tools/credentials/list-cached.ts +0 -66
  219. package/src/mcp/tools/credentials/register.ts +0 -49
  220. package/src/mcp/tools/credentials/resolve-owner-key.ts +0 -78
  221. package/src/mcp/tools/credentials/status.ts +0 -82
  222. package/src/types/openapi.ts +0 -7
  223. /package/lib/mcp/{tools/credentials → credentials}/env-file.d.ts +0 -0
  224. /package/lib/mcp/{tools/credentials → credentials}/env-file.js +0 -0
  225. /package/lib/mcp/{tools/credentials → credentials}/register.d.ts +0 -0
  226. /package/lib/mcp/{tools/credentials → credentials}/register.js +0 -0
  227. /package/lib/mcp/tools/{credentials → authenticate}/authenticate.d.ts +0 -0
  228. /package/lib/mcp/tools/{credentials → authenticate}/import.d.ts +0 -0
  229. /package/lib/mcp/tools/{credentials → authenticate}/issue.d.ts +0 -0
  230. /package/lib/mcp/tools/{credentials → authenticate}/session-cache.d.ts +0 -0
  231. /package/lib/mcp/tools/{credentials → authenticate}/session-cache.js +0 -0
  232. /package/src/mcp/tools/{credentials → authenticate}/session-cache.ts +0 -0
@@ -0,0 +1,24 @@
1
+ import { z } from 'zod';
2
+ import type { JSONSchema } from 'zod/v4/core';
3
+ /**
4
+ * JSON Schema → zod for the generated MCP tool inputs.
5
+ *
6
+ * The generator (`packages/app/scripts/mcp-tools-gen.ts`) inlines `$ref`s it
7
+ * walks through `properties`/`items`, but it does NOT recurse into `allOf` /
8
+ * `anyOf` / `oneOf`. So a schema composed with `allOf` (e.g.
9
+ * `RequestSelectionTemplate =
10
+ * allOf[ RequestSelection, { multiple, required } ]`,
11
+ * surfaced by the proof tools' `allowedJsRequests`) can carry a dangling
12
+ * `#/components/schemas/…` `$ref` that points outside the standalone schema
13
+ * object handed to `z.fromJSONSchema`. zod then throws `Reference not found`
14
+ * at module-load time, taking down every tool.
15
+ *
16
+ * We pre-process here (rather than hand-editing the generated file, which
17
+ * regen would clobber): flatten `allOf` compositions into a single object
18
+ * schema and neutralize any remaining unresolvable `#/components/schemas/…`
19
+ * `$ref` to a permissive `{}`. The schemas affected are deep proof-tool inputs
20
+ * the model rarely hand-builds, so loosening leaf validation there is
21
+ * acceptable; the common tool inputs (uuids, version patterns, contexts) keep
22
+ * their full constraints.
23
+ */
24
+ export declare function toZod(schema: JSONSchema.Schema): z.ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
@@ -0,0 +1,94 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * JSON Schema → zod for the generated MCP tool inputs.
4
+ *
5
+ * The generator (`packages/app/scripts/mcp-tools-gen.ts`) inlines `$ref`s it
6
+ * walks through `properties`/`items`, but it does NOT recurse into `allOf` /
7
+ * `anyOf` / `oneOf`. So a schema composed with `allOf` (e.g.
8
+ * `RequestSelectionTemplate =
9
+ * allOf[ RequestSelection, { multiple, required } ]`,
10
+ * surfaced by the proof tools' `allowedJsRequests`) can carry a dangling
11
+ * `#/components/schemas/…` `$ref` that points outside the standalone schema
12
+ * object handed to `z.fromJSONSchema`. zod then throws `Reference not found`
13
+ * at module-load time, taking down every tool.
14
+ *
15
+ * We pre-process here (rather than hand-editing the generated file, which
16
+ * regen would clobber): flatten `allOf` compositions into a single object
17
+ * schema and neutralize any remaining unresolvable `#/components/schemas/…`
18
+ * `$ref` to a permissive `{}`. The schemas affected are deep proof-tool inputs
19
+ * the model rarely hand-builds, so loosening leaf validation there is
20
+ * acceptable; the common tool inputs (uuids, version patterns, contexts) keep
21
+ * their full constraints.
22
+ */
23
+ export function toZod(schema) {
24
+ return z.fromJSONSchema(sanitize(schema));
25
+ }
26
+ /** True for a `$ref` that can't be resolved in a standalone schema object. */
27
+ function isDanglingComponentRef(node) {
28
+ return (typeof node.$ref === 'string' &&
29
+ (node.$ref).startsWith('#/components/'));
30
+ }
31
+ /**
32
+ * Recursively (a) merge `allOf` members into their parent so composition
33
+ * resolves locally, and (b) replace any dangling `#/components/…` `$ref` with
34
+ * a permissive empty schema. Pure — returns a fresh tree, never mutates input.
35
+ */
36
+ function sanitize(input) {
37
+ if (Array.isArray(input)) {
38
+ return input.map(sanitize);
39
+ }
40
+ if (input === null || typeof input !== 'object') {
41
+ return input;
42
+ }
43
+ const node = input;
44
+ if (isDanglingComponentRef(node)) {
45
+ // Drop the ref; keep any sibling keys (rare) sanitized.
46
+ const { $ref: _ref, ...rest } = node;
47
+ void _ref;
48
+ return sanitizeEntries(rest);
49
+ }
50
+ // Flatten allOf into the parent object. Each member is sanitized first so
51
+ // its own refs/allOf collapse; object members merge their `properties`,
52
+ // `required`, and scalar keys upward.
53
+ if (Array.isArray(node.allOf)) {
54
+ const { allOf, ...rest } = node;
55
+ let merged = sanitizeEntries(rest);
56
+ for (const member of allOf) {
57
+ merged = mergeObjectSchemas(merged, sanitize(member));
58
+ }
59
+ return merged;
60
+ }
61
+ return sanitizeEntries(node);
62
+ }
63
+ /** Sanitize every value of an object schema's keys. */
64
+ function sanitizeEntries(node) {
65
+ const out = {};
66
+ for (const [k, v] of Object.entries(node)) {
67
+ out[k] = sanitize(v);
68
+ }
69
+ return out;
70
+ }
71
+ /**
72
+ * Merge two (already-sanitized) object schemas for `allOf` flattening: union
73
+ * `properties`, union `required`, and let later members win on scalar keys.
74
+ * Both sides are expected to be object-shaped; non-object members fall back to
75
+ * a plain key spread (the rare case).
76
+ */
77
+ function mergeObjectSchemas(a, b) {
78
+ const out = { ...a, ...b };
79
+ const aProps = a.properties;
80
+ const bProps = b.properties;
81
+ if (aProps || bProps) {
82
+ out.properties = { ...aProps, ...bProps };
83
+ }
84
+ const aReq = Array.isArray(a.required) ? a.required : [];
85
+ const bReq = Array.isArray(b.required) ? b.required : [];
86
+ if (aReq.length || bReq.length) {
87
+ out.required = Array.from(new Set([...aReq, ...bReq]));
88
+ }
89
+ // `type` should stay `object` after merging two object schemas.
90
+ if (a.type === 'object' || b.type === 'object' || out.properties) {
91
+ out.type = 'object';
92
+ }
93
+ return out;
94
+ }
@@ -21,7 +21,7 @@ export function registerTools(server, client) {
21
21
  // bug to surface, not silently shadow.
22
22
  server.registerTool(toSnakeCase(opId), shape, async (args) => {
23
23
  try {
24
- const result = (await client.call(opId, args));
24
+ const result = await client.call(opId, args);
25
25
  return {
26
26
  content: [{
27
27
  type: 'text', text: JSON.stringify(result.data, null, 2)
@@ -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;
@@ -1,4 +1,4 @@
1
- import { defineTool } from "../../server.js";
1
+ import { defineTool } from "../server.js";
2
2
  import { writeSession } from "./session-cache.js";
3
3
  export function authenticateTool(client) {
4
4
  return defineTool({
@@ -11,7 +11,7 @@ export function authenticateTool(client) {
11
11
  'with the same `code` from step 1 to check if confirmed. ' +
12
12
  'Returns `LINKED` with a 2-day bearer token when confirmed, ' +
13
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' +
14
+ 'The token is cached in ~/.reclaim/session.json automatically.\n\n' +
15
15
  'To then link a credential to an org (needed only to use it ' +
16
16
  'for verification in production), use the token with the ' +
17
17
  'generated tools: `list_orgs` → `link_credential` with the ' +
@@ -78,7 +78,7 @@ async function checkSession(client, code) {
78
78
  return {
79
79
  status: 'LINKED',
80
80
  token,
81
- note: 'Token cached in ~/.reclaim/config.json and active now.',
81
+ note: 'Token cached in ~/.reclaim/session.json and active now.',
82
82
  };
83
83
  }
84
84
  return {
@@ -0,0 +1,5 @@
1
+ import type { ReclaimClient } from '@reclaimprotocol/client';
2
+ import type { RegisteredTool } from '../server.ts';
3
+ /** Credential lifecycle tools — build the flat list of RegisteredTool
4
+ * entries. Mirrors buildAgentTools() in mcp/register.ts. */
5
+ export declare function buildCredentialTools(client: ReclaimClient): RegisteredTool[];
@@ -0,0 +1,18 @@
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
+ /** Credential lifecycle tools — build the flat list of RegisteredTool
8
+ * entries. Mirrors buildAgentTools() in mcp/register.ts. */
9
+ export function buildCredentialTools(client) {
10
+ return [
11
+ authenticateTool(client),
12
+ listCachedTool(),
13
+ resolveOwnerKeyTool(),
14
+ issueTool(),
15
+ importTool(),
16
+ statusTool(client),
17
+ ];
18
+ }
@@ -14,16 +14,16 @@ type CacheFile = {
14
14
  credentials: CachedCredential[];
15
15
  };
16
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.
17
+ * Read the cache file. Returns an empty doc if missing. Surface-level
18
+ * shape errors fall back to empty — we'd rather lose the cache than
19
+ * crash the CLI on a corrupted file the user can re-create.
20
20
  */
21
- export declare function readCredentialsFromCache(): CacheFile;
21
+ export declare function readCache(): CacheFile;
22
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`.
23
+ * Write the cache file with strict permissions. The directory gets
24
+ * `0700` and the file `0600` — same posture as `~/.ssh`. We rewrite
25
+ * the whole doc each save; the file is tiny and the alternative
26
+ * (append-only) loses our ability to deduplicate `projectDirs`.
27
27
  */
28
28
  export declare function writeCache(doc: CacheFile): void;
29
29
  /**
@@ -34,7 +34,7 @@ export declare function writeCache(doc: CacheFile): void;
34
34
  */
35
35
  export declare function upsertCredential(entry: CachedCredential): CachedCredential;
36
36
  /** Look up a cached credential by address. Case-insensitive. */
37
- export declare function findCredentialFromCachedBy(address: string): CachedCredential | undefined;
37
+ export declare function findCached(address: string): CachedCredential | undefined;
38
38
  export type SuggestionReason = 'createdIn' | 'usedIn' | 'newest';
39
39
  export type CredentialSuggestion = {
40
40
  credential: CachedCredential;
@@ -1,28 +1,45 @@
1
1
  import { deriveFromPrivateKey } from '@reclaimprotocol/client';
2
- import { ConfigStore } from "../../../config/store.js";
3
- import { findProjectRoot } from "../../../paths.js";
2
+ import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync, } from 'node:fs';
3
+ import { credentialsCachePath, findProjectRoot, reclaimHomeDirPath } from "../../paths.js";
4
4
  import { readPrivateKeyFromEnvFile } from "./env-file.js";
5
5
  const EMPTY = { version: 1, credentials: [] };
6
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.
7
+ * Read the cache file. Returns an empty doc if missing. Surface-level
8
+ * shape errors fall back to empty — we'd rather lose the cache than
9
+ * crash the CLI on a corrupted file the user can re-create.
10
10
  */
11
- export function readCredentialsFromCache() {
12
- const creds = new ConfigStore().read()?.credentials;
13
- if (!Array.isArray(creds)) {
11
+ export function readCache() {
12
+ const path = credentialsCachePath();
13
+ if (!existsSync(path)) {
14
+ return { ...EMPTY };
15
+ }
16
+ try {
17
+ const raw = readFileSync(path, 'utf8');
18
+ const parsed = JSON.parse(raw);
19
+ if (parsed.version !== 1 || !Array.isArray(parsed.credentials)) {
20
+ return { ...EMPTY };
21
+ }
22
+ return parsed;
23
+ }
24
+ catch {
14
25
  return { ...EMPTY };
15
26
  }
16
- return { version: 1, credentials: creds };
17
27
  }
18
28
  /**
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`.
29
+ * Write the cache file with strict permissions. The directory gets
30
+ * `0700` and the file `0600` — same posture as `~/.ssh`. We rewrite
31
+ * the whole doc each save; the file is tiny and the alternative
32
+ * (append-only) loses our ability to deduplicate `projectDirs`.
23
33
  */
24
34
  export function writeCache(doc) {
25
- new ConfigStore().write({ credentials: doc.credentials });
35
+ const dir = reclaimHomeDirPath();
36
+ if (!existsSync(dir)) {
37
+ mkdirSync(dir, { recursive: true, mode: 0o700 });
38
+ }
39
+ chmodSync(dir, 0o700);
40
+ const path = credentialsCachePath();
41
+ writeFileSync(path, JSON.stringify(doc, null, 2) + '\n', { mode: 0o600 });
42
+ chmodSync(path, 0o600);
26
43
  }
27
44
  /**
28
45
  * Upsert a credential entry. If a row already exists for `address`,
@@ -31,7 +48,7 @@ export function writeCache(doc) {
31
48
  * (backfilling `createdIn` from the new entry when an old row lacks it).
32
49
  */
33
50
  export function upsertCredential(entry) {
34
- const doc = readCredentialsFromCache();
51
+ const doc = readCache();
35
52
  const idx = doc.credentials.findIndex((c) => c.address === entry.address.toLowerCase());
36
53
  const merged = {
37
54
  ...entry,
@@ -53,8 +70,8 @@ export function upsertCredential(entry) {
53
70
  return merged;
54
71
  }
55
72
  /** Look up a cached credential by address. Case-insensitive. */
56
- export function findCredentialFromCachedBy(address) {
57
- const doc = readCredentialsFromCache();
73
+ export function findCached(address) {
74
+ const doc = readCache();
58
75
  const lc = address.toLowerCase();
59
76
  return doc.credentials.find((c) => c.address === lc);
60
77
  }
@@ -69,7 +86,7 @@ export function findCredentialFromCachedBy(address) {
69
86
  * Pass a pre-read `creds` array to avoid a redundant `readCache()` (and to
70
87
  * rank against the same snapshot the caller already holds).
71
88
  */
72
- export function suggestCredentialForDir(dir, creds = readCredentialsFromCache().credentials) {
89
+ export function suggestCredentialForDir(dir, creds = readCache().credentials) {
73
90
  if (creds.length === 0) {
74
91
  return undefined;
75
92
  }
@@ -110,5 +127,5 @@ export function resolveOwnerPrivateKey(address) {
110
127
  if (envKey && deriveFromPrivateKey(envKey).address === target) {
111
128
  return envKey;
112
129
  }
113
- return findCredentialFromCachedBy(target)?.privateKey;
130
+ return findCached(target)?.privateKey;
114
131
  }
@@ -0,0 +1,2 @@
1
+ import { type RegisteredTool } from '../server.ts';
2
+ export declare function importTool(): RegisteredTool;
@@ -1,6 +1,6 @@
1
1
  import { deriveFromPrivateKey } from '@reclaimprotocol/client';
2
- import { findProjectRoot } from "../../../paths.js";
3
- import { defineTool } from "../../server.js";
2
+ import { findProjectRoot } from "../../paths.js";
3
+ import { defineTool } from "../server.js";
4
4
  import { persistCredentialLocally } from "./register.js";
5
5
  export function importTool() {
6
6
  return defineTool({
@@ -0,0 +1,2 @@
1
+ import { type RegisteredTool } from '../server.ts';
2
+ export declare function issueTool(): RegisteredTool;
@@ -1,7 +1,7 @@
1
1
  import { generateKeypair } from '@reclaimprotocol/client';
2
- import { findProjectRoot } from "../../../paths.js";
3
- import { defineTool } from "../../server.js";
4
- import { findCredentialFromCachedBy } from "./cache.js";
2
+ import { findProjectRoot } from "../../paths.js";
3
+ import { defineTool } from "../server.js";
4
+ import { findCached } from "./cache.js";
5
5
  import { persistCredentialLocally } from "./register.js";
6
6
  export function issueTool() {
7
7
  return defineTool({
@@ -53,7 +53,7 @@ export function issueTool() {
53
53
  let keypair;
54
54
  let reused = false;
55
55
  if (args.reuseCachedAppPk) {
56
- const cached = findCredentialFromCachedBy(args.reuseCachedAppPk);
56
+ const cached = findCached(args.reuseCachedAppPk);
57
57
  if (!cached) {
58
58
  throw new Error(`No cached credential for ${args.reuseCachedAppPk}. ` +
59
59
  'Call list_cached_credentials first.');
@@ -0,0 +1,17 @@
1
+ export type EthKeypair = {
2
+ address: string;
3
+ publicKey: string;
4
+ privateKey: string;
5
+ };
6
+ /**
7
+ * Generate a fresh secp256k1 key-pair and derive its lowercase
8
+ * Ethereum address (keccak-256 of the uncompressed pubkey minus the
9
+ * `0x04` prefix, last 20 bytes).
10
+ */
11
+ export declare function generateKeypair(): EthKeypair;
12
+ /**
13
+ * Recover the address + uncompressed public key from a hex-encoded
14
+ * private key. Used by `importCredentials` to derive the public
15
+ * surface the server needs from a key the user already holds.
16
+ */
17
+ export declare function deriveFromPrivateKey(privateKey: string): EthKeypair;
@@ -0,0 +1,48 @@
1
+ import { keccak_256 } from '@noble/hashes/sha3.js';
2
+ import * as secp from '@noble/secp256k1';
3
+ /**
4
+ * Generate a fresh secp256k1 key-pair and derive its lowercase
5
+ * Ethereum address (keccak-256 of the uncompressed pubkey minus the
6
+ * `0x04` prefix, last 20 bytes).
7
+ */
8
+ export function generateKeypair() {
9
+ const priv = secp.utils.randomSecretKey();
10
+ const pub = secp.getPublicKey(priv, false);
11
+ return {
12
+ address: '0x' + bytesToHex(keccak_256(pub.slice(1))).slice(-40),
13
+ publicKey: '0x' + bytesToHex(pub),
14
+ privateKey: '0x' + bytesToHex(priv),
15
+ };
16
+ }
17
+ /**
18
+ * Recover the address + uncompressed public key from a hex-encoded
19
+ * private key. Used by `importCredentials` to derive the public
20
+ * surface the server needs from a key the user already holds.
21
+ */
22
+ export function deriveFromPrivateKey(privateKey) {
23
+ const hex = privateKey.replace(/^0x/, '');
24
+ const priv = hexToBytes(hex);
25
+ const pub = secp.getPublicKey(priv, false);
26
+ return {
27
+ address: '0x' + bytesToHex(keccak_256(pub.slice(1))).slice(-40),
28
+ publicKey: '0x' + bytesToHex(pub),
29
+ privateKey: '0x' + hex,
30
+ };
31
+ }
32
+ function bytesToHex(bytes) {
33
+ let out = '';
34
+ for (const b of bytes) {
35
+ out += b.toString(16).padStart(2, '0');
36
+ }
37
+ return out;
38
+ }
39
+ function hexToBytes(hex) {
40
+ if (hex.length % 2 !== 0) {
41
+ throw new Error('private key hex must have even length');
42
+ }
43
+ const out = new Uint8Array(hex.length / 2);
44
+ for (let i = 0; i < out.length; i++) {
45
+ out[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
46
+ }
47
+ return out;
48
+ }
@@ -0,0 +1,3 @@
1
+ import type { ReclaimClient } from '@reclaimprotocol/client';
2
+ import { type RegisteredTool } from '../server.ts';
3
+ export declare function linkTool(client: ReclaimClient): RegisteredTool;
@@ -0,0 +1,107 @@
1
+ import { defineTool } from "../server.js";
2
+ import { patchCached } from "./cache.js";
3
+ import { writeSession } from "./session-cache.js";
4
+ export function linkTool(client) {
5
+ return defineTool({
6
+ name: 'link_credential_via_dashboard',
7
+ description: 'Pair a Reclaim app credential to an organization via the dashboard. '
8
+ + 'Two steps:\n\n'
9
+ + 'Step 1 — call with the credential `address`. Returns a `code` '
10
+ + 'and `attachUrl`. Tell the user to open the URL, sign in, pick '
11
+ + 'an organization, and click "Confirm link".\n\n'
12
+ + 'Step 2 — once the user has confirmed, call again with the same '
13
+ + '`code` from step 1 to check the result. Returns `LINKED` with '
14
+ + 'the linked `orgId` when done, `PENDING` if not yet.',
15
+ inputSchema: {
16
+ type: 'object',
17
+ properties: {
18
+ address: {
19
+ type: 'string',
20
+ pattern: '^0x[0-9a-fA-F]{40}$',
21
+ description: 'Lowercase ETH address. Defaults to '
22
+ + 'process.env.RECLAIM_APP_PK.',
23
+ },
24
+ code: {
25
+ type: 'string',
26
+ pattern: '^[A-HJ-NP-Z2-9]{5}$',
27
+ description: '5-character code from a previous step-1 call. '
28
+ + 'When provided, checks whether the user has '
29
+ + 'confirmed that session.',
30
+ },
31
+ },
32
+ },
33
+ }, async (args) => {
34
+ // Step 2: check an existing session
35
+ if (args.code) {
36
+ const address = (args.address || process.env.RECLAIM_APP_PK || '').toLowerCase();
37
+ let session;
38
+ try {
39
+ session = await client.call('GetAttachSession', {
40
+ params: { code: args.code },
41
+ query: {}, body: {},
42
+ });
43
+ }
44
+ catch (err) {
45
+ const msg = err.message;
46
+ if (msg.includes('404') || msg.includes('Not Found')) {
47
+ return {
48
+ status: 'NOT_FOUND',
49
+ hint: 'Session not found or expired. '
50
+ + 'Call link_credential_via_dashboard with just the address to start over.',
51
+ };
52
+ }
53
+ throw new Error(`Failed to check session: ${msg}`);
54
+ }
55
+ if (session.status === 'LINKED') {
56
+ if (session.linkedOrgId && address) {
57
+ patchCached(address, { orgId: session.linkedOrgId });
58
+ }
59
+ const token = session.token?.trim();
60
+ if (token) {
61
+ writeSession({
62
+ token,
63
+ expiresAt: new Date(Date.now() + 2 * 24 * 60 * 60 * 1000).toISOString(),
64
+ });
65
+ client.setToken(token);
66
+ }
67
+ return {
68
+ status: 'LINKED',
69
+ address,
70
+ ...(session.linkedOrgId ? { orgId: session.linkedOrgId } : {}),
71
+ ...(token ? { token } : {}),
72
+ };
73
+ }
74
+ if (session.status === 'EXPIRED') {
75
+ return {
76
+ status: 'EXPIRED',
77
+ hint: 'Session expired. Call link_credential_via_dashboard with the address to start over.',
78
+ };
79
+ }
80
+ // Still PENDING
81
+ return {
82
+ status: 'PENDING',
83
+ code: session.code,
84
+ attachUrl: session.attachUrl,
85
+ hint: `Still waiting. Open ${session.attachUrl}, sign in, pick an org, and confirm. `
86
+ + `Then call link_credential_via_dashboard with code: "${session.code}" again.`,
87
+ };
88
+ }
89
+ // Step 1: open a new session
90
+ const address = (args.address || process.env.RECLAIM_APP_PK || '').toLowerCase();
91
+ if (!address) {
92
+ throw new Error('No address provided and RECLAIM_APP_PK is not set.');
93
+ }
94
+ const session = await client.call('CreateAttachSession', {
95
+ params: {}, query: {},
96
+ body: { address },
97
+ });
98
+ return {
99
+ status: 'PENDING',
100
+ code: session.code,
101
+ attachUrl: session.attachUrl,
102
+ expiresAt: session.expiresAt,
103
+ instructions: `Open ${session.attachUrl} in your browser, sign in, and pick an organization. `
104
+ + `Once done, call link_credential_via_dashboard again with code: "${session.code}" to confirm.`,
105
+ };
106
+ });
107
+ }
@@ -1,2 +1,2 @@
1
- import { type RegisteredTool } from '../../server.ts';
1
+ import { type RegisteredTool } from '../server.ts';
2
2
  export declare function listCachedTool(): RegisteredTool;
@@ -1,11 +1,11 @@
1
- import { findProjectRoot } from "../../../paths.js";
2
- import { defineTool } from "../../server.js";
3
- import { readCredentialsFromCache, suggestCredentialForDir } from "./cache.js";
1
+ import { findProjectRoot } from "../../paths.js";
2
+ import { defineTool } from "../server.js";
3
+ import { readCache, suggestCredentialForDir } from "./cache.js";
4
4
  export function listCachedTool() {
5
5
  return defineTool({
6
6
  name: 'list_cached_credentials',
7
7
  description: 'List Reclaim app credentials (ETH key-pairs) cached ' +
8
- 'locally in ~/.reclaim/config.json. Returns one ' +
8
+ 'locally in ~/.reclaim/credentials.json. Returns one ' +
9
9
  'row per credential with address, optional label and ' +
10
10
  'orgId, createdAt, createdIn (where it was first created), ' +
11
11
  'and the project directories that have used it. Private keys ' +
@@ -19,7 +19,7 @@ export function listCachedTool() {
19
19
  properties: {},
20
20
  },
21
21
  }, async () => {
22
- const doc = readCredentialsFromCache();
22
+ const doc = readCache();
23
23
  const currentDir = findProjectRoot(process.cwd());
24
24
  const suggestion = suggestCredentialForDir(currentDir, doc.credentials);
25
25
  const suggestedAddress = suggestion?.credential.address;
@@ -0,0 +1,23 @@
1
+ export type OwnerKey = {
2
+ privateKey: string;
3
+ address: string;
4
+ publicKey: string;
5
+ /** ISO timestamp when the key was generated. */
6
+ createdAt: string;
7
+ };
8
+ export declare function ownerKeyPath(): string;
9
+ /** Read the saved owner key, or undefined if absent/unreadable. */
10
+ export declare function readOwnerKey(): OwnerKey | undefined;
11
+ /** Persist the owner key to ~/.reclaim/owner-key.json (0600). */
12
+ export declare function writeOwnerKey(key: OwnerKey): void;
13
+ /**
14
+ * Resolve the owner private key for proof generation. Priority:
15
+ * explicit `RECLAIM_AGENT_OWNER_PRIVATE_KEY` env var → saved
16
+ * ~/.reclaim/owner-key.json → undefined. Mirrors the token-resolution
17
+ * order used for the auth session.
18
+ */
19
+ export declare function resolveOwnerPrivateKey(): string | undefined;
20
+ /** True when the env var is set and therefore shadows any saved file. */
21
+ export declare function ownerKeyEnvOverrideActive(): boolean;
22
+ /** Generate a fresh owner key, persist it, and return it. */
23
+ export declare function generateOwnerKey(): OwnerKey;
@@ -0,0 +1,58 @@
1
+ import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync, } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { generateKeypair } from "./keypair.js";
4
+ import { reclaimHome } from "./paths.js";
5
+ const ENV_VAR = 'RECLAIM_AGENT_OWNER_PRIVATE_KEY';
6
+ export function ownerKeyPath() {
7
+ return join(reclaimHome(), 'owner-key.json');
8
+ }
9
+ /** Read the saved owner key, or undefined if absent/unreadable. */
10
+ export function readOwnerKey() {
11
+ const path = ownerKeyPath();
12
+ if (!existsSync(path)) {
13
+ return undefined;
14
+ }
15
+ try {
16
+ const k = JSON.parse(readFileSync(path, 'utf8'));
17
+ return k.privateKey ? k : undefined;
18
+ }
19
+ catch {
20
+ return undefined;
21
+ }
22
+ }
23
+ /** Persist the owner key to ~/.reclaim/owner-key.json (0600). */
24
+ export function writeOwnerKey(key) {
25
+ const dir = reclaimHome();
26
+ if (!existsSync(dir)) {
27
+ mkdirSync(dir, { recursive: true, mode: 0o700 });
28
+ }
29
+ chmodSync(dir, 0o700);
30
+ const path = ownerKeyPath();
31
+ writeFileSync(path, JSON.stringify(key, null, 2) + '\n', { mode: 0o600 });
32
+ chmodSync(path, 0o600);
33
+ }
34
+ /**
35
+ * Resolve the owner private key for proof generation. Priority:
36
+ * explicit `RECLAIM_AGENT_OWNER_PRIVATE_KEY` env var → saved
37
+ * ~/.reclaim/owner-key.json → undefined. Mirrors the token-resolution
38
+ * order used for the auth session.
39
+ */
40
+ export function resolveOwnerPrivateKey() {
41
+ return process.env[ENV_VAR] || readOwnerKey()?.privateKey;
42
+ }
43
+ /** True when the env var is set and therefore shadows any saved file. */
44
+ export function ownerKeyEnvOverrideActive() {
45
+ return Boolean(process.env[ENV_VAR]);
46
+ }
47
+ /** Generate a fresh owner key, persist it, and return it. */
48
+ export function generateOwnerKey() {
49
+ const kp = generateKeypair();
50
+ const key = {
51
+ privateKey: kp.privateKey,
52
+ address: kp.address,
53
+ publicKey: kp.publicKey,
54
+ createdAt: new Date().toISOString(),
55
+ };
56
+ writeOwnerKey(key);
57
+ return key;
58
+ }