@reclaimprotocol/agent 0.1.0-dev.4 → 0.1.1-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.
@@ -7,6 +7,7 @@
7
7
  // reveals), never on responseMatches.
8
8
  import { createClaimOnAttestor, } from '@reclaimprotocol/attestor-core';
9
9
  import { resolveOwnerPrivateKey } from "../mcp/tools/credentials/cache.js";
10
+ import { ensureZkFiles } from "./zk-files.js";
10
11
  /** Trim a captured log stream to the last N entries — for shrinking the
11
12
  * LLM-visible response while the full stream stays in the on-disk dump. */
12
13
  export function tailLogs(entries, n = LOG_TAIL_LINES) {
@@ -109,6 +110,9 @@ export async function runAttestorProof(provider, secrets, attestorUrl, ownerAddr
109
110
  };
110
111
  }
111
112
  try {
113
+ // Circuits aren't downloaded at install time (that blocked the MCP
114
+ // server's first launch). Fetch them lazily on the first proof.
115
+ await ensureZkFiles();
112
116
  const params = {
113
117
  url: provider.url,
114
118
  method: provider.method,
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Ensure the zk circuit resources are on disk before a proof runs. Idempotent
3
+ * and concurrency-safe: the download runs at most once per process, and a
4
+ * failed attempt clears the latch so a later proof can retry.
5
+ *
6
+ * Resolves silently when the resources are already present (the warm npx cache,
7
+ * or after a manual `npm run download:zk-files`) and when
8
+ * `@reclaimprotocol/zk-symmetric-crypto` isn't installed at all (SDK-only
9
+ * consumers have nothing to prove).
10
+ *
11
+ * All output goes to stderr (fd 2): the MCP server speaks JSON-RPC over stdout,
12
+ * so a stray write to fd 1 mid-session would corrupt the protocol.
13
+ */
14
+ export declare function ensureZkFiles(): Promise<void>;
@@ -0,0 +1,73 @@
1
+ // Lazy download of the @reclaimprotocol/zk-symmetric-crypto circuit resources
2
+ // that the snarkjs proof engine needs. These are ~280MB and are NOT fetched in
3
+ // a `postinstall` (that blocked the MCP server's first launch past the connect
4
+ // timeout — see the install notes). Instead we fetch them on the first proof,
5
+ // once, so the server starts immediately and only `run_proof` pays the cost.
6
+ import { spawn } from 'node:child_process';
7
+ import { existsSync } from 'node:fs';
8
+ import { createRequire } from 'node:module';
9
+ import { dirname, join } from 'node:path';
10
+ const require = createRequire(import.meta.url);
11
+ let inFlight;
12
+ /**
13
+ * Ensure the zk circuit resources are on disk before a proof runs. Idempotent
14
+ * and concurrency-safe: the download runs at most once per process, and a
15
+ * failed attempt clears the latch so a later proof can retry.
16
+ *
17
+ * Resolves silently when the resources are already present (the warm npx cache,
18
+ * or after a manual `npm run download:zk-files`) and when
19
+ * `@reclaimprotocol/zk-symmetric-crypto` isn't installed at all (SDK-only
20
+ * consumers have nothing to prove).
21
+ *
22
+ * All output goes to stderr (fd 2): the MCP server speaks JSON-RPC over stdout,
23
+ * so a stray write to fd 1 mid-session would corrupt the protocol.
24
+ */
25
+ export function ensureZkFiles() {
26
+ inFlight ??= download().catch((err) => {
27
+ inFlight = undefined;
28
+ throw err;
29
+ });
30
+ return inFlight;
31
+ }
32
+ async function download() {
33
+ let mainPath;
34
+ try {
35
+ mainPath = require.resolve('@reclaimprotocol/zk-symmetric-crypto');
36
+ }
37
+ catch {
38
+ return;
39
+ }
40
+ // mainPath is <pkg>/lib/index.js → resources live at <pkg>/resources, the
41
+ // downloader at <pkg>/lib/scripts/download-files.js. We only use the snarkjs
42
+ // engine (createClaimOnAttestor zkEngine: 'snarkjs'), so its presence is the
43
+ // signal that the circuits are ready.
44
+ const libDir = dirname(mainPath);
45
+ const pkgDir = dirname(libDir);
46
+ if (existsSync(join(pkgDir, 'resources', 'snarkjs'))) {
47
+ return;
48
+ }
49
+ const downloadScript = join(libDir, 'scripts', 'download-files.js');
50
+ if (!existsSync(downloadScript)) {
51
+ throw new Error('zk circuit downloader not found in '
52
+ + '@reclaimprotocol/zk-symmetric-crypto; '
53
+ + 'run `npm run download:zk-files` and retry.');
54
+ }
55
+ process.stderr.write('[reclaim-agent] downloading zkTLS proof circuits '
56
+ + '(~280MB, one-time)…\n');
57
+ // stdio fd 1→2: send the downloader's stdout to our stderr so its progress
58
+ // never lands on the MCP server's JSON-RPC stdout.
59
+ await new Promise((resolve, reject) => {
60
+ const child = spawn(process.execPath, [downloadScript], {
61
+ stdio: ['ignore', 2, 2],
62
+ });
63
+ child.on('error', reject);
64
+ child.on('exit', (code) => {
65
+ if (code === 0) {
66
+ resolve();
67
+ }
68
+ else {
69
+ reject(new Error(`zk download exited ${code}`));
70
+ }
71
+ });
72
+ });
73
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@reclaimprotocol/agent",
3
3
  "title": "Reclaim Agent",
4
- "version": "0.1.0-dev.4",
4
+ "version": "0.1.1-dev.1",
5
5
  "description": "Reclaim Protocol agent — SDK, CLI, and MCP server in one package. SDK + CLI talk to the builder backend; MCP server adds Chrome CDP capture, provider synthesis, replay, and zkTLS proof.",
6
6
  "type": "module",
7
7
  "main": "./lib/index.js",
@@ -45,8 +45,7 @@
45
45
  "start:cli": "node lib/cli.js",
46
46
  "start:mcp": "node lib/mcp-bin.js",
47
47
  "inspect": "npx @modelcontextprotocol/inspector node lib/mcp-bin.js",
48
- "download:zk-files": "node scripts/download-zk-files.js",
49
- "postinstall": "node scripts/download-zk-files.js"
48
+ "download:zk-files": "node scripts/download-zk-files.js"
50
49
  },
51
50
  "keywords": [
52
51
  "mcp",
@@ -86,4 +85,4 @@
86
85
  "typescript": "^5.9.0",
87
86
  "yaml": "^2.6.0"
88
87
  }
89
- }
88
+ }
@@ -11,6 +11,7 @@ import {
11
11
  } from '@reclaimprotocol/attestor-core'
12
12
  import { resolveOwnerPrivateKey } from '../mcp/tools/credentials/cache.ts'
13
13
  import type { ReclaimProvider } from '../provider/schema.ts'
14
+ import { ensureZkFiles } from './zk-files.ts'
14
15
 
15
16
  export interface AttestorLogEntry {
16
17
  level: 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal'
@@ -179,6 +180,10 @@ export async function runAttestorProof(
179
180
  }
180
181
 
181
182
  try {
183
+ // Circuits aren't downloaded at install time (that blocked the MCP
184
+ // server's first launch). Fetch them lazily on the first proof.
185
+ await ensureZkFiles()
186
+
182
187
  const params: ProviderParams<'http'> = {
183
188
  url: provider.url,
184
189
  method: provider.method,
@@ -0,0 +1,83 @@
1
+ // Lazy download of the @reclaimprotocol/zk-symmetric-crypto circuit resources
2
+ // that the snarkjs proof engine needs. These are ~280MB and are NOT fetched in
3
+ // a `postinstall` (that blocked the MCP server's first launch past the connect
4
+ // timeout — see the install notes). Instead we fetch them on the first proof,
5
+ // once, so the server starts immediately and only `run_proof` pays the cost.
6
+
7
+ import { spawn } from 'node:child_process'
8
+ import { existsSync } from 'node:fs'
9
+ import { createRequire } from 'node:module'
10
+ import { dirname, join } from 'node:path'
11
+
12
+ const require = createRequire(import.meta.url)
13
+
14
+ let inFlight: Promise<void> | undefined
15
+
16
+ /**
17
+ * Ensure the zk circuit resources are on disk before a proof runs. Idempotent
18
+ * and concurrency-safe: the download runs at most once per process, and a
19
+ * failed attempt clears the latch so a later proof can retry.
20
+ *
21
+ * Resolves silently when the resources are already present (the warm npx cache,
22
+ * or after a manual `npm run download:zk-files`) and when
23
+ * `@reclaimprotocol/zk-symmetric-crypto` isn't installed at all (SDK-only
24
+ * consumers have nothing to prove).
25
+ *
26
+ * All output goes to stderr (fd 2): the MCP server speaks JSON-RPC over stdout,
27
+ * so a stray write to fd 1 mid-session would corrupt the protocol.
28
+ */
29
+ export function ensureZkFiles(): Promise<void> {
30
+ inFlight ??= download().catch((err) => {
31
+ inFlight = undefined
32
+ throw err
33
+ })
34
+ return inFlight
35
+ }
36
+
37
+ async function download(): Promise<void> {
38
+ let mainPath: string
39
+ try {
40
+ mainPath = require.resolve('@reclaimprotocol/zk-symmetric-crypto')
41
+ } catch{
42
+ return
43
+ }
44
+
45
+ // mainPath is <pkg>/lib/index.js → resources live at <pkg>/resources, the
46
+ // downloader at <pkg>/lib/scripts/download-files.js. We only use the snarkjs
47
+ // engine (createClaimOnAttestor zkEngine: 'snarkjs'), so its presence is the
48
+ // signal that the circuits are ready.
49
+ const libDir = dirname(mainPath)
50
+ const pkgDir = dirname(libDir)
51
+ if(existsSync(join(pkgDir, 'resources', 'snarkjs'))) {
52
+ return
53
+ }
54
+
55
+ const downloadScript = join(libDir, 'scripts', 'download-files.js')
56
+ if(!existsSync(downloadScript)) {
57
+ throw new Error(
58
+ 'zk circuit downloader not found in '
59
+ + '@reclaimprotocol/zk-symmetric-crypto; '
60
+ + 'run `npm run download:zk-files` and retry.',
61
+ )
62
+ }
63
+
64
+ process.stderr.write(
65
+ '[reclaim-agent] downloading zkTLS proof circuits '
66
+ + '(~280MB, one-time)…\n',
67
+ )
68
+ // stdio fd 1→2: send the downloader's stdout to our stderr so its progress
69
+ // never lands on the MCP server's JSON-RPC stdout.
70
+ await new Promise<void>((resolve, reject) => {
71
+ const child = spawn(process.execPath, [downloadScript], {
72
+ stdio: ['ignore', 2, 2],
73
+ })
74
+ child.on('error', reject)
75
+ child.on('exit', (code) => {
76
+ if(code === 0) {
77
+ resolve()
78
+ } else {
79
+ reject(new Error(`zk download exited ${code}`))
80
+ }
81
+ })
82
+ })
83
+ }