@toon-protocol/client-mcp 0.1.0 → 0.3.0

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 (36) hide show
  1. package/README.md +19 -38
  2. package/dist/app/index.html +166 -0
  3. package/dist/{chunk-5KFXUT5Q.js → chunk-CQ2QIZ6Z.js} +864 -604
  4. package/dist/chunk-CQ2QIZ6Z.js.map +1 -0
  5. package/dist/{chunk-FSS45ZX3.js → chunk-DLYE6U2Z.js} +1516 -316
  6. package/dist/chunk-DLYE6U2Z.js.map +1 -0
  7. package/dist/{chunk-3NAWISI5.js → chunk-QFHCXJ2V.js} +348 -101
  8. package/dist/chunk-QFHCXJ2V.js.map +1 -0
  9. package/dist/chunk-XV52IHVR.js +977 -0
  10. package/dist/chunk-XV52IHVR.js.map +1 -0
  11. package/dist/daemon.js +3 -4
  12. package/dist/daemon.js.map +1 -1
  13. package/dist/e2e/run-journey.d.ts +1 -0
  14. package/dist/e2e/run-journey.js +19953 -0
  15. package/dist/e2e/run-journey.js.map +1 -0
  16. package/dist/{ed25519-2QVPINLS.js → ed25519-2LFQXLYS.js} +6 -2
  17. package/dist/index.d.ts +282 -68
  18. package/dist/index.js +380 -5
  19. package/dist/index.js.map +1 -1
  20. package/dist/mcp.js +39 -6
  21. package/dist/mcp.js.map +1 -1
  22. package/package.json +11 -7
  23. package/dist/anon-proxy-W3KMM7GU-FN7ZJY7P.js +0 -25
  24. package/dist/chunk-3NAWISI5.js.map +0 -1
  25. package/dist/chunk-5KFXUT5Q.js.map +0 -1
  26. package/dist/chunk-FSS45ZX3.js.map +0 -1
  27. package/dist/chunk-SKQTKZIH.js +0 -278
  28. package/dist/chunk-SKQTKZIH.js.map +0 -1
  29. package/dist/chunk-ZQKYZJWT.js +0 -359
  30. package/dist/chunk-ZQKYZJWT.js.map +0 -1
  31. package/dist/ed25519-2QVPINLS.js.map +0 -1
  32. package/dist/gateway-QOK47RKS-SEGTXBR3.js +0 -16
  33. package/dist/gateway-QOK47RKS-SEGTXBR3.js.map +0 -1
  34. package/dist/socks5-WTJBYGME-6COK4LXW.js +0 -139
  35. package/dist/socks5-WTJBYGME-6COK4LXW.js.map +0 -1
  36. /package/dist/{anon-proxy-W3KMM7GU-FN7ZJY7P.js.map → ed25519-2LFQXLYS.js.map} +0 -0
@@ -1,278 +0,0 @@
1
- import { createRequire as __cr } from 'module'; const require = __cr(import.meta.url);
2
-
3
- // ../client/dist/chunk-WHAEQLIW.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: "aad277849b1e63baa75891b9e5109683534e488776ff190e884e34caa04a6d54"
16
- },
17
- "linux-x64": {
18
- assetName: "anon-beta-linux-amd64.zip",
19
- sha256: "370c86f366e7f4cad896e2ef4bbd366a4e78a832c8d58064012f86c88c411a6b"
20
- },
21
- "linux-arm64": {
22
- assetName: "anon-beta-linux-arm64.zip",
23
- sha256: "382d21db1052b6a0f1581bf38c9cf79b370719e313781c0eba53ef0d9570334a"
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-SKQTKZIH.js.map
@@ -1 +0,0 @@
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. All supported platforms are pinned (issue #204); the type stays\n * `string | null` and the download gate still defensively refuses a `null` entry,\n * so adding a new (not-yet-hashed) platform fails closed rather than skipping\n * verification.\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): all four supported platforms are pinned to the\n * sha256 of the `v0.4.10.0-beta` release zips (downloaded + hashed; the\n * darwin-arm64 value matches the previously-verified 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: 'aad277849b1e63baa75891b9e5109683534e488776ff190e884e34caa04a6d54',\n },\n 'linux-x64': {\n assetName: 'anon-beta-linux-amd64.zip',\n sha256: '370c86f366e7f4cad896e2ef4bbd366a4e78a832c8d58064012f86c88c411a6b',\n },\n 'linux-arm64': {\n assetName: 'anon-beta-linux-arm64.zip',\n sha256: '382d21db1052b6a0f1581bf38c9cf79b370719e313781c0eba53ef0d9570334a',\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":"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","names":[]}
@@ -1,359 +0,0 @@
1
- import { createRequire as __cr } from 'module'; const require = __cr(import.meta.url);
2
- import {
3
- ControlApiError,
4
- DaemonUnreachableError
5
- } from "./chunk-5KFXUT5Q.js";
6
-
7
- // src/mcp-tools.ts
8
- var TOOL_DEFINITIONS = [
9
- {
10
- name: "toon_status",
11
- description: "Report TOON client daemon health: bootstrapping/ready state, transport, relay connection, buffered-event count, and per-chain settlement status.",
12
- inputSchema: {
13
- type: "object",
14
- properties: {},
15
- additionalProperties: false
16
- }
17
- },
18
- {
19
- name: "toon_identity",
20
- description: "Return this client's public identity (Nostr pubkey + EVM/Solana/Mina addresses). Never returns private keys.",
21
- inputSchema: {
22
- type: "object",
23
- properties: {},
24
- additionalProperties: false
25
- }
26
- },
27
- {
28
- name: "toon_publish",
29
- description: "Pay-to-write: publish a fully-signed Nostr event to the TOON network. Signs an off-chain payment-channel claim and forwards it over BTP. Returns the event id, channel id, and the advanced channel nonce.",
30
- inputSchema: {
31
- type: "object",
32
- properties: {
33
- event: {
34
- type: "object",
35
- description: "A fully-signed Nostr event (must include id, pubkey, sig, kind, created_at, tags, content)."
36
- },
37
- destination: {
38
- type: "string",
39
- description: "Optional ILP destination override (default: the apex/town)."
40
- },
41
- fee: {
42
- type: "string",
43
- description: "Optional fee override in base units (default: daemon config)."
44
- },
45
- btpUrl: {
46
- type: "string",
47
- description: "Which apex (BTP write target) to publish through (default: the config-seeded apex). Use toon_targets to list registered apexes. Writes always go through BTP \u2014 never a relay directly."
48
- }
49
- },
50
- required: ["event"],
51
- additionalProperties: false
52
- }
53
- },
54
- {
55
- name: "toon_subscribe",
56
- description: "Free read: register a persistent town-relay subscription with NIP-01 filter(s). Returns a subscription id to drain with toon_read.",
57
- inputSchema: {
58
- type: "object",
59
- properties: {
60
- filters: {
61
- description: "A NIP-01 filter object or an array of OR-ed filters."
62
- },
63
- subId: { type: "string", description: "Optional caller-supplied id." },
64
- relayUrl: {
65
- type: "string",
66
- description: "Restrict to one relay. Omit to FAN OUT across every registered relay (reads merge into one ordered stream)."
67
- }
68
- },
69
- required: ["filters"],
70
- additionalProperties: false
71
- }
72
- },
73
- {
74
- name: "toon_read",
75
- description: "Free read: drain buffered events newer than a cursor. Pass back the returned cursor to fetch only events received since the last read.",
76
- inputSchema: {
77
- type: "object",
78
- properties: {
79
- subId: { type: "string", description: "Restrict to one subscription." },
80
- cursor: {
81
- type: "number",
82
- description: "Cursor from a prior toon_read."
83
- },
84
- limit: {
85
- type: "number",
86
- description: "Max events to return (default 200)."
87
- },
88
- relayUrl: {
89
- type: "string",
90
- description: "Restrict the drain to events from a single relay."
91
- }
92
- },
93
- additionalProperties: false
94
- }
95
- },
96
- {
97
- name: "toon_open_channel",
98
- description: "Open (or return the existing) payment channel for a destination. Channels open lazily on first publish; use this to pre-open.",
99
- inputSchema: {
100
- type: "object",
101
- properties: {
102
- destination: {
103
- type: "string",
104
- description: "ILP destination (default: apex)."
105
- }
106
- },
107
- additionalProperties: false
108
- }
109
- },
110
- {
111
- name: "toon_channels",
112
- description: "List tracked payment channels with their nonce watermark and cumulative transferred amount.",
113
- inputSchema: {
114
- type: "object",
115
- properties: {},
116
- additionalProperties: false
117
- }
118
- },
119
- {
120
- name: "toon_swap",
121
- description: "Pay a mill peer (asset A) to receive asset B plus a signed target-chain claim. Builds the NIP-59 gift-wrapped kind:20032 swap rumor and streams it; the source-asset claim is signed against the open apex channel (the mill must be routed via apexChildPeers). Returns the accumulated, decrypted target-chain claim(s) and settlement metadata.",
122
- inputSchema: {
123
- type: "object",
124
- properties: {
125
- destination: {
126
- type: "string",
127
- description: "Mill peer ILP destination (e.g. g.townhouse.mill)."
128
- },
129
- amount: {
130
- type: "string",
131
- description: "Total source-asset amount to swap, source micro-units."
132
- },
133
- millPubkey: {
134
- type: "string",
135
- description: "Mill's 64-char lowercase hex Nostr pubkey (gift-wrap recipient)."
136
- },
137
- pair: {
138
- type: "object",
139
- description: "The swap pair (from kind:10032 discovery or operator-supplied): { from:{assetCode,assetScale,chain}, to:{...}, rate, minAmount?, maxAmount? }."
140
- },
141
- chainRecipient: {
142
- type: "string",
143
- description: "Sender's payout address on pair.to.chain (EVM 0x-hex / Solana / Mina base58)."
144
- },
145
- packetCount: {
146
- type: "number",
147
- description: "Split the swap into N equal packets (default 1)."
148
- }
149
- },
150
- required: [
151
- "destination",
152
- "amount",
153
- "millPubkey",
154
- "pair",
155
- "chainRecipient"
156
- ],
157
- additionalProperties: false
158
- }
159
- },
160
- {
161
- name: "toon_targets",
162
- description: "List every registered target: relays (read sources, with connection + buffered-event status) and apexes (BTP write targets, with ready/channel status). The TOON client is 1-to-many \u2014 many apexes to write through, many relays to read from.",
163
- inputSchema: {
164
- type: "object",
165
- properties: {},
166
- additionalProperties: false
167
- }
168
- },
169
- {
170
- name: "toon_add_relay",
171
- description: "Add a relay READ target at runtime (persisted across restarts). It joins all fan-out reads immediately; `.anyone` hidden-service relays reuse the managed anon read proxy automatically.",
172
- inputSchema: {
173
- type: "object",
174
- properties: {
175
- relayUrl: {
176
- type: "string",
177
- description: "Relay WS URL (ws://host:7100 or a .anyone service)."
178
- }
179
- },
180
- required: ["relayUrl"],
181
- additionalProperties: false
182
- }
183
- },
184
- {
185
- name: "toon_remove_relay",
186
- description: "Remove a relay read target (persisted). The config-seeded default relay cannot be removed.",
187
- inputSchema: {
188
- type: "object",
189
- properties: {
190
- relayUrl: { type: "string", description: "Relay WS URL to remove." }
191
- },
192
- required: ["relayUrl"],
193
- additionalProperties: false
194
- }
195
- },
196
- {
197
- name: "toon_add_apex",
198
- description: "Add an apex WRITE target at runtime (persisted across restarts). Settlement params are DISCOVERED by reading the apex\u2019s kind:10032 announcement off the given relay \u2014 you do not supply chain/settlement details. The relay is added as a read target first if unknown.",
199
- inputSchema: {
200
- type: "object",
201
- properties: {
202
- ilpAddress: {
203
- type: "string",
204
- description: "ILP address of the apex (e.g. g.townhouse.town)."
205
- },
206
- relayUrl: {
207
- type: "string",
208
- description: "Relay to discover the apex\u2019s kind:10032 on."
209
- },
210
- pubkey: {
211
- type: "string",
212
- description: "Optional apex Nostr pubkey (64-char hex) to narrow discovery."
213
- },
214
- chain: {
215
- type: "string",
216
- enum: ["evm", "solana", "mina"],
217
- description: "Preferred settlement chain (default: apex\u2019s first)."
218
- },
219
- childPeers: {
220
- type: "array",
221
- items: { type: "string" },
222
- description: 'Child peers via this apex\u2019s channel (e.g. ["dvm","mill"]).'
223
- },
224
- feePerEvent: {
225
- type: "string",
226
- description: "Per-write fee override for this apex (base units)."
227
- }
228
- },
229
- required: ["ilpAddress", "relayUrl"],
230
- additionalProperties: false
231
- }
232
- },
233
- {
234
- name: "toon_remove_apex",
235
- description: "Remove an apex write target by its BTP URL (persisted). The config-seeded default apex cannot be removed.",
236
- inputSchema: {
237
- type: "object",
238
- properties: {
239
- btpUrl: {
240
- type: "string",
241
- description: "BTP URL of the apex to remove (from toon_targets)."
242
- }
243
- },
244
- required: ["btpUrl"],
245
- additionalProperties: false
246
- }
247
- }
248
- ];
249
- async function dispatchTool(client, name, args) {
250
- try {
251
- switch (name) {
252
- case "toon_status":
253
- return ok(await client.status());
254
- case "toon_identity": {
255
- const s = await client.status();
256
- return ok({
257
- identity: s.identity,
258
- ready: s.ready,
259
- bootstrapping: s.bootstrapping
260
- });
261
- }
262
- case "toon_publish":
263
- return ok(await client.publish(args));
264
- case "toon_subscribe":
265
- return ok(
266
- await client.subscribe({
267
- filters: args["filters"],
268
- ...typeof args["subId"] === "string" ? { subId: args["subId"] } : {},
269
- ...typeof args["relayUrl"] === "string" ? { relayUrl: args["relayUrl"] } : {}
270
- })
271
- );
272
- case "toon_read":
273
- return ok(
274
- await client.events({
275
- ...typeof args["subId"] === "string" ? { subId: args["subId"] } : {},
276
- ...typeof args["cursor"] === "number" ? { cursor: args["cursor"] } : {},
277
- ...typeof args["limit"] === "number" ? { limit: args["limit"] } : {},
278
- ...typeof args["relayUrl"] === "string" ? { relayUrl: args["relayUrl"] } : {}
279
- })
280
- );
281
- case "toon_open_channel":
282
- return ok(
283
- await client.openChannel(
284
- typeof args["destination"] === "string" ? { destination: args["destination"] } : {}
285
- )
286
- );
287
- case "toon_channels":
288
- return ok(await client.channels());
289
- case "toon_swap":
290
- return ok(
291
- await client.swap({
292
- destination: String(args["destination"]),
293
- amount: String(args["amount"]),
294
- millPubkey: String(args["millPubkey"]),
295
- pair: args["pair"],
296
- chainRecipient: String(args["chainRecipient"]),
297
- ...typeof args["packetCount"] === "number" ? { packetCount: args["packetCount"] } : {}
298
- })
299
- );
300
- case "toon_targets":
301
- return ok(await client.targets());
302
- case "toon_add_relay":
303
- return ok(
304
- await client.addRelay({ relayUrl: String(args["relayUrl"]) })
305
- );
306
- case "toon_remove_relay":
307
- return ok(
308
- await client.removeRelay({ relayUrl: String(args["relayUrl"]) })
309
- );
310
- case "toon_add_apex": {
311
- const req = {
312
- ilpAddress: String(args["ilpAddress"]),
313
- relayUrl: String(args["relayUrl"]),
314
- ...typeof args["pubkey"] === "string" ? { pubkey: args["pubkey"] } : {},
315
- ...typeof args["chain"] === "string" ? { chain: args["chain"] } : {},
316
- ...Array.isArray(args["childPeers"]) ? { childPeers: args["childPeers"].map(String) } : {},
317
- ...typeof args["feePerEvent"] === "string" ? { feePerEvent: args["feePerEvent"] } : {}
318
- };
319
- return ok(await client.addApex(req));
320
- }
321
- case "toon_remove_apex":
322
- return ok(await client.removeApex({ btpUrl: String(args["btpUrl"]) }));
323
- default:
324
- return err(`Unknown tool: ${name}`);
325
- }
326
- } catch (e) {
327
- if (e instanceof ControlApiError && e.status === 504) {
328
- return err(
329
- `${e.detail ?? e.message} \u2014 retry once the relay is reachable and the apex is online.`
330
- );
331
- }
332
- if (e instanceof ControlApiError && e.retryable) {
333
- return err(
334
- `TOON client is still bootstrapping (the anon proxy / BTP session can take 30\u201390s) \u2014 retry shortly. (${e.message})`
335
- );
336
- }
337
- if (e instanceof DaemonUnreachableError) {
338
- return err(
339
- `TOON client daemon is not reachable at ${e.baseUrl}. It may have failed to start \u2014 check ~/.toon-client/daemon.log.`
340
- );
341
- }
342
- if (e instanceof ControlApiError) {
343
- return err(`${e.message}${e.detail ? `: ${e.detail}` : ""}`);
344
- }
345
- return err(e instanceof Error ? e.message : String(e));
346
- }
347
- }
348
- function ok(data) {
349
- return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
350
- }
351
- function err(message) {
352
- return { content: [{ type: "text", text: message }], isError: true };
353
- }
354
-
355
- export {
356
- TOOL_DEFINITIONS,
357
- dispatchTool
358
- };
359
- //# sourceMappingURL=chunk-ZQKYZJWT.js.map