@dominusnode/gemini-functions 1.1.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/handler.js CHANGED
@@ -1,6 +1,8 @@
1
1
  /**
2
2
  * Dominus Node Gemini / Vertex AI function calling handler (TypeScript).
3
3
  *
4
+ * 57 tools for AI agents to interact with the Dominus Node REST API.
5
+ *
4
6
  * Provides a factory function that creates a handler for dispatching
5
7
  * Google Gemini function calls to the Dominus Node REST API. Works with
6
8
  * Gemini, Vertex AI, or any system that uses Gemini-format function
@@ -22,10 +24,63 @@
22
24
  *
23
25
  * @module
24
26
  */
27
+ import * as crypto from "node:crypto";
25
28
  import dns from "dns/promises";
26
29
  import * as http from "node:http";
27
30
  import * as tls from "node:tls";
28
31
  // ---------------------------------------------------------------------------
32
+ // SHA-256 Proof-of-Work solver
33
+ // ---------------------------------------------------------------------------
34
+ function countLeadingZeroBits(buf) {
35
+ let count = 0;
36
+ for (const byte of buf) {
37
+ if (byte === 0) {
38
+ count += 8;
39
+ continue;
40
+ }
41
+ let mask = 0x80;
42
+ while (mask && !(byte & mask)) {
43
+ count++;
44
+ mask >>= 1;
45
+ }
46
+ break;
47
+ }
48
+ return count;
49
+ }
50
+ async function solvePoW(baseUrl) {
51
+ try {
52
+ const resp = await fetch(`${baseUrl}/api/auth/pow/challenge`, {
53
+ method: "POST",
54
+ headers: { "Content-Type": "application/json" },
55
+ redirect: "error",
56
+ });
57
+ if (!resp.ok)
58
+ return null;
59
+ const text = await resp.text();
60
+ if (text.length > 10_485_760)
61
+ return null;
62
+ const challenge = JSON.parse(text);
63
+ const prefix = challenge.prefix ?? "";
64
+ const difficulty = challenge.difficulty ?? 20;
65
+ const challengeId = challenge.challengeId ?? "";
66
+ if (!prefix || !challengeId)
67
+ return null;
68
+ for (let nonce = 0; nonce < 100_000_000; nonce++) {
69
+ const hash = crypto
70
+ .createHash("sha256")
71
+ .update(prefix + nonce.toString())
72
+ .digest();
73
+ if (countLeadingZeroBits(hash) >= difficulty) {
74
+ return { challengeId, nonce: nonce.toString() };
75
+ }
76
+ }
77
+ return null;
78
+ }
79
+ catch {
80
+ return null;
81
+ }
82
+ }
83
+ // ---------------------------------------------------------------------------
29
84
  // SSRF Prevention -- URL validation
30
85
  // ---------------------------------------------------------------------------
31
86
  const BLOCKED_HOSTNAMES = new Set([
@@ -294,7 +349,7 @@ async function checkDnsRebinding(hostname) {
294
349
  // ---------------------------------------------------------------------------
295
350
  // Credential sanitization
296
351
  // ---------------------------------------------------------------------------
297
- const CREDENTIAL_RE = /dn_(live|test)_[a-zA-Z0-9]+/g;
352
+ const CREDENTIAL_RE = /dn_(live|test|proxy)_[a-zA-Z0-9]+/g;
298
353
  function sanitizeError(message) {
299
354
  return message.replace(CREDENTIAL_RE, "***");
300
355
  }
@@ -507,8 +562,13 @@ export function createDominusNodeFunctionHandler(config) {
507
562
  try {
508
563
  // Strip security-sensitive headers from user-provided headers
509
564
  const BLOCKED_HEADERS = new Set([
510
- "host", "connection", "content-length", "transfer-encoding",
511
- "proxy-authorization", "authorization", "user-agent",
565
+ "host",
566
+ "connection",
567
+ "content-length",
568
+ "transfer-encoding",
569
+ "proxy-authorization",
570
+ "authorization",
571
+ "user-agent",
512
572
  ]);
513
573
  const safeHeaders = {};
514
574
  if (headers) {
@@ -544,7 +604,10 @@ export function createDominusNodeFunctionHandler(config) {
544
604
  port: proxyPort,
545
605
  method: "CONNECT",
546
606
  path: `${parsed.hostname.includes(":") ? `[${parsed.hostname}]` : parsed.hostname}:${parsed.port || 443}`,
547
- headers: { "Proxy-Authorization": proxyAuth, Host: `${parsed.hostname.includes(":") ? `[${parsed.hostname}]` : parsed.hostname}:${parsed.port || 443}` },
607
+ headers: {
608
+ "Proxy-Authorization": proxyAuth,
609
+ Host: `${parsed.hostname.includes(":") ? `[${parsed.hostname}]` : parsed.hostname}:${parsed.port || 443}`,
610
+ },
548
611
  });
549
612
  connectReq.on("connect", (_res, tunnelSocket) => {
550
613
  if (_res.statusCode !== 200) {
@@ -588,7 +651,9 @@ export function createDominusNodeFunctionHandler(config) {
588
651
  return;
589
652
  }
590
653
  const headerSection = raw.substring(0, headerEnd);
591
- const body = raw.substring(headerEnd + 4).substring(0, MAX_BODY_BYTES);
654
+ const body = raw
655
+ .substring(headerEnd + 4)
656
+ .substring(0, MAX_BODY_BYTES);
592
657
  const statusLine = headerSection.split("\r\n")[0];
593
658
  const statusMatch = statusLine.match(/^HTTP\/\d\.\d\s+(\d+)/);
594
659
  const status = statusMatch ? parseInt(statusMatch[1], 10) : 0;
@@ -596,18 +661,29 @@ export function createDominusNodeFunctionHandler(config) {
596
661
  for (const line of headerSection.split("\r\n").slice(1)) {
597
662
  const ci = line.indexOf(":");
598
663
  if (ci > 0)
599
- headers[line.substring(0, ci).trim().toLowerCase()] = line.substring(ci + 1).trim();
664
+ headers[line.substring(0, ci).trim().toLowerCase()] = line
665
+ .substring(ci + 1)
666
+ .trim();
600
667
  }
601
668
  stripDangerousKeys(headers);
602
669
  resolve({ status, headers, body });
603
670
  };
604
671
  tlsSocket.on("end", finalize);
605
672
  tlsSocket.on("close", finalize);
606
- tlsSocket.on("error", (err) => { clearTimeout(timeout); reject(err); });
673
+ tlsSocket.on("error", (err) => {
674
+ clearTimeout(timeout);
675
+ reject(err);
676
+ });
677
+ });
678
+ tlsSocket.on("error", (err) => {
679
+ clearTimeout(timeout);
680
+ reject(err);
607
681
  });
608
- tlsSocket.on("error", (err) => { clearTimeout(timeout); reject(err); });
609
682
  });
610
- connectReq.on("error", (err) => { clearTimeout(timeout); reject(err); });
683
+ connectReq.on("error", (err) => {
684
+ clearTimeout(timeout);
685
+ reject(err);
686
+ });
611
687
  connectReq.end();
612
688
  }
613
689
  else {
@@ -617,19 +693,28 @@ export function createDominusNodeFunctionHandler(config) {
617
693
  port: proxyPort,
618
694
  method,
619
695
  path: url,
620
- headers: { ...safeHeaders, "Proxy-Authorization": proxyAuth, Host: parsed.host },
696
+ headers: {
697
+ ...safeHeaders,
698
+ "Proxy-Authorization": proxyAuth,
699
+ Host: parsed.host,
700
+ },
621
701
  }, (res) => {
622
702
  const chunks = [];
623
703
  let byteCount = 0;
624
704
  let httpFinalized = false;
625
- res.on("data", (chunk) => { byteCount += chunk.length; if (byteCount <= MAX_BODY_BYTES)
626
- chunks.push(chunk); });
705
+ res.on("data", (chunk) => {
706
+ byteCount += chunk.length;
707
+ if (byteCount <= MAX_BODY_BYTES)
708
+ chunks.push(chunk);
709
+ });
627
710
  const finalize = () => {
628
711
  if (httpFinalized)
629
712
  return;
630
713
  httpFinalized = true;
631
714
  clearTimeout(timeout);
632
- const body = Buffer.concat(chunks).toString("utf-8").substring(0, MAX_BODY_BYTES);
715
+ const body = Buffer.concat(chunks)
716
+ .toString("utf-8")
717
+ .substring(0, MAX_BODY_BYTES);
633
718
  const headers = {};
634
719
  for (const [k, v] of Object.entries(res.headers)) {
635
720
  if (v)
@@ -640,9 +725,15 @@ export function createDominusNodeFunctionHandler(config) {
640
725
  };
641
726
  res.on("end", finalize);
642
727
  res.on("close", finalize);
643
- res.on("error", (err) => { clearTimeout(timeout); reject(err); });
728
+ res.on("error", (err) => {
729
+ clearTimeout(timeout);
730
+ reject(err);
731
+ });
732
+ });
733
+ req.on("error", (err) => {
734
+ clearTimeout(timeout);
735
+ reject(err);
644
736
  });
645
- req.on("error", (err) => { clearTimeout(timeout); reject(err); });
646
737
  req.end();
647
738
  }
648
739
  });
@@ -682,13 +773,17 @@ export function createDominusNodeFunctionHandler(config) {
682
773
  const label = args.label;
683
774
  const spendingLimitCents = args.spending_limit_cents;
684
775
  if (!label || typeof label !== "string") {
685
- return JSON.stringify({ error: "label is required and must be a string" });
776
+ return JSON.stringify({
777
+ error: "label is required and must be a string",
778
+ });
686
779
  }
687
780
  if (label.length > 100) {
688
781
  return JSON.stringify({ error: "label must be 100 characters or fewer" });
689
782
  }
690
783
  if (/[\x00-\x1f\x7f]/.test(label)) {
691
- return JSON.stringify({ error: "label contains invalid control characters" });
784
+ return JSON.stringify({
785
+ error: "label contains invalid control characters",
786
+ });
692
787
  }
693
788
  if (!Number.isInteger(spendingLimitCents) ||
694
789
  spendingLimitCents <= 0 ||
@@ -702,9 +797,12 @@ export function createDominusNodeFunctionHandler(config) {
702
797
  spendingLimitCents,
703
798
  };
704
799
  // Validate optional daily_limit_cents
705
- if (args.daily_limit_cents !== undefined && args.daily_limit_cents !== null) {
800
+ if (args.daily_limit_cents !== undefined &&
801
+ args.daily_limit_cents !== null) {
706
802
  const dailyLimit = Number(args.daily_limit_cents);
707
- if (!Number.isInteger(dailyLimit) || dailyLimit < 1 || dailyLimit > 1_000_000) {
803
+ if (!Number.isInteger(dailyLimit) ||
804
+ dailyLimit < 1 ||
805
+ dailyLimit > 1_000_000) {
708
806
  return JSON.stringify({
709
807
  error: "daily_limit_cents must be a positive integer between 1 and 1,000,000",
710
808
  });
@@ -714,22 +812,32 @@ export function createDominusNodeFunctionHandler(config) {
714
812
  // Validate optional allowed_domains
715
813
  if (args.allowed_domains !== undefined && args.allowed_domains !== null) {
716
814
  if (!Array.isArray(args.allowed_domains)) {
717
- return JSON.stringify({ error: "allowed_domains must be an array of domain strings" });
815
+ return JSON.stringify({
816
+ error: "allowed_domains must be an array of domain strings",
817
+ });
718
818
  }
719
819
  if (args.allowed_domains.length > 100) {
720
- return JSON.stringify({ error: "allowed_domains must have at most 100 entries" });
820
+ return JSON.stringify({
821
+ error: "allowed_domains must have at most 100 entries",
822
+ });
721
823
  }
722
824
  const domainRe = /^(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/;
723
825
  for (let i = 0; i < args.allowed_domains.length; i++) {
724
826
  const d = args.allowed_domains[i];
725
827
  if (typeof d !== "string") {
726
- return JSON.stringify({ error: `allowed_domains[${i}] must be a string` });
828
+ return JSON.stringify({
829
+ error: `allowed_domains[${i}] must be a string`,
830
+ });
727
831
  }
728
832
  if (d.length > 253) {
729
- return JSON.stringify({ error: `allowed_domains[${i}] exceeds 253 characters` });
833
+ return JSON.stringify({
834
+ error: `allowed_domains[${i}] exceeds 253 characters`,
835
+ });
730
836
  }
731
837
  if (!domainRe.test(d)) {
732
- return JSON.stringify({ error: `allowed_domains[${i}] is not a valid domain: ${d}` });
838
+ return JSON.stringify({
839
+ error: `allowed_domains[${i}] is not a valid domain: ${d}`,
840
+ });
733
841
  }
734
842
  }
735
843
  body.allowedDomains = args.allowed_domains;
@@ -741,7 +849,9 @@ export function createDominusNodeFunctionHandler(config) {
741
849
  const walletId = args.wallet_id;
742
850
  const amountCents = args.amount_cents;
743
851
  if (!walletId || typeof walletId !== "string") {
744
- return JSON.stringify({ error: "wallet_id is required and must be a string" });
852
+ return JSON.stringify({
853
+ error: "wallet_id is required and must be a string",
854
+ });
745
855
  }
746
856
  if (!Number.isInteger(amountCents) ||
747
857
  amountCents <= 0 ||
@@ -756,7 +866,9 @@ export function createDominusNodeFunctionHandler(config) {
756
866
  async function handleAgenticWalletBalance(args) {
757
867
  const walletId = args.wallet_id;
758
868
  if (!walletId || typeof walletId !== "string") {
759
- return JSON.stringify({ error: "wallet_id is required and must be a string" });
869
+ return JSON.stringify({
870
+ error: "wallet_id is required and must be a string",
871
+ });
760
872
  }
761
873
  const result = await api("GET", `/api/agent-wallet/${encodeURIComponent(walletId)}`);
762
874
  return JSON.stringify(result);
@@ -768,13 +880,17 @@ export function createDominusNodeFunctionHandler(config) {
768
880
  async function handleAgenticTransactions(args) {
769
881
  const walletId = args.wallet_id;
770
882
  if (!walletId || typeof walletId !== "string") {
771
- return JSON.stringify({ error: "wallet_id is required and must be a string" });
883
+ return JSON.stringify({
884
+ error: "wallet_id is required and must be a string",
885
+ });
772
886
  }
773
887
  const limit = args.limit;
774
888
  const params = new URLSearchParams();
775
889
  if (limit !== undefined) {
776
890
  if (!Number.isInteger(limit) || limit < 1 || limit > 100) {
777
- return JSON.stringify({ error: "limit must be an integer between 1 and 100" });
891
+ return JSON.stringify({
892
+ error: "limit must be an integer between 1 and 100",
893
+ });
778
894
  }
779
895
  params.set("limit", String(limit));
780
896
  }
@@ -785,7 +901,9 @@ export function createDominusNodeFunctionHandler(config) {
785
901
  async function handleFreezeAgenticWallet(args) {
786
902
  const walletId = args.wallet_id;
787
903
  if (!walletId || typeof walletId !== "string") {
788
- return JSON.stringify({ error: "wallet_id is required and must be a string" });
904
+ return JSON.stringify({
905
+ error: "wallet_id is required and must be a string",
906
+ });
789
907
  }
790
908
  const result = await api("POST", `/api/agent-wallet/${encodeURIComponent(walletId)}/freeze`);
791
909
  return JSON.stringify(result);
@@ -793,7 +911,9 @@ export function createDominusNodeFunctionHandler(config) {
793
911
  async function handleUnfreezeAgenticWallet(args) {
794
912
  const walletId = args.wallet_id;
795
913
  if (!walletId || typeof walletId !== "string") {
796
- return JSON.stringify({ error: "wallet_id is required and must be a string" });
914
+ return JSON.stringify({
915
+ error: "wallet_id is required and must be a string",
916
+ });
797
917
  }
798
918
  const result = await api("POST", `/api/agent-wallet/${encodeURIComponent(walletId)}/unfreeze`);
799
919
  return JSON.stringify(result);
@@ -801,7 +921,9 @@ export function createDominusNodeFunctionHandler(config) {
801
921
  async function handleDeleteAgenticWallet(args) {
802
922
  const walletId = args.wallet_id;
803
923
  if (!walletId || typeof walletId !== "string") {
804
- return JSON.stringify({ error: "wallet_id is required and must be a string" });
924
+ return JSON.stringify({
925
+ error: "wallet_id is required and must be a string",
926
+ });
805
927
  }
806
928
  const result = await api("DELETE", `/api/agent-wallet/${encodeURIComponent(walletId)}`);
807
929
  return JSON.stringify(result);
@@ -815,13 +937,17 @@ export function createDominusNodeFunctionHandler(config) {
815
937
  return JSON.stringify({ error: "name must be 100 characters or fewer" });
816
938
  }
817
939
  if (/[\x00-\x1f\x7f]/.test(name)) {
818
- return JSON.stringify({ error: "name contains invalid control characters" });
940
+ return JSON.stringify({
941
+ error: "name contains invalid control characters",
942
+ });
819
943
  }
820
944
  const body = { name };
821
945
  if (args.max_members !== undefined) {
822
946
  const maxMembers = Number(args.max_members);
823
947
  if (!Number.isInteger(maxMembers) || maxMembers < 1 || maxMembers > 100) {
824
- return JSON.stringify({ error: "max_members must be an integer between 1 and 100" });
948
+ return JSON.stringify({
949
+ error: "max_members must be an integer between 1 and 100",
950
+ });
825
951
  }
826
952
  body.maxMembers = maxMembers;
827
953
  }
@@ -835,7 +961,9 @@ export function createDominusNodeFunctionHandler(config) {
835
961
  async function handleTeamDetails(args) {
836
962
  const teamId = args.team_id;
837
963
  if (!teamId || typeof teamId !== "string") {
838
- return JSON.stringify({ error: "team_id is required and must be a string" });
964
+ return JSON.stringify({
965
+ error: "team_id is required and must be a string",
966
+ });
839
967
  }
840
968
  const result = await api("GET", `/api/teams/${encodeURIComponent(teamId)}`);
841
969
  return JSON.stringify(result);
@@ -844,7 +972,9 @@ export function createDominusNodeFunctionHandler(config) {
844
972
  const teamId = args.team_id;
845
973
  const amountCents = args.amount_cents;
846
974
  if (!teamId || typeof teamId !== "string") {
847
- return JSON.stringify({ error: "team_id is required and must be a string" });
975
+ return JSON.stringify({
976
+ error: "team_id is required and must be a string",
977
+ });
848
978
  }
849
979
  if (!Number.isInteger(amountCents) ||
850
980
  amountCents < 100 ||
@@ -860,16 +990,22 @@ export function createDominusNodeFunctionHandler(config) {
860
990
  const teamId = args.team_id;
861
991
  const label = args.label;
862
992
  if (!teamId || typeof teamId !== "string") {
863
- return JSON.stringify({ error: "team_id is required and must be a string" });
993
+ return JSON.stringify({
994
+ error: "team_id is required and must be a string",
995
+ });
864
996
  }
865
997
  if (!label || typeof label !== "string") {
866
- return JSON.stringify({ error: "label is required and must be a string" });
998
+ return JSON.stringify({
999
+ error: "label is required and must be a string",
1000
+ });
867
1001
  }
868
1002
  if (label.length > 100) {
869
1003
  return JSON.stringify({ error: "label must be 100 characters or fewer" });
870
1004
  }
871
1005
  if (/[\x00-\x1f\x7f]/.test(label)) {
872
- return JSON.stringify({ error: "label contains invalid control characters" });
1006
+ return JSON.stringify({
1007
+ error: "label contains invalid control characters",
1008
+ });
873
1009
  }
874
1010
  const result = await api("POST", `/api/teams/${encodeURIComponent(teamId)}/keys`, { label });
875
1011
  return JSON.stringify(result);
@@ -877,13 +1013,17 @@ export function createDominusNodeFunctionHandler(config) {
877
1013
  async function handleTeamUsage(args) {
878
1014
  const teamId = args.team_id;
879
1015
  if (!teamId || typeof teamId !== "string") {
880
- return JSON.stringify({ error: "team_id is required and must be a string" });
1016
+ return JSON.stringify({
1017
+ error: "team_id is required and must be a string",
1018
+ });
881
1019
  }
882
1020
  const limit = args.limit;
883
1021
  const params = new URLSearchParams();
884
1022
  if (limit !== undefined) {
885
1023
  if (!Number.isInteger(limit) || limit < 1 || limit > 100) {
886
- return JSON.stringify({ error: "limit must be an integer between 1 and 100" });
1024
+ return JSON.stringify({
1025
+ error: "limit must be an integer between 1 and 100",
1026
+ });
887
1027
  }
888
1028
  params.set("limit", String(limit));
889
1029
  }
@@ -894,7 +1034,9 @@ export function createDominusNodeFunctionHandler(config) {
894
1034
  async function handleUpdateTeam(args) {
895
1035
  const teamId = args.team_id;
896
1036
  if (!teamId || typeof teamId !== "string") {
897
- return JSON.stringify({ error: "team_id is required and must be a string" });
1037
+ return JSON.stringify({
1038
+ error: "team_id is required and must be a string",
1039
+ });
898
1040
  }
899
1041
  if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(teamId)) {
900
1042
  return JSON.stringify({ error: "team_id must be a valid UUID" });
@@ -906,22 +1048,30 @@ export function createDominusNodeFunctionHandler(config) {
906
1048
  return JSON.stringify({ error: "name must be a non-empty string" });
907
1049
  }
908
1050
  if (name.length > 100) {
909
- return JSON.stringify({ error: "name must be 100 characters or fewer" });
1051
+ return JSON.stringify({
1052
+ error: "name must be 100 characters or fewer",
1053
+ });
910
1054
  }
911
1055
  if (/[\x00-\x1f\x7f]/.test(name)) {
912
- return JSON.stringify({ error: "name contains invalid control characters" });
1056
+ return JSON.stringify({
1057
+ error: "name contains invalid control characters",
1058
+ });
913
1059
  }
914
1060
  body.name = name;
915
1061
  }
916
1062
  if (args.max_members !== undefined) {
917
1063
  const maxMembers = Number(args.max_members);
918
1064
  if (!Number.isInteger(maxMembers) || maxMembers < 1 || maxMembers > 100) {
919
- return JSON.stringify({ error: "max_members must be an integer between 1 and 100" });
1065
+ return JSON.stringify({
1066
+ error: "max_members must be an integer between 1 and 100",
1067
+ });
920
1068
  }
921
1069
  body.maxMembers = maxMembers;
922
1070
  }
923
1071
  if (Object.keys(body).length === 0) {
924
- return JSON.stringify({ error: "At least one of name or max_members must be provided" });
1072
+ return JSON.stringify({
1073
+ error: "At least one of name or max_members must be provided",
1074
+ });
925
1075
  }
926
1076
  const result = await api("PATCH", `/api/teams/${encodeURIComponent(teamId)}`, body);
927
1077
  return JSON.stringify(result);
@@ -931,13 +1081,17 @@ export function createDominusNodeFunctionHandler(config) {
931
1081
  const userId = args.user_id;
932
1082
  const role = args.role;
933
1083
  if (!teamId || typeof teamId !== "string") {
934
- return JSON.stringify({ error: "team_id is required and must be a string" });
1084
+ return JSON.stringify({
1085
+ error: "team_id is required and must be a string",
1086
+ });
935
1087
  }
936
1088
  if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(teamId)) {
937
1089
  return JSON.stringify({ error: "team_id must be a valid UUID" });
938
1090
  }
939
1091
  if (!userId || typeof userId !== "string") {
940
- return JSON.stringify({ error: "user_id is required and must be a string" });
1092
+ return JSON.stringify({
1093
+ error: "user_id is required and must be a string",
1094
+ });
941
1095
  }
942
1096
  if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(userId)) {
943
1097
  return JSON.stringify({ error: "user_id must be a valid UUID" });
@@ -960,7 +1114,9 @@ export function createDominusNodeFunctionHandler(config) {
960
1114
  error: "amount_cents must be an integer between 500 ($5) and 100,000 ($1,000)",
961
1115
  });
962
1116
  }
963
- const result = await api("POST", "/api/wallet/topup/paypal", { amountCents });
1117
+ const result = await api("POST", "/api/wallet/topup/paypal", {
1118
+ amountCents,
1119
+ });
964
1120
  return JSON.stringify(result);
965
1121
  }
966
1122
  async function handleTopupStripe(args) {
@@ -972,11 +1128,23 @@ export function createDominusNodeFunctionHandler(config) {
972
1128
  error: "amount_cents must be an integer between 500 ($5) and 100,000 ($1,000)",
973
1129
  });
974
1130
  }
975
- const result = await api("POST", "/api/wallet/topup/stripe", { amountCents });
1131
+ const result = await api("POST", "/api/wallet/topup/stripe", {
1132
+ amountCents,
1133
+ });
976
1134
  return JSON.stringify(result);
977
1135
  }
978
1136
  const VALID_CRYPTO_CURRENCIES = new Set([
979
- "btc", "eth", "ltc", "xmr", "zec", "usdc", "sol", "usdt", "dai", "bnb", "link",
1137
+ "btc",
1138
+ "eth",
1139
+ "ltc",
1140
+ "xmr",
1141
+ "zec",
1142
+ "usdc",
1143
+ "sol",
1144
+ "usdt",
1145
+ "dai",
1146
+ "bnb",
1147
+ "link",
980
1148
  ]);
981
1149
  async function handleTopupCrypto(args) {
982
1150
  const amountUsd = args.amount_usd;
@@ -1001,6 +1169,567 @@ export function createDominusNodeFunctionHandler(config) {
1001
1169
  return JSON.stringify(result);
1002
1170
  }
1003
1171
  // -----------------------------------------------------------------------
1172
+ // Proxy extended
1173
+ // -----------------------------------------------------------------------
1174
+ async function handleGetProxyStatus() {
1175
+ const result = await api("GET", "/api/proxy/status");
1176
+ return JSON.stringify(result);
1177
+ }
1178
+ // -----------------------------------------------------------------------
1179
+ // Wallet extended
1180
+ // -----------------------------------------------------------------------
1181
+ async function handleGetTransactions(args) {
1182
+ const params = new URLSearchParams();
1183
+ if (args.limit !== undefined) {
1184
+ const limit = Number(args.limit);
1185
+ if (!Number.isInteger(limit) || limit < 1 || limit > 100) {
1186
+ return JSON.stringify({
1187
+ error: "limit must be an integer between 1 and 100",
1188
+ });
1189
+ }
1190
+ params.set("limit", String(limit));
1191
+ }
1192
+ const qs = params.toString();
1193
+ const result = await api("GET", `/api/wallet/transactions${qs ? `?${qs}` : ""}`);
1194
+ return JSON.stringify(result);
1195
+ }
1196
+ async function handleGetForecast() {
1197
+ const result = await api("GET", "/api/wallet/forecast");
1198
+ return JSON.stringify(result);
1199
+ }
1200
+ async function handleCheckPayment(args) {
1201
+ const invoiceId = args.invoice_id;
1202
+ if (!invoiceId || typeof invoiceId !== "string") {
1203
+ return JSON.stringify({
1204
+ error: "invoice_id is required and must be a string",
1205
+ });
1206
+ }
1207
+ if (/[\x00-\x1f\x7f]/.test(invoiceId)) {
1208
+ return JSON.stringify({
1209
+ error: "invoice_id contains invalid control characters",
1210
+ });
1211
+ }
1212
+ const result = await api("GET", `/api/wallet/topup/crypto/${encodeURIComponent(invoiceId)}/status`);
1213
+ return JSON.stringify(result);
1214
+ }
1215
+ // -----------------------------------------------------------------------
1216
+ // Usage extended
1217
+ // -----------------------------------------------------------------------
1218
+ async function handleGetDailyUsage(args) {
1219
+ const params = new URLSearchParams();
1220
+ if (args.days !== undefined) {
1221
+ const days = Number(args.days);
1222
+ if (!Number.isInteger(days) || days < 1 || days > 90) {
1223
+ return JSON.stringify({
1224
+ error: "days must be an integer between 1 and 90",
1225
+ });
1226
+ }
1227
+ params.set("days", String(days));
1228
+ }
1229
+ const qs = params.toString();
1230
+ const result = await api("GET", `/api/usage/daily${qs ? `?${qs}` : ""}`);
1231
+ return JSON.stringify(result);
1232
+ }
1233
+ async function handleGetTopHosts(args) {
1234
+ const params = new URLSearchParams();
1235
+ if (args.limit !== undefined) {
1236
+ const limit = Number(args.limit);
1237
+ if (!Number.isInteger(limit) || limit < 1 || limit > 50) {
1238
+ return JSON.stringify({
1239
+ error: "limit must be an integer between 1 and 50",
1240
+ });
1241
+ }
1242
+ params.set("limit", String(limit));
1243
+ }
1244
+ const qs = params.toString();
1245
+ const result = await api("GET", `/api/usage/top-hosts${qs ? `?${qs}` : ""}`);
1246
+ return JSON.stringify(result);
1247
+ }
1248
+ // -----------------------------------------------------------------------
1249
+ // Account lifecycle
1250
+ // -----------------------------------------------------------------------
1251
+ async function handleRegister(args) {
1252
+ const email = args.email;
1253
+ const password = args.password;
1254
+ if (!email || typeof email !== "string") {
1255
+ return JSON.stringify({
1256
+ error: "email is required and must be a string",
1257
+ });
1258
+ }
1259
+ if (!/^[^@]+@[^@]+\.[^@]+$/.test(email)) {
1260
+ return JSON.stringify({ error: "email must be a valid email address" });
1261
+ }
1262
+ if (/[\x00-\x1f\x7f]/.test(email)) {
1263
+ return JSON.stringify({
1264
+ error: "email contains invalid control characters",
1265
+ });
1266
+ }
1267
+ if (!password || typeof password !== "string") {
1268
+ return JSON.stringify({
1269
+ error: "password is required and must be a string",
1270
+ });
1271
+ }
1272
+ if (password.length < 8 || password.length > 128) {
1273
+ return JSON.stringify({
1274
+ error: "password must be between 8 and 128 characters",
1275
+ });
1276
+ }
1277
+ try {
1278
+ const reqHeaders = {
1279
+ "User-Agent": "dominusnode-gemini/1.0.0",
1280
+ "Content-Type": "application/json",
1281
+ };
1282
+ if (agentSecret) {
1283
+ reqHeaders["X-DominusNode-Agent"] = "mcp";
1284
+ reqHeaders["X-DominusNode-Agent-Secret"] = agentSecret;
1285
+ }
1286
+ // Solve PoW for CAPTCHA-free registration
1287
+ const pow = await solvePoW(baseUrl);
1288
+ const regBody = { email, password };
1289
+ if (pow)
1290
+ regBody.pow = pow;
1291
+ const response = await fetch(`${baseUrl}/api/auth/register`, {
1292
+ method: "POST",
1293
+ headers: reqHeaders,
1294
+ body: JSON.stringify(regBody),
1295
+ signal: AbortSignal.timeout(timeoutMs),
1296
+ redirect: "error",
1297
+ });
1298
+ const responseText = await response.text();
1299
+ if (responseText.length > MAX_RESPONSE_BYTES) {
1300
+ return JSON.stringify({ error: "Response body exceeds size limit" });
1301
+ }
1302
+ if (!response.ok) {
1303
+ let message;
1304
+ try {
1305
+ const parsed = JSON.parse(responseText);
1306
+ message = parsed.error ?? parsed.message ?? responseText;
1307
+ }
1308
+ catch {
1309
+ message = responseText;
1310
+ }
1311
+ if (message.length > 500)
1312
+ message = message.slice(0, 500) + "... [truncated]";
1313
+ return JSON.stringify({ error: sanitizeError(message) });
1314
+ }
1315
+ const data = safeJsonParse(responseText);
1316
+ return JSON.stringify({
1317
+ userId: data.userId,
1318
+ email: data.email,
1319
+ message: data.message ?? "Registration successful",
1320
+ });
1321
+ }
1322
+ catch (err) {
1323
+ return JSON.stringify({
1324
+ error: sanitizeError(err instanceof Error ? err.message : String(err)),
1325
+ });
1326
+ }
1327
+ }
1328
+ async function handleLogin(args) {
1329
+ const email = args.email;
1330
+ const password = args.password;
1331
+ if (!email || typeof email !== "string") {
1332
+ return JSON.stringify({
1333
+ error: "email is required and must be a string",
1334
+ });
1335
+ }
1336
+ if (!/^[^@]+@[^@]+\.[^@]+$/.test(email)) {
1337
+ return JSON.stringify({ error: "email must be a valid email address" });
1338
+ }
1339
+ if (/[\x00-\x1f\x7f]/.test(email)) {
1340
+ return JSON.stringify({
1341
+ error: "email contains invalid control characters",
1342
+ });
1343
+ }
1344
+ if (!password || typeof password !== "string") {
1345
+ return JSON.stringify({
1346
+ error: "password is required and must be a string",
1347
+ });
1348
+ }
1349
+ if (password.length < 8 || password.length > 128) {
1350
+ return JSON.stringify({
1351
+ error: "password must be between 8 and 128 characters",
1352
+ });
1353
+ }
1354
+ try {
1355
+ const reqHeaders = {
1356
+ "User-Agent": "dominusnode-gemini/1.0.0",
1357
+ "Content-Type": "application/json",
1358
+ };
1359
+ if (agentSecret) {
1360
+ reqHeaders["X-DominusNode-Agent"] = "mcp";
1361
+ reqHeaders["X-DominusNode-Agent-Secret"] = agentSecret;
1362
+ }
1363
+ const response = await fetch(`${baseUrl}/api/auth/login`, {
1364
+ method: "POST",
1365
+ headers: reqHeaders,
1366
+ body: JSON.stringify({ email, password }),
1367
+ signal: AbortSignal.timeout(timeoutMs),
1368
+ redirect: "error",
1369
+ });
1370
+ const responseText = await response.text();
1371
+ if (responseText.length > MAX_RESPONSE_BYTES) {
1372
+ return JSON.stringify({ error: "Response body exceeds size limit" });
1373
+ }
1374
+ if (!response.ok) {
1375
+ let message;
1376
+ try {
1377
+ const parsed = JSON.parse(responseText);
1378
+ message = parsed.error ?? parsed.message ?? responseText;
1379
+ }
1380
+ catch {
1381
+ message = responseText;
1382
+ }
1383
+ if (message.length > 500)
1384
+ message = message.slice(0, 500) + "... [truncated]";
1385
+ return JSON.stringify({ error: sanitizeError(message) });
1386
+ }
1387
+ const data = safeJsonParse(responseText);
1388
+ return JSON.stringify({
1389
+ token: data.token,
1390
+ message: data.message ?? "Login successful",
1391
+ });
1392
+ }
1393
+ catch (err) {
1394
+ return JSON.stringify({
1395
+ error: sanitizeError(err instanceof Error ? err.message : String(err)),
1396
+ });
1397
+ }
1398
+ }
1399
+ async function handleGetAccountInfo() {
1400
+ const result = await api("GET", "/api/auth/me");
1401
+ return JSON.stringify(result);
1402
+ }
1403
+ async function handleVerifyEmail(args) {
1404
+ const token = args.token;
1405
+ if (!token || typeof token !== "string") {
1406
+ return JSON.stringify({
1407
+ error: "token is required and must be a string",
1408
+ });
1409
+ }
1410
+ if (/[\x00-\x1f\x7f]/.test(token)) {
1411
+ return JSON.stringify({
1412
+ error: "token contains invalid control characters",
1413
+ });
1414
+ }
1415
+ try {
1416
+ const reqHeaders = {
1417
+ "User-Agent": "dominusnode-gemini/1.0.0",
1418
+ "Content-Type": "application/json",
1419
+ };
1420
+ if (agentSecret) {
1421
+ reqHeaders["X-DominusNode-Agent"] = "mcp";
1422
+ reqHeaders["X-DominusNode-Agent-Secret"] = agentSecret;
1423
+ }
1424
+ const response = await fetch(`${baseUrl}/api/auth/verify-email`, {
1425
+ method: "POST",
1426
+ headers: reqHeaders,
1427
+ body: JSON.stringify({ token }),
1428
+ signal: AbortSignal.timeout(timeoutMs),
1429
+ redirect: "error",
1430
+ });
1431
+ const responseText = await response.text();
1432
+ if (responseText.length > MAX_RESPONSE_BYTES) {
1433
+ return JSON.stringify({ error: "Response body exceeds size limit" });
1434
+ }
1435
+ if (!response.ok) {
1436
+ let message;
1437
+ try {
1438
+ const parsed = JSON.parse(responseText);
1439
+ message = parsed.error ?? parsed.message ?? responseText;
1440
+ }
1441
+ catch {
1442
+ message = responseText;
1443
+ }
1444
+ if (message.length > 500)
1445
+ message = message.slice(0, 500) + "... [truncated]";
1446
+ return JSON.stringify({ error: sanitizeError(message) });
1447
+ }
1448
+ const data = safeJsonParse(responseText);
1449
+ return JSON.stringify(data);
1450
+ }
1451
+ catch (err) {
1452
+ return JSON.stringify({
1453
+ error: sanitizeError(err instanceof Error ? err.message : String(err)),
1454
+ });
1455
+ }
1456
+ }
1457
+ async function handleResendVerification() {
1458
+ const result = await api("POST", "/api/auth/resend-verification");
1459
+ return JSON.stringify(result);
1460
+ }
1461
+ async function handleUpdatePassword(args) {
1462
+ const currentPassword = args.current_password;
1463
+ const newPassword = args.new_password;
1464
+ if (!currentPassword || typeof currentPassword !== "string") {
1465
+ return JSON.stringify({
1466
+ error: "current_password is required and must be a string",
1467
+ });
1468
+ }
1469
+ if (currentPassword.length < 8 || currentPassword.length > 128) {
1470
+ return JSON.stringify({
1471
+ error: "current_password must be between 8 and 128 characters",
1472
+ });
1473
+ }
1474
+ if (!newPassword || typeof newPassword !== "string") {
1475
+ return JSON.stringify({
1476
+ error: "new_password is required and must be a string",
1477
+ });
1478
+ }
1479
+ if (newPassword.length < 8 || newPassword.length > 128) {
1480
+ return JSON.stringify({
1481
+ error: "new_password must be between 8 and 128 characters",
1482
+ });
1483
+ }
1484
+ const result = await api("POST", "/api/auth/change-password", {
1485
+ currentPassword,
1486
+ newPassword,
1487
+ });
1488
+ return JSON.stringify(result);
1489
+ }
1490
+ // -----------------------------------------------------------------------
1491
+ // API Keys
1492
+ // -----------------------------------------------------------------------
1493
+ async function handleListKeys() {
1494
+ const result = await api("GET", "/api/keys");
1495
+ return JSON.stringify(result);
1496
+ }
1497
+ async function handleCreateKey(args) {
1498
+ const label = args.label;
1499
+ if (!label || typeof label !== "string") {
1500
+ return JSON.stringify({
1501
+ error: "label is required and must be a string",
1502
+ });
1503
+ }
1504
+ if (label.length > 100) {
1505
+ return JSON.stringify({ error: "label must be 100 characters or fewer" });
1506
+ }
1507
+ if (/[\x00-\x1f\x7f]/.test(label)) {
1508
+ return JSON.stringify({
1509
+ error: "label contains invalid control characters",
1510
+ });
1511
+ }
1512
+ const result = await api("POST", "/api/keys", { label });
1513
+ return JSON.stringify(result);
1514
+ }
1515
+ async function handleRevokeKey(args) {
1516
+ const keyId = args.key_id;
1517
+ if (!keyId || typeof keyId !== "string") {
1518
+ return JSON.stringify({
1519
+ error: "key_id is required and must be a string",
1520
+ });
1521
+ }
1522
+ if (/[\x00-\x1f\x7f]/.test(keyId)) {
1523
+ return JSON.stringify({
1524
+ error: "key_id contains invalid control characters",
1525
+ });
1526
+ }
1527
+ const result = await api("DELETE", `/api/keys/${encodeURIComponent(keyId)}`);
1528
+ return JSON.stringify(result);
1529
+ }
1530
+ // -----------------------------------------------------------------------
1531
+ // Plans
1532
+ // -----------------------------------------------------------------------
1533
+ async function handleGetPlan() {
1534
+ const result = await api("GET", "/api/plans/user/plan");
1535
+ return JSON.stringify(result);
1536
+ }
1537
+ async function handleListPlans() {
1538
+ const result = await api("GET", "/api/plans");
1539
+ return JSON.stringify(result);
1540
+ }
1541
+ async function handleChangePlan(args) {
1542
+ const planId = args.plan_id;
1543
+ if (!planId || typeof planId !== "string") {
1544
+ return JSON.stringify({
1545
+ error: "plan_id is required and must be a string",
1546
+ });
1547
+ }
1548
+ if (/[\x00-\x1f\x7f]/.test(planId)) {
1549
+ return JSON.stringify({
1550
+ error: "plan_id contains invalid control characters",
1551
+ });
1552
+ }
1553
+ const result = await api("PUT", "/api/plans/user/plan", { planId });
1554
+ return JSON.stringify(result);
1555
+ }
1556
+ // -----------------------------------------------------------------------
1557
+ // Teams extended
1558
+ // -----------------------------------------------------------------------
1559
+ async function handleTeamDelete(args) {
1560
+ const teamId = args.team_id;
1561
+ if (!teamId || typeof teamId !== "string") {
1562
+ return JSON.stringify({
1563
+ error: "team_id is required and must be a string",
1564
+ });
1565
+ }
1566
+ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(teamId)) {
1567
+ return JSON.stringify({ error: "team_id must be a valid UUID" });
1568
+ }
1569
+ const result = await api("DELETE", `/api/teams/${encodeURIComponent(teamId)}`);
1570
+ return JSON.stringify(result);
1571
+ }
1572
+ async function handleTeamRevokeKey(args) {
1573
+ const teamId = args.team_id;
1574
+ const keyId = args.key_id;
1575
+ if (!teamId || typeof teamId !== "string") {
1576
+ return JSON.stringify({
1577
+ error: "team_id is required and must be a string",
1578
+ });
1579
+ }
1580
+ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(teamId)) {
1581
+ return JSON.stringify({ error: "team_id must be a valid UUID" });
1582
+ }
1583
+ if (!keyId || typeof keyId !== "string") {
1584
+ return JSON.stringify({
1585
+ error: "key_id is required and must be a string",
1586
+ });
1587
+ }
1588
+ if (/[\x00-\x1f\x7f]/.test(keyId)) {
1589
+ return JSON.stringify({
1590
+ error: "key_id contains invalid control characters",
1591
+ });
1592
+ }
1593
+ const result = await api("DELETE", `/api/teams/${encodeURIComponent(teamId)}/keys/${encodeURIComponent(keyId)}`);
1594
+ return JSON.stringify(result);
1595
+ }
1596
+ async function handleTeamListKeys(args) {
1597
+ const teamId = args.team_id;
1598
+ if (!teamId || typeof teamId !== "string") {
1599
+ return JSON.stringify({
1600
+ error: "team_id is required and must be a string",
1601
+ });
1602
+ }
1603
+ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(teamId)) {
1604
+ return JSON.stringify({ error: "team_id must be a valid UUID" });
1605
+ }
1606
+ const result = await api("GET", `/api/teams/${encodeURIComponent(teamId)}/keys`);
1607
+ return JSON.stringify(result);
1608
+ }
1609
+ async function handleTeamListMembers(args) {
1610
+ const teamId = args.team_id;
1611
+ if (!teamId || typeof teamId !== "string") {
1612
+ return JSON.stringify({
1613
+ error: "team_id is required and must be a string",
1614
+ });
1615
+ }
1616
+ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(teamId)) {
1617
+ return JSON.stringify({ error: "team_id must be a valid UUID" });
1618
+ }
1619
+ const result = await api("GET", `/api/teams/${encodeURIComponent(teamId)}/members`);
1620
+ return JSON.stringify(result);
1621
+ }
1622
+ async function handleTeamAddMember(args) {
1623
+ const teamId = args.team_id;
1624
+ const userId = args.user_id;
1625
+ if (!teamId || typeof teamId !== "string") {
1626
+ return JSON.stringify({
1627
+ error: "team_id is required and must be a string",
1628
+ });
1629
+ }
1630
+ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(teamId)) {
1631
+ return JSON.stringify({ error: "team_id must be a valid UUID" });
1632
+ }
1633
+ if (!userId || typeof userId !== "string") {
1634
+ return JSON.stringify({
1635
+ error: "user_id is required and must be a string",
1636
+ });
1637
+ }
1638
+ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(userId)) {
1639
+ return JSON.stringify({ error: "user_id must be a valid UUID" });
1640
+ }
1641
+ const result = await api("POST", `/api/teams/${encodeURIComponent(teamId)}/members`, { userId });
1642
+ return JSON.stringify(result);
1643
+ }
1644
+ async function handleTeamRemoveMember(args) {
1645
+ const teamId = args.team_id;
1646
+ const userId = args.user_id;
1647
+ if (!teamId || typeof teamId !== "string") {
1648
+ return JSON.stringify({
1649
+ error: "team_id is required and must be a string",
1650
+ });
1651
+ }
1652
+ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(teamId)) {
1653
+ return JSON.stringify({ error: "team_id must be a valid UUID" });
1654
+ }
1655
+ if (!userId || typeof userId !== "string") {
1656
+ return JSON.stringify({
1657
+ error: "user_id is required and must be a string",
1658
+ });
1659
+ }
1660
+ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(userId)) {
1661
+ return JSON.stringify({ error: "user_id must be a valid UUID" });
1662
+ }
1663
+ const result = await api("DELETE", `/api/teams/${encodeURIComponent(teamId)}/members/${encodeURIComponent(userId)}`);
1664
+ return JSON.stringify(result);
1665
+ }
1666
+ async function handleTeamInviteMember(args) {
1667
+ const teamId = args.team_id;
1668
+ const email = args.email;
1669
+ const role = args.role ?? "member";
1670
+ if (!teamId || typeof teamId !== "string") {
1671
+ return JSON.stringify({
1672
+ error: "team_id is required and must be a string",
1673
+ });
1674
+ }
1675
+ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(teamId)) {
1676
+ return JSON.stringify({ error: "team_id must be a valid UUID" });
1677
+ }
1678
+ if (!email || typeof email !== "string") {
1679
+ return JSON.stringify({
1680
+ error: "email is required and must be a string",
1681
+ });
1682
+ }
1683
+ if (!/^[^@]+@[^@]+\.[^@]+$/.test(email)) {
1684
+ return JSON.stringify({ error: "email must be a valid email address" });
1685
+ }
1686
+ if (/[\x00-\x1f\x7f]/.test(email)) {
1687
+ return JSON.stringify({
1688
+ error: "email contains invalid control characters",
1689
+ });
1690
+ }
1691
+ if (role !== "member" && role !== "admin") {
1692
+ return JSON.stringify({ error: "role must be 'member' or 'admin'" });
1693
+ }
1694
+ const result = await api("POST", `/api/teams/${encodeURIComponent(teamId)}/invites`, { email, role });
1695
+ return JSON.stringify(result);
1696
+ }
1697
+ async function handleTeamListInvites(args) {
1698
+ const teamId = args.team_id;
1699
+ if (!teamId || typeof teamId !== "string") {
1700
+ return JSON.stringify({
1701
+ error: "team_id is required and must be a string",
1702
+ });
1703
+ }
1704
+ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(teamId)) {
1705
+ return JSON.stringify({ error: "team_id must be a valid UUID" });
1706
+ }
1707
+ const result = await api("GET", `/api/teams/${encodeURIComponent(teamId)}/invites`);
1708
+ return JSON.stringify(result);
1709
+ }
1710
+ async function handleTeamCancelInvite(args) {
1711
+ const teamId = args.team_id;
1712
+ const inviteId = args.invite_id;
1713
+ if (!teamId || typeof teamId !== "string") {
1714
+ return JSON.stringify({
1715
+ error: "team_id is required and must be a string",
1716
+ });
1717
+ }
1718
+ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(teamId)) {
1719
+ return JSON.stringify({ error: "team_id must be a valid UUID" });
1720
+ }
1721
+ if (!inviteId || typeof inviteId !== "string") {
1722
+ return JSON.stringify({
1723
+ error: "invite_id is required and must be a string",
1724
+ });
1725
+ }
1726
+ if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(inviteId)) {
1727
+ return JSON.stringify({ error: "invite_id must be a valid UUID" });
1728
+ }
1729
+ const result = await api("DELETE", `/api/teams/${encodeURIComponent(teamId)}/invites/${encodeURIComponent(inviteId)}`);
1730
+ return JSON.stringify(result);
1731
+ }
1732
+ // -----------------------------------------------------------------------
1004
1733
  // Dispatch table
1005
1734
  // -----------------------------------------------------------------------
1006
1735
  const handlers = {
@@ -1008,6 +1737,7 @@ export function createDominusNodeFunctionHandler(config) {
1008
1737
  dominusnode_check_balance: handleCheckBalance,
1009
1738
  dominusnode_check_usage: handleCheckUsage,
1010
1739
  dominusnode_get_proxy_config: handleGetProxyConfig,
1740
+ dominusnode_get_proxy_status: handleGetProxyStatus,
1011
1741
  dominusnode_list_sessions: handleListSessions,
1012
1742
  dominusnode_create_agentic_wallet: handleCreateAgenticWallet,
1013
1743
  dominusnode_fund_agentic_wallet: handleFundAgenticWallet,
@@ -1017,6 +1747,23 @@ export function createDominusNodeFunctionHandler(config) {
1017
1747
  dominusnode_freeze_agentic_wallet: handleFreezeAgenticWallet,
1018
1748
  dominusnode_unfreeze_agentic_wallet: handleUnfreezeAgenticWallet,
1019
1749
  dominusnode_delete_agentic_wallet: handleDeleteAgenticWallet,
1750
+ dominusnode_get_transactions: handleGetTransactions,
1751
+ dominusnode_get_forecast: handleGetForecast,
1752
+ dominusnode_check_payment: handleCheckPayment,
1753
+ dominusnode_get_daily_usage: handleGetDailyUsage,
1754
+ dominusnode_get_top_hosts: handleGetTopHosts,
1755
+ dominusnode_register: handleRegister,
1756
+ dominusnode_login: handleLogin,
1757
+ dominusnode_get_account_info: handleGetAccountInfo,
1758
+ dominusnode_verify_email: handleVerifyEmail,
1759
+ dominusnode_resend_verification: handleResendVerification,
1760
+ dominusnode_update_password: handleUpdatePassword,
1761
+ dominusnode_list_keys: handleListKeys,
1762
+ dominusnode_create_key: handleCreateKey,
1763
+ dominusnode_revoke_key: handleRevokeKey,
1764
+ dominusnode_get_plan: handleGetPlan,
1765
+ dominusnode_list_plans: handleListPlans,
1766
+ dominusnode_change_plan: handleChangePlan,
1020
1767
  dominusnode_create_team: handleCreateTeam,
1021
1768
  dominusnode_list_teams: handleListTeams,
1022
1769
  dominusnode_team_details: handleTeamDetails,
@@ -1025,16 +1772,33 @@ export function createDominusNodeFunctionHandler(config) {
1025
1772
  dominusnode_team_usage: handleTeamUsage,
1026
1773
  dominusnode_update_team: handleUpdateTeam,
1027
1774
  dominusnode_update_team_member_role: handleUpdateTeamMemberRole,
1775
+ dominusnode_team_delete: handleTeamDelete,
1776
+ dominusnode_team_revoke_key: handleTeamRevokeKey,
1777
+ dominusnode_team_list_keys: handleTeamListKeys,
1778
+ dominusnode_team_list_members: handleTeamListMembers,
1779
+ dominusnode_team_add_member: handleTeamAddMember,
1780
+ dominusnode_team_remove_member: handleTeamRemoveMember,
1781
+ dominusnode_team_invite_member: handleTeamInviteMember,
1782
+ dominusnode_team_list_invites: handleTeamListInvites,
1783
+ dominusnode_team_cancel_invite: handleTeamCancelInvite,
1028
1784
  dominusnode_topup_paypal: handleTopupPaypal,
1029
1785
  dominusnode_topup_stripe: handleTopupStripe,
1030
1786
  dominusnode_topup_crypto: handleTopupCrypto,
1031
1787
  dominusnode_x402_info: handleX402Info,
1032
1788
  dominusnode_update_wallet_policy: handleUpdateWalletPolicy,
1789
+ // MPP (5)
1790
+ dominusnode_mpp_info: handleMppInfo,
1791
+ dominusnode_mpp_challenge: handleMppChallenge,
1792
+ dominusnode_pay_mpp: handlePayMpp,
1793
+ dominusnode_mpp_session_open: handleMppSessionOpen,
1794
+ dominusnode_mpp_session_close: handleMppSessionClose,
1033
1795
  };
1034
1796
  async function handleUpdateWalletPolicy(args) {
1035
1797
  const walletId = args.wallet_id;
1036
1798
  if (!walletId || typeof walletId !== "string") {
1037
- return JSON.stringify({ error: "wallet_id is required and must be a string" });
1799
+ return JSON.stringify({
1800
+ error: "wallet_id is required and must be a string",
1801
+ });
1038
1802
  }
1039
1803
  if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(walletId)) {
1040
1804
  return JSON.stringify({ error: "wallet_id must be a valid UUID" });
@@ -1046,7 +1810,9 @@ export function createDominusNodeFunctionHandler(config) {
1046
1810
  }
1047
1811
  else {
1048
1812
  const dailyLimit = Number(args.daily_limit_cents);
1049
- if (!Number.isInteger(dailyLimit) || dailyLimit < 1 || dailyLimit > 1_000_000) {
1813
+ if (!Number.isInteger(dailyLimit) ||
1814
+ dailyLimit < 1 ||
1815
+ dailyLimit > 1_000_000) {
1050
1816
  return JSON.stringify({
1051
1817
  error: "daily_limit_cents must be a positive integer between 1 and 1,000,000, or null to clear",
1052
1818
  });
@@ -1060,22 +1826,32 @@ export function createDominusNodeFunctionHandler(config) {
1060
1826
  }
1061
1827
  else {
1062
1828
  if (!Array.isArray(args.allowed_domains)) {
1063
- return JSON.stringify({ error: "allowed_domains must be an array of domain strings, or null to clear" });
1829
+ return JSON.stringify({
1830
+ error: "allowed_domains must be an array of domain strings, or null to clear",
1831
+ });
1064
1832
  }
1065
1833
  if (args.allowed_domains.length > 100) {
1066
- return JSON.stringify({ error: "allowed_domains must have at most 100 entries" });
1834
+ return JSON.stringify({
1835
+ error: "allowed_domains must have at most 100 entries",
1836
+ });
1067
1837
  }
1068
1838
  const domainRe = /^(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/;
1069
1839
  for (let i = 0; i < args.allowed_domains.length; i++) {
1070
1840
  const d = args.allowed_domains[i];
1071
1841
  if (typeof d !== "string") {
1072
- return JSON.stringify({ error: `allowed_domains[${i}] must be a string` });
1842
+ return JSON.stringify({
1843
+ error: `allowed_domains[${i}] must be a string`,
1844
+ });
1073
1845
  }
1074
1846
  if (d.length > 253) {
1075
- return JSON.stringify({ error: `allowed_domains[${i}] exceeds 253 characters` });
1847
+ return JSON.stringify({
1848
+ error: `allowed_domains[${i}] exceeds 253 characters`,
1849
+ });
1076
1850
  }
1077
1851
  if (!domainRe.test(d)) {
1078
- return JSON.stringify({ error: `allowed_domains[${i}] is not a valid domain: ${d}` });
1852
+ return JSON.stringify({
1853
+ error: `allowed_domains[${i}] is not a valid domain: ${d}`,
1854
+ });
1079
1855
  }
1080
1856
  }
1081
1857
  body.allowedDomains = args.allowed_domains;
@@ -1094,6 +1870,121 @@ export function createDominusNodeFunctionHandler(config) {
1094
1870
  return JSON.stringify(result);
1095
1871
  }
1096
1872
  // -----------------------------------------------------------------------
1873
+ // MPP (Machine Payment Protocol) handlers
1874
+ // -----------------------------------------------------------------------
1875
+ async function handleMppInfo() {
1876
+ // MPP info is public (no auth required), but we use the authenticated path
1877
+ // for simplicity since the handler is already authenticated.
1878
+ const result = await api("GET", "/api/mpp/info");
1879
+ return JSON.stringify(result);
1880
+ }
1881
+ async function handleMppChallenge(args) {
1882
+ const poolType = String(args.pool_type ?? "dc");
1883
+ if (poolType !== "dc" && poolType !== "residential") {
1884
+ return JSON.stringify({
1885
+ error: "pool_type must be 'dc' or 'residential'",
1886
+ });
1887
+ }
1888
+ // This is a public endpoint -- no auth required
1889
+ try {
1890
+ const reqHeaders = {
1891
+ "User-Agent": "dominusnode-gemini/1.0.0",
1892
+ "Content-Type": "application/json",
1893
+ };
1894
+ if (agentSecret) {
1895
+ reqHeaders["X-DominusNode-Agent"] = "mcp";
1896
+ reqHeaders["X-DominusNode-Agent-Secret"] = agentSecret;
1897
+ }
1898
+ const response = await fetch(`${baseUrl}/api/mpp/challenge`, {
1899
+ method: "POST",
1900
+ headers: reqHeaders,
1901
+ body: JSON.stringify({ poolType }),
1902
+ signal: AbortSignal.timeout(timeoutMs),
1903
+ redirect: "error",
1904
+ });
1905
+ const responseText = await response.text();
1906
+ if (responseText.length > MAX_RESPONSE_BYTES) {
1907
+ return JSON.stringify({ error: "Response body exceeds size limit" });
1908
+ }
1909
+ if (!response.ok) {
1910
+ let message;
1911
+ try {
1912
+ const parsed = JSON.parse(responseText);
1913
+ message = parsed.error ?? parsed.message ?? responseText;
1914
+ }
1915
+ catch {
1916
+ message = responseText;
1917
+ }
1918
+ if (message.length > 500)
1919
+ message = message.slice(0, 500) + "... [truncated]";
1920
+ return JSON.stringify({
1921
+ error: `MPP challenge failed: ${sanitizeError(message)}`,
1922
+ });
1923
+ }
1924
+ const data = safeJsonParse(responseText);
1925
+ return JSON.stringify(data);
1926
+ }
1927
+ catch (err) {
1928
+ return JSON.stringify({
1929
+ error: `MPP challenge failed: ${sanitizeError(err instanceof Error ? err.message : String(err))}`,
1930
+ });
1931
+ }
1932
+ }
1933
+ async function handlePayMpp(args) {
1934
+ const amountCents = args.amount_cents;
1935
+ const method = String(args.method ?? "");
1936
+ if (!Number.isInteger(amountCents) ||
1937
+ amountCents < 500 ||
1938
+ amountCents > 100_000) {
1939
+ return JSON.stringify({
1940
+ error: "amount_cents must be an integer between 500 ($5) and 100,000 ($1,000)",
1941
+ });
1942
+ }
1943
+ if (!["tempo", "stripe_spt", "lightning"].includes(method)) {
1944
+ return JSON.stringify({
1945
+ error: "method must be one of: tempo, stripe_spt, lightning",
1946
+ });
1947
+ }
1948
+ const result = await api("POST", "/api/mpp/topup", { amountCents, method });
1949
+ return JSON.stringify(result);
1950
+ }
1951
+ async function handleMppSessionOpen(args) {
1952
+ const maxDepositCents = args.max_deposit_cents;
1953
+ const method = String(args.method ?? "");
1954
+ const poolType = String(args.pool_type ?? "dc");
1955
+ if (!Number.isInteger(maxDepositCents) ||
1956
+ maxDepositCents < 500 ||
1957
+ maxDepositCents > 100_000) {
1958
+ return JSON.stringify({
1959
+ error: "max_deposit_cents must be an integer between 500 and 100,000",
1960
+ });
1961
+ }
1962
+ if (!["tempo", "stripe_spt", "lightning"].includes(method)) {
1963
+ return JSON.stringify({
1964
+ error: "method must be one of: tempo, stripe_spt, lightning",
1965
+ });
1966
+ }
1967
+ if (!["dc", "residential"].includes(poolType)) {
1968
+ return JSON.stringify({
1969
+ error: "pool_type must be one of: dc, residential",
1970
+ });
1971
+ }
1972
+ const result = await api("POST", "/api/mpp/session/open", {
1973
+ maxDepositCents,
1974
+ method,
1975
+ poolType,
1976
+ });
1977
+ return JSON.stringify(result);
1978
+ }
1979
+ async function handleMppSessionClose(args) {
1980
+ const channelId = String(args.channel_id ?? "");
1981
+ if (!channelId) {
1982
+ return JSON.stringify({ error: "channel_id is required" });
1983
+ }
1984
+ const result = await api("POST", "/api/mpp/session/close", { channelId });
1985
+ return JSON.stringify(result);
1986
+ }
1987
+ // -----------------------------------------------------------------------
1097
1988
  // Main handler
1098
1989
  // -----------------------------------------------------------------------
1099
1990
  return async function handler(name, args) {