@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.
@@ -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;