@reclaimprotocol/agent 0.1.18 → 0.1.21

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 (168) hide show
  1. package/README.md +6 -2
  2. package/lib/authoring/backend.d.ts +4 -0
  3. package/lib/authoring/tools.js +4 -1
  4. package/lib/cdp/capture.d.ts +2 -2
  5. package/lib/cdp/capture.js +25 -21
  6. package/lib/cdp/client.d.ts +39 -1
  7. package/lib/cdp/client.js +125 -1
  8. package/lib/mcp/api/operations.gen.js +33 -9
  9. package/lib/mcp/instructions.js +27 -3
  10. package/lib/mcp/remap_errors.js +2 -1
  11. package/lib/mcp/start.js +16 -9
  12. package/lib/mcp/tools/agent/attach.d.ts +18 -4
  13. package/lib/mcp/tools/agent/attach.js +142 -52
  14. package/lib/mcp/tools/agent/build.d.ts +9 -2
  15. package/lib/mcp/tools/agent/build.js +15 -7
  16. package/lib/mcp/tools/agent/capture.d.ts +1 -0
  17. package/lib/mcp/tools/agent/capture.js +8 -1
  18. package/lib/mcp/tools/agent/eval.js +2 -3
  19. package/lib/mcp/tools/agent/how-it-works.d.ts +1 -1
  20. package/lib/mcp/tools/agent/how-it-works.js +1 -1
  21. package/lib/mcp/tools/agent/index.d.ts +1 -1
  22. package/lib/mcp/tools/agent/index.js +1 -1
  23. package/lib/mcp/tools/agent/publish.d.ts +4 -1
  24. package/lib/mcp/tools/agent/publish.js +42 -3
  25. package/lib/mcp/tools/agent/session.d.ts +2 -2
  26. package/lib/mcp/tools/agent/session.js +21 -5
  27. package/lib/mcp/tools/agent/shared.js +4 -6
  28. package/lib/mcp/tools/authenticate/authenticate.d.ts +1 -1
  29. package/lib/mcp/tools/authenticate/authenticate.js +13 -6
  30. package/lib/mcp/tools/authenticate/builder-access.d.ts +8 -0
  31. package/lib/mcp/tools/authenticate/builder-access.js +31 -0
  32. package/lib/mcp/tools/authenticate/index.d.ts +1 -0
  33. package/lib/mcp/tools/authenticate/index.js +1 -0
  34. package/lib/old/login-cdp.js +11 -3
  35. package/lib/old/tools/build.d.ts +8 -1
  36. package/lib/old/tools/build.js +6 -2
  37. package/lib/old/tools/publish.d.ts +10 -1
  38. package/lib/old/tools/publish.js +49 -3
  39. package/lib/provider/draft.d.ts +20 -0
  40. package/lib/provider/draft.js +75 -51
  41. package/lib/provider/index.d.ts +1 -1
  42. package/lib/provider/index.js +1 -1
  43. package/lib/provider/secrets.d.ts +37 -0
  44. package/lib/provider/secrets.js +147 -6
  45. package/lib/provider/to-version.d.ts +7 -0
  46. package/lib/provider/to-version.js +11 -1
  47. package/package.json +2 -2
  48. package/skill/SKILL.builder.md +44 -94
  49. package/skill/SKILL.common.md +102 -0
  50. package/skill/SKILL.old.md +21 -73
  51. package/skill/builder-mode.md +2 -2
  52. package/skill/injection-api.md +95 -10
  53. package/skill/old-mode.md +2 -2
  54. package/src/authoring/backend.ts +4 -0
  55. package/src/authoring/tools.ts +4 -1
  56. package/src/cdp/capture.ts +24 -24
  57. package/src/cdp/client.ts +197 -2
  58. package/src/mcp/api/operations.gen.ts +33 -9
  59. package/src/mcp/instructions.ts +36 -3
  60. package/src/mcp/remap_errors.ts +2 -1
  61. package/src/mcp/start.ts +24 -9
  62. package/src/mcp/tools/agent/attach.ts +198 -57
  63. package/src/mcp/tools/agent/build.ts +20 -6
  64. package/src/mcp/tools/agent/capture.ts +10 -1
  65. package/src/mcp/tools/agent/eval.ts +2 -3
  66. package/src/mcp/tools/agent/how-it-works.ts +1 -1
  67. package/src/mcp/tools/agent/index.ts +1 -1
  68. package/src/mcp/tools/agent/publish.ts +46 -2
  69. package/src/mcp/tools/agent/session.ts +29 -8
  70. package/src/mcp/tools/agent/shared.ts +5 -10
  71. package/src/mcp/tools/authenticate/authenticate.ts +17 -9
  72. package/src/mcp/tools/authenticate/builder-access.ts +42 -0
  73. package/src/mcp/tools/authenticate/index.ts +4 -0
  74. package/src/old/login-cdp.ts +13 -3
  75. package/src/old/tools/build.ts +7 -1
  76. package/src/old/tools/publish.ts +55 -4
  77. package/src/provider/draft.ts +98 -56
  78. package/src/provider/index.ts +9 -1
  79. package/src/provider/secrets.ts +171 -6
  80. package/src/provider/to-version.ts +12 -1
  81. package/lib/authoring/find.d.ts +0 -3
  82. package/lib/authoring/find.js +0 -54
  83. package/lib/authoring/propose.d.ts +0 -3
  84. package/lib/authoring/propose.js +0 -109
  85. package/lib/authoring/replay.d.ts +0 -10
  86. package/lib/authoring/replay.js +0 -34
  87. package/lib/cli/args.d.ts +0 -14
  88. package/lib/cli/args.js +0 -19
  89. package/lib/cli/commands.d.ts +0 -1
  90. package/lib/cli/commands.js +0 -19
  91. package/lib/cli.d.ts +0 -1
  92. package/lib/cli.js +0 -7
  93. package/lib/mcp/credentials/authenticate.d.ts +0 -3
  94. package/lib/mcp/credentials/authenticate.js +0 -108
  95. package/lib/mcp/credentials/build.d.ts +0 -5
  96. package/lib/mcp/credentials/build.js +0 -18
  97. package/lib/mcp/credentials/cache.d.ts +0 -70
  98. package/lib/mcp/credentials/cache.js +0 -131
  99. package/lib/mcp/credentials/env-file.d.ts +0 -25
  100. package/lib/mcp/credentials/env-file.js +0 -71
  101. package/lib/mcp/credentials/import.d.ts +0 -2
  102. package/lib/mcp/credentials/import.js +0 -52
  103. package/lib/mcp/credentials/issue.d.ts +0 -2
  104. package/lib/mcp/credentials/issue.js +0 -84
  105. package/lib/mcp/credentials/keypair.d.ts +0 -17
  106. package/lib/mcp/credentials/keypair.js +0 -48
  107. package/lib/mcp/credentials/link.d.ts +0 -3
  108. package/lib/mcp/credentials/link.js +0 -107
  109. package/lib/mcp/credentials/list-cached.d.ts +0 -2
  110. package/lib/mcp/credentials/list-cached.js +0 -55
  111. package/lib/mcp/credentials/owner-key.d.ts +0 -23
  112. package/lib/mcp/credentials/owner-key.js +0 -58
  113. package/lib/mcp/credentials/paths.d.ts +0 -12
  114. package/lib/mcp/credentials/paths.js +0 -35
  115. package/lib/mcp/credentials/poll.d.ts +0 -27
  116. package/lib/mcp/credentials/poll.js +0 -61
  117. package/lib/mcp/credentials/register.d.ts +0 -18
  118. package/lib/mcp/credentials/register.js +0 -30
  119. package/lib/mcp/credentials/resolve-owner-key.d.ts +0 -12
  120. package/lib/mcp/credentials/resolve-owner-key.js +0 -68
  121. package/lib/mcp/credentials/session-cache.d.ts +0 -14
  122. package/lib/mcp/credentials/session-cache.js +0 -42
  123. package/lib/mcp/credentials/status.d.ts +0 -3
  124. package/lib/mcp/credentials/status.js +0 -76
  125. package/lib/mcp/register.d.ts +0 -38
  126. package/lib/mcp/register.js +0 -80
  127. package/lib/mcp/tools/analyze.d.ts +0 -3
  128. package/lib/mcp/tools/analyze.js +0 -26
  129. package/lib/mcp/tools/attach.d.ts +0 -11
  130. package/lib/mcp/tools/attach.js +0 -72
  131. package/lib/mcp/tools/capture.d.ts +0 -10
  132. package/lib/mcp/tools/capture.js +0 -142
  133. package/lib/mcp/tools/eval.d.ts +0 -5
  134. package/lib/mcp/tools/eval.js +0 -127
  135. package/lib/mcp/tools/find.d.ts +0 -3
  136. package/lib/mcp/tools/find.js +0 -60
  137. package/lib/mcp/tools/owner-key.d.ts +0 -2
  138. package/lib/mcp/tools/owner-key.js +0 -63
  139. package/lib/mcp/tools/proof.d.ts +0 -3
  140. package/lib/mcp/tools/proof.js +0 -214
  141. package/lib/mcp/tools/propose.d.ts +0 -9
  142. package/lib/mcp/tools/propose.js +0 -75
  143. package/lib/mcp/tools/publish.d.ts +0 -3
  144. package/lib/mcp/tools/publish.js +0 -59
  145. package/lib/mcp/tools/replay.d.ts +0 -3
  146. package/lib/mcp/tools/replay.js +0 -46
  147. package/lib/mcp/tools/session.d.ts +0 -10
  148. package/lib/mcp/tools/session.js +0 -29
  149. package/lib/mcp/tools/verification/diagnose.d.ts +0 -11
  150. package/lib/mcp/tools/verification/diagnose.js +0 -132
  151. package/lib/mcp/verifications/build.d.ts +0 -5
  152. package/lib/mcp/verifications/build.js +0 -14
  153. package/lib/mcp/verifications/create.d.ts +0 -3
  154. package/lib/mcp/verifications/create.js +0 -57
  155. package/lib/mcp/verifications/find.d.ts +0 -3
  156. package/lib/mcp/verifications/find.js +0 -37
  157. package/lib/mcp/verifications/get.d.ts +0 -3
  158. package/lib/mcp/verifications/get.js +0 -26
  159. package/lib/mcp/verifications/update.d.ts +0 -3
  160. package/lib/mcp/verifications/update.js +0 -78
  161. package/lib/old/tools/get-version-config.d.ts +0 -17
  162. package/lib/old/tools/get-version-config.js +0 -47
  163. package/lib/routes.gen.d.ts +0 -271
  164. package/lib/routes.gen.js +0 -73
  165. package/lib/types/openapi.d.ts +0 -4
  166. package/lib/types/openapi.gen.d.ts +0 -3967
  167. package/lib/types/openapi.gen.js +0 -5
  168. package/lib/types/openapi.js +0 -1
@@ -105,6 +105,13 @@ const CONTEXT_PARAM_RE = /\{\{context\.([A-Za-z_][A-Za-z0-9_]*)\}\}/g
105
105
  * recipe can never reference a context value the Builder doesn't require.
106
106
  * Returns undefined when nothing references context (the field is optional).
107
107
  * Authors can refine types/descriptions in the version editor.
108
+ *
109
+ * A SECRET-named context param (see `classifyParamName`) is NOT rejected here
110
+ * even when it's templated into the URL/geoLocation — write-redaction can
111
+ * still hide it there, up to `secretUrlCharBudget` characters (summed across
112
+ * every such value in that URL). Whether it actually fits is only knowable
113
+ * once a real value exists, so that check runs at verification time
114
+ * (`verify/run.ts`), against the consumer's actual supplied value — not here.
108
115
  */
109
116
  export function deriveRequiredContext(
110
117
  providers: ReclaimProvider[],
@@ -114,7 +121,11 @@ export function deriveRequiredContext(
114
121
  } = {},
115
122
  ): CreateProviderVersionRequest['requiredContext'] {
116
123
  const parts: string[] = []
117
- for(const p of [...providers, ...(injection.allowedJsRequests ?? [])]) {
124
+ for(const p of providers) {
125
+ parts.push(p.url, p.body ?? '')
126
+ }
127
+
128
+ for(const p of injection.allowedJsRequests ?? []) {
118
129
  parts.push(p.url, p.body ?? '')
119
130
  }
120
131
 
@@ -1,3 +0,0 @@
1
- import { type RegisteredTool } from '../tools/registry.ts';
2
- import { type ResolveBackend } from './util.ts';
3
- export declare function findTool(resolveBackend: ResolveBackend): RegisteredTool;
@@ -1,54 +0,0 @@
1
- /**
2
- * `find_requests_containing` — search captured responses for any encoding of a
3
- * value and rank the requests that return it. Authored once as a plain
4
- * {@link RegisteredTool}; the MCP and cloud loop each pass their own backend
5
- * resolver (see tools.ts / mcp/tools/agent/shared.ts). Server-side: response
6
- * bodies stay here, only metadata + a short snippet leave.
7
- */
8
- import { normalizeCandidates } from "../provider/normalize.js";
9
- import { defineTool } from "../tools/registry.js";
10
- import { objectSchema, publicRequest } from "./util.js";
11
- export function findTool(resolveBackend) {
12
- return defineTool({
13
- name: 'find_requests_containing',
14
- description: 'Search captured responses for any encoding of `value` (raw, '
15
- + 'URL/JSON/HTML-escaped, …). Returns a ranked candidate list with '
16
- + 'the matched variant, score, and a short snippet — used to locate '
17
- + 'the request that returns a target. Server-side; bodies stay here.',
18
- inputSchema: objectSchema({ value: { type: 'string' }, limit: { type: 'number' } }, ['value']),
19
- }, async (args) => {
20
- const backend = resolveBackend(args);
21
- const variants = normalizeCandidates(String(args.value));
22
- const ranked = [];
23
- for (const r of backend.capture.requests.values()) {
24
- if (!r.responseBody) {
25
- continue;
26
- }
27
- let hit;
28
- for (const v of variants) {
29
- const idx = r.responseBody.indexOf(v);
30
- if (idx >= 0) {
31
- const start = Math.max(0, idx - 40);
32
- const end = Math.min(r.responseBody.length, idx + v.length + 40);
33
- hit = { snippet: r.responseBody.slice(start, end), variant: v };
34
- break;
35
- }
36
- }
37
- if (!hit) {
38
- continue;
39
- }
40
- // Prefer JSON responses + smaller bodies (likely API endpoints).
41
- const score = (r.contentType.includes('json') ? 10 : 0)
42
- + Math.max(0, 5 - Math.log10(Math.max(r.size, 1)));
43
- ranked.push({
44
- ...publicRequest(r),
45
- matchedVariant: hit.variant,
46
- snippet: hit.snippet,
47
- score,
48
- });
49
- }
50
- ranked.sort((a, b) => b.score - a.score);
51
- const limit = Number(args.limit) || 10;
52
- return { candidates: ranked.slice(0, limit) };
53
- });
54
- }
@@ -1,3 +0,0 @@
1
- import { type RegisteredTool } from '../tools/registry.ts';
2
- import { type ResolveBackend } from './util.ts';
3
- export declare function proposeTool(resolveBackend: ResolveBackend): RegisteredTool;
@@ -1,109 +0,0 @@
1
- /**
2
- * `propose_provider` — draft a provider (recipe + captured secrets) from ONE
3
- * captured request, one match + redaction per target value. Authored once as a
4
- * plain {@link RegisteredTool}; hosts pass their own backend resolver. The
5
- * draft (recipe + real secret header values) is held server-side under an
6
- * opaque `draftId`; the result carries only the secret-free provider.
7
- */
8
- import { draftProvider } from "../provider/draft.js";
9
- import { OPRF_MAX_BYTES } from "../provider/matcher.js";
10
- import { verificationWarnings } from "../provider/verification-checks.js";
11
- import { defineTool } from "../tools/registry.js";
12
- import { objectSchema } from "./util.js";
13
- export function proposeTool(resolveBackend) {
14
- return defineTool({
15
- name: 'propose_provider',
16
- description: 'Draft a provider from ONE captured request. Pass `targets` (an '
17
- + 'array): one entry per value to extract, each with its `value` '
18
- + '(verbatim) and a distinct `name` (its param). The provider gets '
19
- + 'one match + redaction per target. Returns a draftId + recipe '
20
- + '(never secrets). Prefer a SINGLE request holding ALL values. '
21
- + 'Single-value shorthand: `target` + `name`. Per-target `hash` '
22
- + 'OPRF-hashes that value.',
23
- inputSchema: objectSchema({
24
- requestId: { type: 'string' },
25
- name: { type: 'string' },
26
- targets: {
27
- type: 'array',
28
- items: {
29
- type: 'object',
30
- properties: {
31
- value: { type: 'string' },
32
- name: { type: 'string' },
33
- hash: {
34
- type: 'string',
35
- enum: ['oprf-raw', 'oprf', 'oprf-mpc'],
36
- },
37
- },
38
- required: ['value', 'name'],
39
- },
40
- },
41
- target: { type: 'string' },
42
- hash: { type: 'string', enum: ['oprf-raw', 'oprf', 'oprf-mpc'] },
43
- }, ['requestId', 'name']),
44
- }, async (args) => {
45
- const backend = resolveBackend(args);
46
- const requestId = String(args.requestId);
47
- const providerName = String(args.name);
48
- const defaultHash = args.hash;
49
- // Explicit multi-value `targets`, else the single `target` shorthand.
50
- const raw = Array.isArray(args.targets) ? args.targets : [];
51
- const targets = raw.length
52
- ? raw.map((t) => {
53
- const o = t;
54
- return {
55
- value: String(o.value),
56
- name: String(o.name),
57
- ...(typeof o.hash === 'string'
58
- ? { hash: o.hash }
59
- : {}),
60
- };
61
- })
62
- : typeof args.target === 'string'
63
- ? [{
64
- value: args.target,
65
- name: providerName,
66
- ...(defaultHash ? { hash: defaultHash } : {}),
67
- }]
68
- : [];
69
- if (targets.length === 0) {
70
- throw new Error('propose_provider needs a `target` value or a non-empty '
71
- + '`targets` array');
72
- }
73
- for (const t of targets) {
74
- if ((t.hash ?? defaultHash) !== undefined
75
- && t.value.length > OPRF_MAX_BYTES) {
76
- throw new Error(`OPRF caps hashed values at ${OPRF_MAX_BYTES} bytes; got `
77
- + `${t.value.length}-byte "${t.value}". Use a tighter `
78
- + 'target or drop `hash`.');
79
- }
80
- }
81
- const captured = backend.capture.requests.get(requestId);
82
- if (!captured) {
83
- throw new Error(`no captured request ${requestId}`);
84
- }
85
- if (!captured.responseBody) {
86
- throw new Error(`request body unavailable for ${requestId}`);
87
- }
88
- const { provider, secretRefs, extractions } = draftProvider(captured, targets, providerName, defaultHash);
89
- // Materialize the real secret header values for later replay/prove;
90
- // they stay in the server-side draft, never in this result.
91
- const secrets = {};
92
- for (const ref of secretRefs) {
93
- const real = captured.requestHeaders[ref.name];
94
- if (real !== undefined) {
95
- secrets[ref.name] = real;
96
- }
97
- }
98
- const draftId = backend.drafts.put({
99
- provider, secretRefs, secrets, requestId, extractions,
100
- });
101
- const warnings = verificationWarnings(provider);
102
- return {
103
- draftId,
104
- provider,
105
- secretRefs,
106
- ...(warnings.length ? { verificationWarnings: warnings } : {}),
107
- };
108
- });
109
- }
@@ -1,10 +0,0 @@
1
- /**
2
- * `replay_request` — validate a drafted recipe before proving: confirm its
3
- * selectors extract the target(s), and (with `withoutSecrets`) that the
4
- * endpoint is auth-bound. Authored once as a plain {@link RegisteredTool}; the
5
- * backend does the actual replay (MCP re-runs locally, cloud re-issues in the
6
- * TEE pod).
7
- */
8
- import { type RegisteredTool } from '../tools/registry.ts';
9
- import { type ResolveBackend } from './util.ts';
10
- export declare function replayTool(resolveBackend: ResolveBackend): RegisteredTool;
@@ -1,34 +0,0 @@
1
- /**
2
- * `replay_request` — validate a drafted recipe before proving: confirm its
3
- * selectors extract the target(s), and (with `withoutSecrets`) that the
4
- * endpoint is auth-bound. Authored once as a plain {@link RegisteredTool}; the
5
- * backend does the actual replay (MCP re-runs locally, cloud re-issues in the
6
- * TEE pod).
7
- */
8
- import { defineTool } from "../tools/registry.js";
9
- import { objectSchema } from "./util.js";
10
- export function replayTool(resolveBackend) {
11
- return defineTool({
12
- name: 'replay_request',
13
- description: 'Validate the drafted recipe before proving — confirm its selectors '
14
- + 'extract the target value. Returns diagnostics; run before '
15
- + 'proving. Set `withoutSecrets: true` for the AUTH-BOUND check: it '
16
- + 're-issues the request with NO secrets/cookies (an anonymous '
17
- + 'visitor). If the values still extract (valid:true), the endpoint '
18
- + 'is PUBLIC and the proof would not bind to the user — reject it '
19
- + 'and find an auth-bound request instead.',
20
- inputSchema: objectSchema({
21
- draftId: { type: 'string' },
22
- withoutSecrets: { type: 'boolean' },
23
- }, ['draftId']),
24
- }, async (args) => {
25
- const backend = resolveBackend(args);
26
- const draft = backend.drafts.get(String(args.draftId));
27
- if (!draft) {
28
- throw new Error(`no draft ${String(args.draftId)}`);
29
- }
30
- return backend.replay(draft, {
31
- withoutSecrets: Boolean(args.withoutSecrets),
32
- });
33
- });
34
- }
package/lib/cli/args.d.ts DELETED
@@ -1,14 +0,0 @@
1
- export declare function parseCliArgs(argv: string[]): {
2
- values: {
3
- [longOption: string]: string | boolean | undefined;
4
- } & {
5
- 'base-url'?: string | boolean | undefined;
6
- token?: string | boolean | undefined;
7
- param?: (string | boolean)[] | undefined;
8
- query?: (string | boolean)[] | undefined;
9
- body?: string | boolean | undefined;
10
- help?: string | boolean | undefined;
11
- version?: string | boolean | undefined;
12
- };
13
- positionals: string[];
14
- };
package/lib/cli/args.js DELETED
@@ -1,19 +0,0 @@
1
- import { parseArgs as nodeParseArgs } from 'node:util';
2
- // Placeholder. Final shape decided once the backend openapi stabilises and
3
- // the CLI surface is fleshed out (see AGENTS.md → "CLI").
4
- export function parseCliArgs(argv) {
5
- return nodeParseArgs({
6
- args: argv,
7
- options: {
8
- 'base-url': { type: 'string' },
9
- token: { type: 'string' },
10
- param: { type: 'string', multiple: true },
11
- query: { type: 'string', multiple: true },
12
- body: { type: 'string' },
13
- help: { type: 'boolean', short: 'h' },
14
- version: { type: 'boolean', short: 'v' },
15
- },
16
- allowPositionals: true,
17
- strict: false,
18
- });
19
- }
@@ -1 +0,0 @@
1
- export declare function run(argv: string[]): Promise<void>;
@@ -1,19 +0,0 @@
1
- import { notImplemented } from '@hapi/boom';
2
- import { DESCRIPTION, NAME, VERSION } from "../pkg.js";
3
- import { parseCliArgs } from "./args.js";
4
- // Entry point for the `reclaim` bin. Placeholder until backend openapi
5
- // finalises — `--version` and `--help` work; everything else throws.
6
- export async function run(argv) {
7
- const parsed = parseCliArgs(argv);
8
- if (parsed.values['version']) {
9
- process.stdout.write(`${NAME} ${VERSION}\n`);
10
- return;
11
- }
12
- if (parsed.values['help']) {
13
- process.stdout.write(`${NAME} ${VERSION}\n${DESCRIPTION}\n\n` +
14
- 'Usage: reclaim call <OperationId> ' +
15
- '[--param k=v] [--query k=v] [--body <json>]\n');
16
- return;
17
- }
18
- throw notImplemented('reclaim CLI: pending @reclaimprotocol/app spec finalisation');
19
- }
package/lib/cli.d.ts DELETED
@@ -1 +0,0 @@
1
- export {};
package/lib/cli.js DELETED
@@ -1,7 +0,0 @@
1
- #!/usr/bin/env node
2
- import { run } from "./cli/commands.js";
3
- import { LOGGER } from "./logger.js";
4
- run(process.argv.slice(2)).catch((err) => {
5
- LOGGER.error({ err }, 'cli failed');
6
- process.exitCode = 1;
7
- });
@@ -1,3 +0,0 @@
1
- import type { ReclaimClient } from '@reclaimprotocol/client';
2
- import { type RegisteredTool } from '../server.ts';
3
- export declare function authenticateTool(client: ReclaimClient): RegisteredTool;
@@ -1,108 +0,0 @@
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/session.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/session.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
- }
@@ -1,5 +0,0 @@
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[];
@@ -1,18 +0,0 @@
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
- }
@@ -1,70 +0,0 @@
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 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
- */
21
- export declare function readCache(): CacheFile;
22
- /**
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
- */
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 findCached(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 {};
@@ -1,131 +0,0 @@
1
- import { deriveFromPrivateKey } from '@reclaimprotocol/client';
2
- import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync, } from 'node:fs';
3
- import { credentialsCachePath, findProjectRoot, reclaimHomeDirPath } from "../../paths.js";
4
- import { readPrivateKeyFromEnvFile } from "./env-file.js";
5
- const EMPTY = { version: 1, credentials: [] };
6
- /**
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
- */
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 {
25
- return { ...EMPTY };
26
- }
27
- }
28
- /**
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`.
33
- */
34
- export function writeCache(doc) {
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);
43
- }
44
- /**
45
- * Upsert a credential entry. If a row already exists for `address`,
46
- * we merge the new `projectDirs` into the existing list, refresh
47
- * mutable fields, and keep the original `createdAt` / `createdIn`
48
- * (backfilling `createdIn` from the new entry when an old row lacks it).
49
- */
50
- export function upsertCredential(entry) {
51
- const doc = readCache();
52
- const idx = doc.credentials.findIndex((c) => c.address === entry.address.toLowerCase());
53
- const merged = {
54
- ...entry,
55
- address: entry.address.toLowerCase(),
56
- projectDirs: Array.from(new Set([
57
- ...(doc.credentials[idx]?.projectDirs ?? []),
58
- ...entry.projectDirs,
59
- ])),
60
- createdAt: doc.credentials[idx]?.createdAt ?? entry.createdAt,
61
- createdIn: doc.credentials[idx]?.createdIn ?? entry.createdIn,
62
- };
63
- if (idx >= 0) {
64
- doc.credentials[idx] = merged;
65
- }
66
- else {
67
- doc.credentials.unshift(merged);
68
- }
69
- writeCache(doc);
70
- return merged;
71
- }
72
- /** Look up a cached credential by address. Case-insensitive. */
73
- export function findCached(address) {
74
- const doc = readCache();
75
- const lc = address.toLowerCase();
76
- return doc.credentials.find((c) => c.address === lc);
77
- }
78
- /**
79
- * Pick the best cached credential for `dir`. Ranking tiers:
80
- * 1. created in `dir` (reason: 'createdIn')
81
- * 2. previously used in `dir` (reason: 'usedIn')
82
- * 3. most recently created (reason: 'newest')
83
- * Within every tier, newest `createdAt` wins; address ascending is the
84
- * final deterministic tie-break. Returns undefined only on an empty cache.
85
- *
86
- * Pass a pre-read `creds` array to avoid a redundant `readCache()` (and to
87
- * rank against the same snapshot the caller already holds).
88
- */
89
- export function suggestCredentialForDir(dir, creds = readCache().credentials) {
90
- if (creds.length === 0) {
91
- return undefined;
92
- }
93
- const byNewest = (a, b) => {
94
- return (b.createdAt ?? '').localeCompare(a.createdAt ?? '') ||
95
- a.address.localeCompare(b.address);
96
- };
97
- const createdHere = creds.filter((c) => c.createdIn === dir).sort(byNewest);
98
- if (createdHere[0]) {
99
- return { credential: createdHere[0], reason: 'createdIn' };
100
- }
101
- const usedHere = creds
102
- .filter((c) => c.projectDirs.includes(dir))
103
- .sort(byNewest);
104
- if (usedHere[0]) {
105
- return { credential: usedHere[0], reason: 'usedIn' };
106
- }
107
- return { credential: [...creds].sort(byNewest)[0], reason: 'newest' };
108
- }
109
- /**
110
- * Resolve the private key to sign attestor proofs with (the claim
111
- * "owner"). Reuses the app credential rather than a dedicated key — for
112
- * build-time test proofs the developer is both the app and the proof
113
- * owner, and the owner identity is immaterial to whether the provider's
114
- * matching works.
115
- *
116
- * The owner is chosen up front (see the `resolve_owner_key` tool) and
117
- * passed by `address`; this just fetches the matching private key:
118
- * - the `RECLAIM_PRIVATE_KEY` env / project `.env` key, if it derives
119
- * to `address` (the dev pinned it), else
120
- * - the cached credential with that address.
121
- * Returns undefined when no key for `address` is known.
122
- */
123
- export function resolveOwnerPrivateKey(address) {
124
- const target = address.toLowerCase();
125
- const envKey = process.env.RECLAIM_PRIVATE_KEY ??
126
- readPrivateKeyFromEnvFile(findProjectRoot(process.cwd()));
127
- if (envKey && deriveFromPrivateKey(envKey).address === target) {
128
- return envKey;
129
- }
130
- return findCached(target)?.privateKey;
131
- }