Skip to content

Commit de7c25b

Browse files
Yuxin-Qiaosteipete
andauthored
Show extra-usage spend text in menu bar for Claude/Cursor (#1107)
* Display extra-usage spend in menu bar * docs: update changelog for menu bar extra usage spend --------- Co-authored-by: Peter Steinberger <steipete@gmail.com>
1 parent 17b0651 commit de7c25b

3 files changed

Lines changed: 98 additions & 5 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
### Added
66

77
### Fixed
8+
- Menu bar: show extra-usage spend as currency text for Claude and Cursor when that metric is selected (#1107). Thanks @Yuxin-Qiao!
89

910
## 0.29.0 — 2026-05-22
1011

Sources/CodexBar/StatusItemController+Animation.swift

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -667,6 +667,11 @@ extension StatusItemController {
667667
mode: self.settings.kiroMenuBarDisplayMode,
668668
showUsed: self.settings.usageBarsShowUsed)
669669
}
670+
if self.settings.menuBarMetricPreference(for: provider, snapshot: snapshot) == .extraUsage,
671+
let spend = Self.extraUsageSpendDisplayText(snapshot: snapshot)
672+
{
673+
return spend
674+
}
670675

671676
let percentWindow = self.menuBarPercentWindow(for: provider, snapshot: snapshot)
672677
let mode = self.settings.menuBarDisplayMode
@@ -752,6 +757,16 @@ extension StatusItemController {
752757
removingSuffix: " left")
753758
}
754759

760+
nonisolated static func extraUsageSpendDisplayText(snapshot: UsageSnapshot?) -> String? {
761+
guard let cost = snapshot?.providerCost,
762+
cost.limit > 0,
763+
cost.used >= 0
764+
else {
765+
return nil
766+
}
767+
return UsageFormatter.currencyString(cost.used, currencyCode: cost.currencyCode)
768+
}
769+
755770
nonisolated static func kiroDisplayText(
756771
snapshot: UsageSnapshot?,
757772
mode: KiroMenuBarDisplayMode,

Tests/CodexBarTests/StatusItemExtraUsageMetricTests.swift

Lines changed: 82 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,9 @@ struct StatusItemExtraUsageMetricTests {
3838

3939
@Test
4040
func `menu bar extra usage preference falls back to automatic when cursor on demand budget is missing`() {
41-
let (store, controller) = self.makeCursorController(suiteName: "StatusItemExtraUsageMetricTests-missing-budget")
41+
let (store, controller) = self.makeController(
42+
suiteName: "StatusItemExtraUsageMetricTests-missing-budget",
43+
provider: .cursor)
4244
let snapshot = UsageSnapshot(
4345
primary: RateWindow(usedPercent: 10, windowMinutes: nil, resetsAt: nil, resetDescription: nil),
4446
secondary: RateWindow(usedPercent: 72, windowMinutes: nil, resetsAt: nil, resetDescription: nil),
@@ -54,19 +56,94 @@ struct StatusItemExtraUsageMetricTests {
5456
#expect(window?.usedPercent == 72)
5557
}
5658

59+
@Test
60+
func `menu bar extra usage preference shows currency spend text for cursor when provider cost exists`() {
61+
let (store, controller) = self.makeController(
62+
suiteName: "StatusItemExtraUsageMetricTests-cursor-spend-text",
63+
provider: .cursor)
64+
let snapshot = UsageSnapshot(
65+
primary: RateWindow(usedPercent: 10, windowMinutes: nil, resetsAt: nil, resetDescription: nil),
66+
secondary: RateWindow(usedPercent: 20, windowMinutes: nil, resetsAt: nil, resetDescription: nil),
67+
tertiary: RateWindow(usedPercent: 72, windowMinutes: nil, resetsAt: nil, resetDescription: nil),
68+
providerCost: ProviderCostSnapshot(
69+
used: 12.34,
70+
limit: 100,
71+
currencyCode: "USD",
72+
updatedAt: Date()),
73+
updatedAt: Date())
74+
75+
store._setSnapshotForTesting(snapshot, provider: .cursor)
76+
store._setErrorForTesting(nil, provider: .cursor)
77+
78+
let displayText = controller.menuBarDisplayText(for: .cursor, snapshot: snapshot)
79+
80+
#expect(displayText == "$12.34")
81+
}
82+
83+
@Test
84+
func `menu bar extra usage preference shows currency spend text for claude when provider cost exists`() {
85+
let (store, controller) = self.makeController(
86+
suiteName: "StatusItemExtraUsageMetricTests-claude-spend-text",
87+
provider: .claude)
88+
let snapshot = UsageSnapshot(
89+
primary: RateWindow(usedPercent: 42, windowMinutes: 300, resetsAt: nil, resetDescription: nil),
90+
secondary: nil,
91+
tertiary: nil,
92+
providerCost: ProviderCostSnapshot(
93+
used: 88.8,
94+
limit: 200,
95+
currencyCode: "USD",
96+
period: "Monthly",
97+
updatedAt: Date()),
98+
updatedAt: Date())
99+
100+
store._setSnapshotForTesting(snapshot, provider: .claude)
101+
store._setErrorForTesting(nil, provider: .claude)
102+
103+
let displayText = controller.menuBarDisplayText(for: .claude, snapshot: snapshot)
104+
105+
#expect(displayText == "$88.80")
106+
}
107+
108+
@Test
109+
func `menu bar extra usage preference falls back to existing percent text when provider cost is unavailable`() {
110+
let (store, controller) = self.makeController(
111+
suiteName: "StatusItemExtraUsageMetricTests-fallback-percent",
112+
provider: .cursor)
113+
let snapshot = UsageSnapshot(
114+
primary: RateWindow(usedPercent: 10, windowMinutes: nil, resetsAt: nil, resetDescription: nil),
115+
secondary: RateWindow(usedPercent: 72, windowMinutes: nil, resetsAt: nil, resetDescription: nil),
116+
tertiary: nil,
117+
providerCost: nil,
118+
updatedAt: Date())
119+
120+
store._setSnapshotForTesting(snapshot, provider: .cursor)
121+
store._setErrorForTesting(nil, provider: .cursor)
122+
123+
let displayText = controller.menuBarDisplayText(for: .cursor, snapshot: snapshot)
124+
125+
#expect(displayText == "72%")
126+
}
127+
57128
private func makeCursorController(suiteName: String) -> (UsageStore, StatusItemController) {
129+
self.makeController(suiteName: suiteName, provider: .cursor)
130+
}
131+
132+
private func makeController(suiteName: String, provider: UsageProvider) -> (UsageStore, StatusItemController) {
58133
let settings = SettingsStore(
59134
configStore: testConfigStore(suiteName: suiteName),
60135
zaiTokenStore: NoopZaiTokenStore())
61136
settings.statusChecksEnabled = false
62137
settings.refreshFrequency = .manual
63138
settings.mergeIcons = true
64-
settings.selectedMenuProvider = .cursor
65-
settings.setMenuBarMetricPreference(.extraUsage, for: .cursor)
139+
settings.selectedMenuProvider = provider
140+
settings.menuBarDisplayMode = .percent
141+
settings.usageBarsShowUsed = true
142+
settings.setMenuBarMetricPreference(.extraUsage, for: provider)
66143

67144
let registry = ProviderRegistry.shared
68-
if let cursorMeta = registry.metadata[.cursor] {
69-
settings.setProviderEnabled(provider: .cursor, metadata: cursorMeta, enabled: true)
145+
if let metadata = registry.metadata[provider] {
146+
settings.setProviderEnabled(provider: provider, metadata: metadata, enabled: true)
70147
}
71148

72149
let fetcher = UsageFetcher()

0 commit comments

Comments
 (0)