diff --git a/src/commands/cliproxy/quota-subcommand/handlers.ts b/src/commands/cliproxy/quota-subcommand/handlers.ts index 84886720..a4e78574 100644 --- a/src/commands/cliproxy/quota-subcommand/handlers.ts +++ b/src/commands/cliproxy/quota-subcommand/handlers.ts @@ -138,7 +138,7 @@ export async function handleDoctor(verbose = false): Promise { for (const model of quota.models) { const bar = formatQuotaBar(model.percentage); - console.log(` ${model.name.padEnd(20)} ${bar} ${model.percentage.toFixed(0)}%`); + console.log(` ${model.name.padEnd(20)} ${bar} ${model.percentage.toFixed(0)}% remaining`); } console.log(''); } diff --git a/src/commands/cliproxy/quota-subcommand/sections/claude.ts b/src/commands/cliproxy/quota-subcommand/sections/claude.ts index 88b19f8e..a2d50d68 100644 --- a/src/commands/cliproxy/quota-subcommand/sections/claude.ts +++ b/src/commands/cliproxy/quota-subcommand/sections/claude.ts @@ -93,7 +93,7 @@ export function displayClaudeQuotaSection( ? dim(' [warning]') : ''; console.log( - ` ${getClaudeWindowDisplayLabel(window).padEnd(24)} ${bar} ${window.remainingPercent.toFixed(0)}%${statusLabel}${resetLabel}` + ` ${getClaudeWindowDisplayLabel(window).padEnd(24)} ${bar} ${window.remainingPercent.toFixed(0)}% remaining${statusLabel}${resetLabel}` ); } console.log(''); diff --git a/src/commands/cliproxy/quota-subcommand/sections/codex.ts b/src/commands/cliproxy/quota-subcommand/sections/codex.ts index e2811734..fdc5d1ed 100644 --- a/src/commands/cliproxy/quota-subcommand/sections/codex.ts +++ b/src/commands/cliproxy/quota-subcommand/sections/codex.ts @@ -94,7 +94,7 @@ export function displayCodexQuotaSection( const resetValue = formatCodexWindowReset(window); const resetLabel = resetValue ? dim(` Resets ${resetValue}`) : ''; console.log( - ` ${getCodexWindowDisplayLabel(window, orderedWindows).padEnd(24)} ${bar} ${window.remainingPercent.toFixed(0)}%${resetLabel}` + ` ${getCodexWindowDisplayLabel(window, orderedWindows).padEnd(24)} ${bar} ${window.remainingPercent.toFixed(0)}% remaining${resetLabel}` ); } console.log(''); diff --git a/src/commands/cliproxy/quota-subcommand/sections/gemini-cli.ts b/src/commands/cliproxy/quota-subcommand/sections/gemini-cli.ts index 287d34e4..f95cb8f3 100644 --- a/src/commands/cliproxy/quota-subcommand/sections/gemini-cli.ts +++ b/src/commands/cliproxy/quota-subcommand/sections/gemini-cli.ts @@ -67,7 +67,7 @@ export function displayGeminiCliQuotaSection( ? dim(` Resets ${formatResetTimeISO(bucket.resetTime)}`) : ''; console.log( - ` ${bucket.label.padEnd(24)} ${bar} ${bucket.remainingPercent.toFixed(0)}%${tokenLabel}${amountLabel}${resetLabel}` + ` ${bucket.label.padEnd(24)} ${bar} ${bucket.remainingPercent.toFixed(0)}% remaining${tokenLabel}${amountLabel}${resetLabel}` ); } console.log(''); diff --git a/tests/unit/commands/cliproxy-quota-subcommand.test.ts b/tests/unit/commands/cliproxy-quota-subcommand.test.ts index dc1fa4b9..bf83c5f8 100644 --- a/tests/unit/commands/cliproxy-quota-subcommand.test.ts +++ b/tests/unit/commands/cliproxy-quota-subcommand.test.ts @@ -1,5 +1,7 @@ import { describe, expect, it } from 'bun:test'; import { displayClaudeQuotaSection } from '../../../src/commands/cliproxy/quota-subcommand/sections/claude'; +import { displayCodexQuotaSection } from '../../../src/commands/cliproxy/quota-subcommand/sections/codex'; +import { displayGeminiCliQuotaSection } from '../../../src/commands/cliproxy/quota-subcommand/sections/gemini-cli'; async function loadQuotaCommandTestExports() { const moduleId = Date.now() + Math.random(); @@ -167,3 +169,104 @@ describe('cliproxy quota subcommand Codex label formatting', () => { expect(label).not.toContain('\u0007'); }); }); + +function captureConsoleLog(fn: () => void): string { + const output: string[] = []; + const originalLog = console.log; + console.log = (...args: unknown[]) => output.push(args.map(String).join(' ')); + try { + fn(); + } finally { + console.log = originalLog; + } + return output.join('\n'); +} + +describe('cliproxy quota remaining percent labels', () => { + it('renders Claude remaining percent with a remaining suffix', () => { + const text = captureConsoleLog(() => { + displayClaudeQuotaSection([ + { + account: 'claude@example.com', + quota: { + success: true, + windows: [ + { + rateLimitType: 'five_hour', + label: '5h usage limit', + status: 'allowed', + utilization: 0.28, + usedPercent: 28, + remainingPercent: 72, + resetAt: null, + }, + ], + coreUsage: { fiveHour: null, weekly: null }, + lastUpdated: 1, + accountId: 'claude@example.com', + }, + }, + ]); + }); + + expect(text).toContain('72% remaining'); + }); + + it('renders Codex remaining percent with a remaining suffix', () => { + const text = captureConsoleLog(() => { + displayCodexQuotaSection([ + { + account: 'codex@example.com', + quota: { + success: true, + windows: [ + { + label: 'Primary', + usedPercent: 28, + remainingPercent: 72, + resetAfterSeconds: null, + resetAt: null, + category: 'usage', + cadence: '5h', + }, + ], + planType: 'pro', + lastUpdated: 1, + accountId: 'codex@example.com', + }, + }, + ]); + }); + + expect(text).toContain('72% remaining'); + }); + + it('renders Gemini CLI remaining percent with a remaining suffix', () => { + const text = captureConsoleLog(() => { + displayGeminiCliQuotaSection([ + { + account: 'gemini@example.com', + quota: { + success: true, + buckets: [ + { + id: 'gemini-flash-series', + label: 'Gemini Flash Series', + tokenType: null, + remainingFraction: 0.72, + remainingPercent: 72, + resetTime: null, + modelIds: ['gemini-flash'], + }, + ], + projectId: null, + lastUpdated: 1, + accountId: 'gemini@example.com', + }, + }, + ]); + }); + + expect(text).toContain('72% remaining'); + }); +});