@dominusnode/pi-extension 1.2.0 → 1.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/toolkit.js CHANGED
@@ -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
  });
@@ -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) {
@@ -950,7 +1086,8 @@ class DominusNodeToolkit {
950
1086
  if (labelErr)
951
1087
  return err(labelErr);
952
1088
  const data = await this._req("POST", `/api/teams/${encodeURIComponent(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.");
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);
@@ -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) {
@@ -1132,7 +1288,12 @@ class DominusNodeToolkit {
1132
1288
  headers["X-DominusNode-Agent"] = "mcp";
1133
1289
  headers["X-DominusNode-Agent-Secret"] = this.agentSecret;
1134
1290
  }
1135
- const init = { method, headers, signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS), redirect: "error" };
1291
+ const init = {
1292
+ method,
1293
+ headers,
1294
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
1295
+ redirect: "error",
1296
+ };
1136
1297
  if (body && ["POST", "PUT", "PATCH", "DELETE"].includes(method)) {
1137
1298
  headers["Content-Type"] = "application/json";
1138
1299
  init.body = JSON.stringify(body);
@@ -1309,7 +1470,15 @@ class DominusNodeToolkit {
1309
1470
  try {
1310
1471
  const safeEmail = DominusNodeToolkit.validateEmail(email);
1311
1472
  const safePassword = DominusNodeToolkit.validatePassword(password);
1312
- const data = await this._rawRequest("POST", "/api/auth/register", { email: safeEmail, password: safePassword });
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);
1313
1482
  return ok(JSON.stringify(data, null, 2));
1314
1483
  }
1315
1484
  catch (e) {
@@ -1323,7 +1492,10 @@ class DominusNodeToolkit {
1323
1492
  try {
1324
1493
  const safeEmail = DominusNodeToolkit.validateEmail(email);
1325
1494
  const safePassword = DominusNodeToolkit.validatePassword(password);
1326
- const data = await this._rawRequest("POST", "/api/auth/login", { email: safeEmail, password: safePassword });
1495
+ const data = await this._rawRequest("POST", "/api/auth/login", {
1496
+ email: safeEmail,
1497
+ password: safePassword,
1498
+ });
1327
1499
  return ok(JSON.stringify(data, null, 2));
1328
1500
  }
1329
1501
  catch (e) {
@@ -1353,7 +1525,9 @@ class DominusNodeToolkit {
1353
1525
  if (/[\x00-\x1f\x7f]/.test(token)) {
1354
1526
  return err("Token contains invalid control characters.");
1355
1527
  }
1356
- const data = await this._rawRequest("POST", "/api/auth/verify-email", { token: token.trim() });
1528
+ const data = await this._rawRequest("POST", "/api/auth/verify-email", {
1529
+ token: token.trim(),
1530
+ });
1357
1531
  return ok(JSON.stringify(data, null, 2));
1358
1532
  }
1359
1533
  catch (e) {
@@ -1379,7 +1553,10 @@ class DominusNodeToolkit {
1379
1553
  try {
1380
1554
  DominusNodeToolkit.validatePassword(currentPassword);
1381
1555
  DominusNodeToolkit.validatePassword(newPassword);
1382
- const data = await this._req("POST", "/api/auth/change-password", { currentPassword, newPassword });
1556
+ const data = await this._req("POST", "/api/auth/change-password", {
1557
+ currentPassword,
1558
+ newPassword,
1559
+ });
1383
1560
  return ok(JSON.stringify(data, null, 2));
1384
1561
  }
1385
1562
  catch (e) {
@@ -1406,8 +1583,11 @@ class DominusNodeToolkit {
1406
1583
  const labelErr = validateLabel(label);
1407
1584
  if (labelErr)
1408
1585
  return err(labelErr);
1409
- const data = await this._req("POST", "/api/keys", { label: label.trim() });
1410
- return ok(JSON.stringify(data, null, 2) + "\n\nIMPORTANT: Store this API key securely — it will not be shown again.");
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.");
1411
1591
  }
1412
1592
  catch (e) {
1413
1593
  return err(e);
@@ -1456,7 +1636,9 @@ class DominusNodeToolkit {
1456
1636
  async changePlan(planId) {
1457
1637
  try {
1458
1638
  const id = validateUuid(planId, "planId");
1459
- const data = await this._req("PUT", "/api/plans/user/plan", { planId: id });
1639
+ const data = await this._req("PUT", "/api/plans/user/plan", {
1640
+ planId: id,
1641
+ });
1460
1642
  return ok(JSON.stringify(data, null, 2));
1461
1643
  }
1462
1644
  catch (e) {
@@ -1589,5 +1771,83 @@ class DominusNodeToolkit {
1589
1771
  return err(e);
1590
1772
  }
1591
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
+ });
1846
+ return ok(JSON.stringify(data, null, 2));
1847
+ }
1848
+ catch (e) {
1849
+ return err(e);
1850
+ }
1851
+ }
1592
1852
  }
1593
1853
  exports.DominusNodeToolkit = DominusNodeToolkit;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dominusnode/pi-extension",
3
- "version": "1.2.0",
3
+ "version": "1.5.2",
4
4
  "description": "Dominus Node pi-mono extension — 26 tools for rotating proxy, wallet, agentic wallets, teams, and payments",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -29,11 +29,13 @@
29
29
  },
30
30
  "scripts": {
31
31
  "build": "tsc",
32
+ "prepare": "npm run build",
32
33
  "test": "vitest run",
33
34
  "lint": "tsc --noEmit"
34
35
  },
35
36
  "devDependencies": {
36
37
  "@sinclair/typebox": "^0.34.0",
38
+ "@types/node": "^22.0.0",
37
39
  "typescript": "^5.4.0",
38
40
  "vitest": "^1.6.0"
39
41
  },