@dominusnode/pi-extension 1.1.0 → 1.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.
package/dist/toolkit.js CHANGED
@@ -2,7 +2,7 @@
2
2
  /**
3
3
  * Dominus Node Pi Extension Toolkit
4
4
  *
5
- * Provides 26 tools for the pi-mono agent framework (github.com/badlogic/pi-mono).
5
+ * Provides 53 tools for the pi-mono agent framework (github.com/badlogic/pi-mono).
6
6
  * Covers proxied fetching, wallet management, agentic wallets, teams,
7
7
  * Stripe/PayPal/crypto top-up, and x402 micropayment info.
8
8
  *
@@ -65,6 +65,7 @@ exports.checkDnsRebinding = checkDnsRebinding;
65
65
  exports.stripDangerousKeys = stripDangerousKeys;
66
66
  exports.formatBytes = formatBytes;
67
67
  exports.formatCents = formatCents;
68
+ const crypto = __importStar(require("node:crypto"));
68
69
  const http = __importStar(require("node:http"));
69
70
  const tls = __importStar(require("node:tls"));
70
71
  const dns = __importStar(require("dns/promises"));
@@ -97,7 +98,7 @@ exports.BLOCKED_HOSTNAMES = new Set([
97
98
  // ---------------------------------------------------------------------------
98
99
  /** Remove any dn_live_* or dn_test_* tokens from error messages. */
99
100
  function scrubCredentials(msg) {
100
- return msg.replace(/dn_(live|test)_[A-Za-z0-9_-]+/g, "dn_$1_***REDACTED***");
101
+ return msg.replace(/dn_(live|test|proxy)_[A-Za-z0-9_-]+/g, "dn_$1_***REDACTED***");
101
102
  }
102
103
  function safeError(err) {
103
104
  const raw = err instanceof Error ? err.message : String(err);
@@ -109,7 +110,8 @@ function safeError(err) {
109
110
  function truncate(text, max = MAX_RESPONSE_CHARS) {
110
111
  if (text.length <= max)
111
112
  return text;
112
- return text.slice(0, max) + `\n\n... [truncated, ${text.length - max} chars omitted]`;
113
+ return (text.slice(0, max) +
114
+ `\n\n... [truncated, ${text.length - max} chars omitted]`);
113
115
  }
114
116
  // ---------------------------------------------------------------------------
115
117
  // SSRF Protection
@@ -270,7 +272,9 @@ function validateTargetUrl(url) {
270
272
  if (hostname.endsWith(".localhost")) {
271
273
  throw new Error("Requests to localhost/loopback addresses are blocked");
272
274
  }
273
- if (hostname.endsWith(".local") || hostname.endsWith(".internal") || hostname.endsWith(".arpa")) {
275
+ if (hostname.endsWith(".local") ||
276
+ hostname.endsWith(".internal") ||
277
+ hostname.endsWith(".arpa")) {
274
278
  throw new Error("Requests to internal network hostnames are blocked");
275
279
  }
276
280
  return parsed;
@@ -306,11 +310,15 @@ async function checkDnsRebinding(hostname) {
306
310
  try {
307
311
  resolvedIps.push(...(await dns.resolve4(stripped)));
308
312
  }
309
- catch { /* NODATA/NXDOMAIN for A is fine */ }
313
+ catch {
314
+ /* NODATA/NXDOMAIN for A is fine */
315
+ }
310
316
  try {
311
317
  resolvedIps.push(...(await dns.resolve6(stripped)));
312
318
  }
313
- catch { /* NODATA/NXDOMAIN for AAAA is fine */ }
319
+ catch {
320
+ /* NODATA/NXDOMAIN for AAAA is fine */
321
+ }
314
322
  for (const ip of resolvedIps) {
315
323
  if (isPrivateIp(ip)) {
316
324
  throw new Error(`DNS rebinding detected: hostname resolves to private IP ${ip}`);
@@ -388,7 +396,9 @@ class DominusNodeToolkit {
388
396
  constructor(options = {}) {
389
397
  this.apiKey = options.apiKey || process.env["DOMINUSNODE_API_KEY"] || "";
390
398
  // H-6: Validate baseUrl
391
- const rawBase = (options.baseUrl || process.env["DOMINUSNODE_BASE_URL"] || "https://api.dominusnode.com").replace(/\/+$/, "");
399
+ const rawBase = (options.baseUrl ||
400
+ process.env["DOMINUSNODE_BASE_URL"] ||
401
+ "https://api.dominusnode.com").replace(/\/+$/, "");
392
402
  try {
393
403
  const parsedBase = new URL(rawBase);
394
404
  if (parsedBase.protocol !== "https:" && parsedBase.protocol !== "http:")
@@ -396,7 +406,9 @@ class DominusNodeToolkit {
396
406
  if (parsedBase.username || parsedBase.password)
397
407
  throw new Error("embedded credentials not allowed in base URL");
398
408
  // H-1: Warn on HTTP base URL in production
399
- if (parsedBase.protocol === "http:" && parsedBase.hostname !== "localhost" && parsedBase.hostname !== "127.0.0.1") {
409
+ if (parsedBase.protocol === "http:" &&
410
+ parsedBase.hostname !== "localhost" &&
411
+ parsedBase.hostname !== "127.0.0.1") {
400
412
  console.warn("[DominusNode] WARNING: baseUrl uses HTTP — API key will be sent unencrypted");
401
413
  }
402
414
  }
@@ -405,21 +417,81 @@ class DominusNodeToolkit {
405
417
  }
406
418
  this.baseUrl = rawBase;
407
419
  // H-2: Validate proxyHost against private/internal IPs
408
- const rawProxyHost = options.proxyHost || process.env["DOMINUSNODE_PROXY_HOST"] || "proxy.dominusnode.com";
409
- if (exports.BLOCKED_HOSTNAMES.has(rawProxyHost.toLowerCase()) || isPrivateIp(rawProxyHost)) {
420
+ const rawProxyHost = options.proxyHost ||
421
+ process.env["DOMINUSNODE_PROXY_HOST"] ||
422
+ "proxy.dominusnode.com";
423
+ if (exports.BLOCKED_HOSTNAMES.has(rawProxyHost.toLowerCase()) ||
424
+ isPrivateIp(rawProxyHost)) {
410
425
  throw new Error("DOMINUSNODE_PROXY_HOST must not be a private or loopback address");
411
426
  }
412
- if (rawProxyHost.endsWith(".localhost") || rawProxyHost.endsWith(".local") ||
413
- rawProxyHost.endsWith(".internal") || rawProxyHost.endsWith(".arpa")) {
427
+ if (rawProxyHost.endsWith(".localhost") ||
428
+ rawProxyHost.endsWith(".local") ||
429
+ rawProxyHost.endsWith(".internal") ||
430
+ rawProxyHost.endsWith(".arpa")) {
414
431
  throw new Error("DOMINUSNODE_PROXY_HOST must not use internal network TLDs");
415
432
  }
416
433
  this.proxyHost = rawProxyHost;
417
434
  const portVal = Number(options.proxyPort ?? process.env["DOMINUSNODE_PROXY_PORT"] ?? 8080);
418
- this.proxyPort = isNaN(portVal) || portVal < 1 || portVal > 65535 ? 8080 : portVal;
435
+ this.proxyPort =
436
+ isNaN(portVal) || portVal < 1 || portVal > 65535 ? 8080 : portVal;
419
437
  // H-2: Validate timeout option; store but use REQUEST_TIMEOUT_MS constant for requests
420
438
  const tVal = Number(options.timeout ?? 30000);
421
- this.timeout = Number.isFinite(tVal) && tVal >= 1000 && tVal <= 120000 ? tVal : 30000;
422
- this.agentSecret = options.agentSecret || process.env["DOMINUSNODE_AGENT_SECRET"];
439
+ this.timeout =
440
+ Number.isFinite(tVal) && tVal >= 1000 && tVal <= 120000 ? tVal : 30000;
441
+ this.agentSecret =
442
+ options.agentSecret || process.env["DOMINUSNODE_AGENT_SECRET"];
443
+ }
444
+ // -----------------------------------------------------------------------
445
+ // SHA-256 Proof-of-Work solver
446
+ // -----------------------------------------------------------------------
447
+ static _countLeadingZeroBits(buf) {
448
+ let count = 0;
449
+ for (const byte of buf) {
450
+ if (byte === 0) {
451
+ count += 8;
452
+ continue;
453
+ }
454
+ let mask = 0x80;
455
+ while (mask && !(byte & mask)) {
456
+ count++;
457
+ mask >>= 1;
458
+ }
459
+ break;
460
+ }
461
+ return count;
462
+ }
463
+ async _solvePoW() {
464
+ try {
465
+ const resp = await fetch(`${this.baseUrl}/api/auth/pow/challenge`, {
466
+ method: "POST",
467
+ headers: { "Content-Type": "application/json" },
468
+ redirect: "error",
469
+ });
470
+ if (!resp.ok)
471
+ return null;
472
+ const text = await resp.text();
473
+ if (text.length > 10_485_760)
474
+ return null;
475
+ const challenge = JSON.parse(text);
476
+ const prefix = challenge.prefix ?? "";
477
+ const difficulty = challenge.difficulty ?? 20;
478
+ const challengeId = challenge.challengeId ?? "";
479
+ if (!prefix || !challengeId)
480
+ return null;
481
+ for (let nonce = 0; nonce < 100_000_000; nonce++) {
482
+ const hash = crypto
483
+ .createHash("sha256")
484
+ .update(prefix + nonce.toString())
485
+ .digest();
486
+ if (DominusNodeToolkit._countLeadingZeroBits(hash) >= difficulty) {
487
+ return { challengeId, nonce: nonce.toString() };
488
+ }
489
+ }
490
+ return null;
491
+ }
492
+ catch {
493
+ return null;
494
+ }
423
495
  }
424
496
  // -----------------------------------------------------------------------
425
497
  // Authentication
@@ -427,7 +499,8 @@ class DominusNodeToolkit {
427
499
  async _authenticate() {
428
500
  if (!this.apiKey)
429
501
  throw new Error("Dominus Node API key is required. Set DOMINUSNODE_API_KEY env var.");
430
- if (!this.apiKey.startsWith("dn_live_") && !this.apiKey.startsWith("dn_test_")) {
502
+ if (!this.apiKey.startsWith("dn_live_") &&
503
+ !this.apiKey.startsWith("dn_test_")) {
431
504
  throw new Error('DOMINUSNODE_API_KEY must start with "dn_live_" or "dn_test_".');
432
505
  }
433
506
  const authHeaders = {
@@ -461,7 +534,9 @@ class DominusNodeToolkit {
461
534
  parts.push(value);
462
535
  }
463
536
  }
464
- catch { /* ignore */ }
537
+ catch {
538
+ /* ignore */
539
+ }
465
540
  reader.cancel().catch(() => { });
466
541
  errorText = Buffer.concat(parts).toString("utf-8").slice(0, 500);
467
542
  }
@@ -473,7 +548,9 @@ class DominusNodeToolkit {
473
548
  throw new Error("Auth response too large");
474
549
  const data = JSON.parse(rawText);
475
550
  stripDangerousKeys(data);
476
- if (!data.token || typeof data.token !== "string" || data.token.length > 4096) {
551
+ if (!data.token ||
552
+ typeof data.token !== "string" ||
553
+ data.token.length > 4096) {
477
554
  throw new Error("Invalid token in auth response");
478
555
  }
479
556
  this.token = data.token;
@@ -489,8 +566,13 @@ class DominusNodeToolkit {
489
566
  return;
490
567
  }
491
568
  this._authPromise = this._authenticate()
492
- .then(() => { this._authPromise = null; })
493
- .catch((e) => { this._authPromise = null; throw e; });
569
+ .then(() => {
570
+ this._authPromise = null;
571
+ })
572
+ .catch((e) => {
573
+ this._authPromise = null;
574
+ throw e;
575
+ });
494
576
  await this._authPromise;
495
577
  }
496
578
  async _apiRequest(method, path, body) {
@@ -505,9 +587,16 @@ class DominusNodeToolkit {
505
587
  headers["X-DominusNode-Agent"] = "mcp";
506
588
  headers["X-DominusNode-Agent-Secret"] = this.agentSecret;
507
589
  }
508
- const init = { method, headers, signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS), redirect: "error" };
590
+ const init = {
591
+ method,
592
+ headers,
593
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
594
+ redirect: "error",
595
+ };
509
596
  // M-6: Drop body on safe HTTP methods
510
- if (body && !["GET", "HEAD"].includes(method) && ["POST", "PUT", "PATCH", "DELETE"].includes(method)) {
597
+ if (body &&
598
+ !["GET", "HEAD"].includes(method) &&
599
+ ["POST", "PUT", "PATCH", "DELETE"].includes(method)) {
511
600
  headers["Content-Type"] = "application/json";
512
601
  init.body = JSON.stringify(body);
513
602
  }
@@ -617,11 +706,18 @@ class DominusNodeToolkit {
617
706
  reject(new Error("Proxy request timed out"));
618
707
  }, REQUEST_TIMEOUT_MS);
619
708
  if (parsed.protocol === "https:") {
620
- const connectHost = parsed.hostname.includes(":") ? `[${parsed.hostname}]` : parsed.hostname;
709
+ const connectHost = parsed.hostname.includes(":")
710
+ ? `[${parsed.hostname}]`
711
+ : parsed.hostname;
621
712
  const connectReq = http.request({
622
- hostname: this.proxyHost, port: this.proxyPort, method: "CONNECT",
713
+ hostname: this.proxyHost,
714
+ port: this.proxyPort,
715
+ method: "CONNECT",
623
716
  path: `${connectHost}:${parsed.port || 443}`,
624
- headers: { "Proxy-Authorization": proxyAuth, Host: `${connectHost}:${parsed.port || 443}` },
717
+ headers: {
718
+ "Proxy-Authorization": proxyAuth,
719
+ Host: `${connectHost}:${parsed.port || 443}`,
720
+ },
625
721
  });
626
722
  cleanupFn = () => connectReq.destroy();
627
723
  connectReq.on("connect", (_res, sock) => {
@@ -631,7 +727,12 @@ class DominusNodeToolkit {
631
727
  reject(new Error(`CONNECT failed: ${_res.statusCode}`));
632
728
  return;
633
729
  }
634
- const tlsSock = tls.connect({ host: parsed.hostname, socket: sock, servername: parsed.hostname, minVersion: "TLSv1.2" }, () => {
730
+ const tlsSock = tls.connect({
731
+ host: parsed.hostname,
732
+ socket: sock,
733
+ servername: parsed.hostname,
734
+ minVersion: "TLSv1.2",
735
+ }, () => {
635
736
  const reqLine = `${methodUpper} ${parsed.pathname + parsed.search} HTTP/1.1\r\nHost: ${parsed.host}\r\nUser-Agent: dominusnode-pi/1.0.0\r\nConnection: close\r\n\r\n`;
636
737
  tlsSock.write(reqLine);
637
738
  const chunks = [];
@@ -657,27 +758,47 @@ class DominusNodeToolkit {
657
758
  return;
658
759
  }
659
760
  const hdr = raw.substring(0, hEnd);
660
- const body = raw.substring(hEnd + 4).substring(0, MAX_PROXY_RESPONSE_BYTES);
661
- const sm = hdr.split("\r\n")[0].match(/^HTTP\/\d\.\d\s+(\d+)/);
761
+ const body = raw
762
+ .substring(hEnd + 4)
763
+ .substring(0, MAX_PROXY_RESPONSE_BYTES);
764
+ const sm = hdr
765
+ .split("\r\n")[0]
766
+ .match(/^HTTP\/\d\.\d\s+(\d+)/);
662
767
  resolve({ status: sm ? parseInt(sm[1], 10) : 0, body });
663
768
  };
664
769
  tlsSock.on("end", fin);
665
770
  tlsSock.on("close", fin);
666
- tlsSock.on("error", (e) => { clearTimeout(timer); reject(e); });
771
+ tlsSock.on("error", (e) => {
772
+ clearTimeout(timer);
773
+ reject(e);
774
+ });
667
775
  });
668
776
  // M-1: Update cleanupFn to destroy tlsSock — covers the TLS handshake window
669
777
  cleanupFn = () => tlsSock.destroy();
670
- tlsSock.on("error", (e) => { clearTimeout(timer); reject(e); });
778
+ tlsSock.on("error", (e) => {
779
+ clearTimeout(timer);
780
+ reject(e);
781
+ });
782
+ });
783
+ connectReq.on("error", (e) => {
784
+ clearTimeout(timer);
785
+ reject(e);
671
786
  });
672
- connectReq.on("error", (e) => { clearTimeout(timer); reject(e); });
673
787
  connectReq.end();
674
788
  }
675
789
  else {
676
790
  const reqPath = parsed.pathname + parsed.search;
677
791
  const proxyReq = http.request({
678
- hostname: this.proxyHost, port: this.proxyPort, method: methodUpper,
792
+ hostname: this.proxyHost,
793
+ port: this.proxyPort,
794
+ method: methodUpper,
679
795
  path: `http://${parsed.host}${reqPath}`,
680
- headers: { "Proxy-Authorization": proxyAuth, Host: parsed.host, "User-Agent": "dominusnode-pi/1.0.0", Connection: "close" },
796
+ headers: {
797
+ "Proxy-Authorization": proxyAuth,
798
+ Host: parsed.host,
799
+ "User-Agent": "dominusnode-pi/1.0.0",
800
+ Connection: "close",
801
+ },
681
802
  });
682
803
  cleanupFn = () => proxyReq.destroy(); // H-3: destroy on timeout
683
804
  const chunks = [];
@@ -691,10 +812,22 @@ class DominusNodeToolkit {
691
812
  } // H-4: destroy on oversize
692
813
  chunks.push(c);
693
814
  });
694
- r.on("end", () => { clearTimeout(timer); const body = Buffer.concat(chunks).toString("utf-8").substring(0, MAX_PROXY_RESPONSE_BYTES); resolve({ status: r.statusCode ?? 0, body }); });
695
- r.on("error", (e) => { clearTimeout(timer); reject(e); });
815
+ r.on("end", () => {
816
+ clearTimeout(timer);
817
+ const body = Buffer.concat(chunks)
818
+ .toString("utf-8")
819
+ .substring(0, MAX_PROXY_RESPONSE_BYTES);
820
+ resolve({ status: r.statusCode ?? 0, body });
821
+ });
822
+ r.on("error", (e) => {
823
+ clearTimeout(timer);
824
+ reject(e);
825
+ });
826
+ });
827
+ proxyReq.on("error", (e) => {
828
+ clearTimeout(timer);
829
+ reject(e);
696
830
  });
697
- proxyReq.on("error", (e) => { clearTimeout(timer); reject(e); });
698
831
  proxyReq.end();
699
832
  }
700
833
  });
@@ -789,7 +922,7 @@ class DominusNodeToolkit {
789
922
  const amount = Math.floor(Number(amountCents));
790
923
  if (isNaN(amount) || amount < 1 || amount > 1000000)
791
924
  return err("amountCents must be between 1 and 1,000,000");
792
- const data = await this._req("POST", `/api/agent-wallet/${id}/fund`, { amountCents: amount });
925
+ const data = await this._req("POST", `/api/agent-wallet/${encodeURIComponent(id)}/fund`, { amountCents: amount });
793
926
  return ok(JSON.stringify(data, null, 2));
794
927
  }
795
928
  catch (e) {
@@ -802,7 +935,7 @@ class DominusNodeToolkit {
802
935
  async checkAgenticBalance(walletId) {
803
936
  try {
804
937
  const id = validateUuid(walletId, "walletId");
805
- const data = await this._req("GET", `/api/agent-wallet/${id}`);
938
+ const data = await this._req("GET", `/api/agent-wallet/${encodeURIComponent(id)}`);
806
939
  return ok(JSON.stringify(data, null, 2));
807
940
  }
808
941
  catch (e) {
@@ -831,7 +964,7 @@ class DominusNodeToolkit {
831
964
  if (!Number.isFinite(rawLim))
832
965
  return err("limit must be a finite number");
833
966
  const lim = Math.max(1, Math.min(100, Math.floor(rawLim)));
834
- const data = await this._req("GET", `/api/agent-wallet/${id}/transactions?limit=${lim}`);
967
+ const data = await this._req("GET", `/api/agent-wallet/${encodeURIComponent(id)}/transactions?limit=${lim}`);
835
968
  return ok(JSON.stringify(data, null, 2));
836
969
  }
837
970
  catch (e) {
@@ -844,7 +977,7 @@ class DominusNodeToolkit {
844
977
  async freezeAgenticWallet(walletId) {
845
978
  try {
846
979
  const id = validateUuid(walletId, "walletId");
847
- const data = await this._req("POST", `/api/agent-wallet/${id}/freeze`);
980
+ const data = await this._req("POST", `/api/agent-wallet/${encodeURIComponent(id)}/freeze`);
848
981
  return ok(JSON.stringify(data, null, 2));
849
982
  }
850
983
  catch (e) {
@@ -857,7 +990,7 @@ class DominusNodeToolkit {
857
990
  async unfreezeAgenticWallet(walletId) {
858
991
  try {
859
992
  const id = validateUuid(walletId, "walletId");
860
- const data = await this._req("POST", `/api/agent-wallet/${id}/unfreeze`);
993
+ const data = await this._req("POST", `/api/agent-wallet/${encodeURIComponent(id)}/unfreeze`);
861
994
  return ok(JSON.stringify(data, null, 2));
862
995
  }
863
996
  catch (e) {
@@ -870,7 +1003,7 @@ class DominusNodeToolkit {
870
1003
  async deleteAgenticWallet(walletId) {
871
1004
  try {
872
1005
  const id = validateUuid(walletId, "walletId");
873
- const data = await this._req("DELETE", `/api/agent-wallet/${id}`);
1006
+ const data = await this._req("DELETE", `/api/agent-wallet/${encodeURIComponent(id)}`);
874
1007
  return ok(JSON.stringify(data, null, 2));
875
1008
  }
876
1009
  catch (e) {
@@ -888,7 +1021,10 @@ class DominusNodeToolkit {
888
1021
  const max = Math.max(1, Math.min(100, Math.floor(Number(maxMembers))));
889
1022
  if (isNaN(max))
890
1023
  return err("maxMembers must be a number between 1 and 100");
891
- const data = await this._req("POST", "/api/teams", { name: name.trim(), maxMembers: max });
1024
+ const data = await this._req("POST", "/api/teams", {
1025
+ name: name.trim(),
1026
+ maxMembers: max,
1027
+ });
892
1028
  return ok(JSON.stringify(data, null, 2));
893
1029
  }
894
1030
  catch (e) {
@@ -917,7 +1053,7 @@ class DominusNodeToolkit {
917
1053
  async teamDetails(teamId) {
918
1054
  try {
919
1055
  const id = validateUuid(teamId, "teamId");
920
- const data = await this._req("GET", `/api/teams/${id}`);
1056
+ const data = await this._req("GET", `/api/teams/${encodeURIComponent(id)}`);
921
1057
  return ok(JSON.stringify(data, null, 2));
922
1058
  }
923
1059
  catch (e) {
@@ -933,7 +1069,7 @@ class DominusNodeToolkit {
933
1069
  const amount = Math.floor(Number(amountCents));
934
1070
  if (isNaN(amount) || amount < 1 || amount > 1000000)
935
1071
  return err("amountCents must be between 1 and 1,000,000");
936
- const data = await this._req("POST", `/api/teams/${id}/wallet/fund`, { amountCents: amount });
1072
+ const data = await this._req("POST", `/api/teams/${encodeURIComponent(id)}/wallet/fund`, { amountCents: amount });
937
1073
  return ok(JSON.stringify(data, null, 2));
938
1074
  }
939
1075
  catch (e) {
@@ -949,8 +1085,9 @@ class DominusNodeToolkit {
949
1085
  const labelErr = validateLabel(label);
950
1086
  if (labelErr)
951
1087
  return err(labelErr);
952
- const data = await this._req("POST", `/api/teams/${id}/keys`, { label: label.trim() });
953
- return ok(JSON.stringify(data, null, 2) + "\n\nIMPORTANT: Store this API key securely — it will not be shown again.");
1088
+ const data = await this._req("POST", `/api/teams/${encodeURIComponent(id)}/keys`, { label: label.trim() });
1089
+ return ok(JSON.stringify(data, null, 2) +
1090
+ "\n\nIMPORTANT: Store this API key securely — it will not be shown again.");
954
1091
  }
955
1092
  catch (e) {
956
1093
  return err(e);
@@ -968,7 +1105,7 @@ class DominusNodeToolkit {
968
1105
  const d = Math.max(1, Math.min(365, Math.floor(rawDays)));
969
1106
  const until = new Date().toISOString();
970
1107
  const since = new Date(Date.now() - d * 86400000).toISOString();
971
- const data = await this._req("GET", `/api/teams/${id}/usage?since=${encodeURIComponent(since)}&until=${encodeURIComponent(until)}`);
1108
+ const data = await this._req("GET", `/api/teams/${encodeURIComponent(id)}/usage?since=${encodeURIComponent(since)}&until=${encodeURIComponent(until)}`);
972
1109
  return ok(JSON.stringify(data, null, 2));
973
1110
  }
974
1111
  catch (e) {
@@ -996,7 +1133,7 @@ class DominusNodeToolkit {
996
1133
  }
997
1134
  if (!Object.keys(body).length)
998
1135
  return err("At least one of name or maxMembers must be provided");
999
- const data = await this._req("PATCH", `/api/teams/${id}`, body);
1136
+ const data = await this._req("PATCH", `/api/teams/${encodeURIComponent(id)}`, body);
1000
1137
  return ok(JSON.stringify(data, null, 2));
1001
1138
  }
1002
1139
  catch (e) {
@@ -1027,7 +1164,9 @@ class DominusNodeToolkit {
1027
1164
  const amount = Math.floor(Number(amountCents));
1028
1165
  if (isNaN(amount) || amount < 500 || amount > 1000000)
1029
1166
  return err("amountCents must be between 500 ($5.00) and 1,000,000 ($10,000.00)");
1030
- const data = await this._req("POST", "/api/wallet/topup/paypal", { amountCents: amount });
1167
+ const data = await this._req("POST", "/api/wallet/topup/paypal", {
1168
+ amountCents: amount,
1169
+ });
1031
1170
  return ok(JSON.stringify(data, null, 2));
1032
1171
  }
1033
1172
  catch (e) {
@@ -1042,7 +1181,9 @@ class DominusNodeToolkit {
1042
1181
  const amount = Math.floor(Number(amountCents));
1043
1182
  if (isNaN(amount) || amount < 500 || amount > 1000000)
1044
1183
  return err("amountCents must be between 500 ($5.00) and 1,000,000 ($10,000.00)");
1045
- const data = await this._req("POST", "/api/wallet/topup/stripe", { amountCents: amount });
1184
+ const data = await this._req("POST", "/api/wallet/topup/stripe", {
1185
+ amountCents: amount,
1186
+ });
1046
1187
  return ok(JSON.stringify(data, null, 2));
1047
1188
  }
1048
1189
  catch (e) {
@@ -1059,11 +1200,26 @@ class DominusNodeToolkit {
1059
1200
  if (!Number.isInteger(rawAmount) || rawAmount < 5 || rawAmount > 10000)
1060
1201
  return err("amountUsd must be a whole number between 5 and 10,000");
1061
1202
  const amount = rawAmount;
1062
- const VALID_CURRENCIES = ["BTC", "ETH", "LTC", "XMR", "ZEC", "USDC", "SOL", "USDT", "DAI", "BNB", "LINK"];
1203
+ const VALID_CURRENCIES = [
1204
+ "BTC",
1205
+ "ETH",
1206
+ "LTC",
1207
+ "XMR",
1208
+ "ZEC",
1209
+ "USDC",
1210
+ "SOL",
1211
+ "USDT",
1212
+ "DAI",
1213
+ "BNB",
1214
+ "LINK",
1215
+ ];
1063
1216
  const cur = (currency || "").toUpperCase().trim();
1064
1217
  if (!VALID_CURRENCIES.includes(cur))
1065
1218
  return err(`currency must be one of: ${VALID_CURRENCIES.join(", ")}`);
1066
- const data = await this._req("POST", "/api/wallet/topup/crypto", { amountUsd: amount, currency: cur });
1219
+ const data = await this._req("POST", "/api/wallet/topup/crypto", {
1220
+ amountUsd: amount,
1221
+ currency: cur,
1222
+ });
1067
1223
  return ok(JSON.stringify(data, null, 2));
1068
1224
  }
1069
1225
  catch (e) {
@@ -1112,7 +1268,581 @@ class DominusNodeToolkit {
1112
1268
  }
1113
1269
  if (!Object.keys(body).length)
1114
1270
  return err("At least one of dailyLimitCents or allowedDomains must be provided");
1115
- const data = await this._req("PATCH", `/api/agent-wallet/${id}/policy`, body);
1271
+ const data = await this._req("PATCH", `/api/agent-wallet/${encodeURIComponent(id)}/policy`, body);
1272
+ return ok(JSON.stringify(data, null, 2));
1273
+ }
1274
+ catch (e) {
1275
+ return err(e);
1276
+ }
1277
+ }
1278
+ // -----------------------------------------------------------------------
1279
+ // Unauthenticated request helper (register, login, verify-email)
1280
+ // -----------------------------------------------------------------------
1281
+ async _rawRequest(method, path, body) {
1282
+ const url = `${this.baseUrl}${path}`;
1283
+ const headers = {
1284
+ Accept: "application/json",
1285
+ "User-Agent": "dominusnode-pi/1.0.0",
1286
+ };
1287
+ if (this.agentSecret) {
1288
+ headers["X-DominusNode-Agent"] = "mcp";
1289
+ headers["X-DominusNode-Agent-Secret"] = this.agentSecret;
1290
+ }
1291
+ const init = {
1292
+ method,
1293
+ headers,
1294
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
1295
+ redirect: "error",
1296
+ };
1297
+ if (body && ["POST", "PUT", "PATCH", "DELETE"].includes(method)) {
1298
+ headers["Content-Type"] = "application/json";
1299
+ init.body = JSON.stringify(body);
1300
+ }
1301
+ let res;
1302
+ try {
1303
+ res = await fetch(url, init);
1304
+ }
1305
+ catch (e) {
1306
+ throw new Error(`API request failed: ${safeError(e)}`);
1307
+ }
1308
+ let rawText = "";
1309
+ if (res.body) {
1310
+ const reader = res.body.getReader();
1311
+ const chunks = [];
1312
+ let totalBytes = 0;
1313
+ try {
1314
+ while (true) {
1315
+ const { done, value } = await reader.read();
1316
+ if (done)
1317
+ break;
1318
+ totalBytes += value.length;
1319
+ if (totalBytes > MAX_RESPONSE_BYTES) {
1320
+ reader.cancel().catch(() => { });
1321
+ throw new Error("API response too large");
1322
+ }
1323
+ chunks.push(value);
1324
+ }
1325
+ }
1326
+ catch (e) {
1327
+ reader.cancel().catch(() => { });
1328
+ throw e;
1329
+ }
1330
+ rawText = Buffer.concat(chunks).toString("utf-8");
1331
+ }
1332
+ if (!res.ok) {
1333
+ let msg = `API error ${res.status}`;
1334
+ try {
1335
+ const parsed = JSON.parse(rawText);
1336
+ stripDangerousKeys(parsed);
1337
+ const detail = parsed.error ?? parsed.message;
1338
+ if (detail)
1339
+ msg = `API error ${res.status}: ${scrubCredentials(String(detail))}`;
1340
+ }
1341
+ catch {
1342
+ if (rawText)
1343
+ msg = `API error ${res.status}: ${scrubCredentials(rawText.slice(0, 200))}`;
1344
+ }
1345
+ throw new Error(msg);
1346
+ }
1347
+ if (!rawText || !rawText.trim())
1348
+ return {};
1349
+ try {
1350
+ const parsed = JSON.parse(rawText);
1351
+ stripDangerousKeys(parsed);
1352
+ return parsed;
1353
+ }
1354
+ catch {
1355
+ throw new Error("Failed to parse API response as JSON");
1356
+ }
1357
+ }
1358
+ // -----------------------------------------------------------------------
1359
+ // Validation helpers for account tools
1360
+ // -----------------------------------------------------------------------
1361
+ static EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
1362
+ static validateEmail(email) {
1363
+ const trimmed = email.trim();
1364
+ if (!trimmed || !DominusNodeToolkit.EMAIL_RE.test(trimmed)) {
1365
+ throw new Error("Invalid email address.");
1366
+ }
1367
+ if (trimmed.length > 254) {
1368
+ throw new Error("Email address too long (max 254 characters).");
1369
+ }
1370
+ return trimmed;
1371
+ }
1372
+ static validatePassword(password) {
1373
+ if (!password || password.length < 8) {
1374
+ throw new Error("Password must be at least 8 characters.");
1375
+ }
1376
+ if (password.length > 128) {
1377
+ throw new Error("Password must be at most 128 characters.");
1378
+ }
1379
+ return password;
1380
+ }
1381
+ // -----------------------------------------------------------------------
1382
+ // Tool 27: getProxyStatus
1383
+ // -----------------------------------------------------------------------
1384
+ async getProxyStatus() {
1385
+ try {
1386
+ const data = await this._req("GET", "/api/proxy/status");
1387
+ return ok(JSON.stringify(data, null, 2));
1388
+ }
1389
+ catch (e) {
1390
+ return err(e);
1391
+ }
1392
+ }
1393
+ // -----------------------------------------------------------------------
1394
+ // Tool 28: getTransactions
1395
+ // -----------------------------------------------------------------------
1396
+ async getTransactions(limit = 20) {
1397
+ try {
1398
+ const rawLim = Number(limit);
1399
+ if (!Number.isFinite(rawLim))
1400
+ return err("limit must be a finite number");
1401
+ const lim = Math.max(1, Math.min(100, Math.floor(rawLim)));
1402
+ const data = await this._req("GET", `/api/wallet/transactions?limit=${lim}`);
1403
+ return ok(JSON.stringify(data, null, 2));
1404
+ }
1405
+ catch (e) {
1406
+ return err(e);
1407
+ }
1408
+ }
1409
+ // -----------------------------------------------------------------------
1410
+ // Tool 29: getForecast
1411
+ // -----------------------------------------------------------------------
1412
+ async getForecast() {
1413
+ try {
1414
+ const data = await this._req("GET", "/api/wallet/forecast");
1415
+ return ok(JSON.stringify(data, null, 2));
1416
+ }
1417
+ catch (e) {
1418
+ return err(e);
1419
+ }
1420
+ }
1421
+ // -----------------------------------------------------------------------
1422
+ // Tool 30: checkPayment
1423
+ // -----------------------------------------------------------------------
1424
+ async checkPayment(invoiceId) {
1425
+ try {
1426
+ const id = validateUuid(invoiceId, "invoiceId");
1427
+ const data = await this._req("GET", `/api/wallet/topup/crypto/${encodeURIComponent(id)}/status`);
1428
+ return ok(JSON.stringify(data, null, 2));
1429
+ }
1430
+ catch (e) {
1431
+ return err(e);
1432
+ }
1433
+ }
1434
+ // -----------------------------------------------------------------------
1435
+ // Tool 31: getDailyUsage
1436
+ // -----------------------------------------------------------------------
1437
+ async getDailyUsage(days = 7) {
1438
+ try {
1439
+ const rawDays = Number(days);
1440
+ if (!Number.isFinite(rawDays))
1441
+ return err("days must be a finite number between 1 and 365");
1442
+ const d = Math.max(1, Math.min(365, Math.floor(rawDays)));
1443
+ const data = await this._req("GET", `/api/usage/daily?days=${d}`);
1444
+ return ok(JSON.stringify(data, null, 2));
1445
+ }
1446
+ catch (e) {
1447
+ return err(e);
1448
+ }
1449
+ }
1450
+ // -----------------------------------------------------------------------
1451
+ // Tool 32: getTopHosts
1452
+ // -----------------------------------------------------------------------
1453
+ async getTopHosts(limit = 10) {
1454
+ try {
1455
+ const rawLim = Number(limit);
1456
+ if (!Number.isFinite(rawLim))
1457
+ return err("limit must be a finite number");
1458
+ const lim = Math.max(1, Math.min(100, Math.floor(rawLim)));
1459
+ const data = await this._req("GET", `/api/usage/top-hosts?limit=${lim}`);
1460
+ return ok(JSON.stringify(data, null, 2));
1461
+ }
1462
+ catch (e) {
1463
+ return err(e);
1464
+ }
1465
+ }
1466
+ // -----------------------------------------------------------------------
1467
+ // Tool 33: register
1468
+ // -----------------------------------------------------------------------
1469
+ async register(email, password) {
1470
+ try {
1471
+ const safeEmail = DominusNodeToolkit.validateEmail(email);
1472
+ const safePassword = DominusNodeToolkit.validatePassword(password);
1473
+ // Solve PoW for CAPTCHA-free registration
1474
+ const pow = await this._solvePoW();
1475
+ const regBody = {
1476
+ email: safeEmail,
1477
+ password: safePassword,
1478
+ };
1479
+ if (pow)
1480
+ regBody.pow = pow;
1481
+ const data = await this._rawRequest("POST", "/api/auth/register", regBody);
1482
+ return ok(JSON.stringify(data, null, 2));
1483
+ }
1484
+ catch (e) {
1485
+ return err(e);
1486
+ }
1487
+ }
1488
+ // -----------------------------------------------------------------------
1489
+ // Tool 34: login
1490
+ // -----------------------------------------------------------------------
1491
+ async login(email, password) {
1492
+ try {
1493
+ const safeEmail = DominusNodeToolkit.validateEmail(email);
1494
+ const safePassword = DominusNodeToolkit.validatePassword(password);
1495
+ const data = await this._rawRequest("POST", "/api/auth/login", {
1496
+ email: safeEmail,
1497
+ password: safePassword,
1498
+ });
1499
+ return ok(JSON.stringify(data, null, 2));
1500
+ }
1501
+ catch (e) {
1502
+ return err(e);
1503
+ }
1504
+ }
1505
+ // -----------------------------------------------------------------------
1506
+ // Tool 35: getAccountInfo
1507
+ // -----------------------------------------------------------------------
1508
+ async getAccountInfo() {
1509
+ try {
1510
+ const data = await this._req("GET", "/api/auth/me");
1511
+ return ok(JSON.stringify(data, null, 2));
1512
+ }
1513
+ catch (e) {
1514
+ return err(e);
1515
+ }
1516
+ }
1517
+ // -----------------------------------------------------------------------
1518
+ // Tool 36: verifyEmail
1519
+ // -----------------------------------------------------------------------
1520
+ async verifyEmail(token) {
1521
+ try {
1522
+ if (!token || typeof token !== "string" || token.trim().length === 0) {
1523
+ return err("Verification token is required.");
1524
+ }
1525
+ if (/[\x00-\x1f\x7f]/.test(token)) {
1526
+ return err("Token contains invalid control characters.");
1527
+ }
1528
+ const data = await this._rawRequest("POST", "/api/auth/verify-email", {
1529
+ token: token.trim(),
1530
+ });
1531
+ return ok(JSON.stringify(data, null, 2));
1532
+ }
1533
+ catch (e) {
1534
+ return err(e);
1535
+ }
1536
+ }
1537
+ // -----------------------------------------------------------------------
1538
+ // Tool 37: resendVerification
1539
+ // -----------------------------------------------------------------------
1540
+ async resendVerification() {
1541
+ try {
1542
+ const data = await this._req("POST", "/api/auth/resend-verification");
1543
+ return ok(JSON.stringify(data, null, 2));
1544
+ }
1545
+ catch (e) {
1546
+ return err(e);
1547
+ }
1548
+ }
1549
+ // -----------------------------------------------------------------------
1550
+ // Tool 38: updatePassword
1551
+ // -----------------------------------------------------------------------
1552
+ async updatePassword(currentPassword, newPassword) {
1553
+ try {
1554
+ DominusNodeToolkit.validatePassword(currentPassword);
1555
+ DominusNodeToolkit.validatePassword(newPassword);
1556
+ const data = await this._req("POST", "/api/auth/change-password", {
1557
+ currentPassword,
1558
+ newPassword,
1559
+ });
1560
+ return ok(JSON.stringify(data, null, 2));
1561
+ }
1562
+ catch (e) {
1563
+ return err(e);
1564
+ }
1565
+ }
1566
+ // -----------------------------------------------------------------------
1567
+ // Tool 39: listKeys
1568
+ // -----------------------------------------------------------------------
1569
+ async listKeys() {
1570
+ try {
1571
+ const data = await this._req("GET", "/api/keys");
1572
+ return ok(JSON.stringify(data, null, 2));
1573
+ }
1574
+ catch (e) {
1575
+ return err(e);
1576
+ }
1577
+ }
1578
+ // -----------------------------------------------------------------------
1579
+ // Tool 40: createKey
1580
+ // -----------------------------------------------------------------------
1581
+ async createKey(label) {
1582
+ try {
1583
+ const labelErr = validateLabel(label);
1584
+ if (labelErr)
1585
+ return err(labelErr);
1586
+ const data = await this._req("POST", "/api/keys", {
1587
+ label: label.trim(),
1588
+ });
1589
+ return ok(JSON.stringify(data, null, 2) +
1590
+ "\n\nIMPORTANT: Store this API key securely — it will not be shown again.");
1591
+ }
1592
+ catch (e) {
1593
+ return err(e);
1594
+ }
1595
+ }
1596
+ // -----------------------------------------------------------------------
1597
+ // Tool 41: revokeKey
1598
+ // -----------------------------------------------------------------------
1599
+ async revokeKey(keyId) {
1600
+ try {
1601
+ const id = validateUuid(keyId, "keyId");
1602
+ const data = await this._req("DELETE", `/api/keys/${encodeURIComponent(id)}`);
1603
+ return ok(JSON.stringify(data, null, 2));
1604
+ }
1605
+ catch (e) {
1606
+ return err(e);
1607
+ }
1608
+ }
1609
+ // -----------------------------------------------------------------------
1610
+ // Tool 42: getPlan
1611
+ // -----------------------------------------------------------------------
1612
+ async getPlan() {
1613
+ try {
1614
+ const data = await this._req("GET", "/api/plans/user/plan");
1615
+ return ok(JSON.stringify(data, null, 2));
1616
+ }
1617
+ catch (e) {
1618
+ return err(e);
1619
+ }
1620
+ }
1621
+ // -----------------------------------------------------------------------
1622
+ // Tool 43: listPlans
1623
+ // -----------------------------------------------------------------------
1624
+ async listPlans() {
1625
+ try {
1626
+ const data = await this._req("GET", "/api/plans");
1627
+ return ok(JSON.stringify(data, null, 2));
1628
+ }
1629
+ catch (e) {
1630
+ return err(e);
1631
+ }
1632
+ }
1633
+ // -----------------------------------------------------------------------
1634
+ // Tool 44: changePlan
1635
+ // -----------------------------------------------------------------------
1636
+ async changePlan(planId) {
1637
+ try {
1638
+ const id = validateUuid(planId, "planId");
1639
+ const data = await this._req("PUT", "/api/plans/user/plan", {
1640
+ planId: id,
1641
+ });
1642
+ return ok(JSON.stringify(data, null, 2));
1643
+ }
1644
+ catch (e) {
1645
+ return err(e);
1646
+ }
1647
+ }
1648
+ // -----------------------------------------------------------------------
1649
+ // Tool 45: teamDelete
1650
+ // -----------------------------------------------------------------------
1651
+ async teamDelete(teamId) {
1652
+ try {
1653
+ const id = validateUuid(teamId, "teamId");
1654
+ const data = await this._req("DELETE", `/api/teams/${encodeURIComponent(id)}`);
1655
+ return ok(JSON.stringify(data, null, 2));
1656
+ }
1657
+ catch (e) {
1658
+ return err(e);
1659
+ }
1660
+ }
1661
+ // -----------------------------------------------------------------------
1662
+ // Tool 46: teamRevokeKey
1663
+ // -----------------------------------------------------------------------
1664
+ async teamRevokeKey(teamId, keyId) {
1665
+ try {
1666
+ const tid = validateUuid(teamId, "teamId");
1667
+ const kid = validateUuid(keyId, "keyId");
1668
+ const data = await this._req("DELETE", `/api/teams/${encodeURIComponent(tid)}/keys/${encodeURIComponent(kid)}`);
1669
+ return ok(JSON.stringify(data, null, 2));
1670
+ }
1671
+ catch (e) {
1672
+ return err(e);
1673
+ }
1674
+ }
1675
+ // -----------------------------------------------------------------------
1676
+ // Tool 47: teamListKeys
1677
+ // -----------------------------------------------------------------------
1678
+ async teamListKeys(teamId) {
1679
+ try {
1680
+ const id = validateUuid(teamId, "teamId");
1681
+ const data = await this._req("GET", `/api/teams/${encodeURIComponent(id)}/keys`);
1682
+ return ok(JSON.stringify(data, null, 2));
1683
+ }
1684
+ catch (e) {
1685
+ return err(e);
1686
+ }
1687
+ }
1688
+ // -----------------------------------------------------------------------
1689
+ // Tool 48: teamListMembers
1690
+ // -----------------------------------------------------------------------
1691
+ async teamListMembers(teamId) {
1692
+ try {
1693
+ const id = validateUuid(teamId, "teamId");
1694
+ const data = await this._req("GET", `/api/teams/${encodeURIComponent(id)}/members`);
1695
+ return ok(JSON.stringify(data, null, 2));
1696
+ }
1697
+ catch (e) {
1698
+ return err(e);
1699
+ }
1700
+ }
1701
+ // -----------------------------------------------------------------------
1702
+ // Tool 49: teamAddMember
1703
+ // -----------------------------------------------------------------------
1704
+ async teamAddMember(teamId, userId, role = "member") {
1705
+ try {
1706
+ const tid = validateUuid(teamId, "teamId");
1707
+ const uid = validateUuid(userId, "userId");
1708
+ if (role !== "member" && role !== "admin")
1709
+ return err("role must be 'member' or 'admin'");
1710
+ const data = await this._req("POST", `/api/teams/${encodeURIComponent(tid)}/members`, { userId: uid, role });
1711
+ return ok(JSON.stringify(data, null, 2));
1712
+ }
1713
+ catch (e) {
1714
+ return err(e);
1715
+ }
1716
+ }
1717
+ // -----------------------------------------------------------------------
1718
+ // Tool 50: teamRemoveMember
1719
+ // -----------------------------------------------------------------------
1720
+ async teamRemoveMember(teamId, userId) {
1721
+ try {
1722
+ const tid = validateUuid(teamId, "teamId");
1723
+ const uid = validateUuid(userId, "userId");
1724
+ const data = await this._req("DELETE", `/api/teams/${encodeURIComponent(tid)}/members/${encodeURIComponent(uid)}`);
1725
+ return ok(JSON.stringify(data, null, 2));
1726
+ }
1727
+ catch (e) {
1728
+ return err(e);
1729
+ }
1730
+ }
1731
+ // -----------------------------------------------------------------------
1732
+ // Tool 51: teamInviteMember
1733
+ // -----------------------------------------------------------------------
1734
+ async teamInviteMember(teamId, email, role = "member") {
1735
+ try {
1736
+ const id = validateUuid(teamId, "teamId");
1737
+ const safeEmail = DominusNodeToolkit.validateEmail(email);
1738
+ if (role !== "member" && role !== "admin")
1739
+ return err("role must be 'member' or 'admin'");
1740
+ const data = await this._req("POST", `/api/teams/${encodeURIComponent(id)}/invites`, { email: safeEmail, role });
1741
+ return ok(JSON.stringify(data, null, 2));
1742
+ }
1743
+ catch (e) {
1744
+ return err(e);
1745
+ }
1746
+ }
1747
+ // -----------------------------------------------------------------------
1748
+ // Tool 52: teamListInvites
1749
+ // -----------------------------------------------------------------------
1750
+ async teamListInvites(teamId) {
1751
+ try {
1752
+ const id = validateUuid(teamId, "teamId");
1753
+ const data = await this._req("GET", `/api/teams/${encodeURIComponent(id)}/invites`);
1754
+ return ok(JSON.stringify(data, null, 2));
1755
+ }
1756
+ catch (e) {
1757
+ return err(e);
1758
+ }
1759
+ }
1760
+ // -----------------------------------------------------------------------
1761
+ // Tool 53: teamCancelInvite
1762
+ // -----------------------------------------------------------------------
1763
+ async teamCancelInvite(teamId, inviteId) {
1764
+ try {
1765
+ const tid = validateUuid(teamId, "teamId");
1766
+ const iid = validateUuid(inviteId, "inviteId");
1767
+ const data = await this._req("DELETE", `/api/teams/${encodeURIComponent(tid)}/invites/${encodeURIComponent(iid)}`);
1768
+ return ok(JSON.stringify(data, null, 2));
1769
+ }
1770
+ catch (e) {
1771
+ return err(e);
1772
+ }
1773
+ }
1774
+ // -----------------------------------------------------------------------
1775
+ // Tool 54 (MPP): mppInfo
1776
+ // -----------------------------------------------------------------------
1777
+ async mppInfo() {
1778
+ try {
1779
+ const data = await this._req("GET", "/api/mpp/info");
1780
+ return ok(JSON.stringify(data, null, 2));
1781
+ }
1782
+ catch (e) {
1783
+ return err(e);
1784
+ }
1785
+ }
1786
+ // -----------------------------------------------------------------------
1787
+ // Tool 55 (MPP): payMpp
1788
+ // -----------------------------------------------------------------------
1789
+ async payMpp(amountCents, method) {
1790
+ if (!Number.isInteger(amountCents) ||
1791
+ amountCents < 500 ||
1792
+ amountCents > 100_000) {
1793
+ return err("amount_cents must be an integer between 500 and 100,000");
1794
+ }
1795
+ if (!["tempo", "stripe_spt", "lightning"].includes(method)) {
1796
+ return err("method must be one of: tempo, stripe_spt, lightning");
1797
+ }
1798
+ try {
1799
+ const data = await this._req("POST", "/api/mpp/topup", {
1800
+ amountCents,
1801
+ method,
1802
+ });
1803
+ return ok(JSON.stringify(data, null, 2));
1804
+ }
1805
+ catch (e) {
1806
+ return err(e);
1807
+ }
1808
+ }
1809
+ // -----------------------------------------------------------------------
1810
+ // Tool 56 (MPP): mppSessionOpen
1811
+ // -----------------------------------------------------------------------
1812
+ async mppSessionOpen(maxDepositCents, method, poolType = "dc") {
1813
+ if (!Number.isInteger(maxDepositCents) ||
1814
+ maxDepositCents < 500 ||
1815
+ maxDepositCents > 100_000) {
1816
+ return err("max_deposit_cents must be an integer between 500 and 100,000");
1817
+ }
1818
+ if (!["tempo", "stripe_spt", "lightning"].includes(method)) {
1819
+ return err("method must be one of: tempo, stripe_spt, lightning");
1820
+ }
1821
+ if (!["dc", "residential"].includes(poolType)) {
1822
+ return err("pool_type must be dc or residential");
1823
+ }
1824
+ try {
1825
+ const data = await this._req("POST", "/api/mpp/session/open", {
1826
+ maxDepositCents,
1827
+ method,
1828
+ poolType,
1829
+ });
1830
+ return ok(JSON.stringify(data, null, 2));
1831
+ }
1832
+ catch (e) {
1833
+ return err(e);
1834
+ }
1835
+ }
1836
+ // -----------------------------------------------------------------------
1837
+ // Tool 57 (MPP): mppSessionClose
1838
+ // -----------------------------------------------------------------------
1839
+ async mppSessionClose(channelId) {
1840
+ if (!channelId)
1841
+ return err("channel_id is required");
1842
+ try {
1843
+ const data = await this._req("POST", "/api/mpp/session/close", {
1844
+ channelId,
1845
+ });
1116
1846
  return ok(JSON.stringify(data, null, 2));
1117
1847
  }
1118
1848
  catch (e) {