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.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dominusnode-camel
3
- Version: 1.0.1
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 22 tools as CAMEL `FunctionTool` objects for proxy management, wallet operations, team administration, and payment processing.
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 22 tools as CAMEL `FunctionTool` objects for proxy management, wallet operations, team administration, and payment processing.
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 -- 22 proxy, wallet, team, and PayPal tools.
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 22 tools as
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
- - PayPal top-up
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 22 tools as CAMEL
506
- ``FunctionTool`` objects for proxy, wallet, team, and payment management.
507
- All tool methods return dicts suitable for JSON serialization.
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: update_wallet_policy
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 22 Dominus Node operations.
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 22 ``FunctionTool`` instances.
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.1
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 22 tools as CAMEL `FunctionTool` objects for proxy management, wallet operations, team administration, and payment processing.
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
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "dominusnode-camel"
7
- version = "1.0.1"
7
+ version = "1.2.0"
8
8
  description = "Dominus Node rotating proxy tools for CAMEL-AI agents"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -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 test_get_tools_returns_23_tools(self):
971
- """get_tools should return 23 FunctionTool objects (22 commands + x402)."""
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) == 24
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
  # ===========================================================================