@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/CHANGELOG.md +1 -0
- package/README.md +63 -34
- package/dist/index.d.ts +1 -1
- package/dist/index.js +471 -32
- package/dist/toolkit.d.ts +38 -1
- package/dist/toolkit.js +784 -54
- package/package.json +3 -1
- package/skills/use-dominus-proxy.md +37 -31
package/dist/toolkit.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Dominus Node Pi Extension Toolkit
|
|
4
4
|
*
|
|
5
|
-
* Provides
|
|
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) +
|
|
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") ||
|
|
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 {
|
|
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 {
|
|
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 ||
|
|
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:" &&
|
|
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 ||
|
|
409
|
-
|
|
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") ||
|
|
413
|
-
rawProxyHost.endsWith(".
|
|
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 =
|
|
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 =
|
|
422
|
-
|
|
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_") &&
|
|
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 {
|
|
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 ||
|
|
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(() => {
|
|
493
|
-
|
|
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 = {
|
|
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 &&
|
|
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(":")
|
|
709
|
+
const connectHost = parsed.hostname.includes(":")
|
|
710
|
+
? `[${parsed.hostname}]`
|
|
711
|
+
: parsed.hostname;
|
|
621
712
|
const connectReq = http.request({
|
|
622
|
-
hostname: this.proxyHost,
|
|
713
|
+
hostname: this.proxyHost,
|
|
714
|
+
port: this.proxyPort,
|
|
715
|
+
method: "CONNECT",
|
|
623
716
|
path: `${connectHost}:${parsed.port || 443}`,
|
|
624
|
-
headers: {
|
|
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({
|
|
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
|
|
661
|
-
|
|
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) => {
|
|
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) => {
|
|
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,
|
|
792
|
+
hostname: this.proxyHost,
|
|
793
|
+
port: this.proxyPort,
|
|
794
|
+
method: methodUpper,
|
|
679
795
|
path: `http://${parsed.host}${reqPath}`,
|
|
680
|
-
headers: {
|
|
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", () => {
|
|
695
|
-
|
|
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", {
|
|
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) +
|
|
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", {
|
|
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", {
|
|
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 = [
|
|
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", {
|
|
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) {
|