@toon-protocol/client-mcp 0.10.8 → 0.11.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.
package/dist/daemon.js CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  ClientRunner,
5
5
  registerRoutes,
6
6
  scaffoldFirstRun
7
- } from "./chunk-ADBNZA5O.js";
7
+ } from "./chunk-KVK6OZVD.js";
8
8
  import {
9
9
  ControlClient,
10
10
  ToonClient,
@@ -17,7 +17,7 @@ import {
17
17
  resolveConfig,
18
18
  spawnDaemonDetached,
19
19
  waitForReady
20
- } from "./chunk-3ZBC2HUB.js";
20
+ } from "./chunk-CMGJ3NFT.js";
21
21
  import "./chunk-32QD72IL.js";
22
22
  import "./chunk-DLYE6U2Z.js";
23
23
  import "./chunk-LR7W2ISE.js";
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/daemon.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * `toon-clientd` — the always-on, detached TOON client daemon. It owns the two\n * long-lived connections that cannot live in an ephemeral agent session:\n * • a BTP session to the apex/connector (paid writes), and\n * • a persistent town-relay Nostr-WS subscription (free reads),\n * plus the payment channels (with a persisted nonce watermark) and the signer.\n *\n * Subcommands:\n * toon-clientd run Run the daemon in the foreground (used internally by\n * the detached spawn; logs to stdout).\n * toon-clientd start Spawn the daemon detached and wait until reachable.\n * toon-clientd stop Stop a running daemon (SIGTERM the locked PID).\n * toon-clientd status Print the daemon status as JSON.\n */\n\nimport Fastify from 'fastify';\nimport { ToonClient } from '@toon-protocol/client';\nimport {\n defaultConfigPath,\n readConfigFile,\n resolveConfig,\n type ResolvedDaemonConfig,\n} from './daemon/config.js';\nimport { scaffoldFirstRun } from './daemon/first-run.js';\nimport { ClientRunner, type ToonClientLike } from './daemon/client-runner.js';\nimport { registerRoutes } from './daemon/routes.js';\nimport {\n acquireLock,\n isProcessAlive,\n readPid,\n releaseLock,\n spawnDaemonDetached,\n waitForReady,\n} from './daemon/lifecycle.js';\nimport { ControlClient } from './control-client.js';\n\nfunction baseUrl(config: ResolvedDaemonConfig): string {\n return `http://127.0.0.1:${config.httpPort}`;\n}\n\nfunction loadResolvedConfig(): ResolvedDaemonConfig {\n const path = process.env['TOON_CLIENT_CONFIG'] ?? defaultConfigPath();\n return resolveConfig(readConfigFile(path));\n}\n\n/** Run the daemon in the foreground (the detached child's actual work). */\nasync function runForeground(): Promise<void> {\n acquireLock();\n const config = loadResolvedConfig();\n const log = (msg: string): void => console.error(msg);\n\n const runner = new ClientRunner({\n config,\n createClient: (clientConfig) =>\n new ToonClient(clientConfig) as unknown as ToonClientLike,\n logger: log,\n });\n\n const app = Fastify({\n logger: false,\n // Keep idle keep-alive sockets open longer than the MCP-side undici client\n // would ever pool one (its keepAliveMaxTimeout default is 600s). The client\n // calls this localhost control plane infrequently, so with Node's default\n // 5s keep-alive the daemon was reaping sockets the client still held —\n // causing the next request to fail with ECONNRESET (\"daemon not reachable\")\n // until a retry opened a fresh socket (toon-client#186). Outliving the\n // client's pool removes that race for non-retried (POST) requests too; the\n // client-side retry covers the residual. Localhost-only listener, so a\n // long-lived idle socket is harmless.\n keepAliveTimeout: 650_000,\n });\n registerRoutes(app, runner);\n\n // Begin bootstrap (non-blocking) before listening so /status is immediately\n // reachable and reports `bootstrapping: true` while anon/BTP come up.\n runner.start();\n\n await app.listen({ host: '127.0.0.1', port: config.httpPort });\n log(`[toon-clientd] listening on ${baseUrl(config)}`);\n\n let shuttingDown = false;\n const shutdown = async (signal: string): Promise<void> => {\n if (shuttingDown) return;\n shuttingDown = true;\n log(`[toon-clientd] received ${signal}, shutting down`);\n try {\n await app.close();\n } catch {\n /* ignore */\n }\n await runner.stop();\n releaseLock();\n setTimeout(() => process.exit(0), 500).unref();\n };\n process.on('SIGTERM', () => void shutdown('SIGTERM'));\n process.on('SIGINT', () => void shutdown('SIGINT'));\n}\n\n/** Spawn the daemon detached and wait until the control plane responds. */\nasync function start(): Promise<void> {\n const config = loadResolvedConfig();\n const url = baseUrl(config);\n const existing = readPid();\n if (existing !== null && isProcessAlive(existing)) {\n console.log(`toon-clientd already running (pid ${existing}) at ${url}`);\n return;\n }\n const pid = spawnDaemonDetached();\n const ok = await waitForReady(url, 20_000);\n if (ok) {\n console.log(`toon-clientd started (pid ${pid}) at ${url}`);\n } else {\n console.error(\n `toon-clientd spawned (pid ${pid}) but did not become reachable at ${url} in time. ` +\n `Check the daemon log.`\n );\n process.exitCode = 1;\n }\n}\n\n/** Stop a running daemon via SIGTERM on the locked PID. */\nasync function stop(): Promise<void> {\n const pid = readPid();\n if (pid === null || !isProcessAlive(pid)) {\n console.log('toon-clientd is not running');\n releaseLock();\n return;\n }\n process.kill(pid, 'SIGTERM');\n // Wait briefly for the process to exit.\n for (let i = 0; i < 40; i++) {\n if (!isProcessAlive(pid)) break;\n await new Promise((r) => setTimeout(r, 100));\n }\n console.log(`toon-clientd stopped (pid ${pid})`);\n}\n\n/** Print the daemon status JSON. */\nasync function status(): Promise<void> {\n const config = loadResolvedConfig();\n const client = new ControlClient({ baseUrl: baseUrl(config) });\n try {\n const s = await client.status();\n console.log(JSON.stringify(s, null, 2));\n } catch {\n console.log(JSON.stringify({ running: false }, null, 2));\n process.exitCode = 1;\n }\n}\n\nasync function main(): Promise<void> {\n const cmd = process.argv[2] ?? 'run';\n switch (cmd) {\n case 'run':\n // First-run onboarding (#251): mint + persist an identity and scaffold\n // the transport config so a fresh install starts with no manual setup.\n await scaffoldFirstRun();\n await runForeground();\n break;\n case 'start':\n await scaffoldFirstRun();\n await start();\n break;\n case 'stop':\n await stop();\n break;\n case 'status':\n await status();\n break;\n default:\n console.error(\n `Unknown command \"${cmd}\". Usage: toon-clientd <run|start|stop|status>`\n );\n process.exitCode = 1;\n }\n}\n\nmain().catch((err) => {\n console.error(\n err instanceof Error ? (err.stack ?? err.message) : String(err)\n );\n process.exitCode = 1;\n});\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/daemon.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * `toon-clientd` — the always-on, detached TOON client daemon. It owns the two\n * long-lived connections that cannot live in an ephemeral agent session:\n * • a BTP session to the apex/connector (paid writes), and\n * • a persistent relay Nostr-WS subscription (free reads),\n * plus the payment channels (with a persisted nonce watermark) and the signer.\n *\n * Subcommands:\n * toon-clientd run Run the daemon in the foreground (used internally by\n * the detached spawn; logs to stdout).\n * toon-clientd start Spawn the daemon detached and wait until reachable.\n * toon-clientd stop Stop a running daemon (SIGTERM the locked PID).\n * toon-clientd status Print the daemon status as JSON.\n */\n\nimport Fastify from 'fastify';\nimport { ToonClient } from '@toon-protocol/client';\nimport {\n defaultConfigPath,\n readConfigFile,\n resolveConfig,\n type ResolvedDaemonConfig,\n} from './daemon/config.js';\nimport { scaffoldFirstRun } from './daemon/first-run.js';\nimport { ClientRunner, type ToonClientLike } from './daemon/client-runner.js';\nimport { registerRoutes } from './daemon/routes.js';\nimport {\n acquireLock,\n isProcessAlive,\n readPid,\n releaseLock,\n spawnDaemonDetached,\n waitForReady,\n} from './daemon/lifecycle.js';\nimport { ControlClient } from './control-client.js';\n\nfunction baseUrl(config: ResolvedDaemonConfig): string {\n return `http://127.0.0.1:${config.httpPort}`;\n}\n\nfunction loadResolvedConfig(): ResolvedDaemonConfig {\n const path = process.env['TOON_CLIENT_CONFIG'] ?? defaultConfigPath();\n return resolveConfig(readConfigFile(path));\n}\n\n/** Run the daemon in the foreground (the detached child's actual work). */\nasync function runForeground(): Promise<void> {\n acquireLock();\n const config = loadResolvedConfig();\n const log = (msg: string): void => console.error(msg);\n\n const runner = new ClientRunner({\n config,\n createClient: (clientConfig) =>\n new ToonClient(clientConfig) as unknown as ToonClientLike,\n logger: log,\n });\n\n const app = Fastify({\n logger: false,\n // Keep idle keep-alive sockets open longer than the MCP-side undici client\n // would ever pool one (its keepAliveMaxTimeout default is 600s). The client\n // calls this localhost control plane infrequently, so with Node's default\n // 5s keep-alive the daemon was reaping sockets the client still held —\n // causing the next request to fail with ECONNRESET (\"daemon not reachable\")\n // until a retry opened a fresh socket (toon-client#186). Outliving the\n // client's pool removes that race for non-retried (POST) requests too; the\n // client-side retry covers the residual. Localhost-only listener, so a\n // long-lived idle socket is harmless.\n keepAliveTimeout: 650_000,\n });\n registerRoutes(app, runner);\n\n // Begin bootstrap (non-blocking) before listening so /status is immediately\n // reachable and reports `bootstrapping: true` while anon/BTP come up.\n runner.start();\n\n await app.listen({ host: '127.0.0.1', port: config.httpPort });\n log(`[toon-clientd] listening on ${baseUrl(config)}`);\n\n let shuttingDown = false;\n const shutdown = async (signal: string): Promise<void> => {\n if (shuttingDown) return;\n shuttingDown = true;\n log(`[toon-clientd] received ${signal}, shutting down`);\n try {\n await app.close();\n } catch {\n /* ignore */\n }\n await runner.stop();\n releaseLock();\n setTimeout(() => process.exit(0), 500).unref();\n };\n process.on('SIGTERM', () => void shutdown('SIGTERM'));\n process.on('SIGINT', () => void shutdown('SIGINT'));\n}\n\n/** Spawn the daemon detached and wait until the control plane responds. */\nasync function start(): Promise<void> {\n const config = loadResolvedConfig();\n const url = baseUrl(config);\n const existing = readPid();\n if (existing !== null && isProcessAlive(existing)) {\n console.log(`toon-clientd already running (pid ${existing}) at ${url}`);\n return;\n }\n const pid = spawnDaemonDetached();\n const ok = await waitForReady(url, 20_000);\n if (ok) {\n console.log(`toon-clientd started (pid ${pid}) at ${url}`);\n } else {\n console.error(\n `toon-clientd spawned (pid ${pid}) but did not become reachable at ${url} in time. ` +\n `Check the daemon log.`\n );\n process.exitCode = 1;\n }\n}\n\n/** Stop a running daemon via SIGTERM on the locked PID. */\nasync function stop(): Promise<void> {\n const pid = readPid();\n if (pid === null || !isProcessAlive(pid)) {\n console.log('toon-clientd is not running');\n releaseLock();\n return;\n }\n process.kill(pid, 'SIGTERM');\n // Wait briefly for the process to exit.\n for (let i = 0; i < 40; i++) {\n if (!isProcessAlive(pid)) break;\n await new Promise((r) => setTimeout(r, 100));\n }\n console.log(`toon-clientd stopped (pid ${pid})`);\n}\n\n/** Print the daemon status JSON. */\nasync function status(): Promise<void> {\n const config = loadResolvedConfig();\n const client = new ControlClient({ baseUrl: baseUrl(config) });\n try {\n const s = await client.status();\n console.log(JSON.stringify(s, null, 2));\n } catch {\n console.log(JSON.stringify({ running: false }, null, 2));\n process.exitCode = 1;\n }\n}\n\nasync function main(): Promise<void> {\n const cmd = process.argv[2] ?? 'run';\n switch (cmd) {\n case 'run':\n // First-run onboarding (#251): mint + persist an identity and scaffold\n // the transport config so a fresh install starts with no manual setup.\n await scaffoldFirstRun();\n await runForeground();\n break;\n case 'start':\n await scaffoldFirstRun();\n await start();\n break;\n case 'stop':\n await stop();\n break;\n case 'status':\n await status();\n break;\n default:\n console.error(\n `Unknown command \"${cmd}\". Usage: toon-clientd <run|start|stop|status>`\n );\n process.exitCode = 1;\n }\n}\n\nmain().catch((err) => {\n console.error(\n err instanceof Error ? (err.stack ?? err.message) : String(err)\n );\n process.exitCode = 1;\n});\n"],"mappings":"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","names":[]}
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { NostrEvent, EventTemplate } from 'nostr-tools/pure';
2
2
  import { SwapPair } from '@toon-protocol/core';
3
- import { ToonClientConfig } from '@toon-protocol/client';
3
+ import { ToonClientConfig, FaucetChain } from '@toon-protocol/client';
4
4
  import { FastifyInstance } from 'fastify';
5
5
  import { ViewSpec } from '@toon-protocol/views';
6
6
 
@@ -67,7 +67,7 @@ interface StatusResponse {
67
67
  interface PublishRequest {
68
68
  /** A fully-signed Nostr event (id + sig present). */
69
69
  event: NostrEvent;
70
- /** ILP destination override (default: the configured apex/town address). */
70
+ /** ILP destination override (default: the configured apex/relay address). */
71
71
  destination?: string;
72
72
  /** Fee override in base units. Defaults to the daemon's configured fee. */
73
73
  fee?: string;
@@ -100,7 +100,7 @@ interface PublishUnsignedRequest {
100
100
  content?: string;
101
101
  /** Event tags (array of string arrays). */
102
102
  tags?: string[][];
103
- /** ILP destination override (default: the configured apex/town address). */
103
+ /** ILP destination override (default: the configured apex/relay address). */
104
104
  destination?: string;
105
105
  /** Fee override in base units. Defaults to the daemon's configured fee. */
106
106
  fee?: string;
@@ -288,7 +288,7 @@ interface SwapRequest {
288
288
  swapPubkey: string;
289
289
  /**
290
290
  * The swap pair to execute — from kind:10032 discovery, or operator-supplied
291
- * when the swap peer announces pairs to a relay other than the town relay.
291
+ * when the swap peer announces pairs to a relay other than the default relay.
292
292
  */
293
293
  pair: SwapPair;
294
294
  /**
@@ -453,16 +453,42 @@ interface FundWalletRequest {
453
453
  /** Address to fund (default: this client's own address for `chain`). */
454
454
  address?: string;
455
455
  }
456
- /** `POST /fund-wallet` result. */
456
+ /**
457
+ * `POST /fund-wallet` result — a snapshot of an ASYNC faucet drip job.
458
+ *
459
+ * The drip is non-blocking: `POST /fund-wallet` launches the faucet call in the
460
+ * daemon background and returns immediately with `status: 'pending'`. This avoids
461
+ * the MCP host's ~60s tool-call timeout surfacing a still-working Mina drip
462
+ * (which legitimately settles in ~75s, native MINA + USDC) as a misleading
463
+ * relay/apex timeout (#199-class). Poll `GET /fund-wallet/status` (or just
464
+ * re-read balances) to observe the terminal `'success'` / `'error'` state.
465
+ */
457
466
  interface FundWalletResponse {
458
- /** The chain that was funded. */
467
+ /** The chain being funded. */
459
468
  chain: SettlementChain;
460
- /** The address that was funded. */
469
+ /** The address being funded. */
461
470
  address: string;
462
471
  /** The faucet base URL the drip was requested from. */
463
472
  faucetUrl: string;
464
- /** Raw parsed JSON body from the faucet (shape is faucet-defined). */
465
- response: unknown;
473
+ /**
474
+ * Lifecycle of the background drip. `'timeout'` is distinct from `'error'`:
475
+ * the faucet client gave up but the on-chain drip MAY still have settled
476
+ * (observed on a loaded EVM faucet) — treat it as "uncertain, re-check
477
+ * balances", not a definitive failure, to avoid a misleading double-fund.
478
+ */
479
+ status: 'pending' | 'success' | 'error' | 'timeout';
480
+ /** Unix ms the drip was submitted. */
481
+ startedAt: number;
482
+ /** Unix ms the drip settled or failed (absent while `'pending'`). */
483
+ finishedAt?: number;
484
+ /** Raw parsed JSON body from the faucet on success (shape is faucet-defined). */
485
+ response?: unknown;
486
+ /** Error message on failure. */
487
+ error?: string;
488
+ }
489
+ /** `GET /fund-wallet/status` result — snapshots of tracked drip jobs. */
490
+ interface FundStatusResponse {
491
+ jobs: FundWalletResponse[];
466
492
  }
467
493
  /** Uniform error envelope returned with non-2xx responses. */
468
494
  interface ErrorResponse {
@@ -543,6 +569,7 @@ declare class ControlClient {
543
569
  addApex(body: AddApexRequest): Promise<AddApexResponse>;
544
570
  removeApex(body: RemoveApexRequest): Promise<TargetsResponse>;
545
571
  fundWallet(body?: FundWalletRequest): Promise<FundWalletResponse>;
572
+ fundStatus(chain?: SettlementChain): Promise<FundStatusResponse>;
546
573
  /**
547
574
  * Whether an HTTP method is safe to transparently retry. Idempotent reads
548
575
  * (GET) and deletes can be replayed verbatim; a mutating POST cannot — the
@@ -555,11 +582,11 @@ declare class ControlClient {
555
582
  }
556
583
 
557
584
  /**
558
- * Persistent town-relay Nostr-WS subscription — the read half of the TOON
585
+ * Persistent relay Nostr-WS subscription — the read half of the TOON
559
586
  * client, which `@toon-protocol/client` does not provide (its bootstrap only
560
587
  * issues one-shot WS queries and `DiscoveryTracker` is passive).
561
588
  *
562
- * Reads are FREE: this opens a long-lived NIP-01 connection to the town relay,
589
+ * Reads are FREE: this opens a long-lived NIP-01 connection to the relay,
563
590
  * keeps a bounded ring buffer of received events (de-duplicated by `event.id`),
564
591
  * and lets callers drain new events via a monotonic cursor. It auto-reconnects
565
592
  * with exponential backoff and re-issues every active REQ on reconnect.
@@ -578,7 +605,7 @@ interface MinimalWebSocket {
578
605
  }
579
606
  type WebSocketFactory = (url: string) => MinimalWebSocket;
580
607
  interface RelaySubscriptionOptions {
581
- /** Town relay WS URL, e.g. `ws://localhost:7100`. */
608
+ /** Relay WS URL, e.g. `ws://localhost:7100`. */
582
609
  relayUrl: string;
583
610
  /** Max events retained in the ring buffer (oldest evicted). Default 5000. */
584
611
  bufferSize?: number;
@@ -688,7 +715,7 @@ declare class RelaySubscription {
688
715
  * 3. the `mnemonic` field of the config file (discouraged — plaintext on disk).
689
716
  */
690
717
 
691
- /** Apex/town settlement parameters injected as a peer negotiation. */
718
+ /** Apex/relay settlement parameters injected as a peer negotiation. */
692
719
  interface ApexNegotiationConfig {
693
720
  /** ILP destination address, e.g. `g.proxy`. */
694
721
  destination: string;
@@ -743,7 +770,7 @@ interface DaemonConfigFile {
743
770
  * mina faucet). Env override: `TOON_CLIENT_FAUCET_TIMEOUT_MS`.
744
771
  */
745
772
  faucetTimeoutMs?: number;
746
- /** Town relay WS URL for FREE reads. */
773
+ /** Relay WS URL for FREE reads. */
747
774
  relayUrl?: string;
748
775
  /**
749
776
  * Apex CHANNEL ANCHOR (settlement peer). Defaults to the genesis seed apex's
@@ -1028,11 +1055,29 @@ declare class ClientRunner {
1028
1055
  private readonly createRelay;
1029
1056
  private readonly log;
1030
1057
  private readonly targetsPath?;
1058
+ /**
1059
+ * Identity-level chain-read client. Reading your OWN on-chain wallet balance is
1060
+ * a pure (wallet keys + chain RPC) operation that has nothing to do with the
1061
+ * ILP/payment peer, so it lives at the daemon level rather than inside an apex.
1062
+ * Built once from the daemon's own `toonClientConfig` (the same keys + chain
1063
+ * RPC config every apex shares) and REUSED as the default apex's client, so a
1064
+ * funded apex's `start()` (which derives Solana/Mina keys) also benefits this
1065
+ * reader. `getBalances` uses it directly, so balances work even with zero
1066
+ * apexes registered (follow-up to #199/#200).
1067
+ */
1068
+ private readonly identityClient;
1031
1069
  private readonly startedAt;
1032
1070
  /** Apex write targets, keyed by btpUrl. */
1033
1071
  private readonly apexes;
1034
1072
  /** Relay read targets, keyed by relayUrl. */
1035
1073
  private readonly relays;
1074
+ /**
1075
+ * Async faucet drip jobs, keyed by chain. A drip is launched in the background
1076
+ * (the Mina faucet legitimately takes ~75s — longer than the MCP host's ~60s
1077
+ * tool-call budget) and its terminal state is observed via {@link getFundStatus}
1078
+ * / re-reading balances rather than by blocking the caller.
1079
+ */
1080
+ private readonly fundJobs;
1036
1081
  /** Runner-level merged read buffer across all relays (de-duped by event.id). */
1037
1082
  private merged;
1038
1083
  private readonly mergedSeen;
@@ -1145,7 +1190,13 @@ declare class ClientRunner {
1145
1190
  * (the typical "fund me before I open a channel" flow). The daemon holds the
1146
1191
  * faucet URL + the keys, so the MCP caller never needs either.
1147
1192
  */
1148
- fundWallet(req?: FundWalletRequest): Promise<FundWalletResponse>;
1193
+ fundWallet(req?: FundWalletRequest): FundWalletResponse;
1194
+ /**
1195
+ * Snapshots of tracked faucet drip jobs — all of them, or just the one for
1196
+ * `chain`. Lets a caller poll for the terminal state of an async drip without
1197
+ * re-dripping.
1198
+ */
1199
+ getFundStatus(chain?: FaucetChain): FundStatusResponse;
1149
1200
  /** Full registry of relay + apex targets with per-target status. */
1150
1201
  getTargets(): TargetsResponse;
1151
1202
  /** Pay-to-write a single event through the selected (or default) apex. */
@@ -1190,8 +1241,20 @@ declare class ClientRunner {
1190
1241
  getChannels(): ChannelsResponse;
1191
1242
  /**
1192
1243
  * On-chain wallet balances. The wallet is identity-level (same keys across
1193
- * apexes), so read from the first available apex's client; per-chain reads are
1194
- * best-effort inside the client (a failing chain is simply omitted).
1244
+ * apexes), so this reads from the daemon's {@link identityClient} NOT an apex
1245
+ * and therefore works even with zero apexes / no payment peer configured
1246
+ * (reading your own balance is a pure wallet-keys + chain-RPC operation).
1247
+ * Per-chain reads are best-effort inside the client (a failing chain is simply
1248
+ * omitted).
1249
+ *
1250
+ * Each underlying read hits per-chain RPC providers that can stall
1251
+ * indefinitely on devnet (a provider being `detail: "configured"` in
1252
+ * toon_status means it is WIRED, not that its RPC is live). A stall here used
1253
+ * to block the whole control request until the client aborted, surfacing as a
1254
+ * misleading "relay/apex unreachable" timeout (#199). Bound each attempt well
1255
+ * under the control-plane timeout and retry once so a single transient
1256
+ * provider stall FAST-FAILS with an honest "balances handler / provider
1257
+ * stalled" error instead of hanging.
1195
1258
  */
1196
1259
  getBalances(): Promise<BalancesResponse>;
1197
1260
  /**
@@ -1402,4 +1465,4 @@ interface JourneyResult {
1402
1465
  */
1403
1466
  declare function runJourney(plan: JourneyPlan, client: ControlClient): Promise<JourneyResult>;
1404
1467
 
1405
- export { type AddApexRequest, type AddApexResponse, type AddRelayRequest, type ApexNegotiationConfig, type ApexTargetStatus, type BalanceInfo, type BalancesResponse, type ChainStatus, type ChannelDepositRequest, type ChannelDepositResponse, type ChannelInfo, type ChannelsResponse, ClientRunner, type ClientRunnerDeps, type CloseChannelRequest, type CloseChannelResponse, ControlApiError, ControlClient, type ControlClientOptions, DEFAULT_KEYSTORE_PASSWORD, type DaemonConfigFile, DaemonUnreachableError, type DrainResult, type ErrorResponse, type EventsQuery, type EventsResponse, type FundWalletRequest, type FundWalletResponse, type HttpFetchPaidRequest, type HttpFetchPaidResponse, type JourneyPlan, type JourneyResult, type JourneyState, type JourneyStep, type JourneyStepResult, type MinimalWebSocket, type NostrFilter, NotReadyError, type OpenChannelRequest, PublishRejectedError, type PublishRequest, type PublishResponse, type PublishUnsignedRequest, type QueryRequest, type QueryResponse, RelaySubscription, type RelaySubscriptionOptions, type RelayTargetStatus, type RemoveApexRequest, type RemoveRelayRequest, type ResolvedDaemonConfig, type SettleChannelRequest, type SettleChannelResponse, type SettlementChain, type StatusResponse, type SubscribeRequest, type SubscribeResponse, type SwapClaim, type SwapRequest, type SwapResponse, TOOL_DEFINITIONS, type TargetsResponse, type ToolDefinition, type ToolResult, type ToonClientLike, type UploadMediaRequest, type UploadMediaResponse, type WebSocketFactory, acquireLock, configDir, defaultConfigPath, defaultKeystorePath, dispatchTool, hasConfiguredIdentity, isDaemonRunning, isProcessAlive, readConfigFile, readPid, registerRoutes, releaseLock, resolveConfig, resolveMnemonic, runJourney, scaffoldFirstRun, spawnDaemonDetached, waitForReady };
1468
+ export { type AddApexRequest, type AddApexResponse, type AddRelayRequest, type ApexNegotiationConfig, type ApexTargetStatus, type BalanceInfo, type BalancesResponse, type ChainStatus, type ChannelDepositRequest, type ChannelDepositResponse, type ChannelInfo, type ChannelsResponse, ClientRunner, type ClientRunnerDeps, type CloseChannelRequest, type CloseChannelResponse, ControlApiError, ControlClient, type ControlClientOptions, DEFAULT_KEYSTORE_PASSWORD, type DaemonConfigFile, DaemonUnreachableError, type DrainResult, type ErrorResponse, type EventsQuery, type EventsResponse, type FundStatusResponse, type FundWalletRequest, type FundWalletResponse, type HttpFetchPaidRequest, type HttpFetchPaidResponse, type JourneyPlan, type JourneyResult, type JourneyState, type JourneyStep, type JourneyStepResult, type MinimalWebSocket, type NostrFilter, NotReadyError, type OpenChannelRequest, PublishRejectedError, type PublishRequest, type PublishResponse, type PublishUnsignedRequest, type QueryRequest, type QueryResponse, RelaySubscription, type RelaySubscriptionOptions, type RelayTargetStatus, type RemoveApexRequest, type RemoveRelayRequest, type ResolvedDaemonConfig, type SettleChannelRequest, type SettleChannelResponse, type SettlementChain, type StatusResponse, type SubscribeRequest, type SubscribeResponse, type SwapClaim, type SwapRequest, type SwapResponse, TOOL_DEFINITIONS, type TargetsResponse, type ToolDefinition, type ToolResult, type ToonClientLike, type UploadMediaRequest, type UploadMediaResponse, type WebSocketFactory, acquireLock, configDir, defaultConfigPath, defaultKeystorePath, dispatchTool, hasConfiguredIdentity, isDaemonRunning, isProcessAlive, readConfigFile, readPid, registerRoutes, releaseLock, resolveConfig, resolveMnemonic, runJourney, scaffoldFirstRun, spawnDaemonDetached, waitForReady };
package/dist/index.js CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  hasConfiguredIdentity,
9
9
  registerRoutes,
10
10
  scaffoldFirstRun
11
- } from "./chunk-ADBNZA5O.js";
11
+ } from "./chunk-KVK6OZVD.js";
12
12
  import {
13
13
  PUBLISH_TOOL,
14
14
  TOOL_DEFINITIONS,
@@ -18,7 +18,7 @@ import {
18
18
  buildFollowListFilter,
19
19
  buildProfileFilter,
20
20
  dispatchTool
21
- } from "./chunk-XFRMAETF.js";
21
+ } from "./chunk-JPQ4VCCF.js";
22
22
  import {
23
23
  ControlApiError,
24
24
  ControlClient,
@@ -36,7 +36,7 @@ import {
36
36
  resolveMnemonic,
37
37
  spawnDaemonDetached,
38
38
  waitForReady
39
- } from "./chunk-3ZBC2HUB.js";
39
+ } from "./chunk-CMGJ3NFT.js";
40
40
  import "./chunk-32QD72IL.js";
41
41
  import "./chunk-DLYE6U2Z.js";
42
42
  import "./chunk-LR7W2ISE.js";
package/dist/mcp.js CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  APP_RESOURCE_URI,
5
5
  TOOL_DEFINITIONS,
6
6
  dispatchTool
7
- } from "./chunk-XFRMAETF.js";
7
+ } from "./chunk-JPQ4VCCF.js";
8
8
  import {
9
9
  ARWEAVE_GATEWAYS,
10
10
  ControlClient,
@@ -13,7 +13,7 @@ import {
13
13
  readConfigFile,
14
14
  spawnDaemonDetached,
15
15
  waitForReady
16
- } from "./chunk-3ZBC2HUB.js";
16
+ } from "./chunk-CMGJ3NFT.js";
17
17
  import "./chunk-32QD72IL.js";
18
18
  import "./chunk-DLYE6U2Z.js";
19
19
  import "./chunk-LR7W2ISE.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@toon-protocol/client-mcp",
3
- "version": "0.10.8",
3
+ "version": "0.11.0",
4
4
  "description": "Always-on local daemon + MCP server letting a Claude agent (Desktop or Code) act as a TOON Protocol client: pay-to-write publishing, free reads, channel/balance management, and swaps.",
5
5
  "license": "MIT",
6
6
  "author": "Jonathan Green",
@@ -49,8 +49,8 @@
49
49
  "typescript": "^5.3.0",
50
50
  "vitest": "^1.0.0",
51
51
  "@toon-protocol/arweave": "0.1.1",
52
- "@toon-protocol/client": "0.14.8",
53
- "@toon-protocol/views": "0.10.8"
52
+ "@toon-protocol/client": "0.14.10",
53
+ "@toon-protocol/views": "0.11.0"
54
54
  },
55
55
  "engines": {
56
56
  "node": ">=20"