@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.
Files changed (3) hide show
  1. package/dist/index.js +250 -1
  2. package/package.json +1 -1
  3. 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.8';
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gopherhole/cli",
3
- "version": "0.1.13",
3
+ "version": "0.1.14",
4
4
  "description": "GopherHole CLI - Connect AI agents to the world",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
package/src/index.ts CHANGED
@@ -18,7 +18,7 @@ const brand = {
18
18
  };
19
19
 
20
20
  // Version
21
- const VERSION = '0.1.8';
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