@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 +1 -1
- package/dist/plugin.js +1102 -31
- package/package.json +1 -1
package/dist/plugin.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Dominus Node OpenClaw Plugin
|
|
3
3
|
*
|
|
4
|
-
* Implements
|
|
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
|
|
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
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
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