Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -386,7 +386,7 @@ export class ClaudeCodeUsageExtension {
showCost: s.get<boolean>('showCost'),
showContext: s.get<boolean>('showContext'),
contextWindowOverride: s.get<number>('contextWindowOverride'),
statusBarMetric: s.get<'cost' | 'tokens'>('statusBarMetric'),
statusBarMetric: s.get<'cost' | 'monthly-cost' | 'tokens'>('statusBarMetric'),
showOpusWeekly: s.get<boolean>('showOpusWeekly'),
usageLimitTracking: s.get<boolean>('usageLimitTracking'),
adviceApiKey: s.get<string>('advice.apiKey'),
Expand Down Expand Up @@ -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)
);
Expand Down
5 changes: 3 additions & 2 deletions src/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
38 changes: 30 additions & 8 deletions src/statusBar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand All @@ -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) {
Expand All @@ -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;
Expand All @@ -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
Expand All @@ -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();
}
Expand Down Expand Up @@ -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();
Expand Down
4 changes: 2 additions & 2 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Loading