Skip to content

Commit 8a7e744

Browse files
feat: add unified usage and spend dashboard
Co-authored-by: Alec Gutman <44984861+Chipagosfinest@users.noreply.github.com>
1 parent b7920f1 commit 8a7e744

66 files changed

Lines changed: 9155 additions & 122 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ show an incident indicator.
148148
- Optional Codex web dashboard enrichments (code review remaining, usage breakdown, credits history).
149149
- Inline spend and usage charts for API-backed providers such as OpenAI, Claude Admin API, OpenRouter, LiteLLM, z.ai, MiniMax, Mistral, and AWS Bedrock.
150150
- Configurable cost-usage scans for Codex + Claude, plus reused chart UI for supported provider histories.
151+
- A persistent Settings → Usage & Spend view for local 7/30-day estimates, grouped by native currency and limited to providers that expose cost history.
151152
- Provider status polling with incident badges in the menu and icon overlay.
152153
- Merge Icons mode to combine providers into one status item + switcher.
153154
- Display controls for provider icons, labels, bars, reset-time style, and highest-usage auto-selection.

Sources/CodexBar/Localization.swift

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,15 @@ private func appLanguageDefaults() -> UserDefaults {
3434

3535
private let isRunningTestsProcessAtStartup: Bool = {
3636
let env = ProcessInfo.processInfo.environment
37-
if env["XCTestConfigurationFilePath"] != nil { return true }
38-
if env["TESTING_LIBRARY_VERSION"] != nil { return true }
39-
if env["SWIFT_TESTING"] != nil { return true }
37+
if env["XCTestConfigurationFilePath"] != nil {
38+
return true
39+
}
40+
if env["TESTING_LIBRARY_VERSION"] != nil {
41+
return true
42+
}
43+
if env["SWIFT_TESTING"] != nil {
44+
return true
45+
}
4046
return NSClassFromString("XCTestCase") != nil
4147
}()
4248

@@ -210,7 +216,11 @@ func L(_ key: String, language: String) -> String {
210216
func codexBarLocalizedLocale() -> Locale {
211217
let language = resolvedAppLanguage()
212218
guard !language.isEmpty else { return .current }
213-
switch language.lowercased() {
219+
let normalized = language.lowercased()
220+
if normalized == "ar" || normalized.hasPrefix("ar-") {
221+
return Locale(identifier: "\(language)@numbers=arab")
222+
}
223+
switch normalized {
214224
case "zh-hans":
215225
return Locale(identifier: "zh-Hans")
216226
case "zh-hant":
@@ -222,6 +232,10 @@ func codexBarLocalizedLocale() -> Locale {
222232
}
223233
}
224234

235+
func codexBarLocalizedInteger(_ value: Int) -> String {
236+
value.formatted(.number.locale(codexBarLocalizedLocale()))
237+
}
238+
225239
func codexBarLocalizedString(_ key: String, bundle: Bundle, resourceBundle: Bundle) -> String {
226240
let value = bundle.localizedString(forKey: key, value: nil, table: nil)
227241
let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines)

Sources/CodexBar/MenuCardView+Costs.swift

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,16 @@ extension UsageMenuCardView.Model {
134134
let fallbackTokens = snapshot.daily.compactMap(\.totalTokens).reduce(0, +)
135135
let monthTokensValue = snapshot.last30DaysTokens ?? (fallbackTokens > 0 ? fallbackTokens : nil)
136136
let monthTokens = monthTokensValue.map { UsageFormatter.tokenCountString($0) }
137-
let windowLabel = snapshot.historyLabel ?? Self.costHistoryWindowLabel(days: snapshot.historyDays)
137+
let windowLabel = if let historyLabel = snapshot.historyLabel {
138+
historyLabel
139+
} else if provider == .mistral,
140+
snapshot.historyDays == 1,
141+
Self.bedrockLatestBillingDay(from: snapshot.daily) != nil
142+
{
143+
L("Latest billing day")
144+
} else {
145+
Self.costHistoryWindowLabel(days: snapshot.historyDays)
146+
}
138147
let monthLine: String = {
139148
if let monthTokens {
140149
return String(format: L("%@: %@ · %@ tokens"), windowLabel, monthCost, monthTokens)

Sources/CodexBar/PreferencesSelection.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ extension SettingsPane {
77
var persistenceToken: String {
88
switch self {
99
case .general: "general"
10+
case .usageSpend: "usageSpend"
1011
case .notifications: "notifications"
1112
case .menuBar: "menuBar"
1213
case .menu: "menu"
@@ -20,6 +21,7 @@ extension SettingsPane {
2021
init?(persistenceToken: String) {
2122
switch persistenceToken {
2223
case "general": self = .general
24+
case "usageSpend": self = .usageSpend
2325
case "notifications": self = .notifications
2426
case "menuBar": self = .menuBar
2527
// Pre-0.41.1 releases persisted the retired Display pane; its contents moved to Menu Bar.

Sources/CodexBar/PreferencesSidebar.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ struct SettingsSidebarView: View {
3333
private var appPanesSection: some View {
3434
Section {
3535
SettingsSidebarPaneRow(pane: .general, systemImage: "gearshape.fill", color: .gray)
36+
SettingsSidebarPaneRow(pane: .usageSpend, systemImage: "chart.bar.fill", color: .green)
3637
SettingsSidebarPaneRow(pane: .notifications, systemImage: "bell.badge.fill", color: .red)
3738
SettingsSidebarPaneRow(pane: .menuBar, systemImage: "menubar.rectangle", color: .blue)
3839
SettingsSidebarPaneRow(pane: .menu, systemImage: "filemenu.and.selection", color: .teal)

0 commit comments

Comments
 (0)