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
14 changes: 13 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,30 @@ on Columbus Labs QuotaKit releases and product-facing changes.

### Added
- Menu bar: add customizable drag-and-drop token layouts and weekly session-equivalent forecasting.
- Providers: add DeepInfra usage and balance, ai& spend, OpenRouter token accounts, DeepSeek cost summaries, and broader Doubao arkcli support.
- Providers: add DeepInfra usage and balance, ai& spend, OpenRouter token accounts, DeepSeek cost summaries, broader Doubao arkcli support, Qwen Cloud individual Token Plans, and ZoomMate credits, history, and pacing.
- Alibaba: add Personal/Solo Token Plan variants for mainland Bailian and international Model Studio accounts.
- CLI: add safe cookie re-import and quota-aware guard commands under the QuotaKit command name.
- Claude: show prepaid credit balance from cached or manually configured web sessions and add an option to hide Daily Routines.
- Claude: show model-scoped weekly claude-swap windows and optionally show a card when only one account is available.
- Codex: add local workspace indexing as the foundation for per-workspace usage attribution.
- OpenCode Go: add daily local cost and plan-usage history.
- Overview: raise the merged provider limit from three to six.

### Changed
- CLI: redact stored credentials from `quotakit config dump` by default; use `--show-secrets` to reveal raw values.
- 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 `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
- Keychain: keep Cursor and Claude refresh available when Keychain access is disabled by using memory-only cookie caches and preventing background Claude prompts.
- Claude: isolate cached credentials by profile, preserve enterprise spend-cap extra usage, and keep the account Weekly quota ahead of exhausted model carve-outs.
- Codex: bound and resume cost scans across very large session corpora so they do not pin a CPU core or lose history.
- Amp: map subscription-plan usage to percentage windows instead of reporting a misleading cookie error.
- Grok: let explicit cookie refresh import and validate a browser session for safe background reuse.
- Hooks: preserve configured hooks across config saves and ignore stale hook launches from other installations.
- Menu: prioritize exhausted automatic-display windows while preserving provider-specific preferences.
- Widgets: prevent quota-reset reload loops and remove the unintended dark background overlay.
- Refresh: prevent macOS 14 launch crashes caused by TaskLocal task-allocation corruption.
- Menu bar: render custom-layout provider icons at native size with correct light/dark tinting.
- Menu: prefer weekly quota windows for the switcher’s weekly progress, with provider-specific fallback.
Expand Down
5 changes: 5 additions & 0 deletions CodexBarMobile/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ current Columbus Labs product surface and recent release history.
- Monthly utilization-history labels synced from the Mac now use the selected
iPhone app language.

### Changed

- Provider presentation, colors, icons, and quota-alert subscriptions now
recognize Qwen Cloud and ZoomMate synced from the Mac app.

## [1.11.3 (171)] — 2026-07-12 — Background sync reliability

### Fixed
Expand Down
2 changes: 2 additions & 0 deletions CodexBarMobile/CodexBarMobile/Design/ProviderBrandMark.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ enum ProviderBrandAsset {
"perplexity",
"poe",
"qoder",
"qwencloud",
"sakana",
"stepfun",
"sub2api",
Expand All @@ -61,6 +62,7 @@ enum ProviderBrandAsset {
"zai",
"zed",
"zenmux",
"zoommate",
"aiand",
]

Expand Down
38 changes: 33 additions & 5 deletions CodexBarMobile/CodexBarMobile/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -1044,6 +1044,34 @@
}
}
},
"Balance": {
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Balance"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "残高"
}
},
"zh-Hans": {
"stringUnit": {
"state": "translated",
"value": "余额"
}
},
"zh-Hant": {
"stringUnit": {
"state": "translated",
"value": "餘額"
}
}
}
},
"Bar Chart": {
"localizations": {
"en": {
Expand Down Expand Up @@ -19457,30 +19485,30 @@
}
}
},
"Provider presentation and quota alerts now recognize ClinePass and LongCat synced from your Mac.": {
"Provider presentation and quota alerts now recognize ClinePass, LongCat, Qwen Cloud, and ZoomMate synced from your Mac.": {
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Provider presentation and quota alerts now recognize ClinePass and LongCat synced from your Mac."
"value": "Provider presentation and quota alerts now recognize ClinePass, LongCat, Qwen Cloud, and ZoomMate synced from your Mac."
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "Macから同期されたClinePassとLongCatが、プロバイダ表示とクォータ通知で認識されるようになりました。"
"value": "Macから同期されたClinePass、LongCat、Qwen Cloud、ZoomMateが、プロバイダ表示とクォータ通知で認識されるようになりました。"
}
},
"zh-Hans": {
"stringUnit": {
"state": "translated",
"value": "提供商显示和配额提醒现在可识别从 Mac 同步的 ClinePass 和 LongCat。"
"value": "提供商显示和配额提醒现在可识别从 Mac 同步的 ClinePass、LongCat、Qwen Cloud 和 ZoomMate。"
}
},
"zh-Hant": {
"stringUnit": {
"state": "translated",
"value": "供應商顯示和配額提醒現在可識別從 Mac 同步的 ClinePass 與 LongCat。"
"value": "供應商顯示和配額提醒現在可識別從 Mac 同步的 ClinePass、LongCat、Qwen Cloud 與 ZoomMate。"
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ enum MobileReleaseNotesCatalog {
String(
localized: "Background sync now finishes without leaving a local database write running after iOS closes the silent-push window."),
String(
localized: "Provider presentation and quota alerts now recognize ClinePass and LongCat synced from your Mac."),
localized: "Provider presentation and quota alerts now recognize ClinePass, LongCat, Qwen Cloud, and ZoomMate synced from your Mac."),
String(
localized: "Codex banked resets now show the available count and exact expiration time for every reset detail synced from your Mac."),
String(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ enum ProviderColorPalette {
(
["alibabatokenplan", "alibabatoken", "bailiantokenplan"],
RawColor(red: 1, green: 176 / 255, blue: 32 / 255)),
(["qwencloud"], RawColor(red: 147 / 255, green: 51 / 255, blue: 234 / 255)),
(["factory", "droid"], RawColor(red: 255 / 255, green: 107 / 255, blue: 53 / 255)),
(["gemini"], RawColor(red: 171 / 255, green: 135 / 255, blue: 234 / 255)),
(["antigravity"], RawColor(red: 96 / 255, green: 186 / 255, blue: 126 / 255)),
Expand Down Expand Up @@ -137,6 +138,7 @@ enum ProviderColorPalette {
(["litellm"], RawColor(red: 76 / 255, green: 137 / 255, blue: 192 / 255)),
(["deepgram"], RawColor(red: 0.49, green: 0.23, blue: 0.93)),
(["aiand", "ai&"], RawColor(red: 226 / 255, green: 92 / 255, blue: 43 / 255)),
(["zoommate"], RawColor(red: 64 / 255, green: 176 / 255, blue: 255 / 255)),
(["devin"], RawColor(red: 70 / 255, green: 180 / 255, blue: 130 / 255)),
]

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"images" : [
{
"filename" : "ProviderIcon-qwencloud.svg",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true,
"template-rendering-intent" : "template"
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"images" : [
{
"filename" : "ProviderIcon-zoommate.svg",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true,
"template-rendering-intent" : "template"
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
50 changes: 35 additions & 15 deletions CodexBarMobile/CodexBarMobile/Views/ClaudeExtraUsageCard.swift
Original file line number Diff line number Diff line change
Expand Up @@ -72,28 +72,48 @@ struct ClaudeExtraUsageCard: View {
}

private var detailRow: some View {
HStack {
if let spend = extraUsage.monthlySpendUSD {
if let limit = extraUsage.monthlyLimitUSD {
Text(String(
format: String(localized: "claude_extra_usage_spend_limit_format", defaultValue: "%@ / %@"),
Self.formatUSD(spend),
Self.formatUSD(limit)))
.font(.subheadline.bold().monospacedDigit())
.foregroundStyle(self.tintColor)
} else {
Text(Self.formatUSD(spend))
VStack(alignment: .leading, spacing: 4) {
HStack {
if let spend = extraUsage.monthlySpendUSD {
if let limit = extraUsage.monthlyLimitUSD {
Text(String(
format: String(localized: "claude_extra_usage_spend_limit_format", defaultValue: "%@ / %@"),
Self.formatUSD(spend),
Self.formatUSD(limit)))
.font(.subheadline.bold().monospacedDigit())
.foregroundStyle(self.tintColor)
} else {
Text(Self.formatUSD(spend))
.font(.subheadline.bold().monospacedDigit())
.foregroundStyle(self.tintColor)
}
} else if let balanceText = Self.balanceText(extraUsage.balanceUSD) {
Text(balanceText)
.font(.subheadline.bold().monospacedDigit())
.foregroundStyle(self.tintColor)
}
Spacer()
if self.extraUsage.monthlySpendUSD != nil {
Text(String(localized: "claude_extra_usage_period", defaultValue: "This month"))
.font(.caption)
.foregroundStyle(.secondary)
}
}
if self.extraUsage.monthlySpendUSD != nil,
let balanceText = Self.balanceText(extraUsage.balanceUSD)
{
Text(balanceText)
.font(.caption.monospacedDigit())
.foregroundStyle(.secondary)
}
Spacer()
Text(String(localized: "claude_extra_usage_period", defaultValue: "This month"))
.font(.caption)
.foregroundStyle(.secondary)
}
}

static func balanceText(_ balanceUSD: Double?) -> String? {
guard let balanceUSD else { return nil }
return "\(String(localized: "Balance")): \(Self.formatUSD(balanceUSD))"
}

private static func formatUSD(_ value: Double) -> String {
CostFormatting.usd(value)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,8 @@ final class NotificationService: UNNotificationServiceExtension {
providerName: info.providerName,
window: info.window,
threshold: info.threshold,
accountEmail: info.accountEmail)
accountEmail: info.accountEmail,
windowDisplayLabel: info.windowDisplayLabel)
NSEInvocationLog.shared.recordEntry(
timestamp: startedAt,
event: .ok,
Expand Down Expand Up @@ -206,7 +207,12 @@ final class NotificationService: UNNotificationServiceExtension {
/// returning version, but for now we want the NSE log to record the
/// exact CloudKit error message when fetch fails.
enum WarningFetchResult {
case success(providerName: String, window: String, threshold: Int, accountEmail: String?)
case success(
providerName: String,
window: String,
threshold: Int,
accountEmail: String?,
windowDisplayLabel: String?)
case empty(reason: String)
case error(message: String)
}
Expand Down Expand Up @@ -234,16 +240,14 @@ final class NotificationService: UNNotificationServiceExtension {
// bucketing on the writer side, zones rarely accumulate beyond a few
// dozen records, so the over-fetch is cheap.
//
// v0.27.0 build 65.2 adds `accountEmail` to `desiredKeys` — Mac
// writes it when the triggering provider has a resolvable account
// (Codex managed, Claude multi-account, etc.). Pre-65.2 Macs leave
// it absent so we treat nil as "no account scope" and fall back to
// the existing non-scoped body template.
// Optional fields remain backward compatible. Pre-65.2 Macs omit
// `accountEmail`; older Macs also omit `windowDisplayLabel`. Missing
// values fall back to the bare provider title and localized lane name.
do {
let (matchResults, _) = try await container.privateCloudDatabase.records(
matching: query,
inZoneWith: zoneID,
desiredKeys: ["providerName", "accountEmail"],
desiredKeys: ["providerName", "accountEmail", "windowDisplayLabel"],
resultsLimit: 100)
var newest: CKRecord?
for (_, result) in matchResults {
Expand All @@ -266,20 +270,27 @@ final class NotificationService: UNNotificationServiceExtension {
}
let accountEmail = (record["accountEmail"] as? String)?.trimmingCharacters(in: .whitespacesAndNewlines)
let normalizedAccount = (accountEmail?.isEmpty ?? true) ? nil : accountEmail
let windowDisplayLabel = (record["windowDisplayLabel"] as? String)?
.trimmingCharacters(in: .whitespacesAndNewlines)
let normalizedWindowDisplayLabel = (windowDisplayLabel?.isEmpty ?? true)
? nil
: windowDisplayLabel
guard let parsed = QuotaZoneNotificationParser.parseWarningRecordName(
record.recordID.recordName)
else {
return .success(
providerName: providerName,
window: "",
threshold: 0,
accountEmail: normalizedAccount)
accountEmail: normalizedAccount,
windowDisplayLabel: normalizedWindowDisplayLabel)
}
return .success(
providerName: providerName,
window: parsed.window,
threshold: parsed.threshold,
accountEmail: normalizedAccount)
accountEmail: normalizedAccount,
windowDisplayLabel: normalizedWindowDisplayLabel)
} catch {
let ckErr = error as? CKError
let ckCode = ckErr.map { "code=\($0.code.rawValue)" } ?? "type=\(type(of: error))"
Expand Down Expand Up @@ -413,9 +424,16 @@ final class NotificationService: UNNotificationServiceExtension {
providerName: String,
window: String,
threshold: Int,
accountEmail _: String? = nil) -> String
accountEmail _: String? = nil,
windowDisplayLabel: String? = nil) -> String
{
let windowLabel = self.localizedWindowLabel(window)
let normalizedWindowDisplayLabel = windowDisplayLabel?
.trimmingCharacters(in: .whitespacesAndNewlines)
let windowLabel = if let normalizedWindowDisplayLabel, !normalizedWindowDisplayLabel.isEmpty {
normalizedWindowDisplayLabel
} else {
self.localizedWindowLabel(window)
}
let template = String(localized: "Push.QuotaWarning.detailBody")
// %1$@ providerName · %2$@ windowLabel · %3$lld threshold
return String(format: template, providerName, windowLabel, threshold)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,4 +116,11 @@ struct MobileDisplayFormattingTests {
#expect(MobileChartAxisFormatter.axisValues(for: [0.18, 1.42, 2.48]) == [0, 1, 2, 3])
#expect(MobileChartAxisFormatter.axisLabel(for: 3) == "3")
}

@Test
func `Claude prepaid balance has dedicated display copy`() {
let expected = "\(String(localized: "Balance")): \(CostFormatting.usd(27.5))"
#expect(ClaudeExtraUsageCard.balanceText(27.5) == expected)
#expect(ClaudeExtraUsageCard.balanceText(nil) == nil)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ struct ProviderBrandAssetTests {
#expect(ProviderBrandAsset.assetName(for: "clinepass") == "ProviderIcon-clinepass")
#expect(ProviderBrandAsset.assetName(for: "longcat") == "ProviderIcon-longcat")
#expect(ProviderBrandAsset.assetName(for: "neuralwatt") == "ProviderIcon-neuralwatt")
#expect(ProviderBrandAsset.assetName(for: "qwencloud") == "ProviderIcon-qwencloud")
#expect(ProviderBrandAsset.assetName(for: "zoommate") == "ProviderIcon-zoommate")
}

@Test
Expand Down
Loading
Loading