Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
4b2ccbe
Center stacked menu bar titles
kiranmagic7 Jul 20, 2026
5f21944
fix: allow Alibaba/Qwen Token Plan usage on Linux with a manual cookie
OfficialAbhinavSingh Jul 20, 2026
2de6991
Merge remote-tracking branch 'origin/main' into kiran/center-stacked-…
steipete Jul 21, 2026
83b6b91
Prevent WidgetKit reload loop in BurnDownRefreshSchedule
Yuxin-Qiao Jul 21, 2026
4576313
Stabilize dashboard fixture clock
ProspectOre Jul 22, 2026
5c009dc
Skip optional Safari cookie errors
hxy91819 Jul 23, 2026
c033278
Suppress automatic Safari cookie errors
hxy91819 Jul 23, 2026
f383844
test(spend): serialize dashboard controller cases
Yuxin-Qiao Jul 22, 2026
3eafbb4
test(spend): isolate dashboard date range
Yuxin-Qiao Jul 22, 2026
667beb1
Cache validated Ollama browser sessions
hxy91819 Jul 23, 2026
322f529
Stabilize dashboard token provenance tests
Yuxin-Qiao Jul 23, 2026
1bd8549
Fix localized session equivalents
Yuxin-Qiao Jul 23, 2026
863ca1b
Cover localized stringsdict formatting
Yuxin-Qiao Jul 23, 2026
816de62
fix: keep Claude switcher bar on account weekly quota
Jul 23, 2026
6260caf
fix: keep Claude switcher on usable weekly lanes
Jul 23, 2026
995eafc
fix: keep Claude overview bar on account Weekly
Jul 23, 2026
35cd59f
fix: extract switcher weekly window helpers to unblock lint
Jul 23, 2026
a33a66b
test: isolate SpendDashboard day selection defaults
Jul 23, 2026
281a123
Fix Kimi weekly quota duration
harjothkhara Jul 24, 2026
36b336e
Fix duplicate weekly row from garbled Claude all-models capture
guhyun9454 Jul 24, 2026
aef55fd
Recover weekly from garbled all-models line; move test to own file
guhyun9454 Jul 24, 2026
2f2b8d7
Prefer exact all-models row over garbled duplicate for weekly value
guhyun9454 Jul 24, 2026
856bbd2
Preserve hooks in config persistence
Jul 24, 2026
d106ea1
Show model-scoped weekly rows before Daily Routines
Eimerrrrr Jul 26, 2026
97ce3b8
Add regression coverage for Claude extra window ordering
Eimerrrrr Jul 26, 2026
e07827f
Pin scoped weekly ordering for multiple models
Eimerrrrr Jul 26, 2026
f94f043
Support LongCat Firefox cookie imports
akshayprabhu200 Jul 26, 2026
9615d72
Strengthen LongCat browser order test
akshayprabhu200 Jul 27, 2026
de33897
Merge pull request #2418 from Yuxin-Qiao/codex/stabilize-spend-dashbo…
steipete Jul 28, 2026
b7caf56
Merge remote-tracking branch 'origin/main' into pr-2390-land
steipete Jul 28, 2026
67f976d
Merge branch 'main' into agent/longcat-firefox-cookie-import
steipete Jul 28, 2026
f27b833
Merge pull request #2347 from kiranmagic7/kiran/center-stacked-menuba…
steipete Jul 28, 2026
21f452d
Merge pull request #2371 from Yuxin-Qiao/fix/widget-reload-loop-2369
steipete Jul 28, 2026
0b2b011
Merge pull request #2356 from OfficialAbhinavSingh/fix-alibabatokenpl…
steipete Jul 28, 2026
c895835
fix: omit Daily Routines bar when routines payload is null
urda Jul 28, 2026
e3f9cdc
Merge pull request #2390 from ProspectOre/fix/dashboard-test-clock
steipete Jul 28, 2026
3d423b9
Merge pull request #2424 from gmkbenjamin/fix/claude-switcher-ignore-…
steipete Jul 28, 2026
dd029db
Merge pull request #2462 from akshayprabhu200/agent/longcat-firefox-c…
steipete Jul 28, 2026
8a27009
Merge branch 'main' into fix/claude-garbled-all-models-weekly-duplicate
steipete Jul 28, 2026
6199a8e
Merge branch 'main' into kiran/preserve-hooks-config-20260724
steipete Jul 28, 2026
f0e245a
Merge branch 'main' into claude-scoped-weekly-before-routines
steipete Jul 28, 2026
cd0bf3b
Merge pull request #2436 from kiranmagic7/kiran/preserve-hooks-config…
steipete Jul 28, 2026
cae9873
Merge pull request #2450 from urda-forks/urda/claude-null-routines
steipete Jul 28, 2026
445a76d
Drop changelog edit (maintainer adds on merge)
steipete Jul 28, 2026
463806d
Merge remote-tracking branch 'origin/main' into maint/pr-2433-policy-…
steipete Jul 28, 2026
4b90858
Merge branch 'main' into feature/fix-ollama-safari-cookie-fallback
steipete Jul 28, 2026
5e233b2
Merge pull request #2434 from guhyun9454/fix/claude-garbled-all-model…
steipete Jul 28, 2026
0611e8e
Merge pull request #2461 from Eimerrrrr/claude-scoped-weekly-before-r…
steipete Jul 28, 2026
7246abd
Merge pull request #2404 from hxy91819/feature/fix-ollama-safari-cook…
steipete Jul 28, 2026
02b4ba2
Merge pull request #2433 from harjothkhara/codex/fix-2431-kimi-weekly…
steipete Jul 28, 2026
26b90b1
Merge upstream CodexBar through 02b4ba278
ColumbusLabs Jul 28, 2026
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
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ on Columbus Labs QuotaKit releases and product-facing changes.
### Changed
- Menu bar: remove status-item hover tooltips while retaining VoiceOver titles.
- Cost displays: use consistent labels while preserving reported-versus-estimated provenance in settings and per-value hints.
- Synced upstream CodexBar changes through `f8636cb37`, including OpenCode Go source scoping and sub-1% usage accuracy, macOS 14 TaskLocal launch stability, weekly progress selection, custom-layout icon tinting, widget cost-row compaction, and Command Code contrast while preserving QuotaKit release ownership, CloudKit setup, config paths, and iOS build numbers.
- Synced upstream CodexBar changes through `02b4ba278`, including Claude weekly-window ordering and fallback accuracy, Kimi weekly duration accuracy, Ollama session reuse, safer automatic Safari-cookie handling, LongCat Firefox imports, localized session equivalents, stacked menu-title alignment, WidgetKit refresh-loop prevention, and Linux Alibaba Token Plan support while preserving QuotaKit release ownership, CloudKit setup, config paths, and iOS build numbers.

### Fixed
- Refresh: prevent macOS 14 launch crashes caused by TaskLocal task-allocation corruption.
Expand All @@ -35,6 +35,7 @@ on Columbus Labs QuotaKit releases and product-facing changes.
- Codex: exclude parent-copied prefixes from compact subagent usage when the fork boundary matches the parent snapshot.
- Usage & Spend: render share-card PNG exports without black backgrounds and keep complete model rows visible beside incomplete sources.
- ElevenLabs: clamp character and voice-slot usage percentages at 100% during overage.
- Ollama: reuse validated browser sessions and skip inaccessible Safari cookies during automatic fallback while retaining explicit permission guidance.

## 0.32.4.12 / iOS 1.11.3 — 2026-07-17

Expand Down
17 changes: 16 additions & 1 deletion Sources/CodexBar/Localization.swift
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,22 @@ func L(_ key: String, language: String) -> String {
}

func codexBarLocalizedLocale() -> Locale {
let language = resolvedAppLanguage()
codexBarLocale(forLanguage: resolvedAppLanguage())
}

/// Returns the locale of the resource bundle currently selected by `L`.
///
/// This can differ from `Locale.current` when the app falls back to a supported language. Plural
/// formatting must use this locale so it follows the same language as the resolved strings.
func codexBarLocalizedResourceLocale() -> Locale {
let bundleURL = localizedBundle().bundleURL
guard bundleURL.pathExtension == "lproj" else {
return codexBarLocalizedLocale()
}
return codexBarLocale(forLanguage: bundleURL.deletingPathExtension().lastPathComponent)
}

private func codexBarLocale(forLanguage language: String) -> Locale {
guard !language.isEmpty else { return .current }
let normalized = language.lowercased()
if normalized == "ar" || normalized.hasPrefix("ar-") {
Expand Down
6 changes: 5 additions & 1 deletion Sources/CodexBar/MenuBarLayoutRenderer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ final class MenuBarLayoutTitleCache {
@MainActor
final class MenuBarLayoutRenderer {
private static let missingValue = "–"
private static let stackedBaselineOffset: CGFloat = -3 // Center multi-line NSStatusBarButton titles.

private struct TokenStyle {
let font: NSFont
Expand Down Expand Up @@ -138,11 +139,14 @@ final class MenuBarLayoutRenderer {
paragraphStyle.maximumLineHeight = 9.5
paragraphStyle.lineSpacing = -1
}
let attributes: [NSAttributedString.Key: Any] = [
var attributes: [NSAttributedString.Key: Any] = [
.font: font,
.foregroundColor: foregroundColor,
.paragraphStyle: paragraphStyle,
]
if isStacked {
attributes[.baselineOffset] = Self.stackedBaselineOffset
}
let result = NSMutableAttributedString()
var accessibilityLines: [String] = []

Expand Down
27 changes: 20 additions & 7 deletions Sources/CodexBar/MenuCardView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1408,7 +1408,11 @@ extension UsageMenuCardView.Model {
primaryPaceOnTop = paceDetail.paceOnTop
}
}
} else if let paceDetail = Self.resetWindowPaceDetail(window: primary, input: input) {
} else if let paceDetail = Self.resetWindowPaceDetail(
window: primary,
input: input,
pace: input.provider == .kimi ? input.weeklyPace : nil)
{
primaryDetailLeft = paceDetail.leftLabel
primaryDetailRight = paceDetail.rightLabel
primaryPacePercent = paceDetail.pacePercent
Expand Down Expand Up @@ -1468,12 +1472,21 @@ extension UsageMenuCardView.Model {
title: String? = nil,
zaiTimeDetail: String?) -> Metric
{
var paceDetail = Self.weeklyPaceDetail(
provider: input.provider,
window: weekly,
now: input.now,
pace: input.weeklyPace,
showUsed: input.usageBarsShowUsed)
// Kimi's secondary slot is its 5-hour rate limit rather than a weekly window.
var paceDetail = if input.provider == .kimi {
Self.sessionPaceDetail(
provider: input.provider,
window: weekly,
now: input.now,
showUsed: input.usageBarsShowUsed)
} else {
Self.weeklyPaceDetail(
provider: input.provider,
window: weekly,
now: input.now,
pace: input.weeklyPace,
showUsed: input.usageBarsShowUsed)
}
var weeklyResetText = Self.resetText(for: weekly, style: input.resetTimeDisplayStyle, now: input.now)
var weeklyDetailText: String? = input.provider == .zai ? zaiTimeDetail : nil
if input.provider == .warp,
Expand Down
4 changes: 2 additions & 2 deletions Sources/CodexBar/PreferencesProvidersPane.swift
Original file line number Diff line number Diff line change
Expand Up @@ -539,8 +539,8 @@ struct ProvidersPane: View {
tokenError = nil
}

// Abacus uses primary for monthly credits (no secondary window)
let paceWindow = provider == .abacus ? snapshot?.primary : snapshot?.secondary
// Abacus and Kimi carry their long-cadence window in primary rather than secondary.
let paceWindow = provider == .abacus || provider == .kimi ? snapshot?.primary : snapshot?.secondary
let weeklyPace = if let codexProjection,
let weekly = codexProjection.rateWindow(for: .weekly)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,21 @@ struct OllamaProviderImplementation: ProviderImplementation {
binding: cookieBinding,
options: cookieOptions,
isVisible: nil,
onChange: nil),
onChange: nil,
trailingText: {
guard context.settings.ollamaUsageDataSource != .api else { return nil }
return ProviderCookieRefreshAction.trailingText(
provider: .ollama,
cookieSource: context.settings.ollamaCookieSource,
context: context)
},
trailingActions: [
ProviderCookieRefreshAction.descriptor(
provider: .ollama,
cookieSource: { context.settings.ollamaCookieSource },
additionalVisibility: { context.settings.ollamaUsageDataSource != .api },
context: context),
]),
]
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,14 @@ enum ProviderCookieRefreshAction {
static func descriptor(
provider: UsageProvider,
cookieSource: @escaping () -> ProviderCookieSource,
additionalVisibility: @escaping () -> Bool = { true },
context: ProviderSettingsContext) -> ProviderSettingsActionDescriptor
{
ProviderSettingsActionDescriptor(
id: "\(provider.rawValue)-reimport-cookie",
title: "Refresh",
style: .bordered,
isVisible: { cookieSource() == .auto },
isVisible: { cookieSource() == .auto && additionalVisibility() },
perform: {
await self.perform(provider: provider, context: context)
})
Expand Down
4 changes: 2 additions & 2 deletions Sources/CodexBar/StatusItemController+MenuCardModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,8 @@ extension StatusItemController {

let sourceLabel = surface == .liveCard ? self.store.sourceLabel(for: target) : nil
let kiloAutoMode = target == .kilo && self.settings.kiloUsageDataSource == .auto
// Abacus uses primary for monthly credits (no secondary window)
let paceWindow = target == .abacus ? snapshot?.primary : snapshot?.secondary
// Abacus and Kimi carry their long-cadence window in primary rather than secondary.
let paceWindow = target == .abacus || target == .kimi ? snapshot?.primary : snapshot?.secondary
let sessionEquivalentHistorySelection = self.sessionEquivalentHistorySelection(
provider: target,
snapshot: snapshot,
Expand Down
22 changes: 14 additions & 8 deletions Sources/CodexBar/UsagePaceText.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,19 +39,21 @@ enum UsagePaceText {

static func sessionEquivalentDetail(forecast: SessionEquivalentForecast) -> SessionEquivalentDetail {
let displayedEstimate = Self.boundedFullWindowCount(forecast.estimatedWindowsToExhaustWeekly)
let numberText = String.localizedStringWithFormat(
L("≈%d full 5h windows of weekly left · %d windows until reset"),
displayedEstimate,
forecast.windowsUntilReset)
let formattingLocale = codexBarLocalizedResourceLocale()
let numberText = String(
format: L("≈%d full 5h windows of weekly left · %d windows until reset"),
locale: formattingLocale,
arguments: [displayedEstimate, forecast.windowsUntilReset])
let verdictText: String
if forecast.estimatedWindowsToExhaustWeekly >= forecast.availableWindowsUntilReset {
verdictText = L("Weekly cannot run out before reset at this pace")
} else {
let windowsEarly = Self.boundedWindowCount(
forecast.availableWindowsUntilReset - forecast.estimatedWindowsToExhaustWeekly)
verdictText = String.localizedStringWithFormat(
L("Weekly can run out ≈%d windows early"),
max(1, windowsEarly))
verdictText = String(
format: L("Weekly can run out ≈%d windows early"),
locale: formattingLocale,
arguments: [max(1, windowsEarly)])
}
return SessionEquivalentDetail(
verdictText: verdictText,
Expand Down Expand Up @@ -151,14 +153,18 @@ enum UsagePaceText {
}

static func sessionPace(provider: UsageProvider, window: RateWindow, now: Date) -> UsagePace? {
guard provider == .codex || provider == .claude || provider == .ollama || provider == .antigravity
guard provider == .codex || provider == .claude || provider == .ollama || provider == .antigravity ||
provider == .kimi
else { return nil }
if provider == .ollama, window.windowMinutes == nil {
return nil
}
if provider == .antigravity, let windowMinutes = window.windowMinutes, windowMinutes != 300 {
return nil
}
if provider == .kimi, window.windowMinutes != KimiProviderDescriptor.sessionWindowMinutes {
return nil
}
guard window.remainingPercent > 0 else { return nil }
guard let pace = UsagePace.weekly(window: window, now: now, defaultWindowMinutes: 300) else { return nil }
guard pace.expectedUsedPercent >= 3 else { return nil }
Expand Down
44 changes: 38 additions & 6 deletions Sources/CodexBarCLI/CLIRenderer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -544,10 +544,20 @@ enum CLIRenderer {
now: Date = Date()) -> ProviderPacePayload?
{
let primary = snapshot.primary.flatMap {
self.pacePayload(provider: provider, window: $0, kind: .session, now: now)
self.pacePayload(
provider: provider,
window: $0,
kind: self.paceKind(provider: provider, fallback: .session),
weeklyWorkDays: weeklyWorkDays,
now: now)
}
let secondary = snapshot.secondary.flatMap {
self.pacePayload(provider: provider, window: $0, kind: .weekly, weeklyWorkDays: weeklyWorkDays, now: now)
self.pacePayload(
provider: provider,
window: $0,
kind: self.paceKind(provider: provider, fallback: .weekly),
weeklyWorkDays: weeklyWorkDays,
now: now)
}
guard primary != nil || secondary != nil else { return nil }
return ProviderPacePayload(primary: primary, secondary: secondary)
Expand Down Expand Up @@ -577,7 +587,7 @@ enum CLIRenderer {
provider: provider,
title: labels.primary,
window: primary,
paceKind: .session,
paceKind: self.paceKind(provider: provider, fallback: .session),
context: context,
now: now,
lines: &lines)
Expand Down Expand Up @@ -609,7 +619,7 @@ enum CLIRenderer {
provider: provider,
title: labels.secondary,
window: weekly,
paceKind: .weekly,
paceKind: self.paceKind(provider: provider, fallback: .weekly),
context: context,
now: now,
lines: &lines)
Expand Down Expand Up @@ -1067,13 +1077,25 @@ enum CLIRenderer {
func supports(provider: UsageProvider) -> Bool {
switch self {
case .session:
provider == .codex || provider == .claude || provider == .ollama
provider == .codex || provider == .claude || provider == .ollama || provider == .kimi
case .weekly:
provider == .codex || provider == .claude || provider == .opencode || provider == .ollama
provider == .codex || provider == .claude || provider == .opencode || provider == .ollama ||
provider == .kimi
}
}
}

private static func paceKind(provider: UsageProvider, fallback: PaceKind) -> PaceKind {
guard provider == .kimi else { return fallback }
// Kimi stores weekly in primary and its rate limit in secondary, opposite the legacy CLI slot convention.
switch fallback {
case .session:
return .weekly
case .weekly:
return .session
}
}

private static func computePace(
provider: UsageProvider,
window: RateWindow,
Expand All @@ -1082,6 +1104,16 @@ enum CLIRenderer {
now: Date) -> UsagePace?
{
guard kind.supports(provider: provider) else { return nil }
if provider == .kimi {
let supportsWindow = switch kind {
case .session:
window.windowMinutes == KimiProviderDescriptor.sessionWindowMinutes
case .weekly:
ProviderDescriptorRegistry.descriptor(for: provider).pace
.supportsResetWindowPace(window: window, now: now)
}
guard supportsWindow else { return nil }
}
// Only pace a real session window here; Claude w/o 5-hour data falls a 7-day window into primary.
if case .session = kind, let minutes = window.windowMinutes, minutes > 300 {
return nil
Expand Down
7 changes: 7 additions & 0 deletions Sources/CodexBarCLI/CLIUsageCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -699,6 +699,13 @@ extension CodexBarCLI {
{
return false
}
if provider == .alibabatokenplan,
settings?.alibabaTokenPlan?.cookieSource == .manual
{
// The Alibaba/Qwen Token Plan fetch is plain URLSession + cookies; only browser
// cookie auto-import needs macOS, so a manual cookie header works off macOS too.
return false
}
#if os(Linux)
if provider == .cursor,
settings?.cursor?.cookieSource != .off
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -208,18 +208,14 @@ struct OAuthUsageResponse: Decodable {
in container: KeyedDecodingContainer<DynamicCodingKey>,
keys: [String]) -> (window: OAuthUsageWindow?, sourceKey: String?)
{
var firstNullKey: String?
for keyName in keys {
guard let key = DynamicCodingKey(stringValue: keyName) else { continue }
guard container.contains(key) else { continue }
if let value = try? container.decodeIfPresent(OAuthUsageWindow.self, forKey: key) {
return (value, keyName)
}
if firstNullKey == nil {
firstNullKey = keyName
}
}
return (nil, firstNullKey)
return (nil, nil)
}

private static func decodeValue<T: Decodable>(
Expand Down
Loading