@gopherhole/cli 0.1.13 ā 0.1.14
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/index.js +250 -1
- package/package.json +1 -1
- package/src/index.ts +297 -1
package/dist/index.js
CHANGED
|
@@ -19,7 +19,7 @@ const brand = {
|
|
|
19
19
|
greenDark: chalk_1.default.hex('#16a34a'), // gopher-600 - emphasis
|
|
20
20
|
};
|
|
21
21
|
// Version
|
|
22
|
-
const VERSION = '0.1.
|
|
22
|
+
const VERSION = '0.1.14';
|
|
23
23
|
// ASCII art banner
|
|
24
24
|
function showBanner(context) {
|
|
25
25
|
const gopher = [
|
|
@@ -1723,6 +1723,255 @@ access
|
|
|
1723
1723
|
process.exit(1);
|
|
1724
1724
|
}
|
|
1725
1725
|
});
|
|
1726
|
+
// ========== BUDGET COMMAND ==========
|
|
1727
|
+
const budget = program
|
|
1728
|
+
.command('budget')
|
|
1729
|
+
.description(`View and manage agent spending limits
|
|
1730
|
+
|
|
1731
|
+
${chalk_1.default.bold('Examples:')}
|
|
1732
|
+
$ gopherhole budget my-agent # View budget
|
|
1733
|
+
$ gopherhole budget my-agent -d 10 -w 50 # Set daily=$10, weekly=$50
|
|
1734
|
+
$ gopherhole budget my-agent --clear # Remove all limits
|
|
1735
|
+
$ gopherhole budget --all # List all agents' budgets
|
|
1736
|
+
`);
|
|
1737
|
+
budget
|
|
1738
|
+
.argument('[agentId]', 'Agent ID (optional with --all)')
|
|
1739
|
+
.option('-a, --all', 'List all agents\' budgets')
|
|
1740
|
+
.option('-d, --daily <amount>', 'Set daily limit (dollars)')
|
|
1741
|
+
.option('-w, --weekly <amount>', 'Set weekly limit (dollars)')
|
|
1742
|
+
.option('-r, --per-request <amount>', 'Set per-request max (dollars)')
|
|
1743
|
+
.option('--clear', 'Remove all spending limits')
|
|
1744
|
+
.option('--json', 'Output as JSON')
|
|
1745
|
+
.action(async (agentId, options) => {
|
|
1746
|
+
const sessionId = config.get('sessionId');
|
|
1747
|
+
if (!sessionId) {
|
|
1748
|
+
console.log(chalk_1.default.yellow('Not logged in.'));
|
|
1749
|
+
console.log(chalk_1.default.gray('Run: gopherhole login'));
|
|
1750
|
+
process.exit(1);
|
|
1751
|
+
}
|
|
1752
|
+
// Handle --all flag
|
|
1753
|
+
if (options.all) {
|
|
1754
|
+
const spinner = (0, ora_1.default)('Fetching spending overview...').start();
|
|
1755
|
+
try {
|
|
1756
|
+
const res = await fetch(`${API_URL}/spending`, {
|
|
1757
|
+
headers: { 'X-Session-ID': sessionId },
|
|
1758
|
+
});
|
|
1759
|
+
if (!res.ok) {
|
|
1760
|
+
throw new Error('Failed to fetch spending overview');
|
|
1761
|
+
}
|
|
1762
|
+
const data = await res.json();
|
|
1763
|
+
spinner.stop();
|
|
1764
|
+
if (options.json) {
|
|
1765
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1766
|
+
return;
|
|
1767
|
+
}
|
|
1768
|
+
console.log(chalk_1.default.bold('\nš° Spending Overview\n'));
|
|
1769
|
+
console.log(` Today: $${data.totalSpentToday.toFixed(2)} This week: $${data.totalSpentThisWeek.toFixed(2)}\n`);
|
|
1770
|
+
if (data.agents.length === 0) {
|
|
1771
|
+
console.log(chalk_1.default.gray(' No agents found.\n'));
|
|
1772
|
+
return;
|
|
1773
|
+
}
|
|
1774
|
+
// Header
|
|
1775
|
+
console.log(chalk_1.default.gray(' Agent Daily Weekly Status'));
|
|
1776
|
+
console.log(chalk_1.default.gray(' ā'.padEnd(70, 'ā')));
|
|
1777
|
+
for (const agent of data.agents) {
|
|
1778
|
+
const name = agent.name.padEnd(18).slice(0, 18);
|
|
1779
|
+
let daily = 'ā'.padEnd(12);
|
|
1780
|
+
if (agent.daily?.limit !== null && agent.daily?.limit !== undefined) {
|
|
1781
|
+
const bar = getProgressBar(agent.daily.percent ?? 0, 5);
|
|
1782
|
+
daily = `$${agent.daily.spent.toFixed(0)}/$${agent.daily.limit.toFixed(0)} ${bar}`.padEnd(12);
|
|
1783
|
+
}
|
|
1784
|
+
else if (agent.daily?.spent) {
|
|
1785
|
+
daily = `$${agent.daily.spent.toFixed(2)}`.padEnd(12);
|
|
1786
|
+
}
|
|
1787
|
+
let weekly = 'ā'.padEnd(12);
|
|
1788
|
+
if (agent.weekly?.limit !== null && agent.weekly?.limit !== undefined) {
|
|
1789
|
+
const bar = getProgressBar(agent.weekly.percent ?? 0, 5);
|
|
1790
|
+
weekly = `$${agent.weekly.spent.toFixed(0)}/$${agent.weekly.limit.toFixed(0)} ${bar}`.padEnd(12);
|
|
1791
|
+
}
|
|
1792
|
+
else if (agent.weekly?.spent) {
|
|
1793
|
+
weekly = `$${agent.weekly.spent.toFixed(2)}`.padEnd(12);
|
|
1794
|
+
}
|
|
1795
|
+
const statusEmoji = {
|
|
1796
|
+
ok: brand.green('ā
OK'),
|
|
1797
|
+
warning: chalk_1.default.yellow('ā ļø Warning'),
|
|
1798
|
+
critical: chalk_1.default.red('š“ Critical'),
|
|
1799
|
+
blocked: chalk_1.default.red('š« Blocked'),
|
|
1800
|
+
unlimited: chalk_1.default.gray('ā'),
|
|
1801
|
+
}[agent.status] ?? agent.status;
|
|
1802
|
+
console.log(` ${name} ${daily} ${weekly} ${statusEmoji}`);
|
|
1803
|
+
}
|
|
1804
|
+
console.log('');
|
|
1805
|
+
}
|
|
1806
|
+
catch (err) {
|
|
1807
|
+
spinner.fail(chalk_1.default.red(err.message));
|
|
1808
|
+
process.exit(1);
|
|
1809
|
+
}
|
|
1810
|
+
return;
|
|
1811
|
+
}
|
|
1812
|
+
// Need agentId for single-agent operations
|
|
1813
|
+
if (!agentId) {
|
|
1814
|
+
console.log(chalk_1.default.yellow('Please provide an agent ID or use --all'));
|
|
1815
|
+
console.log(chalk_1.default.gray('Usage: gopherhole budget <agentId> [options]'));
|
|
1816
|
+
console.log(chalk_1.default.gray(' gopherhole budget --all'));
|
|
1817
|
+
process.exit(1);
|
|
1818
|
+
}
|
|
1819
|
+
// Handle --clear flag
|
|
1820
|
+
if (options.clear) {
|
|
1821
|
+
const spinner = (0, ora_1.default)('Removing spending limits...').start();
|
|
1822
|
+
try {
|
|
1823
|
+
const res = await fetch(`${API_URL}/agents/${agentId}/spending-limits`, {
|
|
1824
|
+
method: 'DELETE',
|
|
1825
|
+
headers: { 'X-Session-ID': sessionId },
|
|
1826
|
+
});
|
|
1827
|
+
if (!res.ok) {
|
|
1828
|
+
throw new Error('Failed to remove spending limits');
|
|
1829
|
+
}
|
|
1830
|
+
spinner.succeed('Spending limits removed');
|
|
1831
|
+
console.log(chalk_1.default.gray(` Agent ${agentId} now has no spending limits.\n`));
|
|
1832
|
+
}
|
|
1833
|
+
catch (err) {
|
|
1834
|
+
spinner.fail(chalk_1.default.red(err.message));
|
|
1835
|
+
process.exit(1);
|
|
1836
|
+
}
|
|
1837
|
+
return;
|
|
1838
|
+
}
|
|
1839
|
+
// Handle setting limits
|
|
1840
|
+
if (options.daily !== undefined || options.weekly !== undefined || options.perRequest !== undefined) {
|
|
1841
|
+
const spinner = (0, ora_1.default)('Updating spending limits...').start();
|
|
1842
|
+
try {
|
|
1843
|
+
const body = {};
|
|
1844
|
+
if (options.daily !== undefined) {
|
|
1845
|
+
body.dailyLimit = options.daily === 'unlimited' ? null : parseFloat(options.daily);
|
|
1846
|
+
}
|
|
1847
|
+
if (options.weekly !== undefined) {
|
|
1848
|
+
body.weeklyLimit = options.weekly === 'unlimited' ? null : parseFloat(options.weekly);
|
|
1849
|
+
}
|
|
1850
|
+
if (options.perRequest !== undefined) {
|
|
1851
|
+
body.maxPerRequest = options.perRequest === 'unlimited' ? null : parseFloat(options.perRequest);
|
|
1852
|
+
}
|
|
1853
|
+
const res = await fetch(`${API_URL}/agents/${agentId}/spending-limits`, {
|
|
1854
|
+
method: 'PUT',
|
|
1855
|
+
headers: {
|
|
1856
|
+
'X-Session-ID': sessionId,
|
|
1857
|
+
'Content-Type': 'application/json',
|
|
1858
|
+
},
|
|
1859
|
+
body: JSON.stringify(body),
|
|
1860
|
+
});
|
|
1861
|
+
if (!res.ok) {
|
|
1862
|
+
const err = await res.json();
|
|
1863
|
+
throw new Error(err.error || 'Failed to update limits');
|
|
1864
|
+
}
|
|
1865
|
+
spinner.succeed('Spending limits updated');
|
|
1866
|
+
// Show the updated limits
|
|
1867
|
+
const data = await res.json();
|
|
1868
|
+
console.log(chalk_1.default.gray('\n New limits:'));
|
|
1869
|
+
if (data.limits.daily) {
|
|
1870
|
+
console.log(` Daily: $${data.limits.daily.limit.toFixed(2)}`);
|
|
1871
|
+
}
|
|
1872
|
+
if (data.limits.weekly) {
|
|
1873
|
+
console.log(` Weekly: $${data.limits.weekly.limit.toFixed(2)}`);
|
|
1874
|
+
}
|
|
1875
|
+
if (data.limits.maxPerRequest) {
|
|
1876
|
+
console.log(` Per Request: $${data.limits.maxPerRequest.toFixed(2)}`);
|
|
1877
|
+
}
|
|
1878
|
+
console.log('');
|
|
1879
|
+
}
|
|
1880
|
+
catch (err) {
|
|
1881
|
+
spinner.fail(chalk_1.default.red(err.message));
|
|
1882
|
+
process.exit(1);
|
|
1883
|
+
}
|
|
1884
|
+
return;
|
|
1885
|
+
}
|
|
1886
|
+
// Default: show budget
|
|
1887
|
+
const spinner = (0, ora_1.default)('Fetching budget...').start();
|
|
1888
|
+
try {
|
|
1889
|
+
const res = await fetch(`${API_URL}/agents/${agentId}/budget`, {
|
|
1890
|
+
headers: { 'X-Session-ID': sessionId },
|
|
1891
|
+
});
|
|
1892
|
+
if (!res.ok) {
|
|
1893
|
+
if (res.status === 404) {
|
|
1894
|
+
throw new Error('Agent not found');
|
|
1895
|
+
}
|
|
1896
|
+
throw new Error('Failed to fetch budget');
|
|
1897
|
+
}
|
|
1898
|
+
const data = await res.json();
|
|
1899
|
+
spinner.stop();
|
|
1900
|
+
if (options.json) {
|
|
1901
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1902
|
+
return;
|
|
1903
|
+
}
|
|
1904
|
+
console.log(chalk_1.default.bold(`\nš° Budget for ${agentId}\n`));
|
|
1905
|
+
if (!data.daily && !data.weekly && !data.maxPerRequest) {
|
|
1906
|
+
console.log(chalk_1.default.gray(' No spending limits configured.\n'));
|
|
1907
|
+
console.log(chalk_1.default.gray(' Set limits: gopherhole budget <agentId> -d 10 -w 50\n'));
|
|
1908
|
+
return;
|
|
1909
|
+
}
|
|
1910
|
+
if (data.daily) {
|
|
1911
|
+
const percent = Math.round((data.daily.spent / data.daily.limit) * 100);
|
|
1912
|
+
const bar = getProgressBar(percent, 10);
|
|
1913
|
+
const timeLeft = getTimeUntil(data.daily.resetsAt);
|
|
1914
|
+
const color = percent >= 80 ? chalk_1.default.red : percent >= 50 ? chalk_1.default.yellow : brand.green;
|
|
1915
|
+
console.log(` Daily: ${color(`$${data.daily.spent.toFixed(2)} / $${data.daily.limit.toFixed(2)}`)} (${percent}%) ${bar}`);
|
|
1916
|
+
console.log(chalk_1.default.gray(` Resets in ${timeLeft}`));
|
|
1917
|
+
}
|
|
1918
|
+
if (data.weekly) {
|
|
1919
|
+
const percent = Math.round((data.weekly.spent / data.weekly.limit) * 100);
|
|
1920
|
+
const bar = getProgressBar(percent, 10);
|
|
1921
|
+
const timeLeft = getTimeUntil(data.weekly.resetsAt);
|
|
1922
|
+
const color = percent >= 80 ? chalk_1.default.red : percent >= 50 ? chalk_1.default.yellow : brand.green;
|
|
1923
|
+
console.log(` Weekly: ${color(`$${data.weekly.spent.toFixed(2)} / $${data.weekly.limit.toFixed(2)}`)} (${percent}%) ${bar}`);
|
|
1924
|
+
console.log(chalk_1.default.gray(` Resets ${timeLeft}`));
|
|
1925
|
+
}
|
|
1926
|
+
if (data.maxPerRequest) {
|
|
1927
|
+
console.log(` Per Request: $${data.maxPerRequest.toFixed(2)} max`);
|
|
1928
|
+
}
|
|
1929
|
+
// Status
|
|
1930
|
+
let status = brand.green('ā
OK');
|
|
1931
|
+
const dailyPercent = data.daily ? (data.daily.spent / data.daily.limit) * 100 : 0;
|
|
1932
|
+
const weeklyPercent = data.weekly ? (data.weekly.spent / data.weekly.limit) * 100 : 0;
|
|
1933
|
+
const maxPercent = Math.max(dailyPercent, weeklyPercent);
|
|
1934
|
+
if (maxPercent >= 100) {
|
|
1935
|
+
status = chalk_1.default.red('š« Blocked');
|
|
1936
|
+
}
|
|
1937
|
+
else if (maxPercent >= 80) {
|
|
1938
|
+
status = chalk_1.default.red('š“ Critical');
|
|
1939
|
+
}
|
|
1940
|
+
else if (maxPercent >= 50) {
|
|
1941
|
+
status = chalk_1.default.yellow('ā ļø Warning');
|
|
1942
|
+
}
|
|
1943
|
+
console.log(`\n Status: ${status}\n`);
|
|
1944
|
+
}
|
|
1945
|
+
catch (err) {
|
|
1946
|
+
spinner.fail(chalk_1.default.red(err.message));
|
|
1947
|
+
process.exit(1);
|
|
1948
|
+
}
|
|
1949
|
+
});
|
|
1950
|
+
// Helper: progress bar
|
|
1951
|
+
function getProgressBar(percent, width) {
|
|
1952
|
+
const filled = Math.round((percent / 100) * width);
|
|
1953
|
+
const empty = width - filled;
|
|
1954
|
+
const color = percent >= 80 ? chalk_1.default.red : percent >= 50 ? chalk_1.default.yellow : brand.green;
|
|
1955
|
+
return color('ā'.repeat(filled)) + chalk_1.default.gray('ā'.repeat(empty));
|
|
1956
|
+
}
|
|
1957
|
+
// Helper: time until
|
|
1958
|
+
function getTimeUntil(isoDate) {
|
|
1959
|
+
const target = new Date(isoDate);
|
|
1960
|
+
const now = new Date();
|
|
1961
|
+
const diffMs = target.getTime() - now.getTime();
|
|
1962
|
+
if (diffMs <= 0)
|
|
1963
|
+
return 'now';
|
|
1964
|
+
const hours = Math.floor(diffMs / (1000 * 60 * 60));
|
|
1965
|
+
const minutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
|
|
1966
|
+
if (hours > 24) {
|
|
1967
|
+
const days = Math.floor(hours / 24);
|
|
1968
|
+
return `${days}d ${hours % 24}h`;
|
|
1969
|
+
}
|
|
1970
|
+
if (hours > 0) {
|
|
1971
|
+
return `${hours}h ${minutes}m`;
|
|
1972
|
+
}
|
|
1973
|
+
return `${minutes}m`;
|
|
1974
|
+
}
|
|
1726
1975
|
// ========== STATUS COMMAND ==========
|
|
1727
1976
|
program
|
|
1728
1977
|
.command('status')
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -18,7 +18,7 @@ const brand = {
|
|
|
18
18
|
};
|
|
19
19
|
|
|
20
20
|
// Version
|
|
21
|
-
const VERSION = '0.1.
|
|
21
|
+
const VERSION = '0.1.14';
|
|
22
22
|
|
|
23
23
|
// ASCII art banner
|
|
24
24
|
function showBanner(context?: string) {
|
|
@@ -1891,6 +1891,302 @@ access
|
|
|
1891
1891
|
}
|
|
1892
1892
|
});
|
|
1893
1893
|
|
|
1894
|
+
// ========== BUDGET COMMAND ==========
|
|
1895
|
+
|
|
1896
|
+
const budget = program
|
|
1897
|
+
.command('budget')
|
|
1898
|
+
.description(`View and manage agent spending limits
|
|
1899
|
+
|
|
1900
|
+
${chalk.bold('Examples:')}
|
|
1901
|
+
$ gopherhole budget my-agent # View budget
|
|
1902
|
+
$ gopherhole budget my-agent -d 10 -w 50 # Set daily=$10, weekly=$50
|
|
1903
|
+
$ gopherhole budget my-agent --clear # Remove all limits
|
|
1904
|
+
$ gopherhole budget --all # List all agents' budgets
|
|
1905
|
+
`);
|
|
1906
|
+
|
|
1907
|
+
budget
|
|
1908
|
+
.argument('[agentId]', 'Agent ID (optional with --all)')
|
|
1909
|
+
.option('-a, --all', 'List all agents\' budgets')
|
|
1910
|
+
.option('-d, --daily <amount>', 'Set daily limit (dollars)')
|
|
1911
|
+
.option('-w, --weekly <amount>', 'Set weekly limit (dollars)')
|
|
1912
|
+
.option('-r, --per-request <amount>', 'Set per-request max (dollars)')
|
|
1913
|
+
.option('--clear', 'Remove all spending limits')
|
|
1914
|
+
.option('--json', 'Output as JSON')
|
|
1915
|
+
.action(async (agentId: string | undefined, options) => {
|
|
1916
|
+
const sessionId = config.get('sessionId') as string;
|
|
1917
|
+
if (!sessionId) {
|
|
1918
|
+
console.log(chalk.yellow('Not logged in.'));
|
|
1919
|
+
console.log(chalk.gray('Run: gopherhole login'));
|
|
1920
|
+
process.exit(1);
|
|
1921
|
+
}
|
|
1922
|
+
|
|
1923
|
+
// Handle --all flag
|
|
1924
|
+
if (options.all) {
|
|
1925
|
+
const spinner = ora('Fetching spending overview...').start();
|
|
1926
|
+
try {
|
|
1927
|
+
const res = await fetch(`${API_URL}/spending`, {
|
|
1928
|
+
headers: { 'X-Session-ID': sessionId },
|
|
1929
|
+
});
|
|
1930
|
+
|
|
1931
|
+
if (!res.ok) {
|
|
1932
|
+
throw new Error('Failed to fetch spending overview');
|
|
1933
|
+
}
|
|
1934
|
+
|
|
1935
|
+
const data = await res.json() as {
|
|
1936
|
+
agents: Array<{
|
|
1937
|
+
agentId: string;
|
|
1938
|
+
name: string;
|
|
1939
|
+
daily: { spent: number; limit: number | null; percent: number | null } | null;
|
|
1940
|
+
weekly: { spent: number; limit: number | null; percent: number | null } | null;
|
|
1941
|
+
status: string;
|
|
1942
|
+
}>;
|
|
1943
|
+
totalSpentToday: number;
|
|
1944
|
+
totalSpentThisWeek: number;
|
|
1945
|
+
};
|
|
1946
|
+
spinner.stop();
|
|
1947
|
+
|
|
1948
|
+
if (options.json) {
|
|
1949
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1950
|
+
return;
|
|
1951
|
+
}
|
|
1952
|
+
|
|
1953
|
+
console.log(chalk.bold('\nš° Spending Overview\n'));
|
|
1954
|
+
console.log(` Today: $${data.totalSpentToday.toFixed(2)} This week: $${data.totalSpentThisWeek.toFixed(2)}\n`);
|
|
1955
|
+
|
|
1956
|
+
if (data.agents.length === 0) {
|
|
1957
|
+
console.log(chalk.gray(' No agents found.\n'));
|
|
1958
|
+
return;
|
|
1959
|
+
}
|
|
1960
|
+
|
|
1961
|
+
// Header
|
|
1962
|
+
console.log(chalk.gray(' Agent Daily Weekly Status'));
|
|
1963
|
+
console.log(chalk.gray(' ā'.padEnd(70, 'ā')));
|
|
1964
|
+
|
|
1965
|
+
for (const agent of data.agents) {
|
|
1966
|
+
const name = agent.name.padEnd(18).slice(0, 18);
|
|
1967
|
+
|
|
1968
|
+
let daily = 'ā'.padEnd(12);
|
|
1969
|
+
if (agent.daily?.limit !== null && agent.daily?.limit !== undefined) {
|
|
1970
|
+
const bar = getProgressBar(agent.daily.percent ?? 0, 5);
|
|
1971
|
+
daily = `$${agent.daily.spent.toFixed(0)}/$${agent.daily.limit.toFixed(0)} ${bar}`.padEnd(12);
|
|
1972
|
+
} else if (agent.daily?.spent) {
|
|
1973
|
+
daily = `$${agent.daily.spent.toFixed(2)}`.padEnd(12);
|
|
1974
|
+
}
|
|
1975
|
+
|
|
1976
|
+
let weekly = 'ā'.padEnd(12);
|
|
1977
|
+
if (agent.weekly?.limit !== null && agent.weekly?.limit !== undefined) {
|
|
1978
|
+
const bar = getProgressBar(agent.weekly.percent ?? 0, 5);
|
|
1979
|
+
weekly = `$${agent.weekly.spent.toFixed(0)}/$${agent.weekly.limit.toFixed(0)} ${bar}`.padEnd(12);
|
|
1980
|
+
} else if (agent.weekly?.spent) {
|
|
1981
|
+
weekly = `$${agent.weekly.spent.toFixed(2)}`.padEnd(12);
|
|
1982
|
+
}
|
|
1983
|
+
|
|
1984
|
+
const statusEmoji = {
|
|
1985
|
+
ok: brand.green('ā
OK'),
|
|
1986
|
+
warning: chalk.yellow('ā ļø Warning'),
|
|
1987
|
+
critical: chalk.red('š“ Critical'),
|
|
1988
|
+
blocked: chalk.red('š« Blocked'),
|
|
1989
|
+
unlimited: chalk.gray('ā'),
|
|
1990
|
+
}[agent.status] ?? agent.status;
|
|
1991
|
+
|
|
1992
|
+
console.log(` ${name} ${daily} ${weekly} ${statusEmoji}`);
|
|
1993
|
+
}
|
|
1994
|
+
console.log('');
|
|
1995
|
+
} catch (err) {
|
|
1996
|
+
spinner.fail(chalk.red((err as Error).message));
|
|
1997
|
+
process.exit(1);
|
|
1998
|
+
}
|
|
1999
|
+
return;
|
|
2000
|
+
}
|
|
2001
|
+
|
|
2002
|
+
// Need agentId for single-agent operations
|
|
2003
|
+
if (!agentId) {
|
|
2004
|
+
console.log(chalk.yellow('Please provide an agent ID or use --all'));
|
|
2005
|
+
console.log(chalk.gray('Usage: gopherhole budget <agentId> [options]'));
|
|
2006
|
+
console.log(chalk.gray(' gopherhole budget --all'));
|
|
2007
|
+
process.exit(1);
|
|
2008
|
+
}
|
|
2009
|
+
|
|
2010
|
+
// Handle --clear flag
|
|
2011
|
+
if (options.clear) {
|
|
2012
|
+
const spinner = ora('Removing spending limits...').start();
|
|
2013
|
+
try {
|
|
2014
|
+
const res = await fetch(`${API_URL}/agents/${agentId}/spending-limits`, {
|
|
2015
|
+
method: 'DELETE',
|
|
2016
|
+
headers: { 'X-Session-ID': sessionId },
|
|
2017
|
+
});
|
|
2018
|
+
|
|
2019
|
+
if (!res.ok) {
|
|
2020
|
+
throw new Error('Failed to remove spending limits');
|
|
2021
|
+
}
|
|
2022
|
+
|
|
2023
|
+
spinner.succeed('Spending limits removed');
|
|
2024
|
+
console.log(chalk.gray(` Agent ${agentId} now has no spending limits.\n`));
|
|
2025
|
+
} catch (err) {
|
|
2026
|
+
spinner.fail(chalk.red((err as Error).message));
|
|
2027
|
+
process.exit(1);
|
|
2028
|
+
}
|
|
2029
|
+
return;
|
|
2030
|
+
}
|
|
2031
|
+
|
|
2032
|
+
// Handle setting limits
|
|
2033
|
+
if (options.daily !== undefined || options.weekly !== undefined || options.perRequest !== undefined) {
|
|
2034
|
+
const spinner = ora('Updating spending limits...').start();
|
|
2035
|
+
try {
|
|
2036
|
+
const body: Record<string, number | null> = {};
|
|
2037
|
+
|
|
2038
|
+
if (options.daily !== undefined) {
|
|
2039
|
+
body.dailyLimit = options.daily === 'unlimited' ? null : parseFloat(options.daily);
|
|
2040
|
+
}
|
|
2041
|
+
if (options.weekly !== undefined) {
|
|
2042
|
+
body.weeklyLimit = options.weekly === 'unlimited' ? null : parseFloat(options.weekly);
|
|
2043
|
+
}
|
|
2044
|
+
if (options.perRequest !== undefined) {
|
|
2045
|
+
body.maxPerRequest = options.perRequest === 'unlimited' ? null : parseFloat(options.perRequest);
|
|
2046
|
+
}
|
|
2047
|
+
|
|
2048
|
+
const res = await fetch(`${API_URL}/agents/${agentId}/spending-limits`, {
|
|
2049
|
+
method: 'PUT',
|
|
2050
|
+
headers: {
|
|
2051
|
+
'X-Session-ID': sessionId,
|
|
2052
|
+
'Content-Type': 'application/json',
|
|
2053
|
+
},
|
|
2054
|
+
body: JSON.stringify(body),
|
|
2055
|
+
});
|
|
2056
|
+
|
|
2057
|
+
if (!res.ok) {
|
|
2058
|
+
const err = await res.json();
|
|
2059
|
+
throw new Error((err as { error?: string }).error || 'Failed to update limits');
|
|
2060
|
+
}
|
|
2061
|
+
|
|
2062
|
+
spinner.succeed('Spending limits updated');
|
|
2063
|
+
|
|
2064
|
+
// Show the updated limits
|
|
2065
|
+
const data = await res.json() as { limits: Record<string, unknown> };
|
|
2066
|
+
console.log(chalk.gray('\n New limits:'));
|
|
2067
|
+
if ((data.limits as any).daily) {
|
|
2068
|
+
console.log(` Daily: $${((data.limits as any).daily.limit as number).toFixed(2)}`);
|
|
2069
|
+
}
|
|
2070
|
+
if ((data.limits as any).weekly) {
|
|
2071
|
+
console.log(` Weekly: $${((data.limits as any).weekly.limit as number).toFixed(2)}`);
|
|
2072
|
+
}
|
|
2073
|
+
if ((data.limits as any).maxPerRequest) {
|
|
2074
|
+
console.log(` Per Request: $${((data.limits as any).maxPerRequest as number).toFixed(2)}`);
|
|
2075
|
+
}
|
|
2076
|
+
console.log('');
|
|
2077
|
+
} catch (err) {
|
|
2078
|
+
spinner.fail(chalk.red((err as Error).message));
|
|
2079
|
+
process.exit(1);
|
|
2080
|
+
}
|
|
2081
|
+
return;
|
|
2082
|
+
}
|
|
2083
|
+
|
|
2084
|
+
// Default: show budget
|
|
2085
|
+
const spinner = ora('Fetching budget...').start();
|
|
2086
|
+
try {
|
|
2087
|
+
const res = await fetch(`${API_URL}/agents/${agentId}/budget`, {
|
|
2088
|
+
headers: { 'X-Session-ID': sessionId },
|
|
2089
|
+
});
|
|
2090
|
+
|
|
2091
|
+
if (!res.ok) {
|
|
2092
|
+
if (res.status === 404) {
|
|
2093
|
+
throw new Error('Agent not found');
|
|
2094
|
+
}
|
|
2095
|
+
throw new Error('Failed to fetch budget');
|
|
2096
|
+
}
|
|
2097
|
+
|
|
2098
|
+
const data = await res.json() as {
|
|
2099
|
+
daily: { limit: number; spent: number; remaining: number; resetsAt: string } | null;
|
|
2100
|
+
weekly: { limit: number; spent: number; remaining: number; resetsAt: string } | null;
|
|
2101
|
+
maxPerRequest: number | null;
|
|
2102
|
+
};
|
|
2103
|
+
spinner.stop();
|
|
2104
|
+
|
|
2105
|
+
if (options.json) {
|
|
2106
|
+
console.log(JSON.stringify(data, null, 2));
|
|
2107
|
+
return;
|
|
2108
|
+
}
|
|
2109
|
+
|
|
2110
|
+
console.log(chalk.bold(`\nš° Budget for ${agentId}\n`));
|
|
2111
|
+
|
|
2112
|
+
if (!data.daily && !data.weekly && !data.maxPerRequest) {
|
|
2113
|
+
console.log(chalk.gray(' No spending limits configured.\n'));
|
|
2114
|
+
console.log(chalk.gray(' Set limits: gopherhole budget <agentId> -d 10 -w 50\n'));
|
|
2115
|
+
return;
|
|
2116
|
+
}
|
|
2117
|
+
|
|
2118
|
+
if (data.daily) {
|
|
2119
|
+
const percent = Math.round((data.daily.spent / data.daily.limit) * 100);
|
|
2120
|
+
const bar = getProgressBar(percent, 10);
|
|
2121
|
+
const timeLeft = getTimeUntil(data.daily.resetsAt);
|
|
2122
|
+
const color = percent >= 80 ? chalk.red : percent >= 50 ? chalk.yellow : brand.green;
|
|
2123
|
+
console.log(` Daily: ${color(`$${data.daily.spent.toFixed(2)} / $${data.daily.limit.toFixed(2)}`)} (${percent}%) ${bar}`);
|
|
2124
|
+
console.log(chalk.gray(` Resets in ${timeLeft}`));
|
|
2125
|
+
}
|
|
2126
|
+
|
|
2127
|
+
if (data.weekly) {
|
|
2128
|
+
const percent = Math.round((data.weekly.spent / data.weekly.limit) * 100);
|
|
2129
|
+
const bar = getProgressBar(percent, 10);
|
|
2130
|
+
const timeLeft = getTimeUntil(data.weekly.resetsAt);
|
|
2131
|
+
const color = percent >= 80 ? chalk.red : percent >= 50 ? chalk.yellow : brand.green;
|
|
2132
|
+
console.log(` Weekly: ${color(`$${data.weekly.spent.toFixed(2)} / $${data.weekly.limit.toFixed(2)}`)} (${percent}%) ${bar}`);
|
|
2133
|
+
console.log(chalk.gray(` Resets ${timeLeft}`));
|
|
2134
|
+
}
|
|
2135
|
+
|
|
2136
|
+
if (data.maxPerRequest) {
|
|
2137
|
+
console.log(` Per Request: $${data.maxPerRequest.toFixed(2)} max`);
|
|
2138
|
+
}
|
|
2139
|
+
|
|
2140
|
+
// Status
|
|
2141
|
+
let status = brand.green('ā
OK');
|
|
2142
|
+
const dailyPercent = data.daily ? (data.daily.spent / data.daily.limit) * 100 : 0;
|
|
2143
|
+
const weeklyPercent = data.weekly ? (data.weekly.spent / data.weekly.limit) * 100 : 0;
|
|
2144
|
+
const maxPercent = Math.max(dailyPercent, weeklyPercent);
|
|
2145
|
+
|
|
2146
|
+
if (maxPercent >= 100) {
|
|
2147
|
+
status = chalk.red('š« Blocked');
|
|
2148
|
+
} else if (maxPercent >= 80) {
|
|
2149
|
+
status = chalk.red('š“ Critical');
|
|
2150
|
+
} else if (maxPercent >= 50) {
|
|
2151
|
+
status = chalk.yellow('ā ļø Warning');
|
|
2152
|
+
}
|
|
2153
|
+
|
|
2154
|
+
console.log(`\n Status: ${status}\n`);
|
|
2155
|
+
} catch (err) {
|
|
2156
|
+
spinner.fail(chalk.red((err as Error).message));
|
|
2157
|
+
process.exit(1);
|
|
2158
|
+
}
|
|
2159
|
+
});
|
|
2160
|
+
|
|
2161
|
+
// Helper: progress bar
|
|
2162
|
+
function getProgressBar(percent: number, width: number): string {
|
|
2163
|
+
const filled = Math.round((percent / 100) * width);
|
|
2164
|
+
const empty = width - filled;
|
|
2165
|
+
const color = percent >= 80 ? chalk.red : percent >= 50 ? chalk.yellow : brand.green;
|
|
2166
|
+
return color('ā'.repeat(filled)) + chalk.gray('ā'.repeat(empty));
|
|
2167
|
+
}
|
|
2168
|
+
|
|
2169
|
+
// Helper: time until
|
|
2170
|
+
function getTimeUntil(isoDate: string): string {
|
|
2171
|
+
const target = new Date(isoDate);
|
|
2172
|
+
const now = new Date();
|
|
2173
|
+
const diffMs = target.getTime() - now.getTime();
|
|
2174
|
+
|
|
2175
|
+
if (diffMs <= 0) return 'now';
|
|
2176
|
+
|
|
2177
|
+
const hours = Math.floor(diffMs / (1000 * 60 * 60));
|
|
2178
|
+
const minutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
|
|
2179
|
+
|
|
2180
|
+
if (hours > 24) {
|
|
2181
|
+
const days = Math.floor(hours / 24);
|
|
2182
|
+
return `${days}d ${hours % 24}h`;
|
|
2183
|
+
}
|
|
2184
|
+
if (hours > 0) {
|
|
2185
|
+
return `${hours}h ${minutes}m`;
|
|
2186
|
+
}
|
|
2187
|
+
return `${minutes}m`;
|
|
2188
|
+
}
|
|
2189
|
+
|
|
1894
2190
|
// ========== STATUS COMMAND ==========
|
|
1895
2191
|
|
|
1896
2192
|
program
|