@gopherhole/cli 0.1.12 → 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 +254 -3
- package/package.json +1 -1
- package/src/index.ts +301 -3
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 = [
|
|
@@ -1131,7 +1131,8 @@ ${chalk_1.default.bold('Examples:')}
|
|
|
1131
1131
|
accessBadge = chalk_1.default.blue('👤YOURS');
|
|
1132
1132
|
}
|
|
1133
1133
|
else if (agent.accessStatus === 'approved' || agent.accessStatus === 'open') {
|
|
1134
|
-
|
|
1134
|
+
const discountText = agent.discountPercent ? ` ${agent.discountPercent}% OFF` : '';
|
|
1135
|
+
accessBadge = chalk_1.default.green(`✓ACCESS${discountText}`);
|
|
1135
1136
|
}
|
|
1136
1137
|
else if (agent.accessStatus === 'pending') {
|
|
1137
1138
|
accessBadge = chalk_1.default.yellow('⏳PENDING');
|
|
@@ -1249,7 +1250,8 @@ ${chalk_1.default.bold('Example:')}
|
|
|
1249
1250
|
accessLine = chalk_1.default.blue('👤 Your agent');
|
|
1250
1251
|
}
|
|
1251
1252
|
else if (agent.accessStatus === 'approved') {
|
|
1252
|
-
|
|
1253
|
+
const discountText = agent.discountPercent ? chalk_1.default.cyan(` (${agent.discountPercent}% discount)`) : '';
|
|
1254
|
+
accessLine = chalk_1.default.green('✓ You have access') + discountText;
|
|
1253
1255
|
}
|
|
1254
1256
|
else if (agent.accessStatus === 'open') {
|
|
1255
1257
|
accessLine = chalk_1.default.green('✓ Open access (instant)');
|
|
@@ -1721,6 +1723,255 @@ access
|
|
|
1721
1723
|
process.exit(1);
|
|
1722
1724
|
}
|
|
1723
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
|
+
}
|
|
1724
1975
|
// ========== STATUS COMMAND ==========
|
|
1725
1976
|
program
|
|
1726
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) {
|
|
@@ -1251,7 +1251,8 @@ ${chalk.bold('Examples:')}
|
|
|
1251
1251
|
if (agent.accessStatus === 'owner') {
|
|
1252
1252
|
accessBadge = chalk.blue('👤YOURS');
|
|
1253
1253
|
} else if (agent.accessStatus === 'approved' || agent.accessStatus === 'open') {
|
|
1254
|
-
|
|
1254
|
+
const discountText = agent.discountPercent ? ` ${agent.discountPercent}% OFF` : '';
|
|
1255
|
+
accessBadge = chalk.green(`✓ACCESS${discountText}`);
|
|
1255
1256
|
} else if (agent.accessStatus === 'pending') {
|
|
1256
1257
|
accessBadge = chalk.yellow('⏳PENDING');
|
|
1257
1258
|
} else if (agent.accessStatus === 'denied') {
|
|
@@ -1375,7 +1376,8 @@ ${chalk.bold('Example:')}
|
|
|
1375
1376
|
if (agent.accessStatus === 'owner') {
|
|
1376
1377
|
accessLine = chalk.blue('👤 Your agent');
|
|
1377
1378
|
} else if (agent.accessStatus === 'approved') {
|
|
1378
|
-
|
|
1379
|
+
const discountText = agent.discountPercent ? chalk.cyan(` (${agent.discountPercent}% discount)`) : '';
|
|
1380
|
+
accessLine = chalk.green('✓ You have access') + discountText;
|
|
1379
1381
|
} else if (agent.accessStatus === 'open') {
|
|
1380
1382
|
accessLine = chalk.green('✓ Open access (instant)');
|
|
1381
1383
|
} else if (agent.accessStatus === 'pending') {
|
|
@@ -1889,6 +1891,302 @@ access
|
|
|
1889
1891
|
}
|
|
1890
1892
|
});
|
|
1891
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
|
+
|
|
1892
2190
|
// ========== STATUS COMMAND ==========
|
|
1893
2191
|
|
|
1894
2192
|
program
|