@dominusnode/openclaw-plugin 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/plugin.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Dominus Node OpenClaw Plugin
3
3
  *
4
- * Implements 26 tools for interacting with Dominus Node's rotating proxy service
4
+ * Implements 53 tools for interacting with Dominus Node's rotating proxy service
5
5
  * directly from OpenClaw AI coding sessions.
6
6
  *
7
7
  * Uses native fetch (no external dependencies). Runs via jiti runtime.
package/dist/plugin.js CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Dominus Node OpenClaw Plugin
3
3
  *
4
- * Implements 26 tools for interacting with Dominus Node's rotating proxy service
4
+ * Implements 53 tools for interacting with Dominus Node's rotating proxy service
5
5
  * directly from OpenClaw AI coding sessions.
6
6
  *
7
7
  * Uses native fetch (no external dependencies). Runs via jiti runtime.
@@ -466,6 +466,83 @@ async function apiDelete(path) {
466
466
  async function apiPatch(path, body) {
467
467
  return apiRequest("PATCH", path, body);
468
468
  }
469
+ async function apiPut(path, body) {
470
+ return apiRequest("PUT", path, body);
471
+ }
472
+ /**
473
+ * Make an unauthenticated API request (for register, login, verify-email).
474
+ * Does NOT call ensureAuth(). Includes agent headers if available.
475
+ */
476
+ async function unauthenticatedRequest(method, path, body) {
477
+ const baseUrl = getBaseUrl();
478
+ const url = `${baseUrl}${path}`;
479
+ const headers = {
480
+ "Accept": "application/json",
481
+ "User-Agent": "dominusnode-openclaw-plugin/1.0.0",
482
+ };
483
+ const agentSecret = getAgentSecret();
484
+ if (agentSecret) {
485
+ headers["X-DominusNode-Agent"] = "mcp";
486
+ headers["X-DominusNode-Agent-Secret"] = agentSecret;
487
+ }
488
+ const init = {
489
+ method,
490
+ headers,
491
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
492
+ redirect: "error",
493
+ };
494
+ if (body && (method === "POST" || method === "PUT" || method === "PATCH")) {
495
+ headers["Content-Type"] = "application/json";
496
+ init.body = JSON.stringify(body);
497
+ }
498
+ let res;
499
+ try {
500
+ res = await fetch(url, init);
501
+ }
502
+ catch (err) {
503
+ throw new Error(`API request failed: ${safeError(err)}`);
504
+ }
505
+ let rawText;
506
+ try {
507
+ rawText = await res.text();
508
+ }
509
+ catch {
510
+ rawText = "";
511
+ }
512
+ if (new TextEncoder().encode(rawText).length > MAX_RESPONSE_BYTES) {
513
+ throw new Error("API response too large");
514
+ }
515
+ if (!res.ok) {
516
+ let errorMsg = `API error ${res.status}`;
517
+ if (rawText) {
518
+ try {
519
+ const parsed = JSON.parse(rawText);
520
+ stripDangerousKeys(parsed);
521
+ if (parsed.error) {
522
+ errorMsg = `API error ${res.status}: ${scrubCredentials(String(parsed.error))}`;
523
+ }
524
+ else if (parsed.message) {
525
+ errorMsg = `API error ${res.status}: ${scrubCredentials(String(parsed.message))}`;
526
+ }
527
+ }
528
+ catch {
529
+ errorMsg = `API error ${res.status}: ${scrubCredentials(rawText.slice(0, 200))}`;
530
+ }
531
+ }
532
+ throw new Error(errorMsg);
533
+ }
534
+ if (!rawText || rawText.trim().length === 0) {
535
+ return {};
536
+ }
537
+ try {
538
+ const parsed = JSON.parse(rawText);
539
+ stripDangerousKeys(parsed);
540
+ return parsed;
541
+ }
542
+ catch {
543
+ throw new Error("Failed to parse API response as JSON");
544
+ }
545
+ }
469
546
  // ---------------------------------------------------------------------------
470
547
  // Formatting helpers
471
548
  // ---------------------------------------------------------------------------
@@ -1699,36 +1776,1030 @@ const updateWalletPolicyTool = {
1699
1776
  }
1700
1777
  },
1701
1778
  };
1702
- // ---------------------------------------------------------------------------
1703
- // Plugin export — the tools array that OpenClaw discovers
1704
- // ---------------------------------------------------------------------------
1705
- export const tools = [
1706
- proxiedFetchTool,
1707
- checkBalanceTool,
1708
- checkUsageTool,
1709
- getProxyConfigTool,
1710
- listSessionsTool,
1711
- createAgenticWalletTool,
1712
- fundAgenticWalletTool,
1713
- checkAgenticBalanceTool,
1714
- listAgenticWalletsTool,
1715
- agenticTransactionsTool,
1716
- freezeAgenticWalletTool,
1717
- unfreezeAgenticWalletTool,
1718
- deleteAgenticWalletTool,
1719
- createTeamTool,
1720
- listTeamsTool,
1721
- teamDetailsTool,
1722
- teamFundTool,
1723
- teamCreateKeyTool,
1724
- teamUsageTool,
1725
- updateTeamTool,
1726
- updateTeamMemberRoleTool,
1727
- topupPaypalTool,
1728
- topupStripeTool,
1729
- topupCryptoTool,
1730
- x402InfoTool,
1731
- updateWalletPolicyTool,
1779
+ // 27. get_proxy_status
1780
+ const getProxyStatusTool = {
1781
+ name: "get_proxy_status",
1782
+ description: "Get the current status of the proxy gateway including uptime, active connections, and pool health.",
1783
+ parameters: {},
1784
+ execute: async () => {
1785
+ try {
1786
+ const data = await apiGet("/api/proxy/status");
1787
+ const lines = [
1788
+ "Proxy Gateway Status",
1789
+ "",
1790
+ `Status: ${data.status ?? "unknown"}`,
1791
+ `Uptime: ${data.uptime != null ? `${Math.floor(data.uptime / 3600)}h ${Math.floor((data.uptime % 3600) / 60)}m` : "unknown"}`,
1792
+ `Active Connections: ${data.activeConnections ?? 0}`,
1793
+ ];
1794
+ const pools = data.pools ?? [];
1795
+ if (pools.length > 0) {
1796
+ lines.push("", "Pool Health:");
1797
+ for (const p of pools) {
1798
+ lines.push(` ${p.name}: ${p.healthy ? "healthy" : "unhealthy"} (${p.activeIps} IPs)`);
1799
+ }
1800
+ }
1801
+ return lines.join("\n");
1802
+ }
1803
+ catch (err) {
1804
+ return `Error: ${safeError(err)}`;
1805
+ }
1806
+ },
1807
+ };
1808
+ // 28. get_transactions
1809
+ const getTransactionsTool = {
1810
+ name: "get_transactions",
1811
+ description: "Get your main wallet transaction history including top-ups, usage charges, and refunds.",
1812
+ parameters: {
1813
+ limit: {
1814
+ type: "number",
1815
+ description: "Number of transactions to return (1-100, default 20)",
1816
+ required: false,
1817
+ default: 20,
1818
+ },
1819
+ },
1820
+ execute: async (args) => {
1821
+ try {
1822
+ const limit = Math.min(Math.max(Number(args.limit ?? 20), 1), 100);
1823
+ const data = await apiGet(`/api/wallet/transactions?limit=${limit}`);
1824
+ const txs = data.transactions ?? [];
1825
+ if (txs.length === 0) {
1826
+ return "No wallet transactions found.";
1827
+ }
1828
+ const lines = [`Wallet Transactions (${txs.length})`, ""];
1829
+ for (const tx of txs) {
1830
+ const sign = tx.type === "topup" || tx.type === "refund" || tx.type === "fund" ? "+" : "-";
1831
+ lines.push(` ${sign}${formatCents(Math.abs(tx.amountCents))} [${tx.type}] ${tx.description}`);
1832
+ lines.push(` ${tx.createdAt}`);
1833
+ }
1834
+ return truncate(lines.join("\n"));
1835
+ }
1836
+ catch (err) {
1837
+ return `Error: ${safeError(err)}`;
1838
+ }
1839
+ },
1840
+ };
1841
+ // 29. get_forecast
1842
+ const getForecastTool = {
1843
+ name: "get_forecast",
1844
+ description: "Get a spending forecast based on recent usage patterns. Shows projected balance depletion date and daily burn rate.",
1845
+ parameters: {},
1846
+ execute: async () => {
1847
+ try {
1848
+ const data = await apiGet("/api/wallet/forecast");
1849
+ const lines = [
1850
+ "Spending Forecast",
1851
+ "",
1852
+ `Current Balance: ${formatCents(data.currentBalanceCents ?? 0)}`,
1853
+ `Daily Burn Rate: ${formatCents(data.dailyBurnCents ?? 0)}/day`,
1854
+ `Avg Daily Bandwidth: ${formatBytes(data.avgDailyBytes ?? 0)}`,
1855
+ `Estimated Days Remaining: ${data.estimatedDaysRemaining ?? "N/A"}`,
1856
+ `Projected Depletion: ${data.projectedDepletionDate ?? "N/A"}`,
1857
+ "",
1858
+ "Use topup_stripe, topup_paypal, or topup_crypto to add funds.",
1859
+ ];
1860
+ return lines.join("\n");
1861
+ }
1862
+ catch (err) {
1863
+ return `Error: ${safeError(err)}`;
1864
+ }
1865
+ },
1866
+ };
1867
+ // 30. check_payment
1868
+ const checkPaymentTool = {
1869
+ name: "check_payment",
1870
+ description: "Check the status of a crypto payment invoice. Use the invoice ID returned by topup_crypto.",
1871
+ parameters: {
1872
+ invoice_id: {
1873
+ type: "string",
1874
+ description: "Crypto invoice ID to check",
1875
+ required: true,
1876
+ },
1877
+ },
1878
+ execute: async (args) => {
1879
+ try {
1880
+ const invoiceId = String(args.invoice_id ?? "").trim();
1881
+ if (!invoiceId || invoiceId.length === 0 || invoiceId.length > 200) {
1882
+ return "Error: invoice_id is required (max 200 characters).";
1883
+ }
1884
+ if (/[\x00-\x1f\x7f]/.test(invoiceId)) {
1885
+ return "Error: invoice_id contains invalid control characters.";
1886
+ }
1887
+ const data = await apiGet(`/api/wallet/topup/crypto/${encodeURIComponent(invoiceId)}/status`);
1888
+ const lines = [
1889
+ "Crypto Payment Status",
1890
+ "",
1891
+ `Invoice ID: ${data.invoiceId ?? invoiceId}`,
1892
+ `Status: ${data.status}`,
1893
+ `Amount: $${data.amountUsd}`,
1894
+ `Currency: ${data.payCurrency?.toUpperCase() ?? "N/A"}`,
1895
+ ];
1896
+ if (data.paidAmount != null) {
1897
+ lines.push(`Paid: ${data.paidAmount} ${data.payCurrency?.toUpperCase() ?? ""}`);
1898
+ }
1899
+ lines.push(`Created: ${data.createdAt}`, `Updated: ${data.updatedAt}`);
1900
+ return lines.join("\n");
1901
+ }
1902
+ catch (err) {
1903
+ return `Error: ${safeError(err)}`;
1904
+ }
1905
+ },
1906
+ };
1907
+ // 31. get_daily_usage
1908
+ const getDailyUsageTool = {
1909
+ name: "get_daily_usage",
1910
+ description: "Get daily bandwidth usage breakdown for the specified number of days. Shows per-day bytes, cost, and request count.",
1911
+ parameters: {
1912
+ days: {
1913
+ type: "number",
1914
+ description: "Number of days to look back (1-90, default 7)",
1915
+ required: false,
1916
+ default: 7,
1917
+ },
1918
+ },
1919
+ execute: async (args) => {
1920
+ try {
1921
+ const days = Math.min(Math.max(Number(args.days ?? 7), 1), 90);
1922
+ const data = await apiGet(`/api/usage/daily?days=${days}`);
1923
+ const daily = data.daily ?? [];
1924
+ if (daily.length === 0) {
1925
+ return `No daily usage data found for the last ${days} days.`;
1926
+ }
1927
+ const lines = [`Daily Usage (last ${days} days)`, ""];
1928
+ for (const d of daily) {
1929
+ lines.push(` ${d.date}: ${formatBytes(d.totalBytes)} | ${formatCents(d.totalCostCents)} | ${d.requestCount} reqs`);
1930
+ }
1931
+ return truncate(lines.join("\n"));
1932
+ }
1933
+ catch (err) {
1934
+ return `Error: ${safeError(err)}`;
1935
+ }
1936
+ },
1937
+ };
1938
+ // 32. get_top_hosts
1939
+ const getTopHostsTool = {
1940
+ name: "get_top_hosts",
1941
+ description: "Get the top hosts by bandwidth usage. Shows which domains consume the most proxy bandwidth.",
1942
+ parameters: {
1943
+ limit: {
1944
+ type: "number",
1945
+ description: "Number of top hosts to return (1-100, default 10)",
1946
+ required: false,
1947
+ default: 10,
1948
+ },
1949
+ },
1950
+ execute: async (args) => {
1951
+ try {
1952
+ const limit = Math.min(Math.max(Number(args.limit ?? 10), 1), 100);
1953
+ const data = await apiGet(`/api/usage/top-hosts?limit=${limit}`);
1954
+ const hosts = data.hosts ?? [];
1955
+ if (hosts.length === 0) {
1956
+ return "No host usage data found.";
1957
+ }
1958
+ const lines = [`Top Hosts by Bandwidth (${hosts.length})`, ""];
1959
+ for (let i = 0; i < hosts.length; i++) {
1960
+ const h = hosts[i];
1961
+ lines.push(` ${i + 1}. ${h.host}: ${formatBytes(h.totalBytes)} | ${h.requestCount} reqs | Last: ${h.lastSeen}`);
1962
+ }
1963
+ return truncate(lines.join("\n"));
1964
+ }
1965
+ catch (err) {
1966
+ return `Error: ${safeError(err)}`;
1967
+ }
1968
+ },
1969
+ };
1970
+ // 33. register
1971
+ const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
1972
+ const registerTool = {
1973
+ name: "register",
1974
+ description: "Register a new Dominus Node account. Returns user info and instructions for email verification. " +
1975
+ "This is an unauthenticated endpoint -- no API key required.",
1976
+ parameters: {
1977
+ email: {
1978
+ type: "string",
1979
+ description: "Email address for the new account",
1980
+ required: true,
1981
+ },
1982
+ password: {
1983
+ type: "string",
1984
+ description: "Password for the new account (8-128 characters)",
1985
+ required: true,
1986
+ },
1987
+ },
1988
+ execute: async (args) => {
1989
+ try {
1990
+ const email = String(args.email ?? "").trim();
1991
+ if (!email || !EMAIL_RE.test(email)) {
1992
+ return "Error: A valid email address is required.";
1993
+ }
1994
+ if (email.length > 254) {
1995
+ return "Error: Email address is too long (max 254 characters).";
1996
+ }
1997
+ const password = String(args.password ?? "");
1998
+ if (password.length < 8 || password.length > 128) {
1999
+ return "Error: Password must be 8-128 characters.";
2000
+ }
2001
+ const data = await unauthenticatedRequest("POST", "/api/auth/register", { email, password });
2002
+ return [
2003
+ "Account Registered",
2004
+ "",
2005
+ `User ID: ${data.id}`,
2006
+ `Email: ${data.email}`,
2007
+ `Email Verified: ${data.emailVerified ? "Yes" : "No"}`,
2008
+ `Created: ${data.createdAt}`,
2009
+ "",
2010
+ "Next steps:",
2011
+ " 1. Check your email for a verification code",
2012
+ " 2. Use verify_email to verify your email address",
2013
+ " 3. Set DOMINUSNODE_API_KEY to start using the proxy",
2014
+ ].join("\n");
2015
+ }
2016
+ catch (err) {
2017
+ return `Error: ${safeError(err)}`;
2018
+ }
2019
+ },
2020
+ };
2021
+ // 34. login
2022
+ const loginTool = {
2023
+ name: "login",
2024
+ description: "Log in to a Dominus Node account and receive access/refresh tokens. " +
2025
+ "This is an unauthenticated endpoint -- no API key required.",
2026
+ parameters: {
2027
+ email: {
2028
+ type: "string",
2029
+ description: "Account email address",
2030
+ required: true,
2031
+ },
2032
+ password: {
2033
+ type: "string",
2034
+ description: "Account password (8-128 characters)",
2035
+ required: true,
2036
+ },
2037
+ },
2038
+ execute: async (args) => {
2039
+ try {
2040
+ const email = String(args.email ?? "").trim();
2041
+ if (!email || !EMAIL_RE.test(email)) {
2042
+ return "Error: A valid email address is required.";
2043
+ }
2044
+ if (email.length > 254) {
2045
+ return "Error: Email address is too long (max 254 characters).";
2046
+ }
2047
+ const password = String(args.password ?? "");
2048
+ if (password.length < 8 || password.length > 128) {
2049
+ return "Error: Password must be 8-128 characters.";
2050
+ }
2051
+ const data = await unauthenticatedRequest("POST", "/api/auth/login", { email, password });
2052
+ if (data.mfaRequired) {
2053
+ return [
2054
+ "MFA Required",
2055
+ "",
2056
+ "This account has multi-factor authentication enabled.",
2057
+ "Please complete MFA verification to continue.",
2058
+ ].join("\n");
2059
+ }
2060
+ return [
2061
+ "Login Successful",
2062
+ "",
2063
+ `Access Token: ${scrubCredentials(data.accessToken ?? "")}`,
2064
+ `Refresh Token: ${scrubCredentials(data.refreshToken ?? "")}`,
2065
+ `Expires In: ${data.expiresIn ?? 900} seconds`,
2066
+ "",
2067
+ "Use these tokens for authenticated API requests.",
2068
+ ].join("\n");
2069
+ }
2070
+ catch (err) {
2071
+ return `Error: ${safeError(err)}`;
2072
+ }
2073
+ },
2074
+ };
2075
+ // 35. get_account_info
2076
+ const getAccountInfoTool = {
2077
+ name: "get_account_info",
2078
+ description: "Get your Dominus Node account information including email, plan, verification status, and creation date.",
2079
+ parameters: {},
2080
+ execute: async () => {
2081
+ try {
2082
+ const data = await apiGet("/api/auth/me");
2083
+ return [
2084
+ "Account Info",
2085
+ "",
2086
+ `User ID: ${data.id}`,
2087
+ `Email: ${data.email}`,
2088
+ `Email Verified: ${data.emailVerified ? "Yes" : "No"}`,
2089
+ `Plan: ${data.plan}`,
2090
+ `Status: ${data.status}`,
2091
+ `MFA Enabled: ${data.mfaEnabled ? "Yes" : "No"}`,
2092
+ `Created: ${data.createdAt}`,
2093
+ ].join("\n");
2094
+ }
2095
+ catch (err) {
2096
+ return `Error: ${safeError(err)}`;
2097
+ }
2098
+ },
2099
+ };
2100
+ // 36. verify_email
2101
+ const verifyEmailTool = {
2102
+ name: "verify_email",
2103
+ description: "Verify your email address using the code sent during registration. " +
2104
+ "This is an unauthenticated endpoint -- no API key required.",
2105
+ parameters: {
2106
+ email: {
2107
+ type: "string",
2108
+ description: "Email address to verify",
2109
+ required: true,
2110
+ },
2111
+ code: {
2112
+ type: "string",
2113
+ description: "Verification code from email",
2114
+ required: true,
2115
+ },
2116
+ },
2117
+ execute: async (args) => {
2118
+ try {
2119
+ const email = String(args.email ?? "").trim();
2120
+ if (!email || !EMAIL_RE.test(email)) {
2121
+ return "Error: A valid email address is required.";
2122
+ }
2123
+ if (email.length > 254) {
2124
+ return "Error: Email address is too long (max 254 characters).";
2125
+ }
2126
+ const code = String(args.code ?? "").trim();
2127
+ if (!code || code.length === 0 || code.length > 100) {
2128
+ return "Error: Verification code is required (max 100 characters).";
2129
+ }
2130
+ if (/[\x00-\x1f\x7f]/.test(code)) {
2131
+ return "Error: Verification code contains invalid control characters.";
2132
+ }
2133
+ await unauthenticatedRequest("POST", "/api/auth/verify-email", { email, code });
2134
+ return [
2135
+ "Email Verified Successfully",
2136
+ "",
2137
+ `Email: ${email}`,
2138
+ "",
2139
+ "Your account is now fully activated. You can start using the proxy.",
2140
+ ].join("\n");
2141
+ }
2142
+ catch (err) {
2143
+ return `Error: ${safeError(err)}`;
2144
+ }
2145
+ },
2146
+ };
2147
+ // 37. resend_verification
2148
+ const resendVerificationTool = {
2149
+ name: "resend_verification",
2150
+ description: "Resend the email verification code. Requires authentication.",
2151
+ parameters: {},
2152
+ execute: async () => {
2153
+ try {
2154
+ await apiPost("/api/auth/resend-verification");
2155
+ return [
2156
+ "Verification Email Resent",
2157
+ "",
2158
+ "A new verification code has been sent to your registered email address.",
2159
+ "Use verify_email with the new code to complete verification.",
2160
+ ].join("\n");
2161
+ }
2162
+ catch (err) {
2163
+ return `Error: ${safeError(err)}`;
2164
+ }
2165
+ },
2166
+ };
2167
+ // 38. update_password
2168
+ const updatePasswordTool = {
2169
+ name: "update_password",
2170
+ description: "Change your account password. Requires your current password for verification.",
2171
+ parameters: {
2172
+ current_password: {
2173
+ type: "string",
2174
+ description: "Your current password (8-128 characters)",
2175
+ required: true,
2176
+ },
2177
+ new_password: {
2178
+ type: "string",
2179
+ description: "Your new password (8-128 characters)",
2180
+ required: true,
2181
+ },
2182
+ },
2183
+ execute: async (args) => {
2184
+ try {
2185
+ const currentPassword = String(args.current_password ?? "");
2186
+ if (currentPassword.length < 8 || currentPassword.length > 128) {
2187
+ return "Error: Current password must be 8-128 characters.";
2188
+ }
2189
+ const newPassword = String(args.new_password ?? "");
2190
+ if (newPassword.length < 8 || newPassword.length > 128) {
2191
+ return "Error: New password must be 8-128 characters.";
2192
+ }
2193
+ await apiPost("/api/auth/change-password", {
2194
+ currentPassword,
2195
+ newPassword,
2196
+ });
2197
+ return [
2198
+ "Password Updated Successfully",
2199
+ "",
2200
+ "Your password has been changed.",
2201
+ "All existing sessions remain valid.",
2202
+ ].join("\n");
2203
+ }
2204
+ catch (err) {
2205
+ return `Error: ${safeError(err)}`;
2206
+ }
2207
+ },
2208
+ };
2209
+ // 39. list_keys
2210
+ const listKeysTool = {
2211
+ name: "list_keys",
2212
+ description: "List all your personal API keys. Shows key prefix, label, status, and creation date.",
2213
+ parameters: {},
2214
+ execute: async () => {
2215
+ try {
2216
+ const data = await apiGet("/api/keys");
2217
+ const keys = data.keys ?? [];
2218
+ if (keys.length === 0) {
2219
+ return "No API keys found. Use create_key to create one.";
2220
+ }
2221
+ const lines = [`API Keys (${keys.length})`, ""];
2222
+ for (const k of keys) {
2223
+ const lastUsed = k.lastUsedAt ? ` | Last used: ${k.lastUsedAt}` : "";
2224
+ lines.push(` ${k.prefix}... | ${k.label} | ${k.status}${lastUsed}`);
2225
+ lines.push(` ID: ${k.id} | Created: ${k.createdAt}`);
2226
+ lines.push("");
2227
+ }
2228
+ return truncate(lines.join("\n"));
2229
+ }
2230
+ catch (err) {
2231
+ return `Error: ${safeError(err)}`;
2232
+ }
2233
+ },
2234
+ };
2235
+ // 40. create_key
2236
+ const createKeyTool = {
2237
+ name: "create_key",
2238
+ description: "Create a new personal API key. The full key is shown only once -- save it immediately.",
2239
+ parameters: {
2240
+ label: {
2241
+ type: "string",
2242
+ description: 'Label for the API key (e.g., "production", "dev")',
2243
+ required: true,
2244
+ },
2245
+ },
2246
+ execute: async (args) => {
2247
+ try {
2248
+ const label = String(args.label ?? "").trim();
2249
+ if (!label || label.length === 0 || label.length > 100) {
2250
+ return "Error: label is required and must be 1-100 characters.";
2251
+ }
2252
+ if (/[\x00-\x1f\x7f]/.test(label)) {
2253
+ return "Error: label contains invalid control characters.";
2254
+ }
2255
+ const data = await apiPost("/api/keys", { label });
2256
+ return [
2257
+ "API Key Created",
2258
+ "",
2259
+ `Key ID: ${data.id}`,
2260
+ `API Key: ${data.key}`,
2261
+ `Prefix: ${data.prefix}`,
2262
+ `Label: ${data.label}`,
2263
+ `Created: ${data.createdAt}`,
2264
+ "",
2265
+ "IMPORTANT: Save this API key now -- it will not be shown again.",
2266
+ ].join("\n");
2267
+ }
2268
+ catch (err) {
2269
+ return `Error: ${safeError(err)}`;
2270
+ }
2271
+ },
2272
+ };
2273
+ // 41. revoke_key
2274
+ const revokeKeyTool = {
2275
+ name: "revoke_key",
2276
+ description: "Revoke (delete) a personal API key. This immediately invalidates the key.",
2277
+ parameters: {
2278
+ key_id: {
2279
+ type: "string",
2280
+ description: "API key ID (UUID) to revoke",
2281
+ required: true,
2282
+ },
2283
+ },
2284
+ execute: async (args) => {
2285
+ try {
2286
+ const keyId = validateUuid(String(args.key_id ?? ""), "key_id");
2287
+ await apiDelete(`/api/keys/${encodeURIComponent(keyId)}`);
2288
+ return [
2289
+ "API Key Revoked",
2290
+ "",
2291
+ `Key ID: ${keyId}`,
2292
+ "",
2293
+ "The key has been permanently revoked and can no longer be used.",
2294
+ ].join("\n");
2295
+ }
2296
+ catch (err) {
2297
+ return `Error: ${safeError(err)}`;
2298
+ }
2299
+ },
2300
+ };
2301
+ // 42. get_plan
2302
+ const getPlanTool = {
2303
+ name: "get_plan",
2304
+ description: "Get your current plan details including limits, pricing, and features.",
2305
+ parameters: {},
2306
+ execute: async () => {
2307
+ try {
2308
+ const data = await apiGet("/api/plans/user/plan");
2309
+ const lines = [
2310
+ "Current Plan",
2311
+ "",
2312
+ `Plan: ${data.displayName ?? data.plan}`,
2313
+ `Bandwidth Limit: ${data.bandwidthLimitBytes != null ? formatBytes(data.bandwidthLimitBytes) : "Unlimited"}`,
2314
+ `Rate Limit: ${data.requestsPerMinute} req/min`,
2315
+ `Max API Keys: ${data.maxApiKeys}`,
2316
+ `Max Agentic Wallets: ${data.maxAgenticWallets}`,
2317
+ `Max Teams: ${data.maxTeams}`,
2318
+ ];
2319
+ const features = data.features ?? [];
2320
+ if (features.length > 0) {
2321
+ lines.push("", "Features:");
2322
+ for (const f of features) {
2323
+ lines.push(` - ${f}`);
2324
+ }
2325
+ }
2326
+ lines.push("", "Use list_plans to see available plan options.");
2327
+ return lines.join("\n");
2328
+ }
2329
+ catch (err) {
2330
+ return `Error: ${safeError(err)}`;
2331
+ }
2332
+ },
2333
+ };
2334
+ // 43. list_plans
2335
+ const listPlansTool = {
2336
+ name: "list_plans",
2337
+ description: "List all available plans with pricing, limits, and features.",
2338
+ parameters: {},
2339
+ execute: async () => {
2340
+ try {
2341
+ const data = await apiGet("/api/plans");
2342
+ const plans = data.plans ?? [];
2343
+ if (plans.length === 0) {
2344
+ return "No plans available.";
2345
+ }
2346
+ const lines = [`Available Plans (${plans.length})`, ""];
2347
+ for (const p of plans) {
2348
+ lines.push(` ${p.displayName ?? p.name} ($${(p.priceMonthly / 100).toFixed(2)}/mo)`);
2349
+ lines.push(` Bandwidth: ${p.bandwidthLimitBytes != null ? formatBytes(p.bandwidthLimitBytes) : "Unlimited"} | Rate: ${p.requestsPerMinute} req/min | Keys: ${p.maxApiKeys}`);
2350
+ if (p.features && p.features.length > 0) {
2351
+ lines.push(` Features: ${p.features.join(", ")}`);
2352
+ }
2353
+ lines.push("");
2354
+ }
2355
+ lines.push("Use change_plan to switch plans.");
2356
+ return truncate(lines.join("\n"));
2357
+ }
2358
+ catch (err) {
2359
+ return `Error: ${safeError(err)}`;
2360
+ }
2361
+ },
2362
+ };
2363
+ // 44. change_plan
2364
+ const changePlanTool = {
2365
+ name: "change_plan",
2366
+ description: "Change your account plan. Use list_plans to see available options first.",
2367
+ parameters: {
2368
+ plan: {
2369
+ type: "string",
2370
+ description: "Plan name/ID to switch to",
2371
+ required: true,
2372
+ },
2373
+ },
2374
+ execute: async (args) => {
2375
+ try {
2376
+ const plan = String(args.plan ?? "").trim();
2377
+ if (!plan || plan.length === 0 || plan.length > 100) {
2378
+ return "Error: plan is required and must be 1-100 characters.";
2379
+ }
2380
+ if (/[\x00-\x1f\x7f]/.test(plan)) {
2381
+ return "Error: plan name contains invalid control characters.";
2382
+ }
2383
+ const data = await apiPut("/api/plans/user/plan", { plan });
2384
+ return [
2385
+ "Plan Changed Successfully",
2386
+ "",
2387
+ `New Plan: ${data.displayName ?? data.plan}`,
2388
+ `Effective: ${data.effectiveAt ?? "immediately"}`,
2389
+ "",
2390
+ "Use get_plan to view your updated plan details.",
2391
+ ].join("\n");
2392
+ }
2393
+ catch (err) {
2394
+ return `Error: ${safeError(err)}`;
2395
+ }
2396
+ },
2397
+ };
2398
+ // 45. team_delete
2399
+ const teamDeleteTool = {
2400
+ name: "team_delete",
2401
+ description: "Delete a team. Only the team owner can delete a team. Any remaining team balance is refunded.",
2402
+ parameters: {
2403
+ team_id: {
2404
+ type: "string",
2405
+ description: "Team ID (UUID) to delete",
2406
+ required: true,
2407
+ },
2408
+ },
2409
+ execute: async (args) => {
2410
+ try {
2411
+ const teamId = validateUuid(String(args.team_id ?? ""), "team_id");
2412
+ await apiDelete(`/api/teams/${encodeURIComponent(teamId)}`);
2413
+ return [
2414
+ "Team Deleted",
2415
+ "",
2416
+ `Team ID: ${teamId}`,
2417
+ "",
2418
+ "The team has been permanently deleted.",
2419
+ "Any remaining balance has been refunded to the owner's wallet.",
2420
+ ].join("\n");
2421
+ }
2422
+ catch (err) {
2423
+ return `Error: ${safeError(err)}`;
2424
+ }
2425
+ },
2426
+ };
2427
+ // 46. team_revoke_key
2428
+ const teamRevokeKeyTool = {
2429
+ name: "team_revoke_key",
2430
+ description: "Revoke a shared API key for a team. Only owners and admins can revoke keys.",
2431
+ parameters: {
2432
+ team_id: {
2433
+ type: "string",
2434
+ description: "Team ID (UUID)",
2435
+ required: true,
2436
+ },
2437
+ key_id: {
2438
+ type: "string",
2439
+ description: "API key ID (UUID) to revoke",
2440
+ required: true,
2441
+ },
2442
+ },
2443
+ execute: async (args) => {
2444
+ try {
2445
+ const teamId = validateUuid(String(args.team_id ?? ""), "team_id");
2446
+ const keyId = validateUuid(String(args.key_id ?? ""), "key_id");
2447
+ await apiDelete(`/api/teams/${encodeURIComponent(teamId)}/keys/${encodeURIComponent(keyId)}`);
2448
+ return [
2449
+ "Team API Key Revoked",
2450
+ "",
2451
+ `Team ID: ${teamId}`,
2452
+ `Key ID: ${keyId}`,
2453
+ "",
2454
+ "The key has been permanently revoked and can no longer be used.",
2455
+ ].join("\n");
2456
+ }
2457
+ catch (err) {
2458
+ return `Error: ${safeError(err)}`;
2459
+ }
2460
+ },
2461
+ };
2462
+ // 47. team_list_keys
2463
+ const teamListKeysTool = {
2464
+ name: "team_list_keys",
2465
+ description: "List all API keys for a team. Shows key prefix, label, status, and creation date.",
2466
+ parameters: {
2467
+ team_id: {
2468
+ type: "string",
2469
+ description: "Team ID (UUID)",
2470
+ required: true,
2471
+ },
2472
+ },
2473
+ execute: async (args) => {
2474
+ try {
2475
+ const teamId = validateUuid(String(args.team_id ?? ""), "team_id");
2476
+ const data = await apiGet(`/api/teams/${encodeURIComponent(teamId)}/keys`);
2477
+ const keys = data.keys ?? [];
2478
+ if (keys.length === 0) {
2479
+ return "No API keys found for this team. Use team_create_key to create one.";
2480
+ }
2481
+ const lines = [`Team API Keys (${keys.length})`, ""];
2482
+ for (const k of keys) {
2483
+ const lastUsed = k.lastUsedAt ? ` | Last used: ${k.lastUsedAt}` : "";
2484
+ lines.push(` ${k.prefix}... | ${k.label} | ${k.status}${lastUsed}`);
2485
+ lines.push(` ID: ${k.id} | Created: ${k.createdAt}`);
2486
+ lines.push("");
2487
+ }
2488
+ return truncate(lines.join("\n"));
2489
+ }
2490
+ catch (err) {
2491
+ return `Error: ${safeError(err)}`;
2492
+ }
2493
+ },
2494
+ };
2495
+ // 48. team_list_members
2496
+ const teamListMembersTool = {
2497
+ name: "team_list_members",
2498
+ description: "List all members of a team with their roles and join dates.",
2499
+ parameters: {
2500
+ team_id: {
2501
+ type: "string",
2502
+ description: "Team ID (UUID)",
2503
+ required: true,
2504
+ },
2505
+ },
2506
+ execute: async (args) => {
2507
+ try {
2508
+ const teamId = validateUuid(String(args.team_id ?? ""), "team_id");
2509
+ const data = await apiGet(`/api/teams/${encodeURIComponent(teamId)}/members`);
2510
+ const members = data.members ?? [];
2511
+ if (members.length === 0) {
2512
+ return "No members found in this team.";
2513
+ }
2514
+ const lines = [`Team Members (${members.length})`, ""];
2515
+ for (const m of members) {
2516
+ lines.push(` ${m.email} | ${m.role} | Joined: ${m.joinedAt}`);
2517
+ lines.push(` User ID: ${m.userId}`);
2518
+ lines.push("");
2519
+ }
2520
+ return truncate(lines.join("\n"));
2521
+ }
2522
+ catch (err) {
2523
+ return `Error: ${safeError(err)}`;
2524
+ }
2525
+ },
2526
+ };
2527
+ // 49. team_add_member
2528
+ const teamAddMemberTool = {
2529
+ name: "team_add_member",
2530
+ description: "Add a member directly to a team by user ID. Only owners and admins can add members.",
2531
+ parameters: {
2532
+ team_id: {
2533
+ type: "string",
2534
+ description: "Team ID (UUID)",
2535
+ required: true,
2536
+ },
2537
+ user_id: {
2538
+ type: "string",
2539
+ description: "User ID (UUID) of the person to add",
2540
+ required: true,
2541
+ },
2542
+ role: {
2543
+ type: "string",
2544
+ description: "Role for the new member",
2545
+ required: false,
2546
+ enum: ["member", "admin"],
2547
+ default: "member",
2548
+ },
2549
+ },
2550
+ execute: async (args) => {
2551
+ try {
2552
+ const teamId = validateUuid(String(args.team_id ?? ""), "team_id");
2553
+ const userId = validateUuid(String(args.user_id ?? ""), "user_id");
2554
+ const role = String(args.role ?? "member").trim();
2555
+ if (role !== "member" && role !== "admin") {
2556
+ return "Error: role must be 'member' or 'admin'.";
2557
+ }
2558
+ const data = await apiPost(`/api/teams/${encodeURIComponent(teamId)}/members`, {
2559
+ userId,
2560
+ role,
2561
+ });
2562
+ return [
2563
+ "Team Member Added",
2564
+ "",
2565
+ `Team ID: ${data.teamId ?? teamId}`,
2566
+ `User ID: ${data.userId ?? userId}`,
2567
+ `Role: ${data.role ?? role}`,
2568
+ `Joined: ${data.joinedAt ?? "now"}`,
2569
+ ].join("\n");
2570
+ }
2571
+ catch (err) {
2572
+ return `Error: ${safeError(err)}`;
2573
+ }
2574
+ },
2575
+ };
2576
+ // 50. team_remove_member
2577
+ const teamRemoveMemberTool = {
2578
+ name: "team_remove_member",
2579
+ description: "Remove a member from a team. Only owners and admins can remove members.",
2580
+ parameters: {
2581
+ team_id: {
2582
+ type: "string",
2583
+ description: "Team ID (UUID)",
2584
+ required: true,
2585
+ },
2586
+ user_id: {
2587
+ type: "string",
2588
+ description: "User ID (UUID) of the member to remove",
2589
+ required: true,
2590
+ },
2591
+ },
2592
+ execute: async (args) => {
2593
+ try {
2594
+ const teamId = validateUuid(String(args.team_id ?? ""), "team_id");
2595
+ const userId = validateUuid(String(args.user_id ?? ""), "user_id");
2596
+ await apiDelete(`/api/teams/${encodeURIComponent(teamId)}/members/${encodeURIComponent(userId)}`);
2597
+ return [
2598
+ "Team Member Removed",
2599
+ "",
2600
+ `Team ID: ${teamId}`,
2601
+ `User ID: ${userId}`,
2602
+ "",
2603
+ "The member has been removed from the team.",
2604
+ ].join("\n");
2605
+ }
2606
+ catch (err) {
2607
+ return `Error: ${safeError(err)}`;
2608
+ }
2609
+ },
2610
+ };
2611
+ // 51. team_invite_member
2612
+ const teamInviteMemberTool = {
2613
+ name: "team_invite_member",
2614
+ description: "Send an invitation to join a team by email. The invitee receives an email with a link to accept.",
2615
+ parameters: {
2616
+ team_id: {
2617
+ type: "string",
2618
+ description: "Team ID (UUID)",
2619
+ required: true,
2620
+ },
2621
+ email: {
2622
+ type: "string",
2623
+ description: "Email address of the person to invite",
2624
+ required: true,
2625
+ },
2626
+ role: {
2627
+ type: "string",
2628
+ description: "Role for the invited member",
2629
+ required: false,
2630
+ enum: ["member", "admin"],
2631
+ default: "member",
2632
+ },
2633
+ },
2634
+ execute: async (args) => {
2635
+ try {
2636
+ const teamId = validateUuid(String(args.team_id ?? ""), "team_id");
2637
+ const email = String(args.email ?? "").trim();
2638
+ if (!email || !EMAIL_RE.test(email)) {
2639
+ return "Error: A valid email address is required.";
2640
+ }
2641
+ if (email.length > 254) {
2642
+ return "Error: Email address is too long (max 254 characters).";
2643
+ }
2644
+ const role = String(args.role ?? "member").trim();
2645
+ if (role !== "member" && role !== "admin") {
2646
+ return "Error: role must be 'member' or 'admin'.";
2647
+ }
2648
+ const data = await apiPost(`/api/teams/${encodeURIComponent(teamId)}/invites`, {
2649
+ email,
2650
+ role,
2651
+ });
2652
+ return [
2653
+ "Team Invitation Sent",
2654
+ "",
2655
+ `Invite ID: ${data.id}`,
2656
+ `Team ID: ${data.teamId ?? teamId}`,
2657
+ `Email: ${data.email ?? email}`,
2658
+ `Role: ${data.role ?? role}`,
2659
+ `Status: ${data.status ?? "pending"}`,
2660
+ `Expires: ${data.expiresAt}`,
2661
+ "",
2662
+ "The invitee will receive an email with instructions to accept.",
2663
+ ].join("\n");
2664
+ }
2665
+ catch (err) {
2666
+ return `Error: ${safeError(err)}`;
2667
+ }
2668
+ },
2669
+ };
2670
+ // 52. team_list_invites
2671
+ const teamListInvitesTool = {
2672
+ name: "team_list_invites",
2673
+ description: "List all pending invitations for a team.",
2674
+ parameters: {
2675
+ team_id: {
2676
+ type: "string",
2677
+ description: "Team ID (UUID)",
2678
+ required: true,
2679
+ },
2680
+ },
2681
+ execute: async (args) => {
2682
+ try {
2683
+ const teamId = validateUuid(String(args.team_id ?? ""), "team_id");
2684
+ const data = await apiGet(`/api/teams/${encodeURIComponent(teamId)}/invites`);
2685
+ const invites = data.invites ?? [];
2686
+ if (invites.length === 0) {
2687
+ return "No pending invitations for this team.";
2688
+ }
2689
+ const lines = [`Team Invitations (${invites.length})`, ""];
2690
+ for (const inv of invites) {
2691
+ lines.push(` ${inv.email} | ${inv.role} | ${inv.status}`);
2692
+ lines.push(` ID: ${inv.id} | Expires: ${inv.expiresAt}`);
2693
+ lines.push("");
2694
+ }
2695
+ return truncate(lines.join("\n"));
2696
+ }
2697
+ catch (err) {
2698
+ return `Error: ${safeError(err)}`;
2699
+ }
2700
+ },
2701
+ };
2702
+ // 53. team_cancel_invite
2703
+ const teamCancelInviteTool = {
2704
+ name: "team_cancel_invite",
2705
+ description: "Cancel a pending team invitation. Only owners and admins can cancel invites.",
2706
+ parameters: {
2707
+ team_id: {
2708
+ type: "string",
2709
+ description: "Team ID (UUID)",
2710
+ required: true,
2711
+ },
2712
+ invite_id: {
2713
+ type: "string",
2714
+ description: "Invite ID (UUID) to cancel",
2715
+ required: true,
2716
+ },
2717
+ },
2718
+ execute: async (args) => {
2719
+ try {
2720
+ const teamId = validateUuid(String(args.team_id ?? ""), "team_id");
2721
+ const inviteId = validateUuid(String(args.invite_id ?? ""), "invite_id");
2722
+ await apiDelete(`/api/teams/${encodeURIComponent(teamId)}/invites/${encodeURIComponent(inviteId)}`);
2723
+ return [
2724
+ "Team Invitation Cancelled",
2725
+ "",
2726
+ `Team ID: ${teamId}`,
2727
+ `Invite ID: ${inviteId}`,
2728
+ "",
2729
+ "The invitation has been cancelled.",
2730
+ ].join("\n");
2731
+ }
2732
+ catch (err) {
2733
+ return `Error: ${safeError(err)}`;
2734
+ }
2735
+ },
2736
+ };
2737
+ // ---------------------------------------------------------------------------
2738
+ // Plugin export — the tools array that OpenClaw discovers
2739
+ // ---------------------------------------------------------------------------
2740
+ export const tools = [
2741
+ // Proxy (3)
2742
+ proxiedFetchTool,
2743
+ getProxyConfigTool,
2744
+ getProxyStatusTool,
2745
+ // Sessions (1)
2746
+ listSessionsTool,
2747
+ // Wallet (8)
2748
+ checkBalanceTool,
2749
+ getTransactionsTool,
2750
+ getForecastTool,
2751
+ topupPaypalTool,
2752
+ topupStripeTool,
2753
+ topupCryptoTool,
2754
+ checkPaymentTool,
2755
+ x402InfoTool,
2756
+ // Usage (3)
2757
+ checkUsageTool,
2758
+ getDailyUsageTool,
2759
+ getTopHostsTool,
2760
+ // Account (6)
2761
+ registerTool,
2762
+ loginTool,
2763
+ getAccountInfoTool,
2764
+ verifyEmailTool,
2765
+ resendVerificationTool,
2766
+ updatePasswordTool,
2767
+ // API Keys (3)
2768
+ listKeysTool,
2769
+ createKeyTool,
2770
+ revokeKeyTool,
2771
+ // Plans (3)
2772
+ getPlanTool,
2773
+ listPlansTool,
2774
+ changePlanTool,
2775
+ // Agentic Wallets (9)
2776
+ createAgenticWalletTool,
2777
+ fundAgenticWalletTool,
2778
+ checkAgenticBalanceTool,
2779
+ listAgenticWalletsTool,
2780
+ agenticTransactionsTool,
2781
+ freezeAgenticWalletTool,
2782
+ unfreezeAgenticWalletTool,
2783
+ deleteAgenticWalletTool,
2784
+ updateWalletPolicyTool,
2785
+ // Teams (17)
2786
+ createTeamTool,
2787
+ listTeamsTool,
2788
+ teamDetailsTool,
2789
+ updateTeamTool,
2790
+ teamDeleteTool,
2791
+ teamFundTool,
2792
+ teamCreateKeyTool,
2793
+ teamRevokeKeyTool,
2794
+ teamListKeysTool,
2795
+ teamUsageTool,
2796
+ teamListMembersTool,
2797
+ teamAddMemberTool,
2798
+ teamRemoveMemberTool,
2799
+ updateTeamMemberRoleTool,
2800
+ teamInviteMemberTool,
2801
+ teamListInvitesTool,
2802
+ teamCancelInviteTool,
1732
2803
  ];
1733
2804
  /**
1734
2805
  * Plugin metadata for OpenClaw discovery.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dominusnode/openclaw-plugin",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "Dominus Node proxy plugin for OpenClaw — route web requests through rotating proxy networks",
5
5
  "main": "dist/plugin.js",
6
6
  "types": "dist/plugin.d.ts",