diff --git a/README.md b/README.md index 710d864..8a3c660 100644 --- a/README.md +++ b/README.md @@ -151,7 +151,7 @@ Edit `~/.claude/plugins/claude-hud/config.json` directly for advanced settings s | `display.showConfigCounts` | boolean | false | Show CLAUDE.md, rules, MCPs, hooks counts | | `display.showDuration` | boolean | false | Show session duration `⏱️ 5m` | | `display.showSpeed` | boolean | false | Show output token speed `out: 42.1 tok/s` | -| `display.showUsage` | boolean | true | Show usage limits (Pro/Max/Team only) | +| `display.showUsage` | boolean | true | Show Claude subscriber usage limits when available | | `display.usageBarEnabled` | boolean | true | Display usage as visual bar instead of text | | `display.sevenDayThreshold` | 0-100 | 80 | Show 7-day usage when >= threshold (0 = always) | | `display.showTokenBreakdown` | boolean | true | Show token details at high context (85%+) | @@ -173,9 +173,11 @@ Supported color names: `red`, `green`, `yellow`, `magenta`, `cyan`, `brightBlue` `display.showMemoryUsage` is fully opt-in and only renders in `expanded` layout. It reports approximate system RAM usage from the local machine, not precise memory pressure inside Claude Code or a specific process. The number may overstate actual pressure because reclaimable OS cache and buffers can still be counted as used memory. -### Usage Limits (Pro/Max/Team) +### Usage Limits -Usage display is **enabled by default** for Claude Pro, Max, and Team subscribers. It shows your rate limit consumption on line 2 alongside the context bar. +Usage display is **enabled by default** for Claude subscribers when Claude Code provides rate-limit data or the HUD can fall back to the Anthropic OAuth usage API. It shows your rate limit consumption on line 2 alongside the context bar. + +Free/weekly-only accounts render the weekly window by itself instead of showing a ghost `5h: --` placeholder. The 7-day percentage appears when above the `display.sevenDayThreshold` (default 80%): @@ -186,14 +188,15 @@ Context █████░░░░░ 45% │ Usage ██░░░░░░░ To disable, set `display.showUsage` to `false`. **Requirements:** -- Claude Pro, Max, or Team subscription (not available for API users) -- OAuth credentials from Claude Code (created automatically when you log in) +- Claude subscription usage data from Claude Code stdin or the Anthropic OAuth usage API +- Not available for API-key-only users **Troubleshooting:** If usage doesn't appear: -- Ensure you're logged in with a Pro/Max/Team account (not API key) +- Ensure you're logged in with a Claude subscriber account (not API key) - Check `display.showUsage` is not set to `false` in config - API users see no usage display (they have pay-per-token, not rate limits) - AWS Bedrock models display `Bedrock` and hide usage limits (usage is managed in AWS) +- Claude Code may leave `rate_limits` empty until after the first model response in a session - Non-default `ANTHROPIC_BASE_URL` / `ANTHROPIC_API_BASE_URL` settings skip usage display, because the Anthropic OAuth usage API may not apply - If you are behind a proxy, set `HTTPS_PROXY` (or `HTTP_PROXY`/`ALL_PROXY`) and optional `NO_PROXY` - For high-latency environments, increase the usage API timeout with `CLAUDE_HUD_USAGE_TIMEOUT_MS` (milliseconds) diff --git a/src/stdin.ts b/src/stdin.ts index 14dab93..b287a3e 100644 --- a/src/stdin.ts +++ b/src/stdin.ts @@ -123,7 +123,7 @@ function parseRateLimitPercent(value: number | null | undefined): number | null return null; } - return Math.floor(Math.min(100, Math.max(0, value))); + return Math.round(Math.min(100, Math.max(0, value))); } function parseRateLimitResetAt(value: number | null | undefined): Date | null { diff --git a/src/usage-api.ts b/src/usage-api.ts index 5497ef7..b6d8a73 100644 --- a/src/usage-api.ts +++ b/src/usage-api.ts @@ -843,17 +843,13 @@ function getPlanName(subscriptionType: string): string | null { } export function getUsagePlanNameFallback(homeDir: string = os.homedir()): string | null { - const cachedPlanName = readCachedPlanName(homeDir); - if (cachedPlanName) { - return cachedPlanName; - } - const subscriptionType = readFileSubscriptionType(homeDir); - if (!subscriptionType) { - return null; + if (subscriptionType) { + return getPlanName(subscriptionType); } - return getPlanName(subscriptionType); + const cachedPlanName = readCachedPlanName(homeDir); + return cachedPlanName ?? null; } /** Parse utilization value, clamping to 0-100 and handling NaN/Infinity */ diff --git a/tests/core.test.js b/tests/core.test.js index 63bf5fd..ac73098 100644 --- a/tests/core.test.js +++ b/tests/core.test.js @@ -236,7 +236,7 @@ test('getUsageFromStdin parses official Claude Code rate_limits payload', () => assert.deepEqual(usage, { planName: 'Max', - fiveHour: 7, + fiveHour: 8, sevenDay: 100, fiveHourResetAt: new Date(1710000000 * 1000), sevenDayResetAt: new Date(1710600000 * 1000), diff --git a/tests/index.test.js b/tests/index.test.js index f5beb13..d48841a 100644 --- a/tests/index.test.js +++ b/tests/index.test.js @@ -207,7 +207,7 @@ test('main prefers stdin-native rate_limits over the usage API fallback', async assert.equal(fallbackPlanCalls, 1); assert.deepEqual(renderedContext?.usageData, { planName: 'Max', - fiveHour: 21, + fiveHour: 22, sevenDay: 55, fiveHourResetAt: new Date(1710000000 * 1000), sevenDayResetAt: new Date(1710600000 * 1000), diff --git a/tests/usage-api.test.js b/tests/usage-api.test.js index 0c6ffc0..0cd39f8 100644 --- a/tests/usage-api.test.js +++ b/tests/usage-api.test.js @@ -1596,7 +1596,7 @@ describe('getProxyUrl', () => { }); describe('getUsagePlanNameFallback', () => { - test('prefers cached planName over credentials file state', async () => { + test('prefers current credentials file state over a stale cached planName', async () => { const homeDir = await createTempHome(); try { @@ -1609,7 +1609,7 @@ describe('getUsagePlanNameFallback', () => { }); await writeCredentials(homeDir, buildCredentials({ subscriptionType: 'claude_team_2024' })); - assert.equal(getUsagePlanNameFallback(homeDir), 'Max'); + assert.equal(getUsagePlanNameFallback(homeDir), 'Team'); } finally { await rm(homeDir, { recursive: true, force: true }); }