@dominusnode/mastra-tools 1.0.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/LICENSE +21 -0
- package/README.md +83 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +34 -0
- package/dist/toolkit.d.ts +244 -0
- package/dist/toolkit.js +1370 -0
- package/package.json +26 -0
package/dist/toolkit.js
ADDED
|
@@ -0,0 +1,1370 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* DomiNode Mastra AI Toolkit
|
|
4
|
+
*
|
|
5
|
+
* Implements 24 tools for interacting with DomiNode's rotating proxy service
|
|
6
|
+
* from Mastra AI agents and workflows.
|
|
7
|
+
*
|
|
8
|
+
* Uses createTool() from @mastra/core/tools with Zod schemas.
|
|
9
|
+
*
|
|
10
|
+
* Security:
|
|
11
|
+
* - Full SSRF protection (private IP blocking, DNS rebinding, .localhost, embedded creds)
|
|
12
|
+
* - Credential scrubbing in error messages
|
|
13
|
+
* - Response truncation at 4000 chars for LLM context efficiency
|
|
14
|
+
* - OFAC sanctioned country validation
|
|
15
|
+
* - Prototype pollution prevention (recursive)
|
|
16
|
+
* - HTTP method restriction (GET/HEAD/OPTIONS only for proxied fetch)
|
|
17
|
+
* - 10 MB response cap, 30s timeout
|
|
18
|
+
* - Redirect following disabled
|
|
19
|
+
*/
|
|
20
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
21
|
+
if (k2 === undefined) k2 = k;
|
|
22
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
23
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
24
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
25
|
+
}
|
|
26
|
+
Object.defineProperty(o, k2, desc);
|
|
27
|
+
}) : (function(o, m, k, k2) {
|
|
28
|
+
if (k2 === undefined) k2 = k;
|
|
29
|
+
o[k2] = m[k];
|
|
30
|
+
}));
|
|
31
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
32
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
33
|
+
}) : function(o, v) {
|
|
34
|
+
o["default"] = v;
|
|
35
|
+
});
|
|
36
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
37
|
+
var ownKeys = function(o) {
|
|
38
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
39
|
+
var ar = [];
|
|
40
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
41
|
+
return ar;
|
|
42
|
+
};
|
|
43
|
+
return ownKeys(o);
|
|
44
|
+
};
|
|
45
|
+
return function (mod) {
|
|
46
|
+
if (mod && mod.__esModule) return mod;
|
|
47
|
+
var result = {};
|
|
48
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
49
|
+
__setModuleDefault(result, mod);
|
|
50
|
+
return result;
|
|
51
|
+
};
|
|
52
|
+
})();
|
|
53
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
54
|
+
exports.DominusNodeToolkit = void 0;
|
|
55
|
+
exports.scrubCredentials = scrubCredentials;
|
|
56
|
+
exports.truncate = truncate;
|
|
57
|
+
exports.normalizeIpv4 = normalizeIpv4;
|
|
58
|
+
exports.isPrivateIp = isPrivateIp;
|
|
59
|
+
exports.validateTargetUrl = validateTargetUrl;
|
|
60
|
+
exports.validateCountry = validateCountry;
|
|
61
|
+
exports.validateUuid = validateUuid;
|
|
62
|
+
exports.stripDangerousKeys = stripDangerousKeys;
|
|
63
|
+
exports.formatBytes = formatBytes;
|
|
64
|
+
exports.formatCents = formatCents;
|
|
65
|
+
// @ts-ignore -- available when installed in Mastra project
|
|
66
|
+
const tools_1 = require("@mastra/core/tools");
|
|
67
|
+
const zod_1 = require("zod");
|
|
68
|
+
const http = __importStar(require("node:http"));
|
|
69
|
+
const tls = __importStar(require("node:tls"));
|
|
70
|
+
const dns = __importStar(require("dns/promises"));
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
// Configuration
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
const MAX_RESPONSE_CHARS = 4000;
|
|
75
|
+
const REQUEST_TIMEOUT_MS = 30_000;
|
|
76
|
+
const MAX_RESPONSE_BYTES = 10 * 1024 * 1024; // 10 MB hard cap
|
|
77
|
+
/** OFAC sanctioned countries -- must never be used as geo-targeting destinations. */
|
|
78
|
+
const SANCTIONED_COUNTRIES = new Set(["CU", "IR", "KP", "RU", "SY"]);
|
|
79
|
+
/** ISO 3166-1 alpha-2 country code pattern. */
|
|
80
|
+
const COUNTRY_CODE_RE = /^[A-Z]{2}$/;
|
|
81
|
+
/** UUID v4 pattern for wallet/team IDs. */
|
|
82
|
+
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
83
|
+
// ---------------------------------------------------------------------------
|
|
84
|
+
// Credential scrubbing
|
|
85
|
+
// ---------------------------------------------------------------------------
|
|
86
|
+
/** Remove any dn_live_* or dn_test_* tokens from error messages. */
|
|
87
|
+
function scrubCredentials(msg) {
|
|
88
|
+
return msg.replace(/dn_(live|test)_[A-Za-z0-9_-]+/g, "dn_$1_***REDACTED***");
|
|
89
|
+
}
|
|
90
|
+
function safeError(err) {
|
|
91
|
+
const raw = err instanceof Error ? err.message : String(err);
|
|
92
|
+
return scrubCredentials(raw);
|
|
93
|
+
}
|
|
94
|
+
// ---------------------------------------------------------------------------
|
|
95
|
+
// Response truncation
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
function truncate(text, max = MAX_RESPONSE_CHARS) {
|
|
98
|
+
if (text.length <= max)
|
|
99
|
+
return text;
|
|
100
|
+
return text.slice(0, max) + `\n\n... [truncated, ${text.length - max} chars omitted]`;
|
|
101
|
+
}
|
|
102
|
+
// ---------------------------------------------------------------------------
|
|
103
|
+
// SSRF Protection
|
|
104
|
+
// ---------------------------------------------------------------------------
|
|
105
|
+
const BLOCKED_HOSTNAMES = new Set([
|
|
106
|
+
"localhost",
|
|
107
|
+
"localhost.localdomain",
|
|
108
|
+
"ip6-localhost",
|
|
109
|
+
"ip6-loopback",
|
|
110
|
+
"[::1]",
|
|
111
|
+
"[::ffff:127.0.0.1]",
|
|
112
|
+
"0.0.0.0",
|
|
113
|
+
"[::]",
|
|
114
|
+
]);
|
|
115
|
+
/**
|
|
116
|
+
* Normalize non-standard IPv4 representations to dotted-decimal.
|
|
117
|
+
* Handles: decimal integers (2130706433), hex (0x7f000001), octal octets (0177.0.0.1).
|
|
118
|
+
*/
|
|
119
|
+
function normalizeIpv4(hostname) {
|
|
120
|
+
// Single decimal integer
|
|
121
|
+
if (/^\d+$/.test(hostname)) {
|
|
122
|
+
const n = parseInt(hostname, 10);
|
|
123
|
+
if (n >= 0 && n <= 0xffffffff) {
|
|
124
|
+
return `${(n >>> 24) & 0xff}.${(n >>> 16) & 0xff}.${(n >>> 8) & 0xff}.${n & 0xff}`;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
// Hex notation
|
|
128
|
+
if (/^0x[0-9a-fA-F]+$/i.test(hostname)) {
|
|
129
|
+
const n = parseInt(hostname, 16);
|
|
130
|
+
if (n >= 0 && n <= 0xffffffff) {
|
|
131
|
+
return `${(n >>> 24) & 0xff}.${(n >>> 16) & 0xff}.${(n >>> 8) & 0xff}.${n & 0xff}`;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
// Octal / mixed-radix octets
|
|
135
|
+
const parts = hostname.split(".");
|
|
136
|
+
if (parts.length === 4) {
|
|
137
|
+
const octets = [];
|
|
138
|
+
for (const part of parts) {
|
|
139
|
+
let val;
|
|
140
|
+
if (/^0x[0-9a-fA-F]+$/i.test(part)) {
|
|
141
|
+
val = parseInt(part, 16);
|
|
142
|
+
}
|
|
143
|
+
else if (/^0\d+$/.test(part)) {
|
|
144
|
+
val = parseInt(part, 8);
|
|
145
|
+
}
|
|
146
|
+
else if (/^\d+$/.test(part)) {
|
|
147
|
+
val = parseInt(part, 10);
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
if (isNaN(val) || val < 0 || val > 255)
|
|
153
|
+
return null;
|
|
154
|
+
octets.push(val);
|
|
155
|
+
}
|
|
156
|
+
return octets.join(".");
|
|
157
|
+
}
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
function isPrivateIp(hostname) {
|
|
161
|
+
let ip = hostname.replace(/^\[|\]$/g, "");
|
|
162
|
+
// Strip IPv6 zone ID
|
|
163
|
+
const zoneIdx = ip.indexOf("%");
|
|
164
|
+
if (zoneIdx !== -1) {
|
|
165
|
+
ip = ip.substring(0, zoneIdx);
|
|
166
|
+
}
|
|
167
|
+
const normalized = normalizeIpv4(ip);
|
|
168
|
+
const checkIp = normalized ?? ip;
|
|
169
|
+
// IPv4 private ranges
|
|
170
|
+
const ipv4Match = checkIp.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/);
|
|
171
|
+
if (ipv4Match) {
|
|
172
|
+
const a = Number(ipv4Match[1]);
|
|
173
|
+
const b = Number(ipv4Match[2]);
|
|
174
|
+
if (a === 0)
|
|
175
|
+
return true;
|
|
176
|
+
if (a === 10)
|
|
177
|
+
return true;
|
|
178
|
+
if (a === 127)
|
|
179
|
+
return true;
|
|
180
|
+
if (a === 169 && b === 254)
|
|
181
|
+
return true;
|
|
182
|
+
if (a === 172 && b >= 16 && b <= 31)
|
|
183
|
+
return true;
|
|
184
|
+
if (a === 192 && b === 168)
|
|
185
|
+
return true;
|
|
186
|
+
if (a === 100 && b >= 64 && b <= 127)
|
|
187
|
+
return true;
|
|
188
|
+
if (a >= 224)
|
|
189
|
+
return true;
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
// IPv6 private ranges
|
|
193
|
+
const ipLower = ip.toLowerCase();
|
|
194
|
+
if (ipLower === "::1")
|
|
195
|
+
return true;
|
|
196
|
+
if (ipLower === "::")
|
|
197
|
+
return true;
|
|
198
|
+
if (ipLower.startsWith("fc") || ipLower.startsWith("fd"))
|
|
199
|
+
return true;
|
|
200
|
+
if (ipLower.startsWith("fe80"))
|
|
201
|
+
return true;
|
|
202
|
+
if (ipLower.startsWith("::ffff:")) {
|
|
203
|
+
const embedded = ipLower.slice(7);
|
|
204
|
+
if (embedded.includes(".")) {
|
|
205
|
+
return isPrivateIp(embedded);
|
|
206
|
+
}
|
|
207
|
+
const hexParts = embedded.split(":");
|
|
208
|
+
if (hexParts.length === 2) {
|
|
209
|
+
const hi = parseInt(hexParts[0], 16);
|
|
210
|
+
const lo = parseInt(hexParts[1], 16);
|
|
211
|
+
if (!isNaN(hi) && !isNaN(lo)) {
|
|
212
|
+
const reconstructed = `${(hi >> 8) & 0xff}.${hi & 0xff}.${(lo >> 8) & 0xff}.${lo & 0xff}`;
|
|
213
|
+
return isPrivateIp(reconstructed);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return isPrivateIp(embedded);
|
|
217
|
+
}
|
|
218
|
+
// IPv4-compatible IPv6 (::x.x.x.x) -- deprecated but still parsed
|
|
219
|
+
if (ipLower.startsWith("::") && !ipLower.startsWith("::ffff:")) {
|
|
220
|
+
const rest = ipLower.slice(2);
|
|
221
|
+
if (rest && rest.includes("."))
|
|
222
|
+
return isPrivateIp(rest);
|
|
223
|
+
const hexParts = rest.split(":");
|
|
224
|
+
if (hexParts.length === 2 && hexParts[0] && hexParts[1]) {
|
|
225
|
+
const hi = parseInt(hexParts[0], 16);
|
|
226
|
+
const lo = parseInt(hexParts[1], 16);
|
|
227
|
+
if (!isNaN(hi) && !isNaN(lo)) {
|
|
228
|
+
const reconstructed = `${(hi >> 8) & 0xff}.${hi & 0xff}.${(lo >> 8) & 0xff}.${lo & 0xff}`;
|
|
229
|
+
return isPrivateIp(reconstructed);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
// Teredo (2001:0000::/32) -- block unconditionally
|
|
234
|
+
if (ipLower.startsWith("2001:0000:") || ipLower.startsWith("2001:0:"))
|
|
235
|
+
return true;
|
|
236
|
+
// 6to4 (2002::/16) -- block unconditionally
|
|
237
|
+
if (ipLower.startsWith("2002:"))
|
|
238
|
+
return true;
|
|
239
|
+
// IPv6 multicast (ff00::/8)
|
|
240
|
+
if (ipLower.startsWith("ff"))
|
|
241
|
+
return true;
|
|
242
|
+
return false;
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Validate a URL for safety before proxying.
|
|
246
|
+
* Blocks: private IPs, localhost, .local/.internal/.arpa, embedded credentials, non-http(s).
|
|
247
|
+
*/
|
|
248
|
+
function validateTargetUrl(url) {
|
|
249
|
+
let parsed;
|
|
250
|
+
try {
|
|
251
|
+
parsed = new URL(url);
|
|
252
|
+
}
|
|
253
|
+
catch {
|
|
254
|
+
throw new Error(`Invalid URL: ${scrubCredentials(url)}`);
|
|
255
|
+
}
|
|
256
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
257
|
+
throw new Error(`Only http: and https: protocols are supported, got ${parsed.protocol}`);
|
|
258
|
+
}
|
|
259
|
+
// Block embedded credentials (user:pass@host)
|
|
260
|
+
if (parsed.username || parsed.password) {
|
|
261
|
+
throw new Error("URLs with embedded credentials are not allowed");
|
|
262
|
+
}
|
|
263
|
+
const hostname = parsed.hostname.toLowerCase();
|
|
264
|
+
if (BLOCKED_HOSTNAMES.has(hostname)) {
|
|
265
|
+
throw new Error("Requests to localhost/loopback addresses are blocked");
|
|
266
|
+
}
|
|
267
|
+
if (isPrivateIp(hostname)) {
|
|
268
|
+
throw new Error("Requests to private/internal IP addresses are blocked");
|
|
269
|
+
}
|
|
270
|
+
// Block .localhost TLD (RFC 6761)
|
|
271
|
+
if (hostname.endsWith(".localhost")) {
|
|
272
|
+
throw new Error("Requests to localhost/loopback addresses are blocked");
|
|
273
|
+
}
|
|
274
|
+
// Block internal network TLDs
|
|
275
|
+
if (hostname.endsWith(".local") ||
|
|
276
|
+
hostname.endsWith(".internal") ||
|
|
277
|
+
hostname.endsWith(".arpa")) {
|
|
278
|
+
throw new Error("Requests to internal network hostnames are blocked");
|
|
279
|
+
}
|
|
280
|
+
return parsed;
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Validate a country code: must be 2 uppercase letters and not OFAC sanctioned.
|
|
284
|
+
*/
|
|
285
|
+
function validateCountry(country) {
|
|
286
|
+
if (!country)
|
|
287
|
+
return undefined;
|
|
288
|
+
const upper = country.toUpperCase().trim();
|
|
289
|
+
if (!upper)
|
|
290
|
+
return undefined;
|
|
291
|
+
if (!COUNTRY_CODE_RE.test(upper)) {
|
|
292
|
+
throw new Error(`Invalid country code: "${country}". Must be a 2-letter ISO 3166-1 code.`);
|
|
293
|
+
}
|
|
294
|
+
if (SANCTIONED_COUNTRIES.has(upper)) {
|
|
295
|
+
throw new Error(`Country "${upper}" is OFAC sanctioned and cannot be used as a proxy target.`);
|
|
296
|
+
}
|
|
297
|
+
return upper;
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Validate a UUID string.
|
|
301
|
+
*/
|
|
302
|
+
function validateUuid(id, label) {
|
|
303
|
+
const trimmed = id.trim();
|
|
304
|
+
if (!UUID_RE.test(trimmed)) {
|
|
305
|
+
throw new Error(`Invalid ${label}: must be a valid UUID.`);
|
|
306
|
+
}
|
|
307
|
+
return trimmed;
|
|
308
|
+
}
|
|
309
|
+
// ---------------------------------------------------------------------------
|
|
310
|
+
// DNS rebinding protection
|
|
311
|
+
// ---------------------------------------------------------------------------
|
|
312
|
+
/**
|
|
313
|
+
* Resolve a hostname via DNS and verify none of the resolved IPs are private.
|
|
314
|
+
*/
|
|
315
|
+
async function checkDnsRebinding(hostname) {
|
|
316
|
+
const stripped = hostname.replace(/^\[|\]$/g, "");
|
|
317
|
+
if (/^(\d{1,3}\.){3}\d{1,3}$/.test(stripped) || stripped.includes(":")) {
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
const resolvedIps = [];
|
|
321
|
+
try {
|
|
322
|
+
const ipv4s = await dns.resolve4(hostname);
|
|
323
|
+
resolvedIps.push(...ipv4s);
|
|
324
|
+
}
|
|
325
|
+
catch {
|
|
326
|
+
// NODATA or NXDOMAIN for A records is fine
|
|
327
|
+
}
|
|
328
|
+
try {
|
|
329
|
+
const ipv6s = await dns.resolve6(hostname);
|
|
330
|
+
resolvedIps.push(...ipv6s);
|
|
331
|
+
}
|
|
332
|
+
catch {
|
|
333
|
+
// NODATA or NXDOMAIN for AAAA records is fine
|
|
334
|
+
}
|
|
335
|
+
for (const ip of resolvedIps) {
|
|
336
|
+
if (isPrivateIp(ip)) {
|
|
337
|
+
throw new Error(`DNS rebinding detected: hostname resolves to private IP ${ip}`);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
// ---------------------------------------------------------------------------
|
|
342
|
+
// Prototype pollution prevention (recursive)
|
|
343
|
+
// ---------------------------------------------------------------------------
|
|
344
|
+
const DANGEROUS_KEYS = new Set(["__proto__", "constructor", "prototype"]);
|
|
345
|
+
function stripDangerousKeys(obj, depth = 0) {
|
|
346
|
+
if (depth > 50 || !obj || typeof obj !== "object")
|
|
347
|
+
return;
|
|
348
|
+
if (Array.isArray(obj)) {
|
|
349
|
+
for (const item of obj)
|
|
350
|
+
stripDangerousKeys(item, depth + 1);
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
const record = obj;
|
|
354
|
+
for (const key of Object.keys(record)) {
|
|
355
|
+
if (DANGEROUS_KEYS.has(key)) {
|
|
356
|
+
delete record[key];
|
|
357
|
+
}
|
|
358
|
+
else if (record[key] && typeof record[key] === "object") {
|
|
359
|
+
stripDangerousKeys(record[key], depth + 1);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
// ---------------------------------------------------------------------------
|
|
364
|
+
// Formatting helpers
|
|
365
|
+
// ---------------------------------------------------------------------------
|
|
366
|
+
function formatBytes(bytes) {
|
|
367
|
+
if (bytes < 1024)
|
|
368
|
+
return `${bytes} B`;
|
|
369
|
+
if (bytes < 1024 * 1024)
|
|
370
|
+
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
371
|
+
if (bytes < 1024 * 1024 * 1024)
|
|
372
|
+
return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
|
|
373
|
+
return `${(bytes / (1024 * 1024 * 1024)).toFixed(3)} GB`;
|
|
374
|
+
}
|
|
375
|
+
function formatCents(cents) {
|
|
376
|
+
return `$${(cents / 100).toFixed(2)}`;
|
|
377
|
+
}
|
|
378
|
+
// ---------------------------------------------------------------------------
|
|
379
|
+
// DominusNodeToolkit
|
|
380
|
+
// ---------------------------------------------------------------------------
|
|
381
|
+
class DominusNodeToolkit {
|
|
382
|
+
apiKey;
|
|
383
|
+
baseUrl;
|
|
384
|
+
proxyHost;
|
|
385
|
+
proxyPort;
|
|
386
|
+
cachedJwt = null;
|
|
387
|
+
jwtExpiresAt = 0;
|
|
388
|
+
constructor(config = {}) {
|
|
389
|
+
this.apiKey = config.apiKey || process.env.DOMINUSNODE_API_KEY || "";
|
|
390
|
+
this.baseUrl = (config.baseUrl || process.env.DOMINUSNODE_BASE_URL || "https://api.dominusnode.com").replace(/\/+$/, "");
|
|
391
|
+
this.proxyHost = config.proxyHost || process.env.DOMINUSNODE_PROXY_HOST || "proxy.dominusnode.com";
|
|
392
|
+
const portStr = config.proxyPort?.toString() || process.env.DOMINUSNODE_PROXY_PORT || "8080";
|
|
393
|
+
const port = parseInt(portStr, 10);
|
|
394
|
+
this.proxyPort = isNaN(port) || port < 1 || port > 65535 ? 8080 : port;
|
|
395
|
+
}
|
|
396
|
+
// -----------------------------------------------------------------------
|
|
397
|
+
// Authentication
|
|
398
|
+
// -----------------------------------------------------------------------
|
|
399
|
+
getApiKey() {
|
|
400
|
+
if (!this.apiKey || typeof this.apiKey !== "string" || this.apiKey.trim().length === 0) {
|
|
401
|
+
throw new Error("DOMINUSNODE_API_KEY is required. " +
|
|
402
|
+
'Set it to your DomiNode API key (starts with "dn_live_" or "dn_test_").');
|
|
403
|
+
}
|
|
404
|
+
const trimmed = this.apiKey.trim();
|
|
405
|
+
if (!trimmed.startsWith("dn_live_") && !trimmed.startsWith("dn_test_")) {
|
|
406
|
+
throw new Error('DOMINUSNODE_API_KEY must start with "dn_live_" or "dn_test_".');
|
|
407
|
+
}
|
|
408
|
+
return trimmed;
|
|
409
|
+
}
|
|
410
|
+
async ensureAuth() {
|
|
411
|
+
if (this.cachedJwt && Date.now() < this.jwtExpiresAt)
|
|
412
|
+
return this.cachedJwt;
|
|
413
|
+
const apiKey = this.getApiKey();
|
|
414
|
+
const res = await fetch(`${this.baseUrl}/api/auth/verify-key`, {
|
|
415
|
+
method: "POST",
|
|
416
|
+
headers: {
|
|
417
|
+
"Content-Type": "application/json",
|
|
418
|
+
"User-Agent": "dominusnode-mastra/1.0.0",
|
|
419
|
+
},
|
|
420
|
+
body: JSON.stringify({ apiKey }),
|
|
421
|
+
redirect: "error",
|
|
422
|
+
});
|
|
423
|
+
if (!res.ok) {
|
|
424
|
+
const text = await res.text().catch(() => "");
|
|
425
|
+
throw new Error(`Auth failed (${res.status}): ${scrubCredentials(text.slice(0, 500))}`);
|
|
426
|
+
}
|
|
427
|
+
const data = (await res.json());
|
|
428
|
+
this.cachedJwt = data.token;
|
|
429
|
+
// JWT expires in 15 min, refresh at 14 min for safety
|
|
430
|
+
this.jwtExpiresAt = Date.now() + 14 * 60 * 1000;
|
|
431
|
+
return this.cachedJwt;
|
|
432
|
+
}
|
|
433
|
+
// -----------------------------------------------------------------------
|
|
434
|
+
// HTTP client for DomiNode API
|
|
435
|
+
// -----------------------------------------------------------------------
|
|
436
|
+
async apiRequest(method, path, body) {
|
|
437
|
+
const jwt = await this.ensureAuth();
|
|
438
|
+
const url = `${this.baseUrl}${path}`;
|
|
439
|
+
const headers = {
|
|
440
|
+
Authorization: `Bearer ${jwt}`,
|
|
441
|
+
Accept: "application/json",
|
|
442
|
+
"User-Agent": "dominusnode-mastra/1.0.0",
|
|
443
|
+
};
|
|
444
|
+
const init = {
|
|
445
|
+
method,
|
|
446
|
+
headers,
|
|
447
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
448
|
+
redirect: "error",
|
|
449
|
+
};
|
|
450
|
+
if (body && (method === "POST" || method === "PUT" || method === "PATCH")) {
|
|
451
|
+
headers["Content-Type"] = "application/json";
|
|
452
|
+
init.body = JSON.stringify(body);
|
|
453
|
+
}
|
|
454
|
+
let res;
|
|
455
|
+
try {
|
|
456
|
+
res = await fetch(url, init);
|
|
457
|
+
}
|
|
458
|
+
catch (err) {
|
|
459
|
+
throw new Error(`API request failed: ${safeError(err)}`);
|
|
460
|
+
}
|
|
461
|
+
let rawText;
|
|
462
|
+
try {
|
|
463
|
+
rawText = await res.text();
|
|
464
|
+
}
|
|
465
|
+
catch {
|
|
466
|
+
rawText = "";
|
|
467
|
+
}
|
|
468
|
+
if (new TextEncoder().encode(rawText).length > MAX_RESPONSE_BYTES) {
|
|
469
|
+
throw new Error("API response too large");
|
|
470
|
+
}
|
|
471
|
+
if (!res.ok) {
|
|
472
|
+
let errorMsg = `API error ${res.status}`;
|
|
473
|
+
if (rawText) {
|
|
474
|
+
try {
|
|
475
|
+
const parsed = JSON.parse(rawText);
|
|
476
|
+
stripDangerousKeys(parsed);
|
|
477
|
+
if (parsed.error) {
|
|
478
|
+
errorMsg = `API error ${res.status}: ${scrubCredentials(String(parsed.error))}`;
|
|
479
|
+
}
|
|
480
|
+
else if (parsed.message) {
|
|
481
|
+
errorMsg = `API error ${res.status}: ${scrubCredentials(String(parsed.message))}`;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
catch {
|
|
485
|
+
errorMsg = `API error ${res.status}: ${scrubCredentials(rawText.slice(0, 200))}`;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
throw new Error(errorMsg);
|
|
489
|
+
}
|
|
490
|
+
if (!rawText || rawText.trim().length === 0) {
|
|
491
|
+
return {};
|
|
492
|
+
}
|
|
493
|
+
try {
|
|
494
|
+
const parsed = JSON.parse(rawText);
|
|
495
|
+
stripDangerousKeys(parsed);
|
|
496
|
+
return parsed;
|
|
497
|
+
}
|
|
498
|
+
catch {
|
|
499
|
+
throw new Error("Failed to parse API response as JSON");
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
async apiGet(path) {
|
|
503
|
+
return this.apiRequest("GET", path);
|
|
504
|
+
}
|
|
505
|
+
async apiPost(path, body) {
|
|
506
|
+
return this.apiRequest("POST", path, body);
|
|
507
|
+
}
|
|
508
|
+
async apiDelete(path) {
|
|
509
|
+
return this.apiRequest("DELETE", path);
|
|
510
|
+
}
|
|
511
|
+
async apiPatch(path, body) {
|
|
512
|
+
return this.apiRequest("PATCH", path, body);
|
|
513
|
+
}
|
|
514
|
+
// -----------------------------------------------------------------------
|
|
515
|
+
// Tool implementations
|
|
516
|
+
// -----------------------------------------------------------------------
|
|
517
|
+
async proxiedFetch(url, method = "GET", country, proxyType = "dc") {
|
|
518
|
+
const parsedUrl = validateTargetUrl(url);
|
|
519
|
+
await checkDnsRebinding(parsedUrl.hostname);
|
|
520
|
+
const validCountry = validateCountry(country);
|
|
521
|
+
const ALLOWED_METHODS = new Set(["GET", "HEAD", "OPTIONS"]);
|
|
522
|
+
const methodUpper = method.toUpperCase();
|
|
523
|
+
if (!ALLOWED_METHODS.has(methodUpper)) {
|
|
524
|
+
throw new Error(`HTTP method "${methodUpper}" is not allowed. Only GET, HEAD, and OPTIONS are permitted.`);
|
|
525
|
+
}
|
|
526
|
+
const apiKey = this.getApiKey();
|
|
527
|
+
const parts = [];
|
|
528
|
+
if (proxyType && proxyType !== "auto")
|
|
529
|
+
parts.push(proxyType);
|
|
530
|
+
if (validCountry)
|
|
531
|
+
parts.push(`country-${validCountry.toUpperCase()}`);
|
|
532
|
+
const username = parts.length > 0 ? parts.join("-") : "auto";
|
|
533
|
+
const proxyAuth = "Basic " + Buffer.from(`${username}:${apiKey}`).toString("base64");
|
|
534
|
+
const parsed = new URL(url);
|
|
535
|
+
const MAX_RESP = 1_048_576; // 1MB
|
|
536
|
+
const result = await new Promise((resolve, reject) => {
|
|
537
|
+
const timer = setTimeout(() => reject(new Error("Proxy request timed out")), REQUEST_TIMEOUT_MS);
|
|
538
|
+
if (parsed.protocol === "https:") {
|
|
539
|
+
const connectHost = parsed.hostname.includes(":") ? `[${parsed.hostname}]` : parsed.hostname;
|
|
540
|
+
const connectReq = http.request({
|
|
541
|
+
hostname: this.proxyHost,
|
|
542
|
+
port: this.proxyPort,
|
|
543
|
+
method: "CONNECT",
|
|
544
|
+
path: `${connectHost}:${parsed.port || 443}`,
|
|
545
|
+
headers: { "Proxy-Authorization": proxyAuth, Host: `${connectHost}:${parsed.port || 443}` },
|
|
546
|
+
});
|
|
547
|
+
connectReq.on("connect", (_res, sock) => {
|
|
548
|
+
if (_res.statusCode !== 200) {
|
|
549
|
+
clearTimeout(timer);
|
|
550
|
+
sock.destroy();
|
|
551
|
+
reject(new Error(`CONNECT failed: ${_res.statusCode}`));
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
const tlsSock = tls.connect({ host: parsed.hostname, socket: sock, servername: parsed.hostname, minVersion: "TLSv1.2" }, () => {
|
|
555
|
+
const reqLine = `${methodUpper} ${parsed.pathname + parsed.search} HTTP/1.1\r\nHost: ${parsed.host}\r\nUser-Agent: dominusnode-mastra/1.0.0\r\nConnection: close\r\n\r\n`;
|
|
556
|
+
tlsSock.write(reqLine);
|
|
557
|
+
const chunks = [];
|
|
558
|
+
let bytes = 0;
|
|
559
|
+
tlsSock.on("data", (c) => {
|
|
560
|
+
bytes += c.length;
|
|
561
|
+
if (bytes <= MAX_RESP + 16384)
|
|
562
|
+
chunks.push(c);
|
|
563
|
+
});
|
|
564
|
+
let done = false;
|
|
565
|
+
const fin = () => {
|
|
566
|
+
if (done)
|
|
567
|
+
return;
|
|
568
|
+
done = true;
|
|
569
|
+
clearTimeout(timer);
|
|
570
|
+
const raw = Buffer.concat(chunks).toString("utf-8");
|
|
571
|
+
const hEnd = raw.indexOf("\r\n\r\n");
|
|
572
|
+
if (hEnd === -1) {
|
|
573
|
+
reject(new Error("Malformed response"));
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
576
|
+
const hdr = raw.substring(0, hEnd);
|
|
577
|
+
const body = raw.substring(hEnd + 4).substring(0, MAX_RESP);
|
|
578
|
+
const sm = hdr.split("\r\n")[0].match(/^HTTP\/\d\.\d\s+(\d+)/);
|
|
579
|
+
const hdrs = {};
|
|
580
|
+
for (const l of hdr.split("\r\n").slice(1)) {
|
|
581
|
+
const ci = l.indexOf(":");
|
|
582
|
+
if (ci > 0)
|
|
583
|
+
hdrs[l.substring(0, ci).trim().toLowerCase()] = l.substring(ci + 1).trim();
|
|
584
|
+
}
|
|
585
|
+
resolve({ status: sm ? parseInt(sm[1], 10) : 0, headers: hdrs, body });
|
|
586
|
+
};
|
|
587
|
+
tlsSock.on("end", fin);
|
|
588
|
+
tlsSock.on("close", fin);
|
|
589
|
+
tlsSock.on("error", (e) => { clearTimeout(timer); reject(e); });
|
|
590
|
+
});
|
|
591
|
+
tlsSock.on("error", (e) => { clearTimeout(timer); reject(e); });
|
|
592
|
+
});
|
|
593
|
+
connectReq.on("error", (e) => { clearTimeout(timer); reject(e); });
|
|
594
|
+
connectReq.end();
|
|
595
|
+
}
|
|
596
|
+
else {
|
|
597
|
+
const req = http.request({
|
|
598
|
+
hostname: this.proxyHost,
|
|
599
|
+
port: this.proxyPort,
|
|
600
|
+
method: methodUpper,
|
|
601
|
+
path: url,
|
|
602
|
+
headers: { "Proxy-Authorization": proxyAuth, Host: parsed.host ?? "" },
|
|
603
|
+
}, (res) => {
|
|
604
|
+
const chunks = [];
|
|
605
|
+
let bytes = 0;
|
|
606
|
+
res.on("data", (c) => {
|
|
607
|
+
bytes += c.length;
|
|
608
|
+
if (bytes <= MAX_RESP)
|
|
609
|
+
chunks.push(c);
|
|
610
|
+
});
|
|
611
|
+
let done = false;
|
|
612
|
+
const fin = () => {
|
|
613
|
+
if (done)
|
|
614
|
+
return;
|
|
615
|
+
done = true;
|
|
616
|
+
clearTimeout(timer);
|
|
617
|
+
const body = Buffer.concat(chunks).toString("utf-8").substring(0, MAX_RESP);
|
|
618
|
+
const hdrs = {};
|
|
619
|
+
for (const [k, v] of Object.entries(res.headers)) {
|
|
620
|
+
if (v)
|
|
621
|
+
hdrs[k] = Array.isArray(v) ? v.join(", ") : v;
|
|
622
|
+
}
|
|
623
|
+
resolve({ status: res.statusCode ?? 0, headers: hdrs, body });
|
|
624
|
+
};
|
|
625
|
+
res.on("end", fin);
|
|
626
|
+
res.on("close", fin);
|
|
627
|
+
res.on("error", (e) => { clearTimeout(timer); reject(e); });
|
|
628
|
+
});
|
|
629
|
+
req.on("error", (e) => { clearTimeout(timer); reject(e); });
|
|
630
|
+
req.end();
|
|
631
|
+
}
|
|
632
|
+
});
|
|
633
|
+
return {
|
|
634
|
+
status: result.status,
|
|
635
|
+
headers: result.headers,
|
|
636
|
+
body: truncate(scrubCredentials(result.body)),
|
|
637
|
+
pool: proxyType,
|
|
638
|
+
country: validCountry ?? "auto",
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
async checkBalance() {
|
|
642
|
+
const data = await this.apiGet("/api/wallet");
|
|
643
|
+
const balanceCents = data.balanceCents ?? 0;
|
|
644
|
+
const balanceUsd = data.balanceUsd ?? balanceCents / 100;
|
|
645
|
+
return {
|
|
646
|
+
balanceCents,
|
|
647
|
+
balanceUsd,
|
|
648
|
+
dcGbRemaining: (balanceCents / 300).toFixed(2),
|
|
649
|
+
resGbRemaining: (balanceCents / 500).toFixed(2),
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
async checkUsage(days = 30) {
|
|
653
|
+
const safeDays = Math.min(Math.max(days, 1), 365);
|
|
654
|
+
const since = new Date(Date.now() - safeDays * 24 * 60 * 60 * 1000).toISOString();
|
|
655
|
+
const until = new Date().toISOString();
|
|
656
|
+
const data = await this.apiGet(`/api/usage?since=${encodeURIComponent(since)}&until=${encodeURIComponent(until)}`);
|
|
657
|
+
return { ...data.summary, period: `${safeDays} days` };
|
|
658
|
+
}
|
|
659
|
+
async getProxyConfig() {
|
|
660
|
+
return this.apiGet("/api/proxy/config");
|
|
661
|
+
}
|
|
662
|
+
async listSessions() {
|
|
663
|
+
return this.apiGet("/api/sessions/active");
|
|
664
|
+
}
|
|
665
|
+
async createAgenticWallet(label, spendingLimitCents, dailyLimitCents, allowedDomains) {
|
|
666
|
+
if (!label || label.length === 0 || label.length > 100) {
|
|
667
|
+
throw new Error("label is required and must be 1-100 characters.");
|
|
668
|
+
}
|
|
669
|
+
if (/[\x00-\x1f\x7f]/.test(label)) {
|
|
670
|
+
throw new Error("label contains invalid control characters.");
|
|
671
|
+
}
|
|
672
|
+
const safeLimitCents = Math.min(Math.max(spendingLimitCents, 0), 1000000);
|
|
673
|
+
const body = { label, spendingLimitCents: safeLimitCents };
|
|
674
|
+
if (dailyLimitCents !== undefined && dailyLimitCents !== null) {
|
|
675
|
+
if (!Number.isInteger(dailyLimitCents) || dailyLimitCents < 1 || dailyLimitCents > 1000000) {
|
|
676
|
+
throw new Error("daily_limit_cents must be an integer between 1 and 1,000,000.");
|
|
677
|
+
}
|
|
678
|
+
body.dailyLimitCents = dailyLimitCents;
|
|
679
|
+
}
|
|
680
|
+
if (allowedDomains !== undefined && allowedDomains !== null) {
|
|
681
|
+
if (!Array.isArray(allowedDomains) || allowedDomains.length > 100) {
|
|
682
|
+
throw new Error("allowed_domains must be an array of at most 100 entries.");
|
|
683
|
+
}
|
|
684
|
+
for (const d of allowedDomains) {
|
|
685
|
+
if (typeof d !== "string" || d.length > 253) {
|
|
686
|
+
throw new Error(`invalid domain "${d}". Must be a string of at most 253 characters.`);
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
body.allowedDomains = allowedDomains;
|
|
690
|
+
}
|
|
691
|
+
return this.apiPost("/api/agent-wallet", body);
|
|
692
|
+
}
|
|
693
|
+
async updateWalletPolicy(walletId, dailyLimitCents, allowedDomains) {
|
|
694
|
+
validateUuid(walletId, "wallet_id");
|
|
695
|
+
const body = {};
|
|
696
|
+
if (dailyLimitCents !== undefined) {
|
|
697
|
+
if (dailyLimitCents === null) {
|
|
698
|
+
body.dailyLimitCents = null;
|
|
699
|
+
}
|
|
700
|
+
else {
|
|
701
|
+
if (!Number.isInteger(dailyLimitCents) || dailyLimitCents < 1 || dailyLimitCents > 1000000) {
|
|
702
|
+
throw new Error("daily_limit_cents must be an integer between 1 and 1,000,000, or null to remove.");
|
|
703
|
+
}
|
|
704
|
+
body.dailyLimitCents = dailyLimitCents;
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
if (allowedDomains !== undefined) {
|
|
708
|
+
if (allowedDomains === null) {
|
|
709
|
+
body.allowedDomains = null;
|
|
710
|
+
}
|
|
711
|
+
else {
|
|
712
|
+
if (!Array.isArray(allowedDomains) || allowedDomains.length > 100) {
|
|
713
|
+
throw new Error("allowed_domains must be an array of at most 100 entries, or null to remove.");
|
|
714
|
+
}
|
|
715
|
+
for (const d of allowedDomains) {
|
|
716
|
+
if (typeof d !== "string" || d.length > 253) {
|
|
717
|
+
throw new Error(`invalid domain "${d}". Must be a string of at most 253 characters.`);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
body.allowedDomains = allowedDomains;
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
if (Object.keys(body).length === 0) {
|
|
724
|
+
throw new Error("At least one of daily_limit_cents or allowed_domains must be provided.");
|
|
725
|
+
}
|
|
726
|
+
return this.apiPatch(`/api/agent-wallet/${encodeURIComponent(walletId)}/policy`, body);
|
|
727
|
+
}
|
|
728
|
+
async fundAgenticWallet(walletId, amountCents) {
|
|
729
|
+
validateUuid(walletId, "wallet_id");
|
|
730
|
+
if (!Number.isInteger(amountCents) || amountCents < 100 || amountCents > 1000000) {
|
|
731
|
+
throw new Error("amount_cents must be an integer between 100 ($1) and 1000000 ($10,000).");
|
|
732
|
+
}
|
|
733
|
+
return this.apiPost(`/api/agent-wallet/${encodeURIComponent(walletId)}/fund`, { amountCents });
|
|
734
|
+
}
|
|
735
|
+
async agenticWalletBalance(walletId) {
|
|
736
|
+
validateUuid(walletId, "wallet_id");
|
|
737
|
+
return this.apiGet(`/api/agent-wallet/${encodeURIComponent(walletId)}`);
|
|
738
|
+
}
|
|
739
|
+
async listAgenticWallets() {
|
|
740
|
+
return this.apiGet("/api/agent-wallet");
|
|
741
|
+
}
|
|
742
|
+
async agenticTransactions(walletId, limit = 20) {
|
|
743
|
+
validateUuid(walletId, "wallet_id");
|
|
744
|
+
const safeLimit = Math.min(Math.max(limit, 1), 100);
|
|
745
|
+
return this.apiGet(`/api/agent-wallet/${encodeURIComponent(walletId)}/transactions?limit=${safeLimit}`);
|
|
746
|
+
}
|
|
747
|
+
async freezeAgenticWallet(walletId) {
|
|
748
|
+
validateUuid(walletId, "wallet_id");
|
|
749
|
+
return this.apiPost(`/api/agent-wallet/${encodeURIComponent(walletId)}/freeze`);
|
|
750
|
+
}
|
|
751
|
+
async unfreezeAgenticWallet(walletId) {
|
|
752
|
+
validateUuid(walletId, "wallet_id");
|
|
753
|
+
return this.apiPost(`/api/agent-wallet/${encodeURIComponent(walletId)}/unfreeze`);
|
|
754
|
+
}
|
|
755
|
+
async deleteAgenticWallet(walletId) {
|
|
756
|
+
validateUuid(walletId, "wallet_id");
|
|
757
|
+
return this.apiDelete(`/api/agent-wallet/${encodeURIComponent(walletId)}`);
|
|
758
|
+
}
|
|
759
|
+
async createTeam(name, maxMembers) {
|
|
760
|
+
if (!name || name.length === 0 || name.length > 100) {
|
|
761
|
+
throw new Error("name is required and must be 1-100 characters.");
|
|
762
|
+
}
|
|
763
|
+
if (/[\x00-\x1f\x7f]/.test(name)) {
|
|
764
|
+
throw new Error("team name contains invalid control characters.");
|
|
765
|
+
}
|
|
766
|
+
const body = { name };
|
|
767
|
+
if (maxMembers !== undefined) {
|
|
768
|
+
if (!Number.isInteger(maxMembers) || maxMembers < 1 || maxMembers > 100) {
|
|
769
|
+
throw new Error("max_members must be an integer between 1 and 100.");
|
|
770
|
+
}
|
|
771
|
+
body.maxMembers = maxMembers;
|
|
772
|
+
}
|
|
773
|
+
return this.apiPost("/api/teams", body);
|
|
774
|
+
}
|
|
775
|
+
async listTeams() {
|
|
776
|
+
return this.apiGet("/api/teams");
|
|
777
|
+
}
|
|
778
|
+
async teamDetails(teamId) {
|
|
779
|
+
validateUuid(teamId, "team_id");
|
|
780
|
+
return this.apiGet(`/api/teams/${encodeURIComponent(teamId)}`);
|
|
781
|
+
}
|
|
782
|
+
async teamFund(teamId, amountCents) {
|
|
783
|
+
validateUuid(teamId, "team_id");
|
|
784
|
+
if (!Number.isInteger(amountCents) || amountCents < 100 || amountCents > 1000000) {
|
|
785
|
+
throw new Error("amount_cents must be an integer between 100 ($1) and 1000000 ($10,000).");
|
|
786
|
+
}
|
|
787
|
+
return this.apiPost(`/api/teams/${encodeURIComponent(teamId)}/wallet/fund`, { amountCents });
|
|
788
|
+
}
|
|
789
|
+
async teamCreateKey(teamId, label) {
|
|
790
|
+
validateUuid(teamId, "team_id");
|
|
791
|
+
if (!label || label.length === 0 || label.length > 100) {
|
|
792
|
+
throw new Error("label is required and must be 1-100 characters.");
|
|
793
|
+
}
|
|
794
|
+
if (/[\x00-\x1f\x7f]/.test(label)) {
|
|
795
|
+
throw new Error("label contains invalid control characters.");
|
|
796
|
+
}
|
|
797
|
+
return this.apiPost(`/api/teams/${encodeURIComponent(teamId)}/keys`, { label });
|
|
798
|
+
}
|
|
799
|
+
async teamUsage(teamId, limit = 20) {
|
|
800
|
+
validateUuid(teamId, "team_id");
|
|
801
|
+
const safeLimit = Math.min(Math.max(limit, 1), 100);
|
|
802
|
+
return this.apiGet(`/api/teams/${encodeURIComponent(teamId)}/wallet/transactions?limit=${safeLimit}`);
|
|
803
|
+
}
|
|
804
|
+
async updateTeam(teamId, name, maxMembers) {
|
|
805
|
+
validateUuid(teamId, "team_id");
|
|
806
|
+
const body = {};
|
|
807
|
+
if (name !== undefined) {
|
|
808
|
+
const trimmed = name.trim();
|
|
809
|
+
if (!trimmed || trimmed.length === 0 || trimmed.length > 100) {
|
|
810
|
+
throw new Error("name must be 1-100 characters.");
|
|
811
|
+
}
|
|
812
|
+
if (/[\x00-\x1f\x7f]/.test(trimmed)) {
|
|
813
|
+
throw new Error("team name contains invalid control characters.");
|
|
814
|
+
}
|
|
815
|
+
body.name = trimmed;
|
|
816
|
+
}
|
|
817
|
+
if (maxMembers !== undefined) {
|
|
818
|
+
if (!Number.isInteger(maxMembers) || maxMembers < 1 || maxMembers > 100) {
|
|
819
|
+
throw new Error("max_members must be an integer between 1 and 100.");
|
|
820
|
+
}
|
|
821
|
+
body.maxMembers = maxMembers;
|
|
822
|
+
}
|
|
823
|
+
if (Object.keys(body).length === 0) {
|
|
824
|
+
throw new Error("At least one of name or max_members must be provided.");
|
|
825
|
+
}
|
|
826
|
+
return this.apiPatch(`/api/teams/${encodeURIComponent(teamId)}`, body);
|
|
827
|
+
}
|
|
828
|
+
async updateTeamMemberRole(teamId, userId, role) {
|
|
829
|
+
validateUuid(teamId, "team_id");
|
|
830
|
+
validateUuid(userId, "user_id");
|
|
831
|
+
if (role !== "member" && role !== "admin") {
|
|
832
|
+
throw new Error("role must be 'member' or 'admin'.");
|
|
833
|
+
}
|
|
834
|
+
return this.apiPatch(`/api/teams/${encodeURIComponent(teamId)}/members/${encodeURIComponent(userId)}`, { role });
|
|
835
|
+
}
|
|
836
|
+
async topupPaypal(amountCents) {
|
|
837
|
+
if (!Number.isInteger(amountCents) || amountCents < 500 || amountCents > 100000) {
|
|
838
|
+
throw new Error("amount_cents must be an integer between 500 ($5) and 100000 ($1,000).");
|
|
839
|
+
}
|
|
840
|
+
return this.apiPost("/api/wallet/topup/paypal", { amountCents });
|
|
841
|
+
}
|
|
842
|
+
async x402Info() {
|
|
843
|
+
return this.apiGet("/api/x402/info");
|
|
844
|
+
}
|
|
845
|
+
// -----------------------------------------------------------------------
|
|
846
|
+
// getTools() -- returns Mastra createTool() instances for all 24 tools
|
|
847
|
+
// -----------------------------------------------------------------------
|
|
848
|
+
getTools() {
|
|
849
|
+
return [
|
|
850
|
+
// 1. proxied_fetch
|
|
851
|
+
(0, tools_1.createTool)({
|
|
852
|
+
id: "dominusnode_proxied_fetch",
|
|
853
|
+
description: "Fetch a URL through DomiNode's rotating proxy network with geo-targeting. " +
|
|
854
|
+
"Supports datacenter ($3/GB) and residential ($5/GB) pools. " +
|
|
855
|
+
"Only GET, HEAD, and OPTIONS methods are allowed.",
|
|
856
|
+
inputSchema: zod_1.z.object({
|
|
857
|
+
url: zod_1.z.string().describe("Target URL to fetch (http or https)"),
|
|
858
|
+
method: zod_1.z.enum(["GET", "HEAD", "OPTIONS"]).default("GET").describe("HTTP method"),
|
|
859
|
+
country: zod_1.z.string().optional().describe("ISO 3166-1 alpha-2 country code for geo-targeting (e.g., US, GB, DE)"),
|
|
860
|
+
proxyType: zod_1.z.enum(["dc", "residential"]).default("dc").describe("Proxy pool type: dc (datacenter, $3/GB) or residential ($5/GB)"),
|
|
861
|
+
}),
|
|
862
|
+
outputSchema: zod_1.z.object({
|
|
863
|
+
status: zod_1.z.number(),
|
|
864
|
+
headers: zod_1.z.record(zod_1.z.string()),
|
|
865
|
+
body: zod_1.z.string(),
|
|
866
|
+
pool: zod_1.z.string(),
|
|
867
|
+
country: zod_1.z.string(),
|
|
868
|
+
error: zod_1.z.string().optional(),
|
|
869
|
+
}),
|
|
870
|
+
execute: async ({ context }) => {
|
|
871
|
+
try {
|
|
872
|
+
return await this.proxiedFetch(context.url, context.method, context.country, context.proxyType);
|
|
873
|
+
}
|
|
874
|
+
catch (err) {
|
|
875
|
+
return { status: 0, headers: {}, body: "", pool: context.proxyType ?? "dc", country: context.country ?? "auto", error: safeError(err) };
|
|
876
|
+
}
|
|
877
|
+
},
|
|
878
|
+
}),
|
|
879
|
+
// 2. check_balance
|
|
880
|
+
(0, tools_1.createTool)({
|
|
881
|
+
id: "dominusnode_check_balance",
|
|
882
|
+
description: "Check your DomiNode wallet balance and estimated remaining bandwidth at current pricing. " +
|
|
883
|
+
"Shows balance in USD, datacenter GB remaining ($3/GB), and residential GB remaining ($5/GB).",
|
|
884
|
+
inputSchema: zod_1.z.object({}),
|
|
885
|
+
outputSchema: zod_1.z.object({
|
|
886
|
+
balanceCents: zod_1.z.number(),
|
|
887
|
+
balanceUsd: zod_1.z.number(),
|
|
888
|
+
dcGbRemaining: zod_1.z.string(),
|
|
889
|
+
resGbRemaining: zod_1.z.string(),
|
|
890
|
+
error: zod_1.z.string().optional(),
|
|
891
|
+
}),
|
|
892
|
+
execute: async () => {
|
|
893
|
+
try {
|
|
894
|
+
return await this.checkBalance();
|
|
895
|
+
}
|
|
896
|
+
catch (err) {
|
|
897
|
+
return { balanceCents: 0, balanceUsd: 0, dcGbRemaining: "0", resGbRemaining: "0", error: safeError(err) };
|
|
898
|
+
}
|
|
899
|
+
},
|
|
900
|
+
}),
|
|
901
|
+
// 3. check_usage
|
|
902
|
+
(0, tools_1.createTool)({
|
|
903
|
+
id: "dominusnode_check_usage",
|
|
904
|
+
description: "View bandwidth usage statistics for a specified time period. " +
|
|
905
|
+
"Shows total bytes transferred, total cost, and request count.",
|
|
906
|
+
inputSchema: zod_1.z.object({
|
|
907
|
+
days: zod_1.z.number().min(1).max(365).default(30).describe("Number of days to look back (1-365)"),
|
|
908
|
+
}),
|
|
909
|
+
outputSchema: zod_1.z.object({
|
|
910
|
+
totalBytes: zod_1.z.number(),
|
|
911
|
+
totalCostUsd: zod_1.z.number(),
|
|
912
|
+
requestCount: zod_1.z.number(),
|
|
913
|
+
period: zod_1.z.string(),
|
|
914
|
+
error: zod_1.z.string().optional(),
|
|
915
|
+
}),
|
|
916
|
+
execute: async ({ context }) => {
|
|
917
|
+
try {
|
|
918
|
+
return await this.checkUsage(context.days);
|
|
919
|
+
}
|
|
920
|
+
catch (err) {
|
|
921
|
+
return { totalBytes: 0, totalCostUsd: 0, requestCount: 0, period: `${context.days} days`, error: safeError(err) };
|
|
922
|
+
}
|
|
923
|
+
},
|
|
924
|
+
}),
|
|
925
|
+
// 4. get_proxy_config
|
|
926
|
+
(0, tools_1.createTool)({
|
|
927
|
+
id: "dominusnode_get_proxy_config",
|
|
928
|
+
description: "Get available proxy pools, pricing, supported countries, and endpoint configuration. " +
|
|
929
|
+
"Returns proxy host, ports, and pool details.",
|
|
930
|
+
inputSchema: zod_1.z.object({}),
|
|
931
|
+
outputSchema: zod_1.z.object({
|
|
932
|
+
data: zod_1.z.unknown(),
|
|
933
|
+
error: zod_1.z.string().optional(),
|
|
934
|
+
}),
|
|
935
|
+
execute: async () => {
|
|
936
|
+
try {
|
|
937
|
+
return { data: await this.getProxyConfig() };
|
|
938
|
+
}
|
|
939
|
+
catch (err) {
|
|
940
|
+
return { data: null, error: safeError(err) };
|
|
941
|
+
}
|
|
942
|
+
},
|
|
943
|
+
}),
|
|
944
|
+
// 5. list_sessions
|
|
945
|
+
(0, tools_1.createTool)({
|
|
946
|
+
id: "dominusnode_list_sessions",
|
|
947
|
+
description: "List all active proxy sessions showing target hosts, bandwidth used, and status.",
|
|
948
|
+
inputSchema: zod_1.z.object({}),
|
|
949
|
+
outputSchema: zod_1.z.object({
|
|
950
|
+
data: zod_1.z.unknown(),
|
|
951
|
+
error: zod_1.z.string().optional(),
|
|
952
|
+
}),
|
|
953
|
+
execute: async () => {
|
|
954
|
+
try {
|
|
955
|
+
return { data: await this.listSessions() };
|
|
956
|
+
}
|
|
957
|
+
catch (err) {
|
|
958
|
+
return { data: null, error: safeError(err) };
|
|
959
|
+
}
|
|
960
|
+
},
|
|
961
|
+
}),
|
|
962
|
+
// 6. create_agentic_wallet
|
|
963
|
+
(0, tools_1.createTool)({
|
|
964
|
+
id: "dominusnode_create_agentic_wallet",
|
|
965
|
+
description: "Create a server-side custodial agentic wallet for autonomous proxy billing. " +
|
|
966
|
+
"Set a spending limit per transaction for safety. Optionally set a daily budget cap and domain allowlist.",
|
|
967
|
+
inputSchema: zod_1.z.object({
|
|
968
|
+
label: zod_1.z.string().min(1).max(100).describe('Label for this wallet (e.g., "scraper-bot", "research-agent")'),
|
|
969
|
+
spendingLimitCents: zod_1.z.number().int().min(0).max(1000000).default(10000)
|
|
970
|
+
.describe("Max spend per transaction in cents (0 = no limit, default $100 = 10000 cents)"),
|
|
971
|
+
dailyLimitCents: zod_1.z.number().int().positive().max(1000000).optional()
|
|
972
|
+
.describe("Optional daily budget cap in cents (max 1,000,000 = $10,000)"),
|
|
973
|
+
allowedDomains: zod_1.z.array(zod_1.z.string().max(253)).max(100).optional()
|
|
974
|
+
.describe("Optional list of allowed domains for proxy access (max 100 entries)"),
|
|
975
|
+
}),
|
|
976
|
+
outputSchema: zod_1.z.object({
|
|
977
|
+
data: zod_1.z.unknown(),
|
|
978
|
+
error: zod_1.z.string().optional(),
|
|
979
|
+
}),
|
|
980
|
+
execute: async ({ context }) => {
|
|
981
|
+
try {
|
|
982
|
+
return { data: await this.createAgenticWallet(context.label, context.spendingLimitCents, context.dailyLimitCents, context.allowedDomains) };
|
|
983
|
+
}
|
|
984
|
+
catch (err) {
|
|
985
|
+
return { data: null, error: safeError(err) };
|
|
986
|
+
}
|
|
987
|
+
},
|
|
988
|
+
}),
|
|
989
|
+
// 7. fund_agentic_wallet
|
|
990
|
+
(0, tools_1.createTool)({
|
|
991
|
+
id: "dominusnode_fund_agentic_wallet",
|
|
992
|
+
description: "Transfer funds from your main wallet to an agentic wallet. " +
|
|
993
|
+
"Minimum $1 (100 cents), maximum $10,000 (1000000 cents).",
|
|
994
|
+
inputSchema: zod_1.z.object({
|
|
995
|
+
walletId: zod_1.z.string().describe("Agentic wallet ID (UUID)"),
|
|
996
|
+
amountCents: zod_1.z.number().int().min(100).max(1000000).describe("Amount in cents to transfer (min 100, max 1000000)"),
|
|
997
|
+
}),
|
|
998
|
+
outputSchema: zod_1.z.object({
|
|
999
|
+
data: zod_1.z.unknown(),
|
|
1000
|
+
error: zod_1.z.string().optional(),
|
|
1001
|
+
}),
|
|
1002
|
+
execute: async ({ context }) => {
|
|
1003
|
+
try {
|
|
1004
|
+
return { data: await this.fundAgenticWallet(context.walletId, context.amountCents) };
|
|
1005
|
+
}
|
|
1006
|
+
catch (err) {
|
|
1007
|
+
return { data: null, error: safeError(err) };
|
|
1008
|
+
}
|
|
1009
|
+
},
|
|
1010
|
+
}),
|
|
1011
|
+
// 8. agentic_wallet_balance
|
|
1012
|
+
(0, tools_1.createTool)({
|
|
1013
|
+
id: "dominusnode_agentic_wallet_balance",
|
|
1014
|
+
description: "Check the balance and details of an agentic wallet.",
|
|
1015
|
+
inputSchema: zod_1.z.object({
|
|
1016
|
+
walletId: zod_1.z.string().describe("Agentic wallet ID (UUID)"),
|
|
1017
|
+
}),
|
|
1018
|
+
outputSchema: zod_1.z.object({
|
|
1019
|
+
data: zod_1.z.unknown(),
|
|
1020
|
+
error: zod_1.z.string().optional(),
|
|
1021
|
+
}),
|
|
1022
|
+
execute: async ({ context }) => {
|
|
1023
|
+
try {
|
|
1024
|
+
return { data: await this.agenticWalletBalance(context.walletId) };
|
|
1025
|
+
}
|
|
1026
|
+
catch (err) {
|
|
1027
|
+
return { data: null, error: safeError(err) };
|
|
1028
|
+
}
|
|
1029
|
+
},
|
|
1030
|
+
}),
|
|
1031
|
+
// 9. list_agentic_wallets
|
|
1032
|
+
(0, tools_1.createTool)({
|
|
1033
|
+
id: "dominusnode_list_agentic_wallets",
|
|
1034
|
+
description: "List all your agentic wallets with balances and status.",
|
|
1035
|
+
inputSchema: zod_1.z.object({}),
|
|
1036
|
+
outputSchema: zod_1.z.object({
|
|
1037
|
+
data: zod_1.z.unknown(),
|
|
1038
|
+
error: zod_1.z.string().optional(),
|
|
1039
|
+
}),
|
|
1040
|
+
execute: async () => {
|
|
1041
|
+
try {
|
|
1042
|
+
return { data: await this.listAgenticWallets() };
|
|
1043
|
+
}
|
|
1044
|
+
catch (err) {
|
|
1045
|
+
return { data: null, error: safeError(err) };
|
|
1046
|
+
}
|
|
1047
|
+
},
|
|
1048
|
+
}),
|
|
1049
|
+
// 10. agentic_transactions
|
|
1050
|
+
(0, tools_1.createTool)({
|
|
1051
|
+
id: "dominusnode_agentic_transactions",
|
|
1052
|
+
description: "Get transaction history for an agentic wallet.",
|
|
1053
|
+
inputSchema: zod_1.z.object({
|
|
1054
|
+
walletId: zod_1.z.string().describe("Agentic wallet ID (UUID)"),
|
|
1055
|
+
limit: zod_1.z.number().int().min(1).max(100).default(20).describe("Number of transactions to return (1-100, default 20)"),
|
|
1056
|
+
}),
|
|
1057
|
+
outputSchema: zod_1.z.object({
|
|
1058
|
+
data: zod_1.z.unknown(),
|
|
1059
|
+
error: zod_1.z.string().optional(),
|
|
1060
|
+
}),
|
|
1061
|
+
execute: async ({ context }) => {
|
|
1062
|
+
try {
|
|
1063
|
+
return { data: await this.agenticTransactions(context.walletId, context.limit) };
|
|
1064
|
+
}
|
|
1065
|
+
catch (err) {
|
|
1066
|
+
return { data: null, error: safeError(err) };
|
|
1067
|
+
}
|
|
1068
|
+
},
|
|
1069
|
+
}),
|
|
1070
|
+
// 11. freeze_agentic_wallet
|
|
1071
|
+
(0, tools_1.createTool)({
|
|
1072
|
+
id: "dominusnode_freeze_agentic_wallet",
|
|
1073
|
+
description: "Freeze an agentic wallet to prevent any further spending. " +
|
|
1074
|
+
"The wallet balance is preserved but no transactions are allowed until unfrozen.",
|
|
1075
|
+
inputSchema: zod_1.z.object({
|
|
1076
|
+
walletId: zod_1.z.string().describe("Agentic wallet ID (UUID)"),
|
|
1077
|
+
}),
|
|
1078
|
+
outputSchema: zod_1.z.object({
|
|
1079
|
+
data: zod_1.z.unknown(),
|
|
1080
|
+
error: zod_1.z.string().optional(),
|
|
1081
|
+
}),
|
|
1082
|
+
execute: async ({ context }) => {
|
|
1083
|
+
try {
|
|
1084
|
+
return { data: await this.freezeAgenticWallet(context.walletId) };
|
|
1085
|
+
}
|
|
1086
|
+
catch (err) {
|
|
1087
|
+
return { data: null, error: safeError(err) };
|
|
1088
|
+
}
|
|
1089
|
+
},
|
|
1090
|
+
}),
|
|
1091
|
+
// 12. unfreeze_agentic_wallet
|
|
1092
|
+
(0, tools_1.createTool)({
|
|
1093
|
+
id: "dominusnode_unfreeze_agentic_wallet",
|
|
1094
|
+
description: "Unfreeze a previously frozen agentic wallet to re-enable spending.",
|
|
1095
|
+
inputSchema: zod_1.z.object({
|
|
1096
|
+
walletId: zod_1.z.string().describe("Agentic wallet ID (UUID)"),
|
|
1097
|
+
}),
|
|
1098
|
+
outputSchema: zod_1.z.object({
|
|
1099
|
+
data: zod_1.z.unknown(),
|
|
1100
|
+
error: zod_1.z.string().optional(),
|
|
1101
|
+
}),
|
|
1102
|
+
execute: async ({ context }) => {
|
|
1103
|
+
try {
|
|
1104
|
+
return { data: await this.unfreezeAgenticWallet(context.walletId) };
|
|
1105
|
+
}
|
|
1106
|
+
catch (err) {
|
|
1107
|
+
return { data: null, error: safeError(err) };
|
|
1108
|
+
}
|
|
1109
|
+
},
|
|
1110
|
+
}),
|
|
1111
|
+
// 13. delete_agentic_wallet
|
|
1112
|
+
(0, tools_1.createTool)({
|
|
1113
|
+
id: "dominusnode_delete_agentic_wallet",
|
|
1114
|
+
description: "Delete an agentic wallet. Must be unfrozen first. " +
|
|
1115
|
+
"Any remaining balance is refunded to your main wallet.",
|
|
1116
|
+
inputSchema: zod_1.z.object({
|
|
1117
|
+
walletId: zod_1.z.string().describe("Agentic wallet ID (UUID)"),
|
|
1118
|
+
}),
|
|
1119
|
+
outputSchema: zod_1.z.object({
|
|
1120
|
+
data: zod_1.z.unknown(),
|
|
1121
|
+
error: zod_1.z.string().optional(),
|
|
1122
|
+
}),
|
|
1123
|
+
execute: async ({ context }) => {
|
|
1124
|
+
try {
|
|
1125
|
+
return { data: await this.deleteAgenticWallet(context.walletId) };
|
|
1126
|
+
}
|
|
1127
|
+
catch (err) {
|
|
1128
|
+
return { data: null, error: safeError(err) };
|
|
1129
|
+
}
|
|
1130
|
+
},
|
|
1131
|
+
}),
|
|
1132
|
+
// 13b. update_wallet_policy
|
|
1133
|
+
(0, tools_1.createTool)({
|
|
1134
|
+
id: "dominusnode_update_wallet_policy",
|
|
1135
|
+
description: "Update spending policy for an agentic wallet. " +
|
|
1136
|
+
"Set or remove daily budget caps and domain restrictions.",
|
|
1137
|
+
inputSchema: zod_1.z.object({
|
|
1138
|
+
walletId: zod_1.z.string().describe("Agentic wallet ID (UUID)"),
|
|
1139
|
+
dailyLimitCents: zod_1.z.number().int().positive().max(1000000).nullable().optional()
|
|
1140
|
+
.describe("Daily budget cap in cents (max 1,000,000). Set null to remove."),
|
|
1141
|
+
allowedDomains: zod_1.z.array(zod_1.z.string().max(253)).max(100).nullable().optional()
|
|
1142
|
+
.describe("List of allowed domains (max 100). Set null to remove restriction."),
|
|
1143
|
+
}),
|
|
1144
|
+
outputSchema: zod_1.z.object({
|
|
1145
|
+
data: zod_1.z.unknown(),
|
|
1146
|
+
error: zod_1.z.string().optional(),
|
|
1147
|
+
}),
|
|
1148
|
+
execute: async ({ context }) => {
|
|
1149
|
+
try {
|
|
1150
|
+
return { data: await this.updateWalletPolicy(context.walletId, context.dailyLimitCents, context.allowedDomains) };
|
|
1151
|
+
}
|
|
1152
|
+
catch (err) {
|
|
1153
|
+
return { data: null, error: safeError(err) };
|
|
1154
|
+
}
|
|
1155
|
+
},
|
|
1156
|
+
}),
|
|
1157
|
+
// 14. create_team
|
|
1158
|
+
(0, tools_1.createTool)({
|
|
1159
|
+
id: "dominusnode_create_team",
|
|
1160
|
+
description: "Create a new team with shared wallet billing. " +
|
|
1161
|
+
"Teams allow multiple users to share proxy access and costs.",
|
|
1162
|
+
inputSchema: zod_1.z.object({
|
|
1163
|
+
name: zod_1.z.string().min(1).max(100).describe("Team name (1-100 characters)"),
|
|
1164
|
+
maxMembers: zod_1.z.number().int().min(1).max(100).optional().describe("Maximum number of team members (1-100, optional)"),
|
|
1165
|
+
}),
|
|
1166
|
+
outputSchema: zod_1.z.object({
|
|
1167
|
+
data: zod_1.z.unknown(),
|
|
1168
|
+
error: zod_1.z.string().optional(),
|
|
1169
|
+
}),
|
|
1170
|
+
execute: async ({ context }) => {
|
|
1171
|
+
try {
|
|
1172
|
+
return { data: await this.createTeam(context.name, context.maxMembers) };
|
|
1173
|
+
}
|
|
1174
|
+
catch (err) {
|
|
1175
|
+
return { data: null, error: safeError(err) };
|
|
1176
|
+
}
|
|
1177
|
+
},
|
|
1178
|
+
}),
|
|
1179
|
+
// 15. list_teams
|
|
1180
|
+
(0, tools_1.createTool)({
|
|
1181
|
+
id: "dominusnode_list_teams",
|
|
1182
|
+
description: "List all teams you belong to, with your role and team balance.",
|
|
1183
|
+
inputSchema: zod_1.z.object({}),
|
|
1184
|
+
outputSchema: zod_1.z.object({
|
|
1185
|
+
data: zod_1.z.unknown(),
|
|
1186
|
+
error: zod_1.z.string().optional(),
|
|
1187
|
+
}),
|
|
1188
|
+
execute: async () => {
|
|
1189
|
+
try {
|
|
1190
|
+
return { data: await this.listTeams() };
|
|
1191
|
+
}
|
|
1192
|
+
catch (err) {
|
|
1193
|
+
return { data: null, error: safeError(err) };
|
|
1194
|
+
}
|
|
1195
|
+
},
|
|
1196
|
+
}),
|
|
1197
|
+
// 16. team_details
|
|
1198
|
+
(0, tools_1.createTool)({
|
|
1199
|
+
id: "dominusnode_team_details",
|
|
1200
|
+
description: "Get detailed info about a team including balance, members, settings, and your role.",
|
|
1201
|
+
inputSchema: zod_1.z.object({
|
|
1202
|
+
teamId: zod_1.z.string().describe("Team ID (UUID)"),
|
|
1203
|
+
}),
|
|
1204
|
+
outputSchema: zod_1.z.object({
|
|
1205
|
+
data: zod_1.z.unknown(),
|
|
1206
|
+
error: zod_1.z.string().optional(),
|
|
1207
|
+
}),
|
|
1208
|
+
execute: async ({ context }) => {
|
|
1209
|
+
try {
|
|
1210
|
+
return { data: await this.teamDetails(context.teamId) };
|
|
1211
|
+
}
|
|
1212
|
+
catch (err) {
|
|
1213
|
+
return { data: null, error: safeError(err) };
|
|
1214
|
+
}
|
|
1215
|
+
},
|
|
1216
|
+
}),
|
|
1217
|
+
// 17. team_fund
|
|
1218
|
+
(0, tools_1.createTool)({
|
|
1219
|
+
id: "dominusnode_team_fund",
|
|
1220
|
+
description: "Transfer funds from your personal wallet to a team wallet. " +
|
|
1221
|
+
"Minimum $1 (100 cents), maximum $10,000 (1000000 cents).",
|
|
1222
|
+
inputSchema: zod_1.z.object({
|
|
1223
|
+
teamId: zod_1.z.string().describe("Team ID (UUID)"),
|
|
1224
|
+
amountCents: zod_1.z.number().int().min(100).max(1000000).describe("Amount in cents to transfer (min 100, max 1000000)"),
|
|
1225
|
+
}),
|
|
1226
|
+
outputSchema: zod_1.z.object({
|
|
1227
|
+
data: zod_1.z.unknown(),
|
|
1228
|
+
error: zod_1.z.string().optional(),
|
|
1229
|
+
}),
|
|
1230
|
+
execute: async ({ context }) => {
|
|
1231
|
+
try {
|
|
1232
|
+
return { data: await this.teamFund(context.teamId, context.amountCents) };
|
|
1233
|
+
}
|
|
1234
|
+
catch (err) {
|
|
1235
|
+
return { data: null, error: safeError(err) };
|
|
1236
|
+
}
|
|
1237
|
+
},
|
|
1238
|
+
}),
|
|
1239
|
+
// 18. team_create_key
|
|
1240
|
+
(0, tools_1.createTool)({
|
|
1241
|
+
id: "dominusnode_team_create_key",
|
|
1242
|
+
description: "Create a shared API key for a team. Usage is billed against the team wallet. " +
|
|
1243
|
+
"The key is shown only once -- save it immediately.",
|
|
1244
|
+
inputSchema: zod_1.z.object({
|
|
1245
|
+
teamId: zod_1.z.string().describe("Team ID (UUID)"),
|
|
1246
|
+
label: zod_1.z.string().min(1).max(100).describe('Label for the API key (e.g., "production", "staging")'),
|
|
1247
|
+
}),
|
|
1248
|
+
outputSchema: zod_1.z.object({
|
|
1249
|
+
data: zod_1.z.unknown(),
|
|
1250
|
+
error: zod_1.z.string().optional(),
|
|
1251
|
+
}),
|
|
1252
|
+
execute: async ({ context }) => {
|
|
1253
|
+
try {
|
|
1254
|
+
return { data: await this.teamCreateKey(context.teamId, context.label) };
|
|
1255
|
+
}
|
|
1256
|
+
catch (err) {
|
|
1257
|
+
return { data: null, error: safeError(err) };
|
|
1258
|
+
}
|
|
1259
|
+
},
|
|
1260
|
+
}),
|
|
1261
|
+
// 19. team_usage
|
|
1262
|
+
(0, tools_1.createTool)({
|
|
1263
|
+
id: "dominusnode_team_usage",
|
|
1264
|
+
description: "Get the team wallet transaction history (funding, usage charges, refunds).",
|
|
1265
|
+
inputSchema: zod_1.z.object({
|
|
1266
|
+
teamId: zod_1.z.string().describe("Team ID (UUID)"),
|
|
1267
|
+
limit: zod_1.z.number().int().min(1).max(100).default(20).describe("Number of transactions to return (1-100, default 20)"),
|
|
1268
|
+
}),
|
|
1269
|
+
outputSchema: zod_1.z.object({
|
|
1270
|
+
data: zod_1.z.unknown(),
|
|
1271
|
+
error: zod_1.z.string().optional(),
|
|
1272
|
+
}),
|
|
1273
|
+
execute: async ({ context }) => {
|
|
1274
|
+
try {
|
|
1275
|
+
return { data: await this.teamUsage(context.teamId, context.limit) };
|
|
1276
|
+
}
|
|
1277
|
+
catch (err) {
|
|
1278
|
+
return { data: null, error: safeError(err) };
|
|
1279
|
+
}
|
|
1280
|
+
},
|
|
1281
|
+
}),
|
|
1282
|
+
// 20. update_team
|
|
1283
|
+
(0, tools_1.createTool)({
|
|
1284
|
+
id: "dominusnode_update_team",
|
|
1285
|
+
description: "Update a team's settings (name and/or max member count). Only owners and admins can update.",
|
|
1286
|
+
inputSchema: zod_1.z.object({
|
|
1287
|
+
teamId: zod_1.z.string().describe("Team ID (UUID)"),
|
|
1288
|
+
name: zod_1.z.string().min(1).max(100).optional().describe("New team name (1-100 characters)"),
|
|
1289
|
+
maxMembers: zod_1.z.number().int().min(1).max(100).optional().describe("New max member count (1-100)"),
|
|
1290
|
+
}),
|
|
1291
|
+
outputSchema: zod_1.z.object({
|
|
1292
|
+
data: zod_1.z.unknown(),
|
|
1293
|
+
error: zod_1.z.string().optional(),
|
|
1294
|
+
}),
|
|
1295
|
+
execute: async ({ context }) => {
|
|
1296
|
+
try {
|
|
1297
|
+
return { data: await this.updateTeam(context.teamId, context.name, context.maxMembers) };
|
|
1298
|
+
}
|
|
1299
|
+
catch (err) {
|
|
1300
|
+
return { data: null, error: safeError(err) };
|
|
1301
|
+
}
|
|
1302
|
+
},
|
|
1303
|
+
}),
|
|
1304
|
+
// 21. update_team_member_role
|
|
1305
|
+
(0, tools_1.createTool)({
|
|
1306
|
+
id: "dominusnode_update_team_member_role",
|
|
1307
|
+
description: "Update a team member's role (member or admin). Only owners and admins can change roles.",
|
|
1308
|
+
inputSchema: zod_1.z.object({
|
|
1309
|
+
teamId: zod_1.z.string().describe("Team ID (UUID)"),
|
|
1310
|
+
userId: zod_1.z.string().describe("User ID (UUID) of the member whose role to change"),
|
|
1311
|
+
role: zod_1.z.enum(["member", "admin"]).describe("New role for the member"),
|
|
1312
|
+
}),
|
|
1313
|
+
outputSchema: zod_1.z.object({
|
|
1314
|
+
data: zod_1.z.unknown(),
|
|
1315
|
+
error: zod_1.z.string().optional(),
|
|
1316
|
+
}),
|
|
1317
|
+
execute: async ({ context }) => {
|
|
1318
|
+
try {
|
|
1319
|
+
return { data: await this.updateTeamMemberRole(context.teamId, context.userId, context.role) };
|
|
1320
|
+
}
|
|
1321
|
+
catch (err) {
|
|
1322
|
+
return { data: null, error: safeError(err) };
|
|
1323
|
+
}
|
|
1324
|
+
},
|
|
1325
|
+
}),
|
|
1326
|
+
// 22. topup_paypal
|
|
1327
|
+
(0, tools_1.createTool)({
|
|
1328
|
+
id: "dominusnode_topup_paypal",
|
|
1329
|
+
description: "Top up your DomiNode wallet balance via PayPal. Creates a PayPal order and returns " +
|
|
1330
|
+
"an approval URL to complete payment. Minimum $5 (500 cents), maximum $1,000 (100000 cents).",
|
|
1331
|
+
inputSchema: zod_1.z.object({
|
|
1332
|
+
amountCents: zod_1.z.number().int().min(500).max(100000)
|
|
1333
|
+
.describe("Amount in cents to top up (min 500 = $5, max 100000 = $1,000)"),
|
|
1334
|
+
}),
|
|
1335
|
+
outputSchema: zod_1.z.object({
|
|
1336
|
+
data: zod_1.z.unknown(),
|
|
1337
|
+
error: zod_1.z.string().optional(),
|
|
1338
|
+
}),
|
|
1339
|
+
execute: async ({ context }) => {
|
|
1340
|
+
try {
|
|
1341
|
+
return { data: await this.topupPaypal(context.amountCents) };
|
|
1342
|
+
}
|
|
1343
|
+
catch (err) {
|
|
1344
|
+
return { data: null, error: safeError(err) };
|
|
1345
|
+
}
|
|
1346
|
+
},
|
|
1347
|
+
}),
|
|
1348
|
+
// 24. x402_info
|
|
1349
|
+
(0, tools_1.createTool)({
|
|
1350
|
+
id: "dominusnode_x402_info",
|
|
1351
|
+
description: "Get x402 micropayment protocol information including supported " +
|
|
1352
|
+
"facilitators, pricing, and payment options.",
|
|
1353
|
+
inputSchema: zod_1.z.object({}),
|
|
1354
|
+
outputSchema: zod_1.z.object({
|
|
1355
|
+
data: zod_1.z.unknown(),
|
|
1356
|
+
error: zod_1.z.string().optional(),
|
|
1357
|
+
}),
|
|
1358
|
+
execute: async () => {
|
|
1359
|
+
try {
|
|
1360
|
+
return { data: await this.x402Info() };
|
|
1361
|
+
}
|
|
1362
|
+
catch (err) {
|
|
1363
|
+
return { data: null, error: safeError(err) };
|
|
1364
|
+
}
|
|
1365
|
+
},
|
|
1366
|
+
}),
|
|
1367
|
+
];
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
exports.DominusNodeToolkit = DominusNodeToolkit;
|