dominusnode-camel 1.1.0__tar.gz → 1.2.1__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.1.0
3
+ Version: 1.2.1
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 -- 26 proxy, wallet, team, and payment 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 26 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:
@@ -37,6 +41,7 @@ Requires: httpx (``pip install httpx``), camel-ai (``pip install camel-ai``)
37
41
 
38
42
  from __future__ import annotations
39
43
 
44
+ import hashlib
40
45
  import ipaddress
41
46
  import json
42
47
  import math
@@ -376,6 +381,46 @@ def _sanitize_error(message: str) -> str:
376
381
  return _CREDENTIAL_RE.sub("***", message)
377
382
 
378
383
 
384
+ def _count_leading_zero_bits(data: bytes) -> int:
385
+ """Count leading zero bits in a byte array."""
386
+ count = 0
387
+ for byte in data:
388
+ if byte == 0:
389
+ count += 8
390
+ else:
391
+ mask = 0x80
392
+ while mask and not (byte & mask):
393
+ count += 1
394
+ mask >>= 1
395
+ break
396
+ return count
397
+
398
+
399
+ def _solve_pow(base_url: str) -> Optional[dict]:
400
+ """Solve a Proof-of-Work challenge for CAPTCHA-free registration."""
401
+ try:
402
+ pow_url = f"{base_url.rstrip('/')}/api/auth/pow/challenge"
403
+ with httpx.Client(timeout=30.0, follow_redirects=False) as client:
404
+ resp = client.post(pow_url, headers={"Content-Type": "application/json"})
405
+ if resp.status_code >= 400:
406
+ return None
407
+ challenge = resp.json()
408
+ prefix = challenge.get("prefix", "")
409
+ difficulty = challenge.get("difficulty", 20)
410
+ challenge_id = challenge.get("challengeId", "")
411
+ if not prefix or not challenge_id:
412
+ return None
413
+ nonce = 0
414
+ while nonce < 100_000_000:
415
+ h = hashlib.sha256((prefix + str(nonce)).encode()).digest()
416
+ if _count_leading_zero_bits(h) >= difficulty:
417
+ return {"challengeId": challenge_id, "nonce": str(nonce)}
418
+ nonce += 1
419
+ return None
420
+ except Exception:
421
+ return None
422
+
423
+
379
424
  # ---------------------------------------------------------------------------
380
425
  # Prototype pollution prevention
381
426
  # ---------------------------------------------------------------------------
@@ -495,6 +540,53 @@ def _validate_domains(domains: Any) -> Optional[str]:
495
540
  return None
496
541
 
497
542
 
543
+ _EMAIL_RE = re.compile(r"^[^@\s]+@[^@\s]+\.[^@\s]+$")
544
+
545
+
546
+ def _validate_email(value: Any, field_name: str = "email") -> Optional[str]:
547
+ """Validate an email string, returning an error message or None."""
548
+ if not value or not isinstance(value, str):
549
+ return f"{field_name} is required and must be a string"
550
+ if len(value) > 254:
551
+ return f"{field_name} must be 254 characters or fewer"
552
+ if not _EMAIL_RE.match(value):
553
+ return f"{field_name} must be a valid email address"
554
+ return None
555
+
556
+
557
+ def _validate_password(value: Any, field_name: str = "password") -> Optional[str]:
558
+ """Validate a password string, returning an error message or None."""
559
+ if not value or not isinstance(value, str):
560
+ return f"{field_name} is required and must be a string"
561
+ if len(value) < 8:
562
+ return f"{field_name} must be at least 8 characters"
563
+ if len(value) > 128:
564
+ return f"{field_name} must be 128 characters or fewer"
565
+ return None
566
+
567
+
568
+ def _validate_finite_int(
569
+ value: Any,
570
+ field_name: str,
571
+ min_value: int = 1,
572
+ max_value: int = 2_147_483_647,
573
+ ) -> Optional[str]:
574
+ """Validate integer with NaN/Infinity guards (Python: float('nan') etc.)."""
575
+ if isinstance(value, bool):
576
+ return f"{field_name} must be an integer"
577
+ if isinstance(value, float):
578
+ if not math.isfinite(value):
579
+ return f"{field_name} must be a finite number"
580
+ if value != int(value):
581
+ return f"{field_name} must be an integer"
582
+ value = int(value)
583
+ if not isinstance(value, int):
584
+ return f"{field_name} must be an integer"
585
+ if value < min_value or value > max_value:
586
+ return f"{field_name} must be between {min_value} and {max_value}"
587
+ return None
588
+
589
+
498
590
  # ===========================================================================
499
591
  # DominusNodeToolkit -- main toolkit class
500
592
  # ===========================================================================
@@ -503,9 +595,10 @@ def _validate_domains(domains: Any) -> Optional[str]:
503
595
  class DominusNodeToolkit:
504
596
  """CAMEL-AI toolkit providing Dominus Node rotating proxy tools.
505
597
 
506
- Authenticates using a Dominus Node API key and exposes 22 tools as CAMEL
507
- ``FunctionTool`` objects for proxy, wallet, team, and payment management.
508
- All tool methods return dicts suitable for JSON serialization.
598
+ Authenticates using a Dominus Node API key and exposes 53 tools as CAMEL
599
+ ``FunctionTool`` objects for proxy, wallet, team, account, key, plan,
600
+ and payment management. All tool methods return dicts suitable for JSON
601
+ serialization.
509
602
 
510
603
  Args:
511
604
  api_key: Dominus Node API key (``dn_live_...`` or ``dn_test_...``).
@@ -596,7 +689,69 @@ class DominusNodeToolkit:
596
689
  self._authenticate()
597
690
 
598
691
  # ------------------------------------------------------------------
599
- # API request helper
692
+ # Unauthenticated API request helper
693
+ # ------------------------------------------------------------------
694
+
695
+ def _unauthenticated_request(
696
+ self,
697
+ method: str,
698
+ path: str,
699
+ body: Optional[Dict[str, Any]] = None,
700
+ ) -> Any:
701
+ """Make an unauthenticated API request (for register, login, verify-email).
702
+
703
+ Args:
704
+ method: HTTP method (GET, POST).
705
+ path: API path (e.g., ``/api/auth/register``).
706
+ body: Optional JSON body.
707
+
708
+ Returns:
709
+ Parsed JSON response (with dangerous keys stripped).
710
+
711
+ Raises:
712
+ RuntimeError: On API errors or oversized responses.
713
+ """
714
+ with httpx.Client(
715
+ timeout=self.timeout,
716
+ follow_redirects=False,
717
+ ) as client:
718
+ headers: Dict[str, str] = {
719
+ "User-Agent": "dominusnode-camel/1.0.0",
720
+ "Content-Type": "application/json",
721
+ }
722
+ if self.agent_secret:
723
+ headers["X-DominusNode-Agent"] = "mcp"
724
+ headers["X-DominusNode-Agent-Secret"] = self.agent_secret
725
+
726
+ kwargs: Dict[str, Any] = {"headers": headers}
727
+
728
+ if body is not None and method.upper() not in ("GET", "HEAD", "OPTIONS"):
729
+ kwargs["json"] = body
730
+
731
+ resp = client.request(method, f"{self.base_url}{path}", **kwargs)
732
+
733
+ if len(resp.content) > MAX_RESPONSE_BYTES:
734
+ raise RuntimeError("Response body too large")
735
+
736
+ if resp.status_code >= 400:
737
+ try:
738
+ err_data = resp.json()
739
+ msg = err_data.get("error", resp.text)
740
+ except Exception:
741
+ msg = resp.text
742
+ msg = str(msg)[:500]
743
+ raise RuntimeError(
744
+ f"API error {resp.status_code}: {_sanitize_error(msg)}"
745
+ )
746
+
747
+ if resp.text:
748
+ data = resp.json()
749
+ _strip_dangerous_keys(data)
750
+ return data
751
+ return {}
752
+
753
+ # ------------------------------------------------------------------
754
+ # Authenticated API request helper
600
755
  # ------------------------------------------------------------------
601
756
 
602
757
  def _api_request(
@@ -1546,27 +1701,752 @@ class DominusNodeToolkit:
1546
1701
  except Exception as e:
1547
1702
  return {"error": _sanitize_error(str(e))}
1548
1703
 
1704
+ # ==================================================================
1705
+ # Tool 27: get_proxy_status
1706
+ # ==================================================================
1707
+
1708
+ def get_proxy_status(self) -> Dict[str, Any]:
1709
+ """Get the current status of the proxy gateway.
1710
+
1711
+ Returns:
1712
+ Dict with proxy gateway status information including uptime,
1713
+ active connections, and health metrics.
1714
+ """
1715
+ try:
1716
+ result = self._request_with_retry("GET", "/api/proxy/status")
1717
+ return result
1718
+ except Exception as e:
1719
+ return {"error": _sanitize_error(str(e))}
1720
+
1721
+ # ==================================================================
1722
+ # Tool 28: get_transactions
1723
+ # ==================================================================
1724
+
1725
+ def get_transactions(self, limit: int = 20) -> Dict[str, Any]:
1726
+ """Get wallet transaction history.
1727
+
1728
+ Args:
1729
+ limit: Maximum number of transactions to return (1 to 100,
1730
+ default 20).
1731
+
1732
+ Returns:
1733
+ Dict with wallet transaction history.
1734
+ """
1735
+ err = _validate_finite_int(limit, "limit", min_value=1, max_value=100)
1736
+ if err:
1737
+ return {"error": err}
1738
+ limit = int(limit)
1739
+
1740
+ qs = f"?limit={limit}" if limit != 20 else ""
1741
+
1742
+ try:
1743
+ result = self._request_with_retry("GET", f"/api/wallet/transactions{qs}")
1744
+ return result
1745
+ except Exception as e:
1746
+ return {"error": _sanitize_error(str(e))}
1747
+
1748
+ # ==================================================================
1749
+ # Tool 29: get_forecast
1750
+ # ==================================================================
1751
+
1752
+ def get_forecast(self) -> Dict[str, Any]:
1753
+ """Get wallet balance forecast based on current usage patterns.
1754
+
1755
+ Returns:
1756
+ Dict with forecast data including estimated depletion date
1757
+ and projected daily spend.
1758
+ """
1759
+ try:
1760
+ result = self._request_with_retry("GET", "/api/wallet/forecast")
1761
+ return result
1762
+ except Exception as e:
1763
+ return {"error": _sanitize_error(str(e))}
1764
+
1765
+ # ==================================================================
1766
+ # Tool 30: check_payment
1767
+ # ==================================================================
1768
+
1769
+ def check_payment(self, invoice_id: str) -> Dict[str, Any]:
1770
+ """Check the status of a crypto payment invoice.
1771
+
1772
+ Args:
1773
+ invoice_id: The invoice ID returned from topup_crypto.
1774
+
1775
+ Returns:
1776
+ Dict with the payment status and details.
1777
+ """
1778
+ if not invoice_id or not isinstance(invoice_id, str):
1779
+ return {"error": "invoice_id is required and must be a string"}
1780
+ if len(invoice_id) > 200:
1781
+ return {"error": "invoice_id must be 200 characters or fewer"}
1782
+
1783
+ try:
1784
+ result = self._request_with_retry(
1785
+ "GET",
1786
+ f"/api/wallet/topup/crypto/{quote(invoice_id, safe='')}/status",
1787
+ )
1788
+ return result
1789
+ except Exception as e:
1790
+ return {"error": _sanitize_error(str(e))}
1791
+
1792
+ # ==================================================================
1793
+ # Tool 31: get_daily_usage
1794
+ # ==================================================================
1795
+
1796
+ def get_daily_usage(self, days: int = 30) -> Dict[str, Any]:
1797
+ """Get daily usage breakdown for recent days.
1798
+
1799
+ Args:
1800
+ days: Number of days to retrieve (1 to 90, default 30).
1801
+
1802
+ Returns:
1803
+ Dict with per-day usage statistics.
1804
+ """
1805
+ err = _validate_finite_int(days, "days", min_value=1, max_value=90)
1806
+ if err:
1807
+ return {"error": err}
1808
+ days = int(days)
1809
+
1810
+ try:
1811
+ result = self._request_with_retry(
1812
+ "GET", f"/api/usage/daily?days={days}"
1813
+ )
1814
+ return result
1815
+ except Exception as e:
1816
+ return {"error": _sanitize_error(str(e))}
1817
+
1818
+ # ==================================================================
1819
+ # Tool 32: get_top_hosts
1820
+ # ==================================================================
1821
+
1822
+ def get_top_hosts(self, limit: int = 10) -> Dict[str, Any]:
1823
+ """Get top destination hosts by bandwidth usage.
1824
+
1825
+ Args:
1826
+ limit: Maximum number of hosts to return (1 to 50, default 10).
1827
+
1828
+ Returns:
1829
+ Dict with top hosts and their bandwidth consumption.
1830
+ """
1831
+ err = _validate_finite_int(limit, "limit", min_value=1, max_value=50)
1832
+ if err:
1833
+ return {"error": err}
1834
+ limit = int(limit)
1835
+
1836
+ try:
1837
+ result = self._request_with_retry(
1838
+ "GET", f"/api/usage/top-hosts?limit={limit}"
1839
+ )
1840
+ return result
1841
+ except Exception as e:
1842
+ return {"error": _sanitize_error(str(e))}
1843
+
1844
+ # ==================================================================
1845
+ # Tool 33: register_account (unauthenticated)
1846
+ # ==================================================================
1847
+
1848
+ def register_account(self, email: str, password: str) -> Dict[str, Any]:
1849
+ """Register a new Dominus Node account.
1850
+
1851
+ This is an unauthenticated operation. After registration, verify
1852
+ the email address to activate the account.
1853
+
1854
+ Args:
1855
+ email: Email address for the new account (max 254 characters).
1856
+ password: Password (8 to 128 characters).
1857
+
1858
+ Returns:
1859
+ Dict with the registered account details.
1860
+ """
1861
+ err = _validate_email(email, "email")
1862
+ if err:
1863
+ return {"error": err}
1864
+
1865
+ err = _validate_password(password, "password")
1866
+ if err:
1867
+ return {"error": err}
1868
+
1869
+ try:
1870
+ body: dict = {"email": email, "password": password}
1871
+ pow_result = _solve_pow(self.base_url)
1872
+ if pow_result:
1873
+ body["pow"] = pow_result
1874
+ result = self._unauthenticated_request(
1875
+ "POST",
1876
+ "/api/auth/register",
1877
+ body,
1878
+ )
1879
+ if pow_result and isinstance(result, dict):
1880
+ result["_pow_verified"] = True
1881
+ return result
1882
+ except Exception as e:
1883
+ return {"error": _sanitize_error(str(e))}
1884
+
1885
+ # ==================================================================
1886
+ # Tool 34: login_account (unauthenticated)
1887
+ # ==================================================================
1888
+
1889
+ def login_account(self, email: str, password: str) -> Dict[str, Any]:
1890
+ """Log in to an existing Dominus Node account.
1891
+
1892
+ This is an unauthenticated operation. Returns JWT tokens on success.
1893
+
1894
+ Args:
1895
+ email: Account email address.
1896
+ password: Account password.
1897
+
1898
+ Returns:
1899
+ Dict with ``accessToken`` and ``refreshToken`` on success,
1900
+ or ``mfaRequired`` if MFA is enabled.
1901
+ """
1902
+ err = _validate_email(email, "email")
1903
+ if err:
1904
+ return {"error": err}
1905
+
1906
+ err = _validate_password(password, "password")
1907
+ if err:
1908
+ return {"error": err}
1909
+
1910
+ try:
1911
+ result = self._unauthenticated_request(
1912
+ "POST",
1913
+ "/api/auth/login",
1914
+ {"email": email, "password": password},
1915
+ )
1916
+ # Surface MFA requirement if present
1917
+ if isinstance(result, dict) and result.get("mfaRequired"):
1918
+ return {"mfaRequired": True, "message": "MFA verification required"}
1919
+ return result
1920
+ except Exception as e:
1921
+ return {"error": _sanitize_error(str(e))}
1922
+
1923
+ # ==================================================================
1924
+ # Tool 35: get_account_info
1925
+ # ==================================================================
1926
+
1927
+ def get_account_info(self) -> Dict[str, Any]:
1928
+ """Get current account information.
1929
+
1930
+ Returns:
1931
+ Dict with account details including email, plan, and
1932
+ verification status.
1933
+ """
1934
+ try:
1935
+ result = self._request_with_retry("GET", "/api/auth/me")
1936
+ return result
1937
+ except Exception as e:
1938
+ return {"error": _sanitize_error(str(e))}
1939
+
1940
+ # ==================================================================
1941
+ # Tool 36: verify_email (unauthenticated)
1942
+ # ==================================================================
1943
+
1944
+ def verify_email(self, token: str) -> Dict[str, Any]:
1945
+ """Verify an email address using the verification token.
1946
+
1947
+ This is an unauthenticated operation. The token is sent to the
1948
+ registered email address.
1949
+
1950
+ Args:
1951
+ token: Email verification token from the verification email.
1952
+
1953
+ Returns:
1954
+ Dict confirming email verification.
1955
+ """
1956
+ if not token or not isinstance(token, str):
1957
+ return {"error": "token is required and must be a string"}
1958
+ if len(token) > 500:
1959
+ return {"error": "token must be 500 characters or fewer"}
1960
+
1961
+ try:
1962
+ result = self._unauthenticated_request(
1963
+ "POST",
1964
+ "/api/auth/verify-email",
1965
+ {"token": token},
1966
+ )
1967
+ return result
1968
+ except Exception as e:
1969
+ return {"error": _sanitize_error(str(e))}
1970
+
1971
+ # ==================================================================
1972
+ # Tool 37: resend_verification
1973
+ # ==================================================================
1974
+
1975
+ def resend_verification(self) -> Dict[str, Any]:
1976
+ """Resend the email verification message.
1977
+
1978
+ Returns:
1979
+ Dict confirming the verification email was resent.
1980
+ """
1981
+ try:
1982
+ result = self._request_with_retry(
1983
+ "POST", "/api/auth/resend-verification"
1984
+ )
1985
+ return result
1986
+ except Exception as e:
1987
+ return {"error": _sanitize_error(str(e))}
1988
+
1989
+ # ==================================================================
1990
+ # Tool 38: update_password
1991
+ # ==================================================================
1992
+
1993
+ def update_password(
1994
+ self, current_password: str, new_password: str
1995
+ ) -> Dict[str, Any]:
1996
+ """Update the account password.
1997
+
1998
+ Args:
1999
+ current_password: The current account password.
2000
+ new_password: The new password (8 to 128 characters).
2001
+
2002
+ Returns:
2003
+ Dict confirming the password was updated.
2004
+ """
2005
+ err = _validate_password(current_password, "current_password")
2006
+ if err:
2007
+ return {"error": err}
2008
+
2009
+ err = _validate_password(new_password, "new_password")
2010
+ if err:
2011
+ return {"error": err}
2012
+
2013
+ try:
2014
+ result = self._request_with_retry(
2015
+ "POST",
2016
+ "/api/auth/change-password",
2017
+ {
2018
+ "currentPassword": current_password,
2019
+ "newPassword": new_password,
2020
+ },
2021
+ )
2022
+ return result
2023
+ except Exception as e:
2024
+ return {"error": _sanitize_error(str(e))}
2025
+
2026
+ # ==================================================================
2027
+ # Tool 39: list_keys
2028
+ # ==================================================================
2029
+
2030
+ def list_keys(self) -> Dict[str, Any]:
2031
+ """List all API keys for the current account.
2032
+
2033
+ Returns:
2034
+ Dict with a list of API keys and their metadata (labels,
2035
+ creation dates, last used).
2036
+ """
2037
+ try:
2038
+ result = self._request_with_retry("GET", "/api/keys")
2039
+ return result
2040
+ except Exception as e:
2041
+ return {"error": _sanitize_error(str(e))}
2042
+
2043
+ # ==================================================================
2044
+ # Tool 40: create_key
2045
+ # ==================================================================
2046
+
2047
+ def create_key(self, label: str) -> Dict[str, Any]:
2048
+ """Create a new API key for the current account.
2049
+
2050
+ Args:
2051
+ label: Human-readable label for the key (max 100 characters).
2052
+
2053
+ Returns:
2054
+ Dict with the created API key (shown once -- save it).
2055
+ """
2056
+ err = _validate_label(label, "label")
2057
+ if err:
2058
+ return {"error": err}
2059
+
2060
+ try:
2061
+ result = self._request_with_retry(
2062
+ "POST", "/api/keys", {"label": label}
2063
+ )
2064
+ return result
2065
+ except Exception as e:
2066
+ return {"error": _sanitize_error(str(e))}
2067
+
2068
+ # ==================================================================
2069
+ # Tool 41: revoke_key
2070
+ # ==================================================================
2071
+
2072
+ def revoke_key(self, key_id: str) -> Dict[str, Any]:
2073
+ """Revoke (delete) an API key.
2074
+
2075
+ Args:
2076
+ key_id: UUID of the API key to revoke.
2077
+
2078
+ Returns:
2079
+ Dict confirming the key has been revoked.
2080
+ """
2081
+ err = _validate_uuid(key_id, "key_id")
2082
+ if err:
2083
+ return {"error": err}
2084
+
2085
+ try:
2086
+ result = self._request_with_retry(
2087
+ "DELETE", f"/api/keys/{quote(key_id, safe='')}"
2088
+ )
2089
+ return result
2090
+ except Exception as e:
2091
+ return {"error": _sanitize_error(str(e))}
2092
+
2093
+ # ==================================================================
2094
+ # Tool 42: get_plan
2095
+ # ==================================================================
2096
+
2097
+ def get_plan(self) -> Dict[str, Any]:
2098
+ """Get the current user's active plan.
2099
+
2100
+ Returns:
2101
+ Dict with the user's current plan details including tier,
2102
+ limits, and pricing.
2103
+ """
2104
+ try:
2105
+ result = self._request_with_retry("GET", "/api/plans/user/plan")
2106
+ return result
2107
+ except Exception as e:
2108
+ return {"error": _sanitize_error(str(e))}
2109
+
2110
+ # ==================================================================
2111
+ # Tool 43: list_plans
2112
+ # ==================================================================
2113
+
2114
+ def list_plans(self) -> Dict[str, Any]:
2115
+ """List all available subscription plans.
2116
+
2117
+ Returns:
2118
+ Dict with a list of available plans and their features.
2119
+ """
2120
+ try:
2121
+ result = self._request_with_retry("GET", "/api/plans")
2122
+ return result
2123
+ except Exception as e:
2124
+ return {"error": _sanitize_error(str(e))}
2125
+
2126
+ # ==================================================================
2127
+ # Tool 44: change_plan
2128
+ # ==================================================================
2129
+
2130
+ def change_plan(self, plan_id: str) -> Dict[str, Any]:
2131
+ """Change the user's subscription plan.
2132
+
2133
+ Args:
2134
+ plan_id: UUID of the plan to switch to.
2135
+
2136
+ Returns:
2137
+ Dict with the updated plan details.
2138
+ """
2139
+ err = _validate_uuid(plan_id, "plan_id")
2140
+ if err:
2141
+ return {"error": err}
2142
+
2143
+ try:
2144
+ result = self._request_with_retry(
2145
+ "PUT",
2146
+ "/api/plans/user/plan",
2147
+ {"planId": plan_id},
2148
+ )
2149
+ return result
2150
+ except Exception as e:
2151
+ return {"error": _sanitize_error(str(e))}
2152
+
2153
+ # ==================================================================
2154
+ # Tool 45: team_delete
2155
+ # ==================================================================
2156
+
2157
+ def team_delete(self, team_id: str) -> Dict[str, Any]:
2158
+ """Delete a team.
2159
+
2160
+ Args:
2161
+ team_id: UUID of the team to delete.
2162
+
2163
+ Returns:
2164
+ Dict confirming the team has been deleted.
2165
+ """
2166
+ err = _validate_uuid(team_id, "team_id")
2167
+ if err:
2168
+ return {"error": err}
2169
+
2170
+ try:
2171
+ result = self._request_with_retry(
2172
+ "DELETE", f"/api/teams/{quote(team_id, safe='')}"
2173
+ )
2174
+ return result
2175
+ except Exception as e:
2176
+ return {"error": _sanitize_error(str(e))}
2177
+
2178
+ # ==================================================================
2179
+ # Tool 46: team_revoke_key
2180
+ # ==================================================================
2181
+
2182
+ def team_revoke_key(self, team_id: str, key_id: str) -> Dict[str, Any]:
2183
+ """Revoke (delete) an API key from a team.
2184
+
2185
+ Args:
2186
+ team_id: UUID of the team.
2187
+ key_id: UUID of the API key to revoke.
2188
+
2189
+ Returns:
2190
+ Dict confirming the team API key has been revoked.
2191
+ """
2192
+ err = _validate_uuid(team_id, "team_id")
2193
+ if err:
2194
+ return {"error": err}
2195
+
2196
+ err = _validate_uuid(key_id, "key_id")
2197
+ if err:
2198
+ return {"error": err}
2199
+
2200
+ try:
2201
+ result = self._request_with_retry(
2202
+ "DELETE",
2203
+ f"/api/teams/{quote(team_id, safe='')}/keys/{quote(key_id, safe='')}",
2204
+ )
2205
+ return result
2206
+ except Exception as e:
2207
+ return {"error": _sanitize_error(str(e))}
2208
+
2209
+ # ==================================================================
2210
+ # Tool 47: team_list_keys
2211
+ # ==================================================================
2212
+
2213
+ def team_list_keys(self, team_id: str) -> Dict[str, Any]:
2214
+ """List all API keys for a team.
2215
+
2216
+ Args:
2217
+ team_id: UUID of the team.
2218
+
2219
+ Returns:
2220
+ Dict with a list of the team's API keys and their metadata.
2221
+ """
2222
+ err = _validate_uuid(team_id, "team_id")
2223
+ if err:
2224
+ return {"error": err}
2225
+
2226
+ try:
2227
+ result = self._request_with_retry(
2228
+ "GET", f"/api/teams/{quote(team_id, safe='')}/keys"
2229
+ )
2230
+ return result
2231
+ except Exception as e:
2232
+ return {"error": _sanitize_error(str(e))}
2233
+
2234
+ # ==================================================================
2235
+ # Tool 48: team_list_members
2236
+ # ==================================================================
2237
+
2238
+ def team_list_members(self, team_id: str) -> Dict[str, Any]:
2239
+ """List all members of a team.
2240
+
2241
+ Args:
2242
+ team_id: UUID of the team.
2243
+
2244
+ Returns:
2245
+ Dict with a list of team members and their roles.
2246
+ """
2247
+ err = _validate_uuid(team_id, "team_id")
2248
+ if err:
2249
+ return {"error": err}
2250
+
2251
+ try:
2252
+ result = self._request_with_retry(
2253
+ "GET", f"/api/teams/{quote(team_id, safe='')}/members"
2254
+ )
2255
+ return result
2256
+ except Exception as e:
2257
+ return {"error": _sanitize_error(str(e))}
2258
+
2259
+ # ==================================================================
2260
+ # Tool 49: team_add_member
2261
+ # ==================================================================
2262
+
2263
+ def team_add_member(
2264
+ self, team_id: str, user_id: str, role: str = "member"
2265
+ ) -> Dict[str, Any]:
2266
+ """Add a member to a team.
2267
+
2268
+ Args:
2269
+ team_id: UUID of the team.
2270
+ user_id: UUID of the user to add.
2271
+ role: Role to assign -- ``member`` or ``admin`` (default ``member``).
2272
+
2273
+ Returns:
2274
+ Dict confirming the member was added.
2275
+ """
2276
+ err = _validate_uuid(team_id, "team_id")
2277
+ if err:
2278
+ return {"error": err}
2279
+
2280
+ err = _validate_uuid(user_id, "user_id")
2281
+ if err:
2282
+ return {"error": err}
2283
+
2284
+ if not role or not isinstance(role, str):
2285
+ return {"error": "role is required and must be a string"}
2286
+ if role not in ("member", "admin"):
2287
+ return {"error": "role must be 'member' or 'admin'"}
2288
+
2289
+ try:
2290
+ result = self._request_with_retry(
2291
+ "POST",
2292
+ f"/api/teams/{quote(team_id, safe='')}/members",
2293
+ {"userId": user_id, "role": role},
2294
+ )
2295
+ return result
2296
+ except Exception as e:
2297
+ return {"error": _sanitize_error(str(e))}
2298
+
2299
+ # ==================================================================
2300
+ # Tool 50: team_remove_member
2301
+ # ==================================================================
2302
+
2303
+ def team_remove_member(self, team_id: str, user_id: str) -> Dict[str, Any]:
2304
+ """Remove a member from a team.
2305
+
2306
+ Args:
2307
+ team_id: UUID of the team.
2308
+ user_id: UUID of the user to remove.
2309
+
2310
+ Returns:
2311
+ Dict confirming the member was removed.
2312
+ """
2313
+ err = _validate_uuid(team_id, "team_id")
2314
+ if err:
2315
+ return {"error": err}
2316
+
2317
+ err = _validate_uuid(user_id, "user_id")
2318
+ if err:
2319
+ return {"error": err}
2320
+
2321
+ try:
2322
+ result = self._request_with_retry(
2323
+ "DELETE",
2324
+ f"/api/teams/{quote(team_id, safe='')}/members/{quote(user_id, safe='')}",
2325
+ )
2326
+ return result
2327
+ except Exception as e:
2328
+ return {"error": _sanitize_error(str(e))}
2329
+
2330
+ # ==================================================================
2331
+ # Tool 51: team_invite_member
2332
+ # ==================================================================
2333
+
2334
+ def team_invite_member(
2335
+ self, team_id: str, email: str, role: str = "member"
2336
+ ) -> Dict[str, Any]:
2337
+ """Invite a user to a team by email.
2338
+
2339
+ Args:
2340
+ team_id: UUID of the team.
2341
+ email: Email address of the user to invite.
2342
+ role: Role to assign on acceptance -- ``member`` or ``admin``
2343
+ (default ``member``).
2344
+
2345
+ Returns:
2346
+ Dict with the invite details.
2347
+ """
2348
+ err = _validate_uuid(team_id, "team_id")
2349
+ if err:
2350
+ return {"error": err}
2351
+
2352
+ err = _validate_email(email, "email")
2353
+ if err:
2354
+ return {"error": err}
2355
+
2356
+ if not role or not isinstance(role, str):
2357
+ return {"error": "role is required and must be a string"}
2358
+ if role not in ("member", "admin"):
2359
+ return {"error": "role must be 'member' or 'admin'"}
2360
+
2361
+ try:
2362
+ result = self._request_with_retry(
2363
+ "POST",
2364
+ f"/api/teams/{quote(team_id, safe='')}/invites",
2365
+ {"email": email, "role": role},
2366
+ )
2367
+ return result
2368
+ except Exception as e:
2369
+ return {"error": _sanitize_error(str(e))}
2370
+
2371
+ # ==================================================================
2372
+ # Tool 52: team_list_invites
2373
+ # ==================================================================
2374
+
2375
+ def team_list_invites(self, team_id: str) -> Dict[str, Any]:
2376
+ """List pending invites for a team.
2377
+
2378
+ Args:
2379
+ team_id: UUID of the team.
2380
+
2381
+ Returns:
2382
+ Dict with a list of pending team invites.
2383
+ """
2384
+ err = _validate_uuid(team_id, "team_id")
2385
+ if err:
2386
+ return {"error": err}
2387
+
2388
+ try:
2389
+ result = self._request_with_retry(
2390
+ "GET", f"/api/teams/{quote(team_id, safe='')}/invites"
2391
+ )
2392
+ return result
2393
+ except Exception as e:
2394
+ return {"error": _sanitize_error(str(e))}
2395
+
2396
+ # ==================================================================
2397
+ # Tool 53: team_cancel_invite
2398
+ # ==================================================================
2399
+
2400
+ def team_cancel_invite(self, team_id: str, invite_id: str) -> Dict[str, Any]:
2401
+ """Cancel a pending team invite.
2402
+
2403
+ Args:
2404
+ team_id: UUID of the team.
2405
+ invite_id: UUID of the invite to cancel.
2406
+
2407
+ Returns:
2408
+ Dict confirming the invite has been cancelled.
2409
+ """
2410
+ err = _validate_uuid(team_id, "team_id")
2411
+ if err:
2412
+ return {"error": err}
2413
+
2414
+ err = _validate_uuid(invite_id, "invite_id")
2415
+ if err:
2416
+ return {"error": err}
2417
+
2418
+ try:
2419
+ result = self._request_with_retry(
2420
+ "DELETE",
2421
+ f"/api/teams/{quote(team_id, safe='')}/invites/{quote(invite_id, safe='')}",
2422
+ )
2423
+ return result
2424
+ except Exception as e:
2425
+ return {"error": _sanitize_error(str(e))}
2426
+
1549
2427
  # ==================================================================
1550
2428
  # get_tools -- return CAMEL FunctionTool objects
1551
2429
  # ==================================================================
1552
2430
 
1553
2431
  def get_tools(self) -> list:
1554
- """Return CAMEL FunctionTool objects for all 26 Dominus Node operations.
2432
+ """Return CAMEL FunctionTool objects for all 53 Dominus Node operations.
1555
2433
 
1556
2434
  Each method is wrapped in a ``camel.toolkits.FunctionTool`` so that
1557
2435
  CAMEL agents can discover and invoke them automatically.
1558
2436
 
1559
2437
  Returns:
1560
- A list of 26 ``FunctionTool`` instances.
2438
+ A list of 53 ``FunctionTool`` instances.
1561
2439
  """
1562
2440
  from camel.toolkits import FunctionTool
1563
2441
 
1564
2442
  return [
2443
+ # Proxy tools (1-5)
1565
2444
  FunctionTool(self.proxied_fetch),
1566
2445
  FunctionTool(self.check_balance),
1567
2446
  FunctionTool(self.check_usage),
1568
2447
  FunctionTool(self.get_proxy_config),
1569
2448
  FunctionTool(self.list_sessions),
2449
+ # Agentic wallet tools (6-13)
1570
2450
  FunctionTool(self.create_agentic_wallet),
1571
2451
  FunctionTool(self.fund_agentic_wallet),
1572
2452
  FunctionTool(self.agentic_wallet_balance),
@@ -1575,6 +2455,7 @@ class DominusNodeToolkit:
1575
2455
  FunctionTool(self.freeze_agentic_wallet),
1576
2456
  FunctionTool(self.unfreeze_agentic_wallet),
1577
2457
  FunctionTool(self.delete_agentic_wallet),
2458
+ # Team tools (14-21)
1578
2459
  FunctionTool(self.create_team),
1579
2460
  FunctionTool(self.list_teams),
1580
2461
  FunctionTool(self.team_details),
@@ -1583,9 +2464,44 @@ class DominusNodeToolkit:
1583
2464
  FunctionTool(self.team_usage),
1584
2465
  FunctionTool(self.update_team),
1585
2466
  FunctionTool(self.update_team_member_role),
2467
+ # Payment tools (22-26)
1586
2468
  FunctionTool(self.x402_info),
1587
2469
  FunctionTool(self.topup_paypal),
1588
2470
  FunctionTool(self.topup_stripe),
1589
2471
  FunctionTool(self.topup_crypto),
1590
2472
  FunctionTool(self.update_wallet_policy),
2473
+ # Proxy status (27)
2474
+ FunctionTool(self.get_proxy_status),
2475
+ # Wallet extras (28-30)
2476
+ FunctionTool(self.get_transactions),
2477
+ FunctionTool(self.get_forecast),
2478
+ FunctionTool(self.check_payment),
2479
+ # Usage extras (31-32)
2480
+ FunctionTool(self.get_daily_usage),
2481
+ FunctionTool(self.get_top_hosts),
2482
+ # Account management (33-38)
2483
+ FunctionTool(self.register_account),
2484
+ FunctionTool(self.login_account),
2485
+ FunctionTool(self.get_account_info),
2486
+ FunctionTool(self.verify_email),
2487
+ FunctionTool(self.resend_verification),
2488
+ FunctionTool(self.update_password),
2489
+ # API key management (39-41)
2490
+ FunctionTool(self.list_keys),
2491
+ FunctionTool(self.create_key),
2492
+ FunctionTool(self.revoke_key),
2493
+ # Plan management (42-44)
2494
+ FunctionTool(self.get_plan),
2495
+ FunctionTool(self.list_plans),
2496
+ FunctionTool(self.change_plan),
2497
+ # Extended team management (45-53)
2498
+ FunctionTool(self.team_delete),
2499
+ FunctionTool(self.team_revoke_key),
2500
+ FunctionTool(self.team_list_keys),
2501
+ FunctionTool(self.team_list_members),
2502
+ FunctionTool(self.team_add_member),
2503
+ FunctionTool(self.team_remove_member),
2504
+ FunctionTool(self.team_invite_member),
2505
+ FunctionTool(self.team_list_invites),
2506
+ FunctionTool(self.team_cancel_invite),
1591
2507
  ]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dominusnode-camel
3
- Version: 1.1.0
3
+ Version: 1.2.1
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.1.0"
7
+ version = "1.2.1"
8
8
  description = "Dominus Node rotating proxy tools for CAMEL-AI agents"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -979,8 +979,8 @@ class TestToolMethods:
979
979
  class TestGetTools:
980
980
  """Test the get_tools method returns correct FunctionTool objects."""
981
981
 
982
- def test_get_tools_returns_26_tools(self):
983
- """get_tools should return 26 FunctionTool objects."""
982
+ def test_get_tools_returns_53_tools(self):
983
+ """get_tools should return 53 FunctionTool objects."""
984
984
  toolkit = DominusNodeToolkit(api_key="dn_test_key123")
985
985
 
986
986
  # Mock FunctionTool since camel-ai may not be installed in test env
@@ -996,8 +996,9 @@ class TestGetTools:
996
996
  with patch("camel.toolkits.FunctionTool", side_effect=mock_function_tool):
997
997
  tools = toolkit.get_tools()
998
998
 
999
- assert len(tools) == 26
999
+ assert len(tools) == 53
1000
1000
  tool_names = [t.name for t in tools]
1001
+ # Original 26 tools
1001
1002
  assert "proxied_fetch" in tool_names
1002
1003
  assert "check_balance" in tool_names
1003
1004
  assert "check_usage" in tool_names
@@ -1024,6 +1025,34 @@ class TestGetTools:
1024
1025
  assert "topup_stripe" in tool_names
1025
1026
  assert "topup_crypto" in tool_names
1026
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
1027
1056
 
1028
1057
 
1029
1058
  # ===========================================================================