@toon-protocol/client-mcp 0.26.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 (40) hide show
  1. package/LICENSE +190 -0
  2. package/README.md +261 -0
  3. package/dist/anon-proxy-6N362VEV-M7AX2QD7.js +24 -0
  4. package/dist/anon-proxy-6N362VEV-M7AX2QD7.js.map +1 -0
  5. package/dist/chunk-245J23EB.js +278 -0
  6. package/dist/chunk-245J23EB.js.map +1 -0
  7. package/dist/chunk-2SGZPDGE.js +625 -0
  8. package/dist/chunk-2SGZPDGE.js.map +1 -0
  9. package/dist/chunk-32QD72IL.js +83 -0
  10. package/dist/chunk-32QD72IL.js.map +1 -0
  11. package/dist/chunk-5YIZ2JQO.js +205 -0
  12. package/dist/chunk-5YIZ2JQO.js.map +1 -0
  13. package/dist/chunk-LR7W2ISE.js +657 -0
  14. package/dist/chunk-LR7W2ISE.js.map +1 -0
  15. package/dist/chunk-QTDCFXPF.js +2802 -0
  16. package/dist/chunk-QTDCFXPF.js.map +1 -0
  17. package/dist/chunk-VA7XC4FD.js +185 -0
  18. package/dist/chunk-VA7XC4FD.js.map +1 -0
  19. package/dist/chunk-WMYY5I3H.js +10818 -0
  20. package/dist/chunk-WMYY5I3H.js.map +1 -0
  21. package/dist/daemon.d.ts +1 -0
  22. package/dist/daemon.js +137 -0
  23. package/dist/daemon.js.map +1 -0
  24. package/dist/ed25519-OFFWPWRE.js +26 -0
  25. package/dist/ed25519-OFFWPWRE.js.map +1 -0
  26. package/dist/gateway-QOK47RKS-HB65KIKC.js +15 -0
  27. package/dist/gateway-QOK47RKS-HB65KIKC.js.map +1 -0
  28. package/dist/hmac-7WSXTWW4.js +11 -0
  29. package/dist/hmac-7WSXTWW4.js.map +1 -0
  30. package/dist/index.d.ts +642 -0
  31. package/dist/index.js +59 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/mcp.d.ts +1 -0
  34. package/dist/mcp.js +80 -0
  35. package/dist/mcp.js.map +1 -0
  36. package/dist/sha512-LMOIUNFJ.js +33 -0
  37. package/dist/sha512-LMOIUNFJ.js.map +1 -0
  38. package/dist/socks5-WTJBYGME-IXWLQDE7.js +138 -0
  39. package/dist/socks5-WTJBYGME-IXWLQDE7.js.map +1 -0
  40. package/package.json +59 -0
@@ -0,0 +1,278 @@
1
+ import { createRequire as __cr } from 'module'; const require = __cr(import.meta.url);
2
+
3
+ // ../client/dist/chunk-4YBYBFSN.js
4
+ import { createRequire } from "module";
5
+ var nodeRequire = createRequire(import.meta.url);
6
+ var ANON_VERSION = "v0.4.10.0-beta";
7
+ var RELEASE_BASE = `https://github.com/anyone-protocol/ator-protocol/releases/download/${ANON_VERSION}`;
8
+ var ANON_ASSETS = {
9
+ "darwin-arm64": {
10
+ assetName: "anon-beta-macos-arm64.zip",
11
+ sha256: "3b8724afc56354aa93d2fe804d6b8a685d3bff65dac0ca3384cae1ef010977b2"
12
+ },
13
+ "darwin-x64": {
14
+ assetName: "anon-beta-macos-amd64.zip",
15
+ sha256: null
16
+ },
17
+ "linux-x64": {
18
+ assetName: "anon-beta-linux-amd64.zip",
19
+ sha256: null
20
+ },
21
+ "linux-arm64": {
22
+ assetName: "anon-beta-linux-arm64.zip",
23
+ sha256: null
24
+ }
25
+ };
26
+ function selectAnonAsset(platform, arch) {
27
+ const key = `${platform}-${arch}`;
28
+ const asset = ANON_ASSETS[key];
29
+ if (!asset) {
30
+ throw new Error(
31
+ `No managed anon binary available for platform "${platform}" arch "${arch}". Supported: ${Object.keys(ANON_ASSETS).join(", ")}. Provide an explicit transport.socksProxy or set ANYONE_PROXY_URLS to use your own proxy.`
32
+ );
33
+ }
34
+ return asset;
35
+ }
36
+ function defaultCacheDir() {
37
+ const os = nodeRequire("node:os");
38
+ const path = nodeRequire("node:path");
39
+ const xdg = process.env["XDG_CACHE_HOME"];
40
+ if (xdg) {
41
+ return path.join(xdg, "toon-client", "anon", ANON_VERSION);
42
+ }
43
+ return path.join(os.homedir(), ".toon-client", "anon", ANON_VERSION);
44
+ }
45
+ function renderTorrc(cacheDir, socksPort) {
46
+ const path = nodeRequire("node:path");
47
+ return [
48
+ "AgreeToTerms 1",
49
+ `DataDirectory ${path.join(cacheDir, "data")}`,
50
+ `SOCKSPort 127.0.0.1:${socksPort}`,
51
+ "SOCKSPolicy accept *",
52
+ `GeoIPFile ${path.join(cacheDir, "geoip")}`,
53
+ `GeoIPv6File ${path.join(cacheDir, "geoip6")}`,
54
+ "Log notice stdout",
55
+ "RunAsDaemon 0",
56
+ ""
57
+ ].join("\n");
58
+ }
59
+ async function tcpProbe(host, port, timeoutMs) {
60
+ const net = nodeRequire("node:net");
61
+ return new Promise((resolve, reject) => {
62
+ const sock = net.createConnection({ host, port }, () => {
63
+ sock.destroy();
64
+ resolve();
65
+ });
66
+ sock.once("error", (err) => {
67
+ sock.destroy();
68
+ reject(err);
69
+ });
70
+ sock.setTimeout(timeoutMs, () => {
71
+ sock.destroy();
72
+ reject(new Error("timeout"));
73
+ });
74
+ });
75
+ }
76
+ async function sha256File(filePath) {
77
+ const fs = nodeRequire("node:fs");
78
+ const crypto = nodeRequire("node:crypto");
79
+ return new Promise((resolve, reject) => {
80
+ const hash = crypto.createHash("sha256");
81
+ const stream = fs.createReadStream(filePath);
82
+ stream.on("error", reject);
83
+ stream.on("data", (chunk) => hash.update(chunk));
84
+ stream.on("end", () => resolve(hash.digest("hex")));
85
+ });
86
+ }
87
+ async function downloadToFile(url, destPath) {
88
+ const fs = nodeRequire("node:fs");
89
+ const https = nodeRequire("node:https");
90
+ const fetchOnce = (u, redirectsLeft) => new Promise((resolve, reject) => {
91
+ const req = https.get(u, (res) => {
92
+ const status = res.statusCode ?? 0;
93
+ if (status >= 300 && status < 400 && res.headers.location) {
94
+ res.resume();
95
+ if (redirectsLeft <= 0) {
96
+ reject(new Error(`Too many redirects downloading ${url}`));
97
+ return;
98
+ }
99
+ resolve(fetchOnce(res.headers.location, redirectsLeft - 1));
100
+ return;
101
+ }
102
+ if (status !== 200) {
103
+ res.resume();
104
+ reject(new Error(`Download failed (HTTP ${status}) for ${u}`));
105
+ return;
106
+ }
107
+ const out = fs.createWriteStream(destPath);
108
+ res.pipe(out);
109
+ out.on("error", reject);
110
+ out.on("finish", () => out.close(() => resolve()));
111
+ });
112
+ req.on("error", reject);
113
+ req.setTimeout(12e4, () => {
114
+ req.destroy(new Error(`Download timeout for ${u}`));
115
+ });
116
+ });
117
+ await fetchOnce(url, 5);
118
+ }
119
+ async function extractZip(zipPath, destDir) {
120
+ const cp = nodeRequire("node:child_process");
121
+ await new Promise((resolve, reject) => {
122
+ const child = cp.spawn("unzip", ["-o", zipPath, "-d", destDir], {
123
+ stdio: ["ignore", "ignore", "pipe"]
124
+ });
125
+ let stderr = "";
126
+ child.stderr?.on("data", (d) => {
127
+ stderr += d.toString();
128
+ });
129
+ child.on(
130
+ "error",
131
+ (err) => reject(
132
+ new Error(`Failed to spawn unzip (is it installed?): ${err.message}`)
133
+ )
134
+ );
135
+ child.on("exit", (code) => {
136
+ if (code === 0) resolve();
137
+ else
138
+ reject(
139
+ new Error(`unzip exited ${code} extracting ${zipPath}: ${stderr}`)
140
+ );
141
+ });
142
+ });
143
+ }
144
+ async function ensureAnonBinary(opts) {
145
+ const fs = nodeRequire("node:fs");
146
+ const path = nodeRequire("node:path");
147
+ const download = opts.download ?? downloadToFile;
148
+ const extract = opts.extract ?? extractZip;
149
+ const asset = selectAnonAsset(opts.platform, opts.arch);
150
+ const anonPath = path.join(opts.cacheDir, "anon");
151
+ if (fs.existsSync(anonPath)) {
152
+ return anonPath;
153
+ }
154
+ if (asset.sha256 === null) {
155
+ throw new Error(
156
+ `Managed anon binary for "${opts.platform}-${opts.arch}" (${asset.assetName}) has no pinned checksum yet (see issue #204). Provide an explicit transport.socksProxy to use your own proxy.`
157
+ );
158
+ }
159
+ fs.mkdirSync(opts.cacheDir, { recursive: true });
160
+ const zipPath = path.join(opts.cacheDir, asset.assetName);
161
+ const url = `${RELEASE_BASE}/${asset.assetName}`;
162
+ await download(url, zipPath);
163
+ const actual = await sha256File(zipPath);
164
+ if (actual !== asset.sha256) {
165
+ try {
166
+ fs.rmSync(zipPath, { force: true });
167
+ } catch {
168
+ }
169
+ throw new Error(
170
+ `Checksum mismatch for ${asset.assetName}: expected ${asset.sha256}, got ${actual}. Refusing to run an unverified anon binary.`
171
+ );
172
+ }
173
+ await extract(zipPath, opts.cacheDir);
174
+ if (!fs.existsSync(anonPath)) {
175
+ throw new Error(
176
+ `Extraction of ${asset.assetName} did not produce an "anon" binary at ${anonPath}.`
177
+ );
178
+ }
179
+ try {
180
+ fs.chmodSync(anonPath, 493);
181
+ } catch {
182
+ }
183
+ return anonPath;
184
+ }
185
+ async function waitForAnonSocks(opts) {
186
+ const probe = opts.probe ?? tcpProbe;
187
+ const sleep = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
188
+ opts.log(`[anon] waiting for SOCKS5 bind on 127.0.0.1:${opts.port}\u2026`);
189
+ let lastErr = null;
190
+ while (Date.now() < opts.deadlineMs) {
191
+ if (opts.childExited()) {
192
+ throw new Error("[anon] process exited before SOCKS5 port bound");
193
+ }
194
+ try {
195
+ await probe("127.0.0.1", opts.port, 2e3);
196
+ opts.log(`[anon] SOCKS5 bound on 127.0.0.1:${opts.port}`);
197
+ return;
198
+ } catch (err) {
199
+ const msg = err.message;
200
+ if (msg !== lastErr) {
201
+ opts.log(`[anon] SOCKS5 not ready: ${msg}`);
202
+ lastErr = msg;
203
+ }
204
+ }
205
+ await sleep(2e3);
206
+ }
207
+ throw new Error(
208
+ `[anon] SOCKS5 never bound on 127.0.0.1:${opts.port} by deadline`
209
+ );
210
+ }
211
+ async function startManagedAnonProxy(options = {}) {
212
+ const fs = nodeRequire("node:fs");
213
+ const path = nodeRequire("node:path");
214
+ const os = nodeRequire("node:os");
215
+ const cp = nodeRequire("node:child_process");
216
+ const platform = options.platform ?? os.platform();
217
+ const arch = options.arch ?? os.arch();
218
+ const cacheDir = options.cacheDir ?? defaultCacheDir();
219
+ const socksPort = options.socksPort ?? 9050;
220
+ const bootstrapTimeoutMs = options.bootstrapTimeoutMs ?? 18e4;
221
+ const log = options.log ?? (() => {
222
+ });
223
+ const anonPath = await ensureAnonBinary({ cacheDir, platform, arch });
224
+ fs.mkdirSync(path.join(cacheDir, "data"), { recursive: true });
225
+ const torrcPath = path.join(cacheDir, "torrc");
226
+ fs.writeFileSync(torrcPath, renderTorrc(cacheDir, socksPort), {
227
+ mode: 420
228
+ });
229
+ log(`[anon] spawning: ${anonPath} -f ${torrcPath}`);
230
+ const child = cp.spawn(anonPath, ["-f", torrcPath], {
231
+ stdio: ["ignore", "inherit", "inherit"],
232
+ detached: false
233
+ });
234
+ let exited = false;
235
+ child.on("exit", (code, signal) => {
236
+ exited = true;
237
+ log(`[anon] child exited code=${code} signal=${signal}`);
238
+ });
239
+ child.on("error", (err) => {
240
+ log(`[anon] spawn error: ${err.message}`);
241
+ });
242
+ const stop = async () => {
243
+ if (!child.killed && !exited) {
244
+ try {
245
+ child.kill("SIGTERM");
246
+ } catch {
247
+ }
248
+ }
249
+ };
250
+ try {
251
+ await waitForAnonSocks({
252
+ port: socksPort,
253
+ deadlineMs: Date.now() + bootstrapTimeoutMs,
254
+ childExited: () => exited,
255
+ log
256
+ });
257
+ } catch (err) {
258
+ await stop();
259
+ throw err;
260
+ }
261
+ return {
262
+ socksProxy: `socks5h://127.0.0.1:${socksPort}`,
263
+ stop
264
+ };
265
+ }
266
+
267
+ export {
268
+ ANON_VERSION,
269
+ ANON_ASSETS,
270
+ selectAnonAsset,
271
+ defaultCacheDir,
272
+ renderTorrc,
273
+ tcpProbe,
274
+ ensureAnonBinary,
275
+ waitForAnonSocks,
276
+ startManagedAnonProxy
277
+ };
278
+ //# sourceMappingURL=chunk-245J23EB.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../client/src/transport/anon-proxy.ts"],"sourcesContent":["/**\n * Self-managed `anon` (anyone-protocol / ATOR) SOCKS5h proxy (Node.js only).\n *\n * Lets a `@toon-protocol/client` consumer reach a `.anyone` hidden service with\n * ZERO manual proxy setup: the SDK downloads, verifies, extracts, and spawns its\n * own `anon` daemon, waits for it to bootstrap + bind a loopback SOCKS5 port, and\n * hands back a `socks5h://127.0.0.1:<port>` URL. The proven reference is the\n * server-side pod entrypoint `docker/src/entrypoint-toon-client.ts` (`writeTorrc`,\n * `spawnAnon`, `waitForAnonSocks`, `tcpProbe`); this module ports that daemon\n * logic into the client package and adds the binary download + checksum gate so it\n * works without an OS-level `anon` install.\n *\n * BROWSER SAFETY: this module is dynamically imported only from `resolveTransport`\n * when a managed proxy is actually needed (Node-only path). Every Node built-in is\n * pulled in lazily via the ESM-safe `require(...)` built off `import.meta.url`\n * (the same pattern as `socks5.ts`), so a browser bundler that statically analyses\n * the package never reaches `node:child_process`/`node:fs`/`node:https`/`node:net`.\n */\n\nimport { createRequire } from 'node:module';\nimport type childProcessModule from 'node:child_process';\nimport type fsModule from 'node:fs';\nimport type netModule from 'node:net';\nimport type osModule from 'node:os';\nimport type pathModule from 'node:path';\nimport type httpsModule from 'node:https';\nimport type * as cryptoModule from 'node:crypto';\n\n// ESM-safe require — see socks5.ts for the full rationale. The published bundle\n// is ESM with Node built-ins external; a bare `require` would be rewritten into a\n// throwing `__require` shim. Building a real require off import.meta.url keeps the\n// synchronous, browser-guarded `require(...)` calls below working. This file is\n// only ever dynamically imported on the Node path, so browser bundlers that tree-\n// shake the static graph never include it.\nconst nodeRequire = createRequire(import.meta.url);\n\n/**\n * Pinned `anon` release. \"beta\" is the channel slug embedded in the per-platform\n * zip asset names (e.g. `anon-beta-macos-arm64.zip`).\n */\nexport const ANON_VERSION = 'v0.4.10.0-beta';\n\nconst RELEASE_BASE = `https://github.com/anyone-protocol/ator-protocol/releases/download/${ANON_VERSION}`;\n\n/**\n * Per-platform `anon` zip asset descriptor. `sha256` is the pinned checksum of the\n * release zip; entries with `sha256: null` are recognised (correct asset name) but\n * cannot be auto-downloaded until a checksum is pinned — cross-reference issue #204\n * to add the remaining hashes (linux amd64/arm64, macos amd64).\n */\nexport interface AnonAsset {\n /** Release asset file name, e.g. `anon-beta-macos-arm64.zip`. */\n assetName: string;\n /** Pinned sha256 of the zip, or null when not yet pinned (issue #204). */\n sha256: string | null;\n}\n\n/**\n * Platform → asset map keyed by `${os.platform()}-${os.arch()}` (Node values).\n * Only macOS + Linux on x64/arm64 are supported (the `anon` releases that ship a\n * SOCKS-capable binary). Windows is intentionally absent.\n *\n * Pinned checksums (issue #204 tracks filling in the nulls):\n * - darwin-arm64 (anon-beta-macos-arm64.zip): verified working manual flow.\n */\nexport const ANON_ASSETS: Record<string, AnonAsset> = {\n 'darwin-arm64': {\n assetName: 'anon-beta-macos-arm64.zip',\n sha256: '3b8724afc56354aa93d2fe804d6b8a685d3bff65dac0ca3384cae1ef010977b2',\n },\n 'darwin-x64': {\n assetName: 'anon-beta-macos-amd64.zip',\n sha256: null,\n },\n 'linux-x64': {\n assetName: 'anon-beta-linux-amd64.zip',\n sha256: null,\n },\n 'linux-arm64': {\n assetName: 'anon-beta-linux-arm64.zip',\n sha256: null,\n },\n};\n\n/**\n * Resolves the `anon` release asset for a platform/arch pair (Node\n * `os.platform()` / `os.arch()` values).\n *\n * @throws If the platform/arch combination has no known `anon` asset.\n */\nexport function selectAnonAsset(platform: string, arch: string): AnonAsset {\n const key = `${platform}-${arch}`;\n const asset = ANON_ASSETS[key];\n if (!asset) {\n throw new Error(\n `No managed anon binary available for platform \"${platform}\" arch \"${arch}\". ` +\n `Supported: ${Object.keys(ANON_ASSETS).join(', ')}. ` +\n 'Provide an explicit transport.socksProxy or set ANYONE_PROXY_URLS to use your own proxy.'\n );\n }\n return asset;\n}\n\n/**\n * Default cache directory for the downloaded/extracted `anon` binary.\n * Honours `XDG_CACHE_HOME`; otherwise `~/.toon-client/anon`.\n */\nexport function defaultCacheDir(): string {\n const os = nodeRequire('node:os') as typeof osModule;\n const path = nodeRequire('node:path') as typeof pathModule;\n const xdg = process.env['XDG_CACHE_HOME'];\n if (xdg) {\n return path.join(xdg, 'toon-client', 'anon', ANON_VERSION);\n }\n return path.join(os.homedir(), '.toon-client', 'anon', ANON_VERSION);\n}\n\n/**\n * Renders a SOCKS-only torrc. Mirrors `writeTorrc` in the proven docker\n * entrypoint. `AgreeToTerms 1` is REQUIRED — omitting it makes `anon` exit\n * immediately.\n */\nexport function renderTorrc(cacheDir: string, socksPort: number): string {\n const path = nodeRequire('node:path') as typeof pathModule;\n return [\n 'AgreeToTerms 1',\n `DataDirectory ${path.join(cacheDir, 'data')}`,\n `SOCKSPort 127.0.0.1:${socksPort}`,\n 'SOCKSPolicy accept *',\n `GeoIPFile ${path.join(cacheDir, 'geoip')}`,\n `GeoIPv6File ${path.join(cacheDir, 'geoip6')}`,\n 'Log notice stdout',\n 'RunAsDaemon 0',\n '',\n ].join('\\n');\n}\n\n/**\n * Simple TCP connect probe — confirms the SOCKS5 port has bound and accepts\n * connections. Mirrors `tcpProbe` in the docker entrypoint / `probeSocks5Proxy`.\n */\nexport async function tcpProbe(\n host: string,\n port: number,\n timeoutMs: number\n): Promise<void> {\n const net = nodeRequire('node:net') as typeof netModule;\n return new Promise<void>((resolve, reject) => {\n const sock = net.createConnection({ host, port }, () => {\n sock.destroy();\n resolve();\n });\n sock.once('error', (err: Error) => {\n sock.destroy();\n reject(err);\n });\n sock.setTimeout(timeoutMs, () => {\n sock.destroy();\n reject(new Error('timeout'));\n });\n });\n}\n\n/**\n * Computes the sha256 (hex) of a file using node:crypto streaming.\n */\nasync function sha256File(filePath: string): Promise<string> {\n const fs = nodeRequire('node:fs') as typeof fsModule;\n const crypto = nodeRequire('node:crypto') as typeof cryptoModule;\n return new Promise<string>((resolve, reject) => {\n const hash = crypto.createHash('sha256');\n const stream = fs.createReadStream(filePath);\n stream.on('error', reject);\n stream.on('data', (chunk) => hash.update(chunk));\n stream.on('end', () => resolve(hash.digest('hex')));\n });\n}\n\n/**\n * Downloads a URL to a file, following GitHub release redirects. Node-only\n * (node:https + node:fs).\n */\nasync function downloadToFile(url: string, destPath: string): Promise<void> {\n const fs = nodeRequire('node:fs') as typeof fsModule;\n const https = nodeRequire('node:https') as typeof httpsModule;\n\n const fetchOnce = (u: string, redirectsLeft: number): Promise<void> =>\n new Promise<void>((resolve, reject) => {\n const req = https.get(u, (res) => {\n const status = res.statusCode ?? 0;\n // GitHub release assets redirect to a signed S3 URL.\n if (status >= 300 && status < 400 && res.headers.location) {\n res.resume();\n if (redirectsLeft <= 0) {\n reject(new Error(`Too many redirects downloading ${url}`));\n return;\n }\n resolve(fetchOnce(res.headers.location, redirectsLeft - 1));\n return;\n }\n if (status !== 200) {\n res.resume();\n reject(new Error(`Download failed (HTTP ${status}) for ${u}`));\n return;\n }\n const out = fs.createWriteStream(destPath);\n res.pipe(out);\n out.on('error', reject);\n out.on('finish', () => out.close(() => resolve()));\n });\n req.on('error', reject);\n req.setTimeout(120_000, () => {\n req.destroy(new Error(`Download timeout for ${u}`));\n });\n });\n\n await fetchOnce(url, 5);\n}\n\n/**\n * Extracts a zip into a directory by shelling out to the system `unzip` binary\n * (present on macOS + Linux). Kept here (not a JS unzip dep) to avoid adding a\n * runtime dependency to the browser-facing client package.\n */\nasync function extractZip(zipPath: string, destDir: string): Promise<void> {\n const cp = nodeRequire('node:child_process') as typeof childProcessModule;\n await new Promise<void>((resolve, reject) => {\n const child = cp.spawn('unzip', ['-o', zipPath, '-d', destDir], {\n stdio: ['ignore', 'ignore', 'pipe'],\n });\n let stderr = '';\n child.stderr?.on('data', (d: Buffer) => {\n stderr += d.toString();\n });\n child.on('error', (err: Error) =>\n reject(\n new Error(`Failed to spawn unzip (is it installed?): ${err.message}`)\n )\n );\n child.on('exit', (code) => {\n if (code === 0) resolve();\n else\n reject(\n new Error(`unzip exited ${code} extracting ${zipPath}: ${stderr}`)\n );\n });\n });\n}\n\n/**\n * Ensures a verified `anon` binary exists in the cache directory, downloading +\n * checksum-verifying + extracting it if not. Returns the absolute path to the\n * extracted `anon` executable.\n *\n * Skips re-download when a previously extracted `anon` binary is already present\n * (the checksum gate runs on the freshly downloaded zip; an already-extracted\n * binary in a version-pinned cache dir is trusted).\n */\nexport async function ensureAnonBinary(opts: {\n cacheDir: string;\n platform: string;\n arch: string;\n /** Injectable downloader (tests). Default: node:https GET with redirects. */\n download?: (url: string, destPath: string) => Promise<void>;\n /** Injectable extractor (tests). Default: shell out to `unzip`. */\n extract?: (zipPath: string, destDir: string) => Promise<void>;\n}): Promise<string> {\n const fs = nodeRequire('node:fs') as typeof fsModule;\n const path = nodeRequire('node:path') as typeof pathModule;\n\n const download = opts.download ?? downloadToFile;\n const extract = opts.extract ?? extractZip;\n\n const asset = selectAnonAsset(opts.platform, opts.arch);\n const anonPath = path.join(opts.cacheDir, 'anon');\n\n // Fast path: already extracted (version-pinned cache dir).\n if (fs.existsSync(anonPath)) {\n return anonPath;\n }\n\n if (asset.sha256 === null) {\n throw new Error(\n `Managed anon binary for \"${opts.platform}-${opts.arch}\" ` +\n `(${asset.assetName}) has no pinned checksum yet (see issue #204). ` +\n 'Provide an explicit transport.socksProxy to use your own proxy.'\n );\n }\n\n fs.mkdirSync(opts.cacheDir, { recursive: true });\n const zipPath = path.join(opts.cacheDir, asset.assetName);\n const url = `${RELEASE_BASE}/${asset.assetName}`;\n\n await download(url, zipPath);\n\n const actual = await sha256File(zipPath);\n if (actual !== asset.sha256) {\n // Remove the bad artifact so a retry re-downloads cleanly.\n try {\n fs.rmSync(zipPath, { force: true });\n } catch {\n /* best-effort cleanup */\n }\n throw new Error(\n `Checksum mismatch for ${asset.assetName}: expected ${asset.sha256}, got ${actual}. ` +\n 'Refusing to run an unverified anon binary.'\n );\n }\n\n await extract(zipPath, opts.cacheDir);\n\n if (!fs.existsSync(anonPath)) {\n throw new Error(\n `Extraction of ${asset.assetName} did not produce an \"anon\" binary at ${anonPath}.`\n );\n }\n // Ensure executable (zip may not preserve the bit on all platforms).\n try {\n fs.chmodSync(anonPath, 0o755);\n } catch {\n /* best-effort */\n }\n return anonPath;\n}\n\n/**\n * Polls for the SOCKS5 port to bind. `anon` typically takes 30-90s to bootstrap\n * (build a circuit + consensus) before SOCKS5 accepts connections. Mirrors\n * `waitForAnonSocks` in the docker entrypoint, but also fails fast if the child\n * exits before binding.\n */\nexport async function waitForAnonSocks(opts: {\n port: number;\n deadlineMs: number;\n childExited: () => boolean;\n log: (msg: string) => void;\n probe?: (host: string, port: number, timeoutMs: number) => Promise<void>;\n sleep?: (ms: number) => Promise<void>;\n}): Promise<void> {\n const probe = opts.probe ?? tcpProbe;\n const sleep =\n opts.sleep ?? ((ms: number) => new Promise((r) => setTimeout(r, ms)));\n opts.log(`[anon] waiting for SOCKS5 bind on 127.0.0.1:${opts.port}…`);\n let lastErr: string | null = null;\n while (Date.now() < opts.deadlineMs) {\n if (opts.childExited()) {\n throw new Error('[anon] process exited before SOCKS5 port bound');\n }\n try {\n await probe('127.0.0.1', opts.port, 2_000);\n opts.log(`[anon] SOCKS5 bound on 127.0.0.1:${opts.port}`);\n return;\n } catch (err) {\n const msg = (err as Error).message;\n if (msg !== lastErr) {\n opts.log(`[anon] SOCKS5 not ready: ${msg}`);\n lastErr = msg;\n }\n }\n await sleep(2_000);\n }\n throw new Error(\n `[anon] SOCKS5 never bound on 127.0.0.1:${opts.port} by deadline`\n );\n}\n\n/**\n * Handle returned by `startManagedAnonProxy`. `socksProxy` is the loopback\n * `socks5h://` URL to wire into `transport: { type: 'socks5', socksProxy }`.\n * `stop()` SIGTERMs the daemon and is idempotent.\n */\nexport interface ManagedAnonProxy {\n socksProxy: string;\n stop(): Promise<void>;\n}\n\n/**\n * Options for `startManagedAnonProxy`. All have sensible defaults; tests inject\n * the deps to avoid real downloads/spawns.\n */\nexport interface StartManagedAnonProxyOptions {\n /** Cache dir for the binary + torrc + data. Default: {@link defaultCacheDir}. */\n cacheDir?: string;\n /** Loopback SOCKS5 port. Default 9050. */\n socksPort?: number;\n /** Bootstrap deadline in ms. Default 180_000. */\n bootstrapTimeoutMs?: number;\n /** Logger. Default: no-op. */\n log?: (msg: string) => void;\n /** os.platform() override (tests). */\n platform?: string;\n /** os.arch() override (tests). */\n arch?: string;\n}\n\n/**\n * Downloads (if needed) + spawns a managed `anon` daemon and waits for its SOCKS5\n * port to bind. Returns a {@link ManagedAnonProxy} whose `socksProxy` is ready for\n * `transport: { type: 'socks5', socksProxy }`.\n *\n * @throws If the platform is unsupported, the checksum fails, or anon never binds.\n */\nexport async function startManagedAnonProxy(\n options: StartManagedAnonProxyOptions = {}\n): Promise<ManagedAnonProxy> {\n const fs = nodeRequire('node:fs') as typeof fsModule;\n const path = nodeRequire('node:path') as typeof pathModule;\n const os = nodeRequire('node:os') as typeof osModule;\n const cp = nodeRequire('node:child_process') as typeof childProcessModule;\n\n const platform = options.platform ?? os.platform();\n const arch = options.arch ?? os.arch();\n const cacheDir = options.cacheDir ?? defaultCacheDir();\n const socksPort = options.socksPort ?? 9050;\n const bootstrapTimeoutMs = options.bootstrapTimeoutMs ?? 180_000;\n const log =\n options.log ??\n ((): void => {\n /* default: silent */\n });\n\n const anonPath = await ensureAnonBinary({ cacheDir, platform, arch });\n\n // Write the SOCKS-only torrc.\n fs.mkdirSync(path.join(cacheDir, 'data'), { recursive: true });\n const torrcPath = path.join(cacheDir, 'torrc');\n fs.writeFileSync(torrcPath, renderTorrc(cacheDir, socksPort), {\n mode: 0o644,\n });\n\n log(`[anon] spawning: ${anonPath} -f ${torrcPath}`);\n const child = cp.spawn(anonPath, ['-f', torrcPath], {\n stdio: ['ignore', 'inherit', 'inherit'],\n detached: false,\n });\n let exited = false;\n child.on('exit', (code, signal) => {\n exited = true;\n log(`[anon] child exited code=${code} signal=${signal}`);\n });\n child.on('error', (err: Error) => {\n log(`[anon] spawn error: ${err.message}`);\n });\n\n const stop = async (): Promise<void> => {\n if (!child.killed && !exited) {\n try {\n child.kill('SIGTERM');\n } catch {\n /* best-effort */\n }\n }\n };\n\n try {\n await waitForAnonSocks({\n port: socksPort,\n deadlineMs: Date.now() + bootstrapTimeoutMs,\n childExited: () => exited,\n log,\n });\n } catch (err) {\n await stop();\n throw err;\n }\n\n return {\n socksProxy: `socks5h://127.0.0.1:${socksPort}`,\n stop,\n };\n}\n"],"mappings":";;;AAmBA,SAAS,qBAAqB;AAe9B,IAAM,cAAc,cAAc,YAAY,GAAG;AAM1C,IAAM,eAAe;AAE5B,IAAM,eAAe,sEAAsE,YAAY;AAuBhG,IAAM,cAAyC;EACpD,gBAAgB;IACd,WAAW;IACX,QAAQ;EACV;EACA,cAAc;IACZ,WAAW;IACX,QAAQ;EACV;EACA,aAAa;IACX,WAAW;IACX,QAAQ;EACV;EACA,eAAe;IACb,WAAW;IACX,QAAQ;EACV;AACF;AAQO,SAAS,gBAAgB,UAAkB,MAAyB;AACzE,QAAM,MAAM,GAAG,QAAQ,IAAI,IAAI;AAC/B,QAAM,QAAQ,YAAY,GAAG;AAC7B,MAAI,CAAC,OAAO;AACV,UAAM,IAAI;MACR,kDAAkD,QAAQ,WAAW,IAAI,iBACzD,OAAO,KAAK,WAAW,EAAE,KAAK,IAAI,CAAC;IAErD;EACF;AACA,SAAO;AACT;AAMO,SAAS,kBAA0B;AACxC,QAAM,KAAK,YAAY,SAAS;AAChC,QAAM,OAAO,YAAY,WAAW;AACpC,QAAM,MAAM,QAAQ,IAAI,gBAAgB;AACxC,MAAI,KAAK;AACP,WAAO,KAAK,KAAK,KAAK,eAAe,QAAQ,YAAY;EAC3D;AACA,SAAO,KAAK,KAAK,GAAG,QAAQ,GAAG,gBAAgB,QAAQ,YAAY;AACrE;AAOO,SAAS,YAAY,UAAkB,WAA2B;AACvE,QAAM,OAAO,YAAY,WAAW;AACpC,SAAO;IACL;IACA,iBAAiB,KAAK,KAAK,UAAU,MAAM,CAAC;IAC5C,uBAAuB,SAAS;IAChC;IACA,aAAa,KAAK,KAAK,UAAU,OAAO,CAAC;IACzC,eAAe,KAAK,KAAK,UAAU,QAAQ,CAAC;IAC5C;IACA;IACA;EACF,EAAE,KAAK,IAAI;AACb;AAMA,eAAsB,SACpB,MACA,MACA,WACe;AACf,QAAM,MAAM,YAAY,UAAU;AAClC,SAAO,IAAI,QAAc,CAAC,SAAS,WAAW;AAC5C,UAAM,OAAO,IAAI,iBAAiB,EAAE,MAAM,KAAK,GAAG,MAAM;AACtD,WAAK,QAAQ;AACb,cAAQ;IACV,CAAC;AACD,SAAK,KAAK,SAAS,CAAC,QAAe;AACjC,WAAK,QAAQ;AACb,aAAO,GAAG;IACZ,CAAC;AACD,SAAK,WAAW,WAAW,MAAM;AAC/B,WAAK,QAAQ;AACb,aAAO,IAAI,MAAM,SAAS,CAAC;IAC7B,CAAC;EACH,CAAC;AACH;AAKA,eAAe,WAAW,UAAmC;AAC3D,QAAM,KAAK,YAAY,SAAS;AAChC,QAAM,SAAS,YAAY,aAAa;AACxC,SAAO,IAAI,QAAgB,CAAC,SAAS,WAAW;AAC9C,UAAM,OAAO,OAAO,WAAW,QAAQ;AACvC,UAAM,SAAS,GAAG,iBAAiB,QAAQ;AAC3C,WAAO,GAAG,SAAS,MAAM;AACzB,WAAO,GAAG,QAAQ,CAAC,UAAU,KAAK,OAAO,KAAK,CAAC;AAC/C,WAAO,GAAG,OAAO,MAAM,QAAQ,KAAK,OAAO,KAAK,CAAC,CAAC;EACpD,CAAC;AACH;AAMA,eAAe,eAAe,KAAa,UAAiC;AAC1E,QAAM,KAAK,YAAY,SAAS;AAChC,QAAM,QAAQ,YAAY,YAAY;AAEtC,QAAM,YAAY,CAAC,GAAW,kBAC5B,IAAI,QAAc,CAAC,SAAS,WAAW;AACrC,UAAM,MAAM,MAAM,IAAI,GAAG,CAAC,QAAQ;AAChC,YAAM,SAAS,IAAI,cAAc;AAEjC,UAAI,UAAU,OAAO,SAAS,OAAO,IAAI,QAAQ,UAAU;AACzD,YAAI,OAAO;AACX,YAAI,iBAAiB,GAAG;AACtB,iBAAO,IAAI,MAAM,kCAAkC,GAAG,EAAE,CAAC;AACzD;QACF;AACA,gBAAQ,UAAU,IAAI,QAAQ,UAAU,gBAAgB,CAAC,CAAC;AAC1D;MACF;AACA,UAAI,WAAW,KAAK;AAClB,YAAI,OAAO;AACX,eAAO,IAAI,MAAM,yBAAyB,MAAM,SAAS,CAAC,EAAE,CAAC;AAC7D;MACF;AACA,YAAM,MAAM,GAAG,kBAAkB,QAAQ;AACzC,UAAI,KAAK,GAAG;AACZ,UAAI,GAAG,SAAS,MAAM;AACtB,UAAI,GAAG,UAAU,MAAM,IAAI,MAAM,MAAM,QAAQ,CAAC,CAAC;IACnD,CAAC;AACD,QAAI,GAAG,SAAS,MAAM;AACtB,QAAI,WAAW,MAAS,MAAM;AAC5B,UAAI,QAAQ,IAAI,MAAM,wBAAwB,CAAC,EAAE,CAAC;IACpD,CAAC;EACH,CAAC;AAEH,QAAM,UAAU,KAAK,CAAC;AACxB;AAOA,eAAe,WAAW,SAAiB,SAAgC;AACzE,QAAM,KAAK,YAAY,oBAAoB;AAC3C,QAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AAC3C,UAAM,QAAQ,GAAG,MAAM,SAAS,CAAC,MAAM,SAAS,MAAM,OAAO,GAAG;MAC9D,OAAO,CAAC,UAAU,UAAU,MAAM;IACpC,CAAC;AACD,QAAI,SAAS;AACb,UAAM,QAAQ,GAAG,QAAQ,CAAC,MAAc;AACtC,gBAAU,EAAE,SAAS;IACvB,CAAC;AACD,UAAM;MAAG;MAAS,CAAC,QACjB;QACE,IAAI,MAAM,6CAA6C,IAAI,OAAO,EAAE;MACtE;IACF;AACA,UAAM,GAAG,QAAQ,CAAC,SAAS;AACzB,UAAI,SAAS,EAAG,SAAQ;;AAEtB;UACE,IAAI,MAAM,gBAAgB,IAAI,eAAe,OAAO,KAAK,MAAM,EAAE;QACnE;IACJ,CAAC;EACH,CAAC;AACH;AAWA,eAAsB,iBAAiB,MAQnB;AAClB,QAAM,KAAK,YAAY,SAAS;AAChC,QAAM,OAAO,YAAY,WAAW;AAEpC,QAAM,WAAW,KAAK,YAAY;AAClC,QAAM,UAAU,KAAK,WAAW;AAEhC,QAAM,QAAQ,gBAAgB,KAAK,UAAU,KAAK,IAAI;AACtD,QAAM,WAAW,KAAK,KAAK,KAAK,UAAU,MAAM;AAGhD,MAAI,GAAG,WAAW,QAAQ,GAAG;AAC3B,WAAO;EACT;AAEA,MAAI,MAAM,WAAW,MAAM;AACzB,UAAM,IAAI;MACR,4BAA4B,KAAK,QAAQ,IAAI,KAAK,IAAI,MAChD,MAAM,SAAS;IAEvB;EACF;AAEA,KAAG,UAAU,KAAK,UAAU,EAAE,WAAW,KAAK,CAAC;AAC/C,QAAM,UAAU,KAAK,KAAK,KAAK,UAAU,MAAM,SAAS;AACxD,QAAM,MAAM,GAAG,YAAY,IAAI,MAAM,SAAS;AAE9C,QAAM,SAAS,KAAK,OAAO;AAE3B,QAAM,SAAS,MAAM,WAAW,OAAO;AACvC,MAAI,WAAW,MAAM,QAAQ;AAE3B,QAAI;AACF,SAAG,OAAO,SAAS,EAAE,OAAO,KAAK,CAAC;IACpC,QAAQ;IAER;AACA,UAAM,IAAI;MACR,yBAAyB,MAAM,SAAS,cAAc,MAAM,MAAM,SAAS,MAAM;IAEnF;EACF;AAEA,QAAM,QAAQ,SAAS,KAAK,QAAQ;AAEpC,MAAI,CAAC,GAAG,WAAW,QAAQ,GAAG;AAC5B,UAAM,IAAI;MACR,iBAAiB,MAAM,SAAS,wCAAwC,QAAQ;IAClF;EACF;AAEA,MAAI;AACF,OAAG,UAAU,UAAU,GAAK;EAC9B,QAAQ;EAER;AACA,SAAO;AACT;AAQA,eAAsB,iBAAiB,MAOrB;AAChB,QAAM,QAAQ,KAAK,SAAS;AAC5B,QAAM,QACJ,KAAK,UAAU,CAAC,OAAe,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,EAAE,CAAC;AACrE,OAAK,IAAI,+CAA+C,KAAK,IAAI,QAAG;AACpE,MAAI,UAAyB;AAC7B,SAAO,KAAK,IAAI,IAAI,KAAK,YAAY;AACnC,QAAI,KAAK,YAAY,GAAG;AACtB,YAAM,IAAI,MAAM,gDAAgD;IAClE;AACA,QAAI;AACF,YAAM,MAAM,aAAa,KAAK,MAAM,GAAK;AACzC,WAAK,IAAI,oCAAoC,KAAK,IAAI,EAAE;AACxD;IACF,SAAS,KAAK;AACZ,YAAM,MAAO,IAAc;AAC3B,UAAI,QAAQ,SAAS;AACnB,aAAK,IAAI,4BAA4B,GAAG,EAAE;AAC1C,kBAAU;MACZ;IACF;AACA,UAAM,MAAM,GAAK;EACnB;AACA,QAAM,IAAI;IACR,0CAA0C,KAAK,IAAI;EACrD;AACF;AAsCA,eAAsB,sBACpB,UAAwC,CAAC,GACd;AAC3B,QAAM,KAAK,YAAY,SAAS;AAChC,QAAM,OAAO,YAAY,WAAW;AACpC,QAAM,KAAK,YAAY,SAAS;AAChC,QAAM,KAAK,YAAY,oBAAoB;AAE3C,QAAM,WAAW,QAAQ,YAAY,GAAG,SAAS;AACjD,QAAM,OAAO,QAAQ,QAAQ,GAAG,KAAK;AACrC,QAAM,WAAW,QAAQ,YAAY,gBAAgB;AACrD,QAAM,YAAY,QAAQ,aAAa;AACvC,QAAM,qBAAqB,QAAQ,sBAAsB;AACzD,QAAM,MACJ,QAAQ,QACP,MAAY;EAEb;AAEF,QAAM,WAAW,MAAM,iBAAiB,EAAE,UAAU,UAAU,KAAK,CAAC;AAGpE,KAAG,UAAU,KAAK,KAAK,UAAU,MAAM,GAAG,EAAE,WAAW,KAAK,CAAC;AAC7D,QAAM,YAAY,KAAK,KAAK,UAAU,OAAO;AAC7C,KAAG,cAAc,WAAW,YAAY,UAAU,SAAS,GAAG;IAC5D,MAAM;EACR,CAAC;AAED,MAAI,oBAAoB,QAAQ,OAAO,SAAS,EAAE;AAClD,QAAM,QAAQ,GAAG,MAAM,UAAU,CAAC,MAAM,SAAS,GAAG;IAClD,OAAO,CAAC,UAAU,WAAW,SAAS;IACtC,UAAU;EACZ,CAAC;AACD,MAAI,SAAS;AACb,QAAM,GAAG,QAAQ,CAAC,MAAM,WAAW;AACjC,aAAS;AACT,QAAI,4BAA4B,IAAI,WAAW,MAAM,EAAE;EACzD,CAAC;AACD,QAAM,GAAG,SAAS,CAAC,QAAe;AAChC,QAAI,uBAAuB,IAAI,OAAO,EAAE;EAC1C,CAAC;AAED,QAAM,OAAO,YAA2B;AACtC,QAAI,CAAC,MAAM,UAAU,CAAC,QAAQ;AAC5B,UAAI;AACF,cAAM,KAAK,SAAS;MACtB,QAAQ;MAER;IACF;EACF;AAEA,MAAI;AACF,UAAM,iBAAiB;MACrB,MAAM;MACN,YAAY,KAAK,IAAI,IAAI;MACzB,aAAa,MAAM;MACnB;IACF,CAAC;EACH,SAAS,KAAK;AACZ,UAAM,KAAK;AACX,UAAM;EACR;AAEA,SAAO;IACL,YAAY,uBAAuB,SAAS;IAC5C;EACF;AACF;","names":[]}