dominusnode-camel 1.0.1__tar.gz → 1.2.0__tar.gz
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.
Potentially problematic release.
This version of dominusnode-camel might be problematic. Click here for more details.
- {dominusnode_camel-1.0.1 → dominusnode_camel-1.2.0}/PKG-INFO +2 -2
- {dominusnode_camel-1.0.1 → dominusnode_camel-1.2.0}/README.md +1 -1
- {dominusnode_camel-1.0.1 → dominusnode_camel-1.2.0}/dominusnode_camel/tools.py +975 -17
- {dominusnode_camel-1.0.1 → dominusnode_camel-1.2.0}/dominusnode_camel.egg-info/PKG-INFO +2 -2
- {dominusnode_camel-1.0.1 → dominusnode_camel-1.2.0}/pyproject.toml +1 -1
- {dominusnode_camel-1.0.1 → dominusnode_camel-1.2.0}/tests/test_tools.py +46 -3
- {dominusnode_camel-1.0.1 → dominusnode_camel-1.2.0}/LICENSE +0 -0
- {dominusnode_camel-1.0.1 → dominusnode_camel-1.2.0}/dominusnode_camel/__init__.py +0 -0
- {dominusnode_camel-1.0.1 → dominusnode_camel-1.2.0}/dominusnode_camel.egg-info/SOURCES.txt +0 -0
- {dominusnode_camel-1.0.1 → dominusnode_camel-1.2.0}/dominusnode_camel.egg-info/dependency_links.txt +0 -0
- {dominusnode_camel-1.0.1 → dominusnode_camel-1.2.0}/dominusnode_camel.egg-info/requires.txt +0 -0
- {dominusnode_camel-1.0.1 → dominusnode_camel-1.2.0}/dominusnode_camel.egg-info/top_level.txt +0 -0
- {dominusnode_camel-1.0.1 → dominusnode_camel-1.2.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dominusnode-camel
|
|
3
|
-
Version: 1.0
|
|
3
|
+
Version: 1.2.0
|
|
4
4
|
Summary: Dominus Node rotating proxy tools for CAMEL-AI agents
|
|
5
5
|
License: MIT
|
|
6
6
|
Requires-Python: >=3.10
|
|
@@ -15,7 +15,7 @@ Dynamic: license-file
|
|
|
15
15
|
|
|
16
16
|
# dominusnode-camel
|
|
17
17
|
|
|
18
|
-
CAMEL-AI toolkit for the [Dominus Node](https://dominusnode.com) rotating proxy-as-a-service platform. Provides
|
|
18
|
+
CAMEL-AI toolkit for the [Dominus Node](https://dominusnode.com) rotating proxy-as-a-service platform. Provides 53 tools as CAMEL `FunctionTool` objects for proxy management, wallet operations, team administration, account management, API key management, plan management, and payment processing.
|
|
19
19
|
|
|
20
20
|
## Installation
|
|
21
21
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# dominusnode-camel
|
|
2
2
|
|
|
3
|
-
CAMEL-AI toolkit for the [Dominus Node](https://dominusnode.com) rotating proxy-as-a-service platform. Provides
|
|
3
|
+
CAMEL-AI toolkit for the [Dominus Node](https://dominusnode.com) rotating proxy-as-a-service platform. Provides 53 tools as CAMEL `FunctionTool` objects for proxy management, wallet operations, team administration, account management, API key management, plan management, and payment processing.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -1,15 +1,19 @@
|
|
|
1
|
-
"""Dominus Node CAMEL-AI toolkit --
|
|
1
|
+
"""Dominus Node CAMEL-AI toolkit -- 53 proxy, wallet, team, and payment tools.
|
|
2
2
|
|
|
3
|
-
Provides a CAMEL-AI compatible toolkit class that exposes
|
|
3
|
+
Provides a CAMEL-AI compatible toolkit class that exposes 53 tools as
|
|
4
4
|
``FunctionTool`` objects for use with CAMEL agents.
|
|
5
5
|
|
|
6
6
|
Tools cover:
|
|
7
7
|
- Proxied HTTP fetching through rotating proxies
|
|
8
|
-
- Wallet balance and usage monitoring
|
|
9
|
-
- Proxy configuration and session listing
|
|
8
|
+
- Wallet balance, transactions, forecast, and usage monitoring
|
|
9
|
+
- Proxy configuration, status, and session listing
|
|
10
10
|
- Agentic wallet management (create, fund, freeze, unfreeze, delete)
|
|
11
|
-
- Team management (create, list, fund, keys, usage, update, role changes
|
|
12
|
-
|
|
11
|
+
- Team management (create, list, fund, keys, usage, update, role changes,
|
|
12
|
+
members, invites, delete)
|
|
13
|
+
- Account management (register, login, info, email verification, password)
|
|
14
|
+
- API key management (list, create, revoke)
|
|
15
|
+
- Plan management (get, list, change)
|
|
16
|
+
- PayPal, Stripe, and crypto top-up
|
|
13
17
|
- x402 micropayment info
|
|
14
18
|
|
|
15
19
|
Security:
|
|
@@ -39,6 +43,7 @@ from __future__ import annotations
|
|
|
39
43
|
|
|
40
44
|
import ipaddress
|
|
41
45
|
import json
|
|
46
|
+
import math
|
|
42
47
|
import os
|
|
43
48
|
import re
|
|
44
49
|
import socket
|
|
@@ -494,6 +499,53 @@ def _validate_domains(domains: Any) -> Optional[str]:
|
|
|
494
499
|
return None
|
|
495
500
|
|
|
496
501
|
|
|
502
|
+
_EMAIL_RE = re.compile(r"^[^@\s]+@[^@\s]+\.[^@\s]+$")
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
def _validate_email(value: Any, field_name: str = "email") -> Optional[str]:
|
|
506
|
+
"""Validate an email string, returning an error message or None."""
|
|
507
|
+
if not value or not isinstance(value, str):
|
|
508
|
+
return f"{field_name} is required and must be a string"
|
|
509
|
+
if len(value) > 254:
|
|
510
|
+
return f"{field_name} must be 254 characters or fewer"
|
|
511
|
+
if not _EMAIL_RE.match(value):
|
|
512
|
+
return f"{field_name} must be a valid email address"
|
|
513
|
+
return None
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
def _validate_password(value: Any, field_name: str = "password") -> Optional[str]:
|
|
517
|
+
"""Validate a password string, returning an error message or None."""
|
|
518
|
+
if not value or not isinstance(value, str):
|
|
519
|
+
return f"{field_name} is required and must be a string"
|
|
520
|
+
if len(value) < 8:
|
|
521
|
+
return f"{field_name} must be at least 8 characters"
|
|
522
|
+
if len(value) > 128:
|
|
523
|
+
return f"{field_name} must be 128 characters or fewer"
|
|
524
|
+
return None
|
|
525
|
+
|
|
526
|
+
|
|
527
|
+
def _validate_finite_int(
|
|
528
|
+
value: Any,
|
|
529
|
+
field_name: str,
|
|
530
|
+
min_value: int = 1,
|
|
531
|
+
max_value: int = 2_147_483_647,
|
|
532
|
+
) -> Optional[str]:
|
|
533
|
+
"""Validate integer with NaN/Infinity guards (Python: float('nan') etc.)."""
|
|
534
|
+
if isinstance(value, bool):
|
|
535
|
+
return f"{field_name} must be an integer"
|
|
536
|
+
if isinstance(value, float):
|
|
537
|
+
if not math.isfinite(value):
|
|
538
|
+
return f"{field_name} must be a finite number"
|
|
539
|
+
if value != int(value):
|
|
540
|
+
return f"{field_name} must be an integer"
|
|
541
|
+
value = int(value)
|
|
542
|
+
if not isinstance(value, int):
|
|
543
|
+
return f"{field_name} must be an integer"
|
|
544
|
+
if value < min_value or value > max_value:
|
|
545
|
+
return f"{field_name} must be between {min_value} and {max_value}"
|
|
546
|
+
return None
|
|
547
|
+
|
|
548
|
+
|
|
497
549
|
# ===========================================================================
|
|
498
550
|
# DominusNodeToolkit -- main toolkit class
|
|
499
551
|
# ===========================================================================
|
|
@@ -502,9 +554,10 @@ def _validate_domains(domains: Any) -> Optional[str]:
|
|
|
502
554
|
class DominusNodeToolkit:
|
|
503
555
|
"""CAMEL-AI toolkit providing Dominus Node rotating proxy tools.
|
|
504
556
|
|
|
505
|
-
Authenticates using a Dominus Node API key and exposes
|
|
506
|
-
``FunctionTool`` objects for proxy, wallet, team,
|
|
507
|
-
All tool methods return dicts suitable for JSON
|
|
557
|
+
Authenticates using a Dominus Node API key and exposes 53 tools as CAMEL
|
|
558
|
+
``FunctionTool`` objects for proxy, wallet, team, account, key, plan,
|
|
559
|
+
and payment management. All tool methods return dicts suitable for JSON
|
|
560
|
+
serialization.
|
|
508
561
|
|
|
509
562
|
Args:
|
|
510
563
|
api_key: Dominus Node API key (``dn_live_...`` or ``dn_test_...``).
|
|
@@ -530,12 +583,14 @@ class DominusNodeToolkit:
|
|
|
530
583
|
proxy_host: str = "proxy.dominusnode.com",
|
|
531
584
|
proxy_port: int = 8080,
|
|
532
585
|
timeout: float = 30.0,
|
|
586
|
+
agent_secret: str | None = None,
|
|
533
587
|
):
|
|
534
588
|
self.api_key = api_key or os.environ.get("DOMINUSNODE_API_KEY", "")
|
|
535
589
|
self.base_url = base_url.rstrip("/")
|
|
536
590
|
self.proxy_host = os.environ.get("DOMINUSNODE_PROXY_HOST", proxy_host)
|
|
537
591
|
self.proxy_port = int(os.environ.get("DOMINUSNODE_PROXY_PORT", str(proxy_port)))
|
|
538
592
|
self.timeout = timeout
|
|
593
|
+
self.agent_secret: str | None = agent_secret or os.environ.get("DOMINUSNODE_AGENT_SECRET")
|
|
539
594
|
self._token: str | None = None
|
|
540
595
|
|
|
541
596
|
# ------------------------------------------------------------------
|
|
@@ -561,13 +616,17 @@ class DominusNodeToolkit:
|
|
|
561
616
|
timeout=self.timeout,
|
|
562
617
|
follow_redirects=False,
|
|
563
618
|
) as client:
|
|
619
|
+
auth_headers = {
|
|
620
|
+
"User-Agent": "dominusnode-camel/1.0.0",
|
|
621
|
+
"Content-Type": "application/json",
|
|
622
|
+
}
|
|
623
|
+
if self.agent_secret:
|
|
624
|
+
auth_headers["X-DominusNode-Agent"] = "mcp"
|
|
625
|
+
auth_headers["X-DominusNode-Agent-Secret"] = self.agent_secret
|
|
564
626
|
resp = client.post(
|
|
565
627
|
f"{self.base_url}/api/auth/verify-key",
|
|
566
628
|
json={"apiKey": self.api_key},
|
|
567
|
-
headers=
|
|
568
|
-
"User-Agent": "dominusnode-camel/1.0.0",
|
|
569
|
-
"Content-Type": "application/json",
|
|
570
|
-
},
|
|
629
|
+
headers=auth_headers,
|
|
571
630
|
)
|
|
572
631
|
|
|
573
632
|
if resp.status_code != 200:
|
|
@@ -589,7 +648,69 @@ class DominusNodeToolkit:
|
|
|
589
648
|
self._authenticate()
|
|
590
649
|
|
|
591
650
|
# ------------------------------------------------------------------
|
|
592
|
-
# API request helper
|
|
651
|
+
# Unauthenticated API request helper
|
|
652
|
+
# ------------------------------------------------------------------
|
|
653
|
+
|
|
654
|
+
def _unauthenticated_request(
|
|
655
|
+
self,
|
|
656
|
+
method: str,
|
|
657
|
+
path: str,
|
|
658
|
+
body: Optional[Dict[str, Any]] = None,
|
|
659
|
+
) -> Any:
|
|
660
|
+
"""Make an unauthenticated API request (for register, login, verify-email).
|
|
661
|
+
|
|
662
|
+
Args:
|
|
663
|
+
method: HTTP method (GET, POST).
|
|
664
|
+
path: API path (e.g., ``/api/auth/register``).
|
|
665
|
+
body: Optional JSON body.
|
|
666
|
+
|
|
667
|
+
Returns:
|
|
668
|
+
Parsed JSON response (with dangerous keys stripped).
|
|
669
|
+
|
|
670
|
+
Raises:
|
|
671
|
+
RuntimeError: On API errors or oversized responses.
|
|
672
|
+
"""
|
|
673
|
+
with httpx.Client(
|
|
674
|
+
timeout=self.timeout,
|
|
675
|
+
follow_redirects=False,
|
|
676
|
+
) as client:
|
|
677
|
+
headers: Dict[str, str] = {
|
|
678
|
+
"User-Agent": "dominusnode-camel/1.0.0",
|
|
679
|
+
"Content-Type": "application/json",
|
|
680
|
+
}
|
|
681
|
+
if self.agent_secret:
|
|
682
|
+
headers["X-DominusNode-Agent"] = "mcp"
|
|
683
|
+
headers["X-DominusNode-Agent-Secret"] = self.agent_secret
|
|
684
|
+
|
|
685
|
+
kwargs: Dict[str, Any] = {"headers": headers}
|
|
686
|
+
|
|
687
|
+
if body is not None and method.upper() not in ("GET", "HEAD", "OPTIONS"):
|
|
688
|
+
kwargs["json"] = body
|
|
689
|
+
|
|
690
|
+
resp = client.request(method, f"{self.base_url}{path}", **kwargs)
|
|
691
|
+
|
|
692
|
+
if len(resp.content) > MAX_RESPONSE_BYTES:
|
|
693
|
+
raise RuntimeError("Response body too large")
|
|
694
|
+
|
|
695
|
+
if resp.status_code >= 400:
|
|
696
|
+
try:
|
|
697
|
+
err_data = resp.json()
|
|
698
|
+
msg = err_data.get("error", resp.text)
|
|
699
|
+
except Exception:
|
|
700
|
+
msg = resp.text
|
|
701
|
+
msg = str(msg)[:500]
|
|
702
|
+
raise RuntimeError(
|
|
703
|
+
f"API error {resp.status_code}: {_sanitize_error(msg)}"
|
|
704
|
+
)
|
|
705
|
+
|
|
706
|
+
if resp.text:
|
|
707
|
+
data = resp.json()
|
|
708
|
+
_strip_dangerous_keys(data)
|
|
709
|
+
return data
|
|
710
|
+
return {}
|
|
711
|
+
|
|
712
|
+
# ------------------------------------------------------------------
|
|
713
|
+
# Authenticated API request helper
|
|
593
714
|
# ------------------------------------------------------------------
|
|
594
715
|
|
|
595
716
|
def _api_request(
|
|
@@ -625,6 +746,9 @@ class DominusNodeToolkit:
|
|
|
625
746
|
"Authorization": f"Bearer {self._token}",
|
|
626
747
|
},
|
|
627
748
|
}
|
|
749
|
+
if self.agent_secret:
|
|
750
|
+
kwargs["headers"]["X-DominusNode-Agent"] = "mcp"
|
|
751
|
+
kwargs["headers"]["X-DominusNode-Agent-Secret"] = self.agent_secret
|
|
628
752
|
|
|
629
753
|
if body is not None and method.upper() not in ("GET", "HEAD", "OPTIONS"):
|
|
630
754
|
kwargs["json"] = body
|
|
@@ -1407,7 +1531,84 @@ class DominusNodeToolkit:
|
|
|
1407
1531
|
return {"error": _sanitize_error(str(e))}
|
|
1408
1532
|
|
|
1409
1533
|
# ==================================================================
|
|
1410
|
-
# Tool 24:
|
|
1534
|
+
# Tool 24: topup_stripe
|
|
1535
|
+
# ==================================================================
|
|
1536
|
+
|
|
1537
|
+
def topup_stripe(self, amount_cents: int) -> Dict[str, Any]:
|
|
1538
|
+
"""Create a Stripe checkout session for wallet top-up.
|
|
1539
|
+
|
|
1540
|
+
Args:
|
|
1541
|
+
amount_cents: Amount to top up in cents. Minimum $5.00 (500 cents),
|
|
1542
|
+
maximum $1,000.00 (100,000 cents).
|
|
1543
|
+
|
|
1544
|
+
Returns:
|
|
1545
|
+
Dict with ``sessionId`` and ``checkoutUrl`` for completing the
|
|
1546
|
+
Stripe checkout flow.
|
|
1547
|
+
"""
|
|
1548
|
+
err = _validate_positive_int(
|
|
1549
|
+
amount_cents, "amount_cents", min_value=500, max_value=100_000
|
|
1550
|
+
)
|
|
1551
|
+
if err:
|
|
1552
|
+
return {"error": err}
|
|
1553
|
+
|
|
1554
|
+
try:
|
|
1555
|
+
result = self._request_with_retry(
|
|
1556
|
+
"POST",
|
|
1557
|
+
"/api/wallet/topup/stripe",
|
|
1558
|
+
{"amountCents": amount_cents},
|
|
1559
|
+
)
|
|
1560
|
+
return result
|
|
1561
|
+
except Exception as e:
|
|
1562
|
+
return {"error": _sanitize_error(str(e))}
|
|
1563
|
+
|
|
1564
|
+
# ==================================================================
|
|
1565
|
+
# Tool 25: topup_crypto
|
|
1566
|
+
# ==================================================================
|
|
1567
|
+
|
|
1568
|
+
_VALID_CRYPTO_CURRENCIES: Set[str] = {
|
|
1569
|
+
"btc", "eth", "ltc", "xmr", "zec", "usdc", "sol", "usdt", "dai",
|
|
1570
|
+
"bnb", "link",
|
|
1571
|
+
}
|
|
1572
|
+
|
|
1573
|
+
def topup_crypto(self, amount_usd: float, currency: str) -> Dict[str, Any]:
|
|
1574
|
+
"""Create a crypto invoice for wallet top-up via NOWPayments.
|
|
1575
|
+
|
|
1576
|
+
Args:
|
|
1577
|
+
amount_usd: Amount in USD (5 to 1,000).
|
|
1578
|
+
currency: Cryptocurrency to pay with. One of: BTC, ETH, LTC,
|
|
1579
|
+
XMR, ZEC, USDC, SOL, USDT, DAI, BNB, LINK.
|
|
1580
|
+
|
|
1581
|
+
Returns:
|
|
1582
|
+
Dict with ``invoiceId`` and ``paymentUrl`` for completing the
|
|
1583
|
+
crypto payment.
|
|
1584
|
+
"""
|
|
1585
|
+
if not isinstance(amount_usd, (int, float)) or isinstance(amount_usd, bool):
|
|
1586
|
+
return {"error": "amount_usd must be a number between 5 and 1000"}
|
|
1587
|
+
if not math.isfinite(amount_usd):
|
|
1588
|
+
return {"error": "amount_usd must be a number between 5 and 1000"}
|
|
1589
|
+
if amount_usd < 5 or amount_usd > 1000:
|
|
1590
|
+
return {"error": "amount_usd must be a number between 5 and 1000"}
|
|
1591
|
+
|
|
1592
|
+
if not isinstance(currency, str) or currency.lower() not in self._VALID_CRYPTO_CURRENCIES:
|
|
1593
|
+
return {
|
|
1594
|
+
"error": (
|
|
1595
|
+
"currency must be one of: "
|
|
1596
|
+
"BTC, ETH, LTC, XMR, ZEC, USDC, SOL, USDT, DAI, BNB, LINK"
|
|
1597
|
+
)
|
|
1598
|
+
}
|
|
1599
|
+
|
|
1600
|
+
try:
|
|
1601
|
+
result = self._request_with_retry(
|
|
1602
|
+
"POST",
|
|
1603
|
+
"/api/wallet/topup/crypto",
|
|
1604
|
+
{"amountUsd": amount_usd, "currency": currency.lower()},
|
|
1605
|
+
)
|
|
1606
|
+
return result
|
|
1607
|
+
except Exception as e:
|
|
1608
|
+
return {"error": _sanitize_error(str(e))}
|
|
1609
|
+
|
|
1610
|
+
# ==================================================================
|
|
1611
|
+
# Tool 26: update_wallet_policy
|
|
1411
1612
|
# ==================================================================
|
|
1412
1613
|
|
|
1413
1614
|
def update_wallet_policy(
|
|
@@ -1459,27 +1660,746 @@ class DominusNodeToolkit:
|
|
|
1459
1660
|
except Exception as e:
|
|
1460
1661
|
return {"error": _sanitize_error(str(e))}
|
|
1461
1662
|
|
|
1663
|
+
# ==================================================================
|
|
1664
|
+
# Tool 27: get_proxy_status
|
|
1665
|
+
# ==================================================================
|
|
1666
|
+
|
|
1667
|
+
def get_proxy_status(self) -> Dict[str, Any]:
|
|
1668
|
+
"""Get the current status of the proxy gateway.
|
|
1669
|
+
|
|
1670
|
+
Returns:
|
|
1671
|
+
Dict with proxy gateway status information including uptime,
|
|
1672
|
+
active connections, and health metrics.
|
|
1673
|
+
"""
|
|
1674
|
+
try:
|
|
1675
|
+
result = self._request_with_retry("GET", "/api/proxy/status")
|
|
1676
|
+
return result
|
|
1677
|
+
except Exception as e:
|
|
1678
|
+
return {"error": _sanitize_error(str(e))}
|
|
1679
|
+
|
|
1680
|
+
# ==================================================================
|
|
1681
|
+
# Tool 28: get_transactions
|
|
1682
|
+
# ==================================================================
|
|
1683
|
+
|
|
1684
|
+
def get_transactions(self, limit: int = 20) -> Dict[str, Any]:
|
|
1685
|
+
"""Get wallet transaction history.
|
|
1686
|
+
|
|
1687
|
+
Args:
|
|
1688
|
+
limit: Maximum number of transactions to return (1 to 100,
|
|
1689
|
+
default 20).
|
|
1690
|
+
|
|
1691
|
+
Returns:
|
|
1692
|
+
Dict with wallet transaction history.
|
|
1693
|
+
"""
|
|
1694
|
+
err = _validate_finite_int(limit, "limit", min_value=1, max_value=100)
|
|
1695
|
+
if err:
|
|
1696
|
+
return {"error": err}
|
|
1697
|
+
limit = int(limit)
|
|
1698
|
+
|
|
1699
|
+
qs = f"?limit={limit}" if limit != 20 else ""
|
|
1700
|
+
|
|
1701
|
+
try:
|
|
1702
|
+
result = self._request_with_retry("GET", f"/api/wallet/transactions{qs}")
|
|
1703
|
+
return result
|
|
1704
|
+
except Exception as e:
|
|
1705
|
+
return {"error": _sanitize_error(str(e))}
|
|
1706
|
+
|
|
1707
|
+
# ==================================================================
|
|
1708
|
+
# Tool 29: get_forecast
|
|
1709
|
+
# ==================================================================
|
|
1710
|
+
|
|
1711
|
+
def get_forecast(self) -> Dict[str, Any]:
|
|
1712
|
+
"""Get wallet balance forecast based on current usage patterns.
|
|
1713
|
+
|
|
1714
|
+
Returns:
|
|
1715
|
+
Dict with forecast data including estimated depletion date
|
|
1716
|
+
and projected daily spend.
|
|
1717
|
+
"""
|
|
1718
|
+
try:
|
|
1719
|
+
result = self._request_with_retry("GET", "/api/wallet/forecast")
|
|
1720
|
+
return result
|
|
1721
|
+
except Exception as e:
|
|
1722
|
+
return {"error": _sanitize_error(str(e))}
|
|
1723
|
+
|
|
1724
|
+
# ==================================================================
|
|
1725
|
+
# Tool 30: check_payment
|
|
1726
|
+
# ==================================================================
|
|
1727
|
+
|
|
1728
|
+
def check_payment(self, invoice_id: str) -> Dict[str, Any]:
|
|
1729
|
+
"""Check the status of a crypto payment invoice.
|
|
1730
|
+
|
|
1731
|
+
Args:
|
|
1732
|
+
invoice_id: The invoice ID returned from topup_crypto.
|
|
1733
|
+
|
|
1734
|
+
Returns:
|
|
1735
|
+
Dict with the payment status and details.
|
|
1736
|
+
"""
|
|
1737
|
+
if not invoice_id or not isinstance(invoice_id, str):
|
|
1738
|
+
return {"error": "invoice_id is required and must be a string"}
|
|
1739
|
+
if len(invoice_id) > 200:
|
|
1740
|
+
return {"error": "invoice_id must be 200 characters or fewer"}
|
|
1741
|
+
|
|
1742
|
+
try:
|
|
1743
|
+
result = self._request_with_retry(
|
|
1744
|
+
"GET",
|
|
1745
|
+
f"/api/wallet/topup/crypto/{quote(invoice_id, safe='')}/status",
|
|
1746
|
+
)
|
|
1747
|
+
return result
|
|
1748
|
+
except Exception as e:
|
|
1749
|
+
return {"error": _sanitize_error(str(e))}
|
|
1750
|
+
|
|
1751
|
+
# ==================================================================
|
|
1752
|
+
# Tool 31: get_daily_usage
|
|
1753
|
+
# ==================================================================
|
|
1754
|
+
|
|
1755
|
+
def get_daily_usage(self, days: int = 30) -> Dict[str, Any]:
|
|
1756
|
+
"""Get daily usage breakdown for recent days.
|
|
1757
|
+
|
|
1758
|
+
Args:
|
|
1759
|
+
days: Number of days to retrieve (1 to 90, default 30).
|
|
1760
|
+
|
|
1761
|
+
Returns:
|
|
1762
|
+
Dict with per-day usage statistics.
|
|
1763
|
+
"""
|
|
1764
|
+
err = _validate_finite_int(days, "days", min_value=1, max_value=90)
|
|
1765
|
+
if err:
|
|
1766
|
+
return {"error": err}
|
|
1767
|
+
days = int(days)
|
|
1768
|
+
|
|
1769
|
+
try:
|
|
1770
|
+
result = self._request_with_retry(
|
|
1771
|
+
"GET", f"/api/usage/daily?days={days}"
|
|
1772
|
+
)
|
|
1773
|
+
return result
|
|
1774
|
+
except Exception as e:
|
|
1775
|
+
return {"error": _sanitize_error(str(e))}
|
|
1776
|
+
|
|
1777
|
+
# ==================================================================
|
|
1778
|
+
# Tool 32: get_top_hosts
|
|
1779
|
+
# ==================================================================
|
|
1780
|
+
|
|
1781
|
+
def get_top_hosts(self, limit: int = 10) -> Dict[str, Any]:
|
|
1782
|
+
"""Get top destination hosts by bandwidth usage.
|
|
1783
|
+
|
|
1784
|
+
Args:
|
|
1785
|
+
limit: Maximum number of hosts to return (1 to 50, default 10).
|
|
1786
|
+
|
|
1787
|
+
Returns:
|
|
1788
|
+
Dict with top hosts and their bandwidth consumption.
|
|
1789
|
+
"""
|
|
1790
|
+
err = _validate_finite_int(limit, "limit", min_value=1, max_value=50)
|
|
1791
|
+
if err:
|
|
1792
|
+
return {"error": err}
|
|
1793
|
+
limit = int(limit)
|
|
1794
|
+
|
|
1795
|
+
try:
|
|
1796
|
+
result = self._request_with_retry(
|
|
1797
|
+
"GET", f"/api/usage/top-hosts?limit={limit}"
|
|
1798
|
+
)
|
|
1799
|
+
return result
|
|
1800
|
+
except Exception as e:
|
|
1801
|
+
return {"error": _sanitize_error(str(e))}
|
|
1802
|
+
|
|
1803
|
+
# ==================================================================
|
|
1804
|
+
# Tool 33: register_account (unauthenticated)
|
|
1805
|
+
# ==================================================================
|
|
1806
|
+
|
|
1807
|
+
def register_account(self, email: str, password: str) -> Dict[str, Any]:
|
|
1808
|
+
"""Register a new Dominus Node account.
|
|
1809
|
+
|
|
1810
|
+
This is an unauthenticated operation. After registration, verify
|
|
1811
|
+
the email address to activate the account.
|
|
1812
|
+
|
|
1813
|
+
Args:
|
|
1814
|
+
email: Email address for the new account (max 254 characters).
|
|
1815
|
+
password: Password (8 to 128 characters).
|
|
1816
|
+
|
|
1817
|
+
Returns:
|
|
1818
|
+
Dict with the registered account details.
|
|
1819
|
+
"""
|
|
1820
|
+
err = _validate_email(email, "email")
|
|
1821
|
+
if err:
|
|
1822
|
+
return {"error": err}
|
|
1823
|
+
|
|
1824
|
+
err = _validate_password(password, "password")
|
|
1825
|
+
if err:
|
|
1826
|
+
return {"error": err}
|
|
1827
|
+
|
|
1828
|
+
try:
|
|
1829
|
+
result = self._unauthenticated_request(
|
|
1830
|
+
"POST",
|
|
1831
|
+
"/api/auth/register",
|
|
1832
|
+
{"email": email, "password": password},
|
|
1833
|
+
)
|
|
1834
|
+
return result
|
|
1835
|
+
except Exception as e:
|
|
1836
|
+
return {"error": _sanitize_error(str(e))}
|
|
1837
|
+
|
|
1838
|
+
# ==================================================================
|
|
1839
|
+
# Tool 34: login_account (unauthenticated)
|
|
1840
|
+
# ==================================================================
|
|
1841
|
+
|
|
1842
|
+
def login_account(self, email: str, password: str) -> Dict[str, Any]:
|
|
1843
|
+
"""Log in to an existing Dominus Node account.
|
|
1844
|
+
|
|
1845
|
+
This is an unauthenticated operation. Returns JWT tokens on success.
|
|
1846
|
+
|
|
1847
|
+
Args:
|
|
1848
|
+
email: Account email address.
|
|
1849
|
+
password: Account password.
|
|
1850
|
+
|
|
1851
|
+
Returns:
|
|
1852
|
+
Dict with ``accessToken`` and ``refreshToken`` on success,
|
|
1853
|
+
or ``mfaRequired`` if MFA is enabled.
|
|
1854
|
+
"""
|
|
1855
|
+
err = _validate_email(email, "email")
|
|
1856
|
+
if err:
|
|
1857
|
+
return {"error": err}
|
|
1858
|
+
|
|
1859
|
+
err = _validate_password(password, "password")
|
|
1860
|
+
if err:
|
|
1861
|
+
return {"error": err}
|
|
1862
|
+
|
|
1863
|
+
try:
|
|
1864
|
+
result = self._unauthenticated_request(
|
|
1865
|
+
"POST",
|
|
1866
|
+
"/api/auth/login",
|
|
1867
|
+
{"email": email, "password": password},
|
|
1868
|
+
)
|
|
1869
|
+
# Surface MFA requirement if present
|
|
1870
|
+
if isinstance(result, dict) and result.get("mfaRequired"):
|
|
1871
|
+
return {"mfaRequired": True, "message": "MFA verification required"}
|
|
1872
|
+
return result
|
|
1873
|
+
except Exception as e:
|
|
1874
|
+
return {"error": _sanitize_error(str(e))}
|
|
1875
|
+
|
|
1876
|
+
# ==================================================================
|
|
1877
|
+
# Tool 35: get_account_info
|
|
1878
|
+
# ==================================================================
|
|
1879
|
+
|
|
1880
|
+
def get_account_info(self) -> Dict[str, Any]:
|
|
1881
|
+
"""Get current account information.
|
|
1882
|
+
|
|
1883
|
+
Returns:
|
|
1884
|
+
Dict with account details including email, plan, and
|
|
1885
|
+
verification status.
|
|
1886
|
+
"""
|
|
1887
|
+
try:
|
|
1888
|
+
result = self._request_with_retry("GET", "/api/auth/me")
|
|
1889
|
+
return result
|
|
1890
|
+
except Exception as e:
|
|
1891
|
+
return {"error": _sanitize_error(str(e))}
|
|
1892
|
+
|
|
1893
|
+
# ==================================================================
|
|
1894
|
+
# Tool 36: verify_email (unauthenticated)
|
|
1895
|
+
# ==================================================================
|
|
1896
|
+
|
|
1897
|
+
def verify_email(self, token: str) -> Dict[str, Any]:
|
|
1898
|
+
"""Verify an email address using the verification token.
|
|
1899
|
+
|
|
1900
|
+
This is an unauthenticated operation. The token is sent to the
|
|
1901
|
+
registered email address.
|
|
1902
|
+
|
|
1903
|
+
Args:
|
|
1904
|
+
token: Email verification token from the verification email.
|
|
1905
|
+
|
|
1906
|
+
Returns:
|
|
1907
|
+
Dict confirming email verification.
|
|
1908
|
+
"""
|
|
1909
|
+
if not token or not isinstance(token, str):
|
|
1910
|
+
return {"error": "token is required and must be a string"}
|
|
1911
|
+
if len(token) > 500:
|
|
1912
|
+
return {"error": "token must be 500 characters or fewer"}
|
|
1913
|
+
|
|
1914
|
+
try:
|
|
1915
|
+
result = self._unauthenticated_request(
|
|
1916
|
+
"POST",
|
|
1917
|
+
"/api/auth/verify-email",
|
|
1918
|
+
{"token": token},
|
|
1919
|
+
)
|
|
1920
|
+
return result
|
|
1921
|
+
except Exception as e:
|
|
1922
|
+
return {"error": _sanitize_error(str(e))}
|
|
1923
|
+
|
|
1924
|
+
# ==================================================================
|
|
1925
|
+
# Tool 37: resend_verification
|
|
1926
|
+
# ==================================================================
|
|
1927
|
+
|
|
1928
|
+
def resend_verification(self) -> Dict[str, Any]:
|
|
1929
|
+
"""Resend the email verification message.
|
|
1930
|
+
|
|
1931
|
+
Returns:
|
|
1932
|
+
Dict confirming the verification email was resent.
|
|
1933
|
+
"""
|
|
1934
|
+
try:
|
|
1935
|
+
result = self._request_with_retry(
|
|
1936
|
+
"POST", "/api/auth/resend-verification"
|
|
1937
|
+
)
|
|
1938
|
+
return result
|
|
1939
|
+
except Exception as e:
|
|
1940
|
+
return {"error": _sanitize_error(str(e))}
|
|
1941
|
+
|
|
1942
|
+
# ==================================================================
|
|
1943
|
+
# Tool 38: update_password
|
|
1944
|
+
# ==================================================================
|
|
1945
|
+
|
|
1946
|
+
def update_password(
|
|
1947
|
+
self, current_password: str, new_password: str
|
|
1948
|
+
) -> Dict[str, Any]:
|
|
1949
|
+
"""Update the account password.
|
|
1950
|
+
|
|
1951
|
+
Args:
|
|
1952
|
+
current_password: The current account password.
|
|
1953
|
+
new_password: The new password (8 to 128 characters).
|
|
1954
|
+
|
|
1955
|
+
Returns:
|
|
1956
|
+
Dict confirming the password was updated.
|
|
1957
|
+
"""
|
|
1958
|
+
err = _validate_password(current_password, "current_password")
|
|
1959
|
+
if err:
|
|
1960
|
+
return {"error": err}
|
|
1961
|
+
|
|
1962
|
+
err = _validate_password(new_password, "new_password")
|
|
1963
|
+
if err:
|
|
1964
|
+
return {"error": err}
|
|
1965
|
+
|
|
1966
|
+
try:
|
|
1967
|
+
result = self._request_with_retry(
|
|
1968
|
+
"POST",
|
|
1969
|
+
"/api/auth/change-password",
|
|
1970
|
+
{
|
|
1971
|
+
"currentPassword": current_password,
|
|
1972
|
+
"newPassword": new_password,
|
|
1973
|
+
},
|
|
1974
|
+
)
|
|
1975
|
+
return result
|
|
1976
|
+
except Exception as e:
|
|
1977
|
+
return {"error": _sanitize_error(str(e))}
|
|
1978
|
+
|
|
1979
|
+
# ==================================================================
|
|
1980
|
+
# Tool 39: list_keys
|
|
1981
|
+
# ==================================================================
|
|
1982
|
+
|
|
1983
|
+
def list_keys(self) -> Dict[str, Any]:
|
|
1984
|
+
"""List all API keys for the current account.
|
|
1985
|
+
|
|
1986
|
+
Returns:
|
|
1987
|
+
Dict with a list of API keys and their metadata (labels,
|
|
1988
|
+
creation dates, last used).
|
|
1989
|
+
"""
|
|
1990
|
+
try:
|
|
1991
|
+
result = self._request_with_retry("GET", "/api/keys")
|
|
1992
|
+
return result
|
|
1993
|
+
except Exception as e:
|
|
1994
|
+
return {"error": _sanitize_error(str(e))}
|
|
1995
|
+
|
|
1996
|
+
# ==================================================================
|
|
1997
|
+
# Tool 40: create_key
|
|
1998
|
+
# ==================================================================
|
|
1999
|
+
|
|
2000
|
+
def create_key(self, label: str) -> Dict[str, Any]:
|
|
2001
|
+
"""Create a new API key for the current account.
|
|
2002
|
+
|
|
2003
|
+
Args:
|
|
2004
|
+
label: Human-readable label for the key (max 100 characters).
|
|
2005
|
+
|
|
2006
|
+
Returns:
|
|
2007
|
+
Dict with the created API key (shown once -- save it).
|
|
2008
|
+
"""
|
|
2009
|
+
err = _validate_label(label, "label")
|
|
2010
|
+
if err:
|
|
2011
|
+
return {"error": err}
|
|
2012
|
+
|
|
2013
|
+
try:
|
|
2014
|
+
result = self._request_with_retry(
|
|
2015
|
+
"POST", "/api/keys", {"label": label}
|
|
2016
|
+
)
|
|
2017
|
+
return result
|
|
2018
|
+
except Exception as e:
|
|
2019
|
+
return {"error": _sanitize_error(str(e))}
|
|
2020
|
+
|
|
2021
|
+
# ==================================================================
|
|
2022
|
+
# Tool 41: revoke_key
|
|
2023
|
+
# ==================================================================
|
|
2024
|
+
|
|
2025
|
+
def revoke_key(self, key_id: str) -> Dict[str, Any]:
|
|
2026
|
+
"""Revoke (delete) an API key.
|
|
2027
|
+
|
|
2028
|
+
Args:
|
|
2029
|
+
key_id: UUID of the API key to revoke.
|
|
2030
|
+
|
|
2031
|
+
Returns:
|
|
2032
|
+
Dict confirming the key has been revoked.
|
|
2033
|
+
"""
|
|
2034
|
+
err = _validate_uuid(key_id, "key_id")
|
|
2035
|
+
if err:
|
|
2036
|
+
return {"error": err}
|
|
2037
|
+
|
|
2038
|
+
try:
|
|
2039
|
+
result = self._request_with_retry(
|
|
2040
|
+
"DELETE", f"/api/keys/{quote(key_id, safe='')}"
|
|
2041
|
+
)
|
|
2042
|
+
return result
|
|
2043
|
+
except Exception as e:
|
|
2044
|
+
return {"error": _sanitize_error(str(e))}
|
|
2045
|
+
|
|
2046
|
+
# ==================================================================
|
|
2047
|
+
# Tool 42: get_plan
|
|
2048
|
+
# ==================================================================
|
|
2049
|
+
|
|
2050
|
+
def get_plan(self) -> Dict[str, Any]:
|
|
2051
|
+
"""Get the current user's active plan.
|
|
2052
|
+
|
|
2053
|
+
Returns:
|
|
2054
|
+
Dict with the user's current plan details including tier,
|
|
2055
|
+
limits, and pricing.
|
|
2056
|
+
"""
|
|
2057
|
+
try:
|
|
2058
|
+
result = self._request_with_retry("GET", "/api/plans/user/plan")
|
|
2059
|
+
return result
|
|
2060
|
+
except Exception as e:
|
|
2061
|
+
return {"error": _sanitize_error(str(e))}
|
|
2062
|
+
|
|
2063
|
+
# ==================================================================
|
|
2064
|
+
# Tool 43: list_plans
|
|
2065
|
+
# ==================================================================
|
|
2066
|
+
|
|
2067
|
+
def list_plans(self) -> Dict[str, Any]:
|
|
2068
|
+
"""List all available subscription plans.
|
|
2069
|
+
|
|
2070
|
+
Returns:
|
|
2071
|
+
Dict with a list of available plans and their features.
|
|
2072
|
+
"""
|
|
2073
|
+
try:
|
|
2074
|
+
result = self._request_with_retry("GET", "/api/plans")
|
|
2075
|
+
return result
|
|
2076
|
+
except Exception as e:
|
|
2077
|
+
return {"error": _sanitize_error(str(e))}
|
|
2078
|
+
|
|
2079
|
+
# ==================================================================
|
|
2080
|
+
# Tool 44: change_plan
|
|
2081
|
+
# ==================================================================
|
|
2082
|
+
|
|
2083
|
+
def change_plan(self, plan_id: str) -> Dict[str, Any]:
|
|
2084
|
+
"""Change the user's subscription plan.
|
|
2085
|
+
|
|
2086
|
+
Args:
|
|
2087
|
+
plan_id: UUID of the plan to switch to.
|
|
2088
|
+
|
|
2089
|
+
Returns:
|
|
2090
|
+
Dict with the updated plan details.
|
|
2091
|
+
"""
|
|
2092
|
+
err = _validate_uuid(plan_id, "plan_id")
|
|
2093
|
+
if err:
|
|
2094
|
+
return {"error": err}
|
|
2095
|
+
|
|
2096
|
+
try:
|
|
2097
|
+
result = self._request_with_retry(
|
|
2098
|
+
"PUT",
|
|
2099
|
+
"/api/plans/user/plan",
|
|
2100
|
+
{"planId": plan_id},
|
|
2101
|
+
)
|
|
2102
|
+
return result
|
|
2103
|
+
except Exception as e:
|
|
2104
|
+
return {"error": _sanitize_error(str(e))}
|
|
2105
|
+
|
|
2106
|
+
# ==================================================================
|
|
2107
|
+
# Tool 45: team_delete
|
|
2108
|
+
# ==================================================================
|
|
2109
|
+
|
|
2110
|
+
def team_delete(self, team_id: str) -> Dict[str, Any]:
|
|
2111
|
+
"""Delete a team.
|
|
2112
|
+
|
|
2113
|
+
Args:
|
|
2114
|
+
team_id: UUID of the team to delete.
|
|
2115
|
+
|
|
2116
|
+
Returns:
|
|
2117
|
+
Dict confirming the team has been deleted.
|
|
2118
|
+
"""
|
|
2119
|
+
err = _validate_uuid(team_id, "team_id")
|
|
2120
|
+
if err:
|
|
2121
|
+
return {"error": err}
|
|
2122
|
+
|
|
2123
|
+
try:
|
|
2124
|
+
result = self._request_with_retry(
|
|
2125
|
+
"DELETE", f"/api/teams/{quote(team_id, safe='')}"
|
|
2126
|
+
)
|
|
2127
|
+
return result
|
|
2128
|
+
except Exception as e:
|
|
2129
|
+
return {"error": _sanitize_error(str(e))}
|
|
2130
|
+
|
|
2131
|
+
# ==================================================================
|
|
2132
|
+
# Tool 46: team_revoke_key
|
|
2133
|
+
# ==================================================================
|
|
2134
|
+
|
|
2135
|
+
def team_revoke_key(self, team_id: str, key_id: str) -> Dict[str, Any]:
|
|
2136
|
+
"""Revoke (delete) an API key from a team.
|
|
2137
|
+
|
|
2138
|
+
Args:
|
|
2139
|
+
team_id: UUID of the team.
|
|
2140
|
+
key_id: UUID of the API key to revoke.
|
|
2141
|
+
|
|
2142
|
+
Returns:
|
|
2143
|
+
Dict confirming the team API key has been revoked.
|
|
2144
|
+
"""
|
|
2145
|
+
err = _validate_uuid(team_id, "team_id")
|
|
2146
|
+
if err:
|
|
2147
|
+
return {"error": err}
|
|
2148
|
+
|
|
2149
|
+
err = _validate_uuid(key_id, "key_id")
|
|
2150
|
+
if err:
|
|
2151
|
+
return {"error": err}
|
|
2152
|
+
|
|
2153
|
+
try:
|
|
2154
|
+
result = self._request_with_retry(
|
|
2155
|
+
"DELETE",
|
|
2156
|
+
f"/api/teams/{quote(team_id, safe='')}/keys/{quote(key_id, safe='')}",
|
|
2157
|
+
)
|
|
2158
|
+
return result
|
|
2159
|
+
except Exception as e:
|
|
2160
|
+
return {"error": _sanitize_error(str(e))}
|
|
2161
|
+
|
|
2162
|
+
# ==================================================================
|
|
2163
|
+
# Tool 47: team_list_keys
|
|
2164
|
+
# ==================================================================
|
|
2165
|
+
|
|
2166
|
+
def team_list_keys(self, team_id: str) -> Dict[str, Any]:
|
|
2167
|
+
"""List all API keys for a team.
|
|
2168
|
+
|
|
2169
|
+
Args:
|
|
2170
|
+
team_id: UUID of the team.
|
|
2171
|
+
|
|
2172
|
+
Returns:
|
|
2173
|
+
Dict with a list of the team's API keys and their metadata.
|
|
2174
|
+
"""
|
|
2175
|
+
err = _validate_uuid(team_id, "team_id")
|
|
2176
|
+
if err:
|
|
2177
|
+
return {"error": err}
|
|
2178
|
+
|
|
2179
|
+
try:
|
|
2180
|
+
result = self._request_with_retry(
|
|
2181
|
+
"GET", f"/api/teams/{quote(team_id, safe='')}/keys"
|
|
2182
|
+
)
|
|
2183
|
+
return result
|
|
2184
|
+
except Exception as e:
|
|
2185
|
+
return {"error": _sanitize_error(str(e))}
|
|
2186
|
+
|
|
2187
|
+
# ==================================================================
|
|
2188
|
+
# Tool 48: team_list_members
|
|
2189
|
+
# ==================================================================
|
|
2190
|
+
|
|
2191
|
+
def team_list_members(self, team_id: str) -> Dict[str, Any]:
|
|
2192
|
+
"""List all members of a team.
|
|
2193
|
+
|
|
2194
|
+
Args:
|
|
2195
|
+
team_id: UUID of the team.
|
|
2196
|
+
|
|
2197
|
+
Returns:
|
|
2198
|
+
Dict with a list of team members and their roles.
|
|
2199
|
+
"""
|
|
2200
|
+
err = _validate_uuid(team_id, "team_id")
|
|
2201
|
+
if err:
|
|
2202
|
+
return {"error": err}
|
|
2203
|
+
|
|
2204
|
+
try:
|
|
2205
|
+
result = self._request_with_retry(
|
|
2206
|
+
"GET", f"/api/teams/{quote(team_id, safe='')}/members"
|
|
2207
|
+
)
|
|
2208
|
+
return result
|
|
2209
|
+
except Exception as e:
|
|
2210
|
+
return {"error": _sanitize_error(str(e))}
|
|
2211
|
+
|
|
2212
|
+
# ==================================================================
|
|
2213
|
+
# Tool 49: team_add_member
|
|
2214
|
+
# ==================================================================
|
|
2215
|
+
|
|
2216
|
+
def team_add_member(
|
|
2217
|
+
self, team_id: str, user_id: str, role: str = "member"
|
|
2218
|
+
) -> Dict[str, Any]:
|
|
2219
|
+
"""Add a member to a team.
|
|
2220
|
+
|
|
2221
|
+
Args:
|
|
2222
|
+
team_id: UUID of the team.
|
|
2223
|
+
user_id: UUID of the user to add.
|
|
2224
|
+
role: Role to assign -- ``member`` or ``admin`` (default ``member``).
|
|
2225
|
+
|
|
2226
|
+
Returns:
|
|
2227
|
+
Dict confirming the member was added.
|
|
2228
|
+
"""
|
|
2229
|
+
err = _validate_uuid(team_id, "team_id")
|
|
2230
|
+
if err:
|
|
2231
|
+
return {"error": err}
|
|
2232
|
+
|
|
2233
|
+
err = _validate_uuid(user_id, "user_id")
|
|
2234
|
+
if err:
|
|
2235
|
+
return {"error": err}
|
|
2236
|
+
|
|
2237
|
+
if not role or not isinstance(role, str):
|
|
2238
|
+
return {"error": "role is required and must be a string"}
|
|
2239
|
+
if role not in ("member", "admin"):
|
|
2240
|
+
return {"error": "role must be 'member' or 'admin'"}
|
|
2241
|
+
|
|
2242
|
+
try:
|
|
2243
|
+
result = self._request_with_retry(
|
|
2244
|
+
"POST",
|
|
2245
|
+
f"/api/teams/{quote(team_id, safe='')}/members",
|
|
2246
|
+
{"userId": user_id, "role": role},
|
|
2247
|
+
)
|
|
2248
|
+
return result
|
|
2249
|
+
except Exception as e:
|
|
2250
|
+
return {"error": _sanitize_error(str(e))}
|
|
2251
|
+
|
|
2252
|
+
# ==================================================================
|
|
2253
|
+
# Tool 50: team_remove_member
|
|
2254
|
+
# ==================================================================
|
|
2255
|
+
|
|
2256
|
+
def team_remove_member(self, team_id: str, user_id: str) -> Dict[str, Any]:
|
|
2257
|
+
"""Remove a member from a team.
|
|
2258
|
+
|
|
2259
|
+
Args:
|
|
2260
|
+
team_id: UUID of the team.
|
|
2261
|
+
user_id: UUID of the user to remove.
|
|
2262
|
+
|
|
2263
|
+
Returns:
|
|
2264
|
+
Dict confirming the member was removed.
|
|
2265
|
+
"""
|
|
2266
|
+
err = _validate_uuid(team_id, "team_id")
|
|
2267
|
+
if err:
|
|
2268
|
+
return {"error": err}
|
|
2269
|
+
|
|
2270
|
+
err = _validate_uuid(user_id, "user_id")
|
|
2271
|
+
if err:
|
|
2272
|
+
return {"error": err}
|
|
2273
|
+
|
|
2274
|
+
try:
|
|
2275
|
+
result = self._request_with_retry(
|
|
2276
|
+
"DELETE",
|
|
2277
|
+
f"/api/teams/{quote(team_id, safe='')}/members/{quote(user_id, safe='')}",
|
|
2278
|
+
)
|
|
2279
|
+
return result
|
|
2280
|
+
except Exception as e:
|
|
2281
|
+
return {"error": _sanitize_error(str(e))}
|
|
2282
|
+
|
|
2283
|
+
# ==================================================================
|
|
2284
|
+
# Tool 51: team_invite_member
|
|
2285
|
+
# ==================================================================
|
|
2286
|
+
|
|
2287
|
+
def team_invite_member(
|
|
2288
|
+
self, team_id: str, email: str, role: str = "member"
|
|
2289
|
+
) -> Dict[str, Any]:
|
|
2290
|
+
"""Invite a user to a team by email.
|
|
2291
|
+
|
|
2292
|
+
Args:
|
|
2293
|
+
team_id: UUID of the team.
|
|
2294
|
+
email: Email address of the user to invite.
|
|
2295
|
+
role: Role to assign on acceptance -- ``member`` or ``admin``
|
|
2296
|
+
(default ``member``).
|
|
2297
|
+
|
|
2298
|
+
Returns:
|
|
2299
|
+
Dict with the invite details.
|
|
2300
|
+
"""
|
|
2301
|
+
err = _validate_uuid(team_id, "team_id")
|
|
2302
|
+
if err:
|
|
2303
|
+
return {"error": err}
|
|
2304
|
+
|
|
2305
|
+
err = _validate_email(email, "email")
|
|
2306
|
+
if err:
|
|
2307
|
+
return {"error": err}
|
|
2308
|
+
|
|
2309
|
+
if not role or not isinstance(role, str):
|
|
2310
|
+
return {"error": "role is required and must be a string"}
|
|
2311
|
+
if role not in ("member", "admin"):
|
|
2312
|
+
return {"error": "role must be 'member' or 'admin'"}
|
|
2313
|
+
|
|
2314
|
+
try:
|
|
2315
|
+
result = self._request_with_retry(
|
|
2316
|
+
"POST",
|
|
2317
|
+
f"/api/teams/{quote(team_id, safe='')}/invites",
|
|
2318
|
+
{"email": email, "role": role},
|
|
2319
|
+
)
|
|
2320
|
+
return result
|
|
2321
|
+
except Exception as e:
|
|
2322
|
+
return {"error": _sanitize_error(str(e))}
|
|
2323
|
+
|
|
2324
|
+
# ==================================================================
|
|
2325
|
+
# Tool 52: team_list_invites
|
|
2326
|
+
# ==================================================================
|
|
2327
|
+
|
|
2328
|
+
def team_list_invites(self, team_id: str) -> Dict[str, Any]:
|
|
2329
|
+
"""List pending invites for a team.
|
|
2330
|
+
|
|
2331
|
+
Args:
|
|
2332
|
+
team_id: UUID of the team.
|
|
2333
|
+
|
|
2334
|
+
Returns:
|
|
2335
|
+
Dict with a list of pending team invites.
|
|
2336
|
+
"""
|
|
2337
|
+
err = _validate_uuid(team_id, "team_id")
|
|
2338
|
+
if err:
|
|
2339
|
+
return {"error": err}
|
|
2340
|
+
|
|
2341
|
+
try:
|
|
2342
|
+
result = self._request_with_retry(
|
|
2343
|
+
"GET", f"/api/teams/{quote(team_id, safe='')}/invites"
|
|
2344
|
+
)
|
|
2345
|
+
return result
|
|
2346
|
+
except Exception as e:
|
|
2347
|
+
return {"error": _sanitize_error(str(e))}
|
|
2348
|
+
|
|
2349
|
+
# ==================================================================
|
|
2350
|
+
# Tool 53: team_cancel_invite
|
|
2351
|
+
# ==================================================================
|
|
2352
|
+
|
|
2353
|
+
def team_cancel_invite(self, team_id: str, invite_id: str) -> Dict[str, Any]:
|
|
2354
|
+
"""Cancel a pending team invite.
|
|
2355
|
+
|
|
2356
|
+
Args:
|
|
2357
|
+
team_id: UUID of the team.
|
|
2358
|
+
invite_id: UUID of the invite to cancel.
|
|
2359
|
+
|
|
2360
|
+
Returns:
|
|
2361
|
+
Dict confirming the invite has been cancelled.
|
|
2362
|
+
"""
|
|
2363
|
+
err = _validate_uuid(team_id, "team_id")
|
|
2364
|
+
if err:
|
|
2365
|
+
return {"error": err}
|
|
2366
|
+
|
|
2367
|
+
err = _validate_uuid(invite_id, "invite_id")
|
|
2368
|
+
if err:
|
|
2369
|
+
return {"error": err}
|
|
2370
|
+
|
|
2371
|
+
try:
|
|
2372
|
+
result = self._request_with_retry(
|
|
2373
|
+
"DELETE",
|
|
2374
|
+
f"/api/teams/{quote(team_id, safe='')}/invites/{quote(invite_id, safe='')}",
|
|
2375
|
+
)
|
|
2376
|
+
return result
|
|
2377
|
+
except Exception as e:
|
|
2378
|
+
return {"error": _sanitize_error(str(e))}
|
|
2379
|
+
|
|
1462
2380
|
# ==================================================================
|
|
1463
2381
|
# get_tools -- return CAMEL FunctionTool objects
|
|
1464
2382
|
# ==================================================================
|
|
1465
2383
|
|
|
1466
2384
|
def get_tools(self) -> list:
|
|
1467
|
-
"""Return CAMEL FunctionTool objects for all
|
|
2385
|
+
"""Return CAMEL FunctionTool objects for all 53 Dominus Node operations.
|
|
1468
2386
|
|
|
1469
2387
|
Each method is wrapped in a ``camel.toolkits.FunctionTool`` so that
|
|
1470
2388
|
CAMEL agents can discover and invoke them automatically.
|
|
1471
2389
|
|
|
1472
2390
|
Returns:
|
|
1473
|
-
A list of
|
|
2391
|
+
A list of 53 ``FunctionTool`` instances.
|
|
1474
2392
|
"""
|
|
1475
2393
|
from camel.toolkits import FunctionTool
|
|
1476
2394
|
|
|
1477
2395
|
return [
|
|
2396
|
+
# Proxy tools (1-5)
|
|
1478
2397
|
FunctionTool(self.proxied_fetch),
|
|
1479
2398
|
FunctionTool(self.check_balance),
|
|
1480
2399
|
FunctionTool(self.check_usage),
|
|
1481
2400
|
FunctionTool(self.get_proxy_config),
|
|
1482
2401
|
FunctionTool(self.list_sessions),
|
|
2402
|
+
# Agentic wallet tools (6-13)
|
|
1483
2403
|
FunctionTool(self.create_agentic_wallet),
|
|
1484
2404
|
FunctionTool(self.fund_agentic_wallet),
|
|
1485
2405
|
FunctionTool(self.agentic_wallet_balance),
|
|
@@ -1488,6 +2408,7 @@ class DominusNodeToolkit:
|
|
|
1488
2408
|
FunctionTool(self.freeze_agentic_wallet),
|
|
1489
2409
|
FunctionTool(self.unfreeze_agentic_wallet),
|
|
1490
2410
|
FunctionTool(self.delete_agentic_wallet),
|
|
2411
|
+
# Team tools (14-21)
|
|
1491
2412
|
FunctionTool(self.create_team),
|
|
1492
2413
|
FunctionTool(self.list_teams),
|
|
1493
2414
|
FunctionTool(self.team_details),
|
|
@@ -1496,7 +2417,44 @@ class DominusNodeToolkit:
|
|
|
1496
2417
|
FunctionTool(self.team_usage),
|
|
1497
2418
|
FunctionTool(self.update_team),
|
|
1498
2419
|
FunctionTool(self.update_team_member_role),
|
|
2420
|
+
# Payment tools (22-26)
|
|
1499
2421
|
FunctionTool(self.x402_info),
|
|
1500
2422
|
FunctionTool(self.topup_paypal),
|
|
2423
|
+
FunctionTool(self.topup_stripe),
|
|
2424
|
+
FunctionTool(self.topup_crypto),
|
|
1501
2425
|
FunctionTool(self.update_wallet_policy),
|
|
2426
|
+
# Proxy status (27)
|
|
2427
|
+
FunctionTool(self.get_proxy_status),
|
|
2428
|
+
# Wallet extras (28-30)
|
|
2429
|
+
FunctionTool(self.get_transactions),
|
|
2430
|
+
FunctionTool(self.get_forecast),
|
|
2431
|
+
FunctionTool(self.check_payment),
|
|
2432
|
+
# Usage extras (31-32)
|
|
2433
|
+
FunctionTool(self.get_daily_usage),
|
|
2434
|
+
FunctionTool(self.get_top_hosts),
|
|
2435
|
+
# Account management (33-38)
|
|
2436
|
+
FunctionTool(self.register_account),
|
|
2437
|
+
FunctionTool(self.login_account),
|
|
2438
|
+
FunctionTool(self.get_account_info),
|
|
2439
|
+
FunctionTool(self.verify_email),
|
|
2440
|
+
FunctionTool(self.resend_verification),
|
|
2441
|
+
FunctionTool(self.update_password),
|
|
2442
|
+
# API key management (39-41)
|
|
2443
|
+
FunctionTool(self.list_keys),
|
|
2444
|
+
FunctionTool(self.create_key),
|
|
2445
|
+
FunctionTool(self.revoke_key),
|
|
2446
|
+
# Plan management (42-44)
|
|
2447
|
+
FunctionTool(self.get_plan),
|
|
2448
|
+
FunctionTool(self.list_plans),
|
|
2449
|
+
FunctionTool(self.change_plan),
|
|
2450
|
+
# Extended team management (45-53)
|
|
2451
|
+
FunctionTool(self.team_delete),
|
|
2452
|
+
FunctionTool(self.team_revoke_key),
|
|
2453
|
+
FunctionTool(self.team_list_keys),
|
|
2454
|
+
FunctionTool(self.team_list_members),
|
|
2455
|
+
FunctionTool(self.team_add_member),
|
|
2456
|
+
FunctionTool(self.team_remove_member),
|
|
2457
|
+
FunctionTool(self.team_invite_member),
|
|
2458
|
+
FunctionTool(self.team_list_invites),
|
|
2459
|
+
FunctionTool(self.team_cancel_invite),
|
|
1502
2460
|
]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dominusnode-camel
|
|
3
|
-
Version: 1.0
|
|
3
|
+
Version: 1.2.0
|
|
4
4
|
Summary: Dominus Node rotating proxy tools for CAMEL-AI agents
|
|
5
5
|
License: MIT
|
|
6
6
|
Requires-Python: >=3.10
|
|
@@ -15,7 +15,7 @@ Dynamic: license-file
|
|
|
15
15
|
|
|
16
16
|
# dominusnode-camel
|
|
17
17
|
|
|
18
|
-
CAMEL-AI toolkit for the [Dominus Node](https://dominusnode.com) rotating proxy-as-a-service platform. Provides
|
|
18
|
+
CAMEL-AI toolkit for the [Dominus Node](https://dominusnode.com) rotating proxy-as-a-service platform. Provides 53 tools as CAMEL `FunctionTool` objects for proxy management, wallet operations, team administration, account management, API key management, plan management, and payment processing.
|
|
19
19
|
|
|
20
20
|
## Installation
|
|
21
21
|
|
|
@@ -958,6 +958,18 @@ class TestToolMethods:
|
|
|
958
958
|
result = toolkit.topup_paypal(amount_cents=500)
|
|
959
959
|
assert result["orderId"] == "PAY-999"
|
|
960
960
|
|
|
961
|
+
@patch("dominusnode_camel.tools.httpx.Client")
|
|
962
|
+
def test_topup_stripe(self, mock_httpx_cls):
|
|
963
|
+
toolkit = self._make_toolkit(mock_httpx_cls, {"sessionId": "cs_123", "checkoutUrl": "https://checkout.stripe.com/x"})
|
|
964
|
+
result = toolkit.topup_stripe(amount_cents=1000)
|
|
965
|
+
assert result["sessionId"] == "cs_123"
|
|
966
|
+
|
|
967
|
+
@patch("dominusnode_camel.tools.httpx.Client")
|
|
968
|
+
def test_topup_crypto(self, mock_httpx_cls):
|
|
969
|
+
toolkit = self._make_toolkit(mock_httpx_cls, {"invoiceId": "INV-1", "paymentUrl": "https://nowpayments.io/x"})
|
|
970
|
+
result = toolkit.topup_crypto(amount_usd=10, currency="BTC")
|
|
971
|
+
assert result["invoiceId"] == "INV-1"
|
|
972
|
+
|
|
961
973
|
|
|
962
974
|
# ===========================================================================
|
|
963
975
|
# get_tools Tests
|
|
@@ -967,8 +979,8 @@ class TestToolMethods:
|
|
|
967
979
|
class TestGetTools:
|
|
968
980
|
"""Test the get_tools method returns correct FunctionTool objects."""
|
|
969
981
|
|
|
970
|
-
def
|
|
971
|
-
"""get_tools should return
|
|
982
|
+
def test_get_tools_returns_53_tools(self):
|
|
983
|
+
"""get_tools should return 53 FunctionTool objects."""
|
|
972
984
|
toolkit = DominusNodeToolkit(api_key="dn_test_key123")
|
|
973
985
|
|
|
974
986
|
# Mock FunctionTool since camel-ai may not be installed in test env
|
|
@@ -984,8 +996,9 @@ class TestGetTools:
|
|
|
984
996
|
with patch("camel.toolkits.FunctionTool", side_effect=mock_function_tool):
|
|
985
997
|
tools = toolkit.get_tools()
|
|
986
998
|
|
|
987
|
-
assert len(tools) ==
|
|
999
|
+
assert len(tools) == 53
|
|
988
1000
|
tool_names = [t.name for t in tools]
|
|
1001
|
+
# Original 26 tools
|
|
989
1002
|
assert "proxied_fetch" in tool_names
|
|
990
1003
|
assert "check_balance" in tool_names
|
|
991
1004
|
assert "check_usage" in tool_names
|
|
@@ -1009,7 +1022,37 @@ class TestGetTools:
|
|
|
1009
1022
|
assert "update_team_member_role" in tool_names
|
|
1010
1023
|
assert "x402_info" in tool_names
|
|
1011
1024
|
assert "topup_paypal" in tool_names
|
|
1025
|
+
assert "topup_stripe" in tool_names
|
|
1026
|
+
assert "topup_crypto" in tool_names
|
|
1012
1027
|
assert "update_wallet_policy" in tool_names
|
|
1028
|
+
# New 27 tools
|
|
1029
|
+
assert "get_proxy_status" in tool_names
|
|
1030
|
+
assert "get_transactions" in tool_names
|
|
1031
|
+
assert "get_forecast" in tool_names
|
|
1032
|
+
assert "check_payment" in tool_names
|
|
1033
|
+
assert "get_daily_usage" in tool_names
|
|
1034
|
+
assert "get_top_hosts" in tool_names
|
|
1035
|
+
assert "register_account" in tool_names
|
|
1036
|
+
assert "login_account" in tool_names
|
|
1037
|
+
assert "get_account_info" in tool_names
|
|
1038
|
+
assert "verify_email" in tool_names
|
|
1039
|
+
assert "resend_verification" in tool_names
|
|
1040
|
+
assert "update_password" in tool_names
|
|
1041
|
+
assert "list_keys" in tool_names
|
|
1042
|
+
assert "create_key" in tool_names
|
|
1043
|
+
assert "revoke_key" in tool_names
|
|
1044
|
+
assert "get_plan" in tool_names
|
|
1045
|
+
assert "list_plans" in tool_names
|
|
1046
|
+
assert "change_plan" in tool_names
|
|
1047
|
+
assert "team_delete" in tool_names
|
|
1048
|
+
assert "team_revoke_key" in tool_names
|
|
1049
|
+
assert "team_list_keys" in tool_names
|
|
1050
|
+
assert "team_list_members" in tool_names
|
|
1051
|
+
assert "team_add_member" in tool_names
|
|
1052
|
+
assert "team_remove_member" in tool_names
|
|
1053
|
+
assert "team_invite_member" in tool_names
|
|
1054
|
+
assert "team_list_invites" in tool_names
|
|
1055
|
+
assert "team_cancel_invite" in tool_names
|
|
1013
1056
|
|
|
1014
1057
|
|
|
1015
1058
|
# ===========================================================================
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dominusnode_camel-1.0.1 → dominusnode_camel-1.2.0}/dominusnode_camel.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
{dominusnode_camel-1.0.1 → dominusnode_camel-1.2.0}/dominusnode_camel.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|