diff --git a/CHANGELOG.md b/CHANGELOG.md index 5718d89f..2cb0f2b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ upstream release: 1.0.8). Format follows [Keep a Changelog](https://keepachangel ## [2.1.0] — Unreleased ### Added +- **Monthly cost in the status bar** — the `statusBarMetric` setting gains a + new `monthly-cost` option. When selected, the first status-bar item shows the + current calendar month's total cost ($(calendar) icon) instead of today's + cost. Hover tooltip mirrors the today tooltip with month-to-date token and + cost breakdown. - **Weekly Opus limit in the status bar** — opt-in `showOpusWeekly` (default off) appends `opus:NN%` after the 5h / weekly quota figures, for heavy Opus users who want an at-a-glance weekly Opus signal. Merged from diff --git a/src/extension.ts b/src/extension.ts index d2e0fb1f..7adad76d 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -386,7 +386,7 @@ export class ClaudeCodeUsageExtension { showCost: s.get('showCost'), showContext: s.get('showContext'), contextWindowOverride: s.get('contextWindowOverride'), - statusBarMetric: s.get<'cost' | 'tokens'>('statusBarMetric'), + statusBarMetric: s.get<'cost' | 'monthly-cost' | 'tokens'>('statusBarMetric'), showOpusWeekly: s.get('showOpusWeekly'), usageLimitTracking: s.get('usageLimitTracking'), adviceApiKey: s.get('advice.apiKey'), @@ -707,7 +707,7 @@ export class ClaudeCodeUsageExtension { // Update UI. Quota is pushed asynchronously by the fire-and-forget fetch // above; passing undefined leaves the quota item untouched here. - this.statusBar.updateUsageData(todayData, workspaceTodayData, undefined, undefined); + this.statusBar.updateUsageData(todayData, workspaceTodayData, undefined, undefined, monthData); this.statusBar.updateContext( ClaudeDataLoader.getCurrentContextInfo(records, workspacePath, config.contextWindowOverride) ); diff --git a/src/settings.ts b/src/settings.ts index c87a242d..eeec5882 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -108,8 +108,9 @@ export const SETTINGS: SettingDef[] = [ storage: 'state', group: 'statusBar', label: 'Status-bar metric', - help: "What the first status-bar item shows: today's cost, or today's total token count (k/M).", - enumValues: ['cost', 'tokens'], + help: "What the first status-bar item shows: today's cost, this month's cost, or today's total token count (k/M).", + enumValues: ['cost', 'monthly-cost', 'tokens'], + enumLabels: ["Today's cost", "Monthly cost", 'Token count'], }, { key: 'showContext', diff --git a/src/statusBar.ts b/src/statusBar.ts index a69449c1..9300e9b9 100644 --- a/src/statusBar.ts +++ b/src/statusBar.ts @@ -11,8 +11,8 @@ export class StatusBarManager { private showCost: boolean = true; private showContext: boolean = true; private usageLimitTracking: boolean = true; - // First item shows today's cost ('cost') or today's total token count ('tokens'). - private metric: 'cost' | 'tokens' = 'cost'; + // First item shows today's cost ('cost'), this month's cost ('monthly-cost'), or today's token count ('tokens'). + private metric: 'cost' | 'monthly-cost' | 'tokens' = 'cost'; // Opt-in: append the weekly Opus limit (opus:NN%) to the quota item (PR #38, // @wheelbarrel00). private showOpusWeekly: boolean = false; @@ -49,7 +49,7 @@ export class StatusBarManager { showCost: boolean, showContext: boolean, usageLimitTracking: boolean = true, - metric: 'cost' | 'tokens' = 'cost', + metric: 'cost' | 'monthly-cost' | 'tokens' = 'cost', showOpusWeekly: boolean = false ): void { this.showCost = showCost; @@ -90,7 +90,8 @@ export class StatusBarManager { todayData: UsageData | null, workspaceTodayData?: UsageData | null, error?: string, - usageLimits?: ClaudeApiUsageResponse | null + usageLimits?: ClaudeApiUsageResponse | null, + monthData?: UsageData | null ): void { // Quota is account-level and decoupled from local-data state: the caller // is expected to call updateQuota() separately so workspaces without @@ -107,7 +108,7 @@ export class StatusBarManager { return; } - this.showTodayData(todayData, workspaceTodayData ?? null); + this.showTodayData(todayData, workspaceTodayData ?? null, monthData ?? null); // The usageLimits arg is kept for callers that want a single-call update // path; quota was already refreshed earlier in this cycle. if (usageLimits !== undefined) { @@ -124,10 +125,10 @@ export class StatusBarManager { } } - private showTodayData(todayData: UsageData, workspaceTodayData: UsageData | null): void { + private showTodayData(todayData: UsageData, workspaceTodayData: UsageData | null, monthData: UsageData | null): void { // Primary figure = today across all projects; secondary = today for the // current workspace, so you can see this project's share next to the global - // total. Both reset at midnight. The metric setting switches cost ↔ tokens. + // total. Both reset at midnight. The metric setting switches cost ↔ tokens ↔ monthly cost. const ws = workspaceTodayData ?? null; const totalTokens = (d: UsageData): number => d.totalInputTokens + d.totalOutputTokens + d.totalCacheCreationTokens + d.totalCacheReadTokens; @@ -137,6 +138,8 @@ export class StatusBarManager { if (ws) { text += ` $(folder) ${I18n.formatTokensCompact(totalTokens(ws))}`; } + } else if (this.metric === 'monthly-cost') { + text = `$(calendar) ${I18n.formatCurrency(monthData?.totalCost ?? 0)}`; } else { text = `$(pulse) ${I18n.formatCurrency(todayData.totalCost)}`; // Show the workspace figure whenever a workspace is open — including @@ -147,7 +150,9 @@ export class StatusBarManager { } this.statusBarItem.text = text; - this.statusBarItem.tooltip = this.createTooltip(todayData, ws); + this.statusBarItem.tooltip = this.metric === 'monthly-cost' + ? this.createMonthlyTooltip(monthData) + : this.createTooltip(todayData, ws); this.statusBarItem.backgroundColor = undefined; this.applyCostVisibility(); } @@ -377,6 +382,23 @@ export class StatusBarManager { return md; } + private createMonthlyTooltip(monthData: UsageData | null): vscode.MarkdownString { + const t = I18n.t.popup; + const md = new vscode.MarkdownString(); + md.supportThemeIcons = true; + + md.appendMarkdown(`| | $(calendar) ${t.thisMonth} |\n`); + md.appendMarkdown(`|:--|--:|\n`); + md.appendMarkdown(`| ${t.cost} | ${I18n.formatCurrency(monthData?.totalCost ?? 0)} |\n`); + md.appendMarkdown(`| ${t.inputTokens} | ${I18n.formatNumber(monthData?.totalInputTokens ?? 0)} |\n`); + md.appendMarkdown(`| ${t.outputTokens} | ${I18n.formatNumber(monthData?.totalOutputTokens ?? 0)} |\n`); + md.appendMarkdown(`| ${t.cacheCreation} | ${I18n.formatNumber(monthData?.totalCacheCreationTokens ?? 0)} |\n`); + md.appendMarkdown(`| ${t.cacheRead} | ${I18n.formatNumber(monthData?.totalCacheReadTokens ?? 0)} |\n`); + md.appendMarkdown(`| ${t.messages} | ${I18n.formatNumber(monthData?.messageCount ?? 0)} |\n`); + md.appendMarkdown(`\n\n*Click for detailed breakdown*`); + return md; + } + private createQuotaTooltip(usageLimits: ClaudeApiUsageResponse): vscode.MarkdownString { const t = I18n.t.popup; const md = new vscode.MarkdownString(); diff --git a/src/types.ts b/src/types.ts index c3883760..46f2fb1e 100644 --- a/src/types.ts +++ b/src/types.ts @@ -230,8 +230,8 @@ export interface ExtensionConfig { showContext: boolean; // Manual context-window size override in tokens (0 = auto-detect). contextWindowOverride: number; - // First status-bar item: today's cost, or today's total token count. - statusBarMetric: 'cost' | 'tokens'; + // First status-bar item: today's cost, this month's cost, or today's total token count. + statusBarMetric: 'cost' | 'monthly-cost' | 'tokens'; // Opt-in: append the weekly Opus limit (opus:NN%) to the quota item (PR #38). showOpusWeekly: boolean; // Fetch real 5-hour / weekly limit utilisation via Claude Code's OAuth session.