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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
- Antigravity: exclude model quotas without a remaining fraction from family summaries so they no longer mask tracked usage in the automatic menu-bar metric (#1369). Thanks @Martin-Hausleitner!
- Claude: add bundled Fable 5 pricing, account for native 1-hour cache-write usage, and refresh Sonnet 4.6 full-context rates (#1368). Thanks @MoollaMore!
- Menu bar: defer data-refresh rebuilds until the tracked menu closes, avoiding multi-second WindowServer stalls with slower providers such as Grok (#1376). Thanks @jangisaac-dev!
- Xiaomi MiMo: import automatic session cookies from Safari, Chrome variants, Firefox, and Edge instead of limiting discovery to Chrome (#1304). Thanks @Yuxin-Qiao!

## 0.32.5 — 2026-06-09

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ struct MiMoProviderImplementation: ProviderImplementation {
ProviderCookieSourceUI.subtitle(
source: context.settings.miMoCookieSource,
keychainDisabled: context.settings.debugDisableKeychainAccess,
auto: "Automatic imports Chrome browser cookies from Xiaomi MiMo.",
auto: "Automatic imports browser cookies from Xiaomi MiMo.",
manual: "Paste a Cookie header from platform.xiaomimimo.com.",
off: "Xiaomi MiMo cookies are disabled.")
}
Expand All @@ -48,7 +48,7 @@ struct MiMoProviderImplementation: ProviderImplementation {
ProviderSettingsPickerDescriptor(
id: "mimo-cookie-source",
title: "Cookie source",
subtitle: "Automatic imports Chrome browser cookies from Xiaomi MiMo.",
subtitle: "Automatic imports browser cookies from Xiaomi MiMo.",
dynamicSubtitle: cookieSubtitle,
binding: cookieBinding,
options: cookieOptions,
Expand Down
2 changes: 1 addition & 1 deletion Sources/CodexBar/Resources/ca.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -765,7 +765,7 @@
"Antigravity login failed" = "L'inici de sessió d'Antigravity ha fallat";
"Antigravity login timed out" = "L'inici de sessió d'Antigravity ha esgotat el temps";
"Auth source" = "Font d'autenticació";
"Automatic imports Chrome browser cookies from Xiaomi MiMo." = "Importa automàticament les galetes de Chrome de Xiaomi MiMo.";
"Automatic imports browser cookies from Xiaomi MiMo." = "Importa automàticament les galetes del navegador de Xiaomi MiMo.";
"Automatic imports Windsurf session data from Chromium browser localStorage." = "Importa automàticament dades de sessió de Windsurf del localStorage de Chromium.";
"Automatic imports browser cookies from Bailian." = "Importa automàticament galetes del navegador de Bailian.";
"Automatically imports browser cookies." = "Importa automàticament galetes del navegador.";
Expand Down
2 changes: 1 addition & 1 deletion Sources/CodexBar/Resources/en.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -908,7 +908,7 @@
"Antigravity login failed" = "Antigravity login failed";
"Antigravity login timed out" = "Antigravity login timed out";
"Auth source" = "Auth source";
"Automatic imports Chrome browser cookies from Xiaomi MiMo." = "Automatic imports Chrome browser cookies from Xiaomi MiMo.";
"Automatic imports browser cookies from Xiaomi MiMo." = "Automatic imports browser cookies from Xiaomi MiMo.";
"Automatic imports Windsurf session data from Chromium browser localStorage." = "Automatic imports Windsurf session data from Chromium browser localStorage.";
"Automatic imports browser cookies from Bailian." = "Automatic imports browser cookies from Bailian.";
"Automatically imports browser cookies." = "Automatically imports browser cookies.";
Expand Down
2 changes: 1 addition & 1 deletion Sources/CodexBar/Resources/es.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -765,7 +765,7 @@
"Antigravity login failed" = "Error al iniciar sesión en Antigravity";
"Antigravity login timed out" = "El inicio de sesión en Antigravity agotó el tiempo";
"Auth source" = "Fuente de autenticación";
"Automatic imports Chrome browser cookies from Xiaomi MiMo." = "Importa automáticamente las cookies de Chrome desde Xiaomi MiMo.";
"Automatic imports browser cookies from Xiaomi MiMo." = "Importa automáticamente las cookies del navegador desde Xiaomi MiMo.";
"Automatic imports Windsurf session data from Chromium browser localStorage." = "Importa automáticamente datos de sesión de Windsurf desde localStorage de Chromium.";
"Automatic imports browser cookies from Bailian." = "Importa automáticamente cookies del navegador desde Bailian.";
"Automatically imports browser cookies." = "Importa automáticamente cookies del navegador.";
Expand Down
2 changes: 1 addition & 1 deletion Sources/CodexBar/Resources/fr.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -909,7 +909,7 @@
"Antigravity login failed" = "La connexion antigravité a échoué";
"Antigravity login timed out" = "La connexion antigravité a expiré";
"Auth source" = "Source d'authentification";
"Automatic imports Chrome browser cookies from Xiaomi MiMo." = "Importe automatiquement les cookies du navigateur Chrome de Xiaomi MiMo.";
"Automatic imports browser cookies from Xiaomi MiMo." = "Importe automatiquement les cookies du navigateur de Xiaomi MiMo.";
"Automatic imports Windsurf session data from Chromium browser localStorage." = "Importe automatiquement les données de session Windsurf à partir du navigateur Chromium localStorage.";
"Automatic imports browser cookies from Bailian." = "Importe automatiquement les cookies du navigateur depuis Bailian.";
"Automatically imports browser cookies." = "Importe automatiquement les cookies du navigateur.";
Expand Down
2 changes: 1 addition & 1 deletion Sources/CodexBar/Resources/nl.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -909,7 +909,7 @@
"Antigravity login failed" = "Antigravity-aanmelding mislukt";
"Antigravity login timed out" = "Er is een time-out opgetreden bij het inloggen op anti-zwaartekracht";
"Auth source" = "Authenticatiebron";
"Automatic imports Chrome browser cookies from Xiaomi MiMo." = "Importeert automatisch Chrome-browsercookies van Xiaomi MiMo.";
"Automatic imports browser cookies from Xiaomi MiMo." = "Importeert automatisch browsercookies van Xiaomi MiMo.";
"Automatic imports Windsurf session data from Chromium browser localStorage." = "Automatische import van windsurfsessiegegevens uit de Chromium-browser localStorage.";
"Automatic imports browser cookies from Bailian." = "Importeert automatisch browsercookies van Bailian.";
"Automatically imports browser cookies." = "Importeert automatisch browsercookies.";
Expand Down
2 changes: 1 addition & 1 deletion Sources/CodexBar/Resources/pt-BR.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -908,7 +908,7 @@
"Antigravity login failed" = "Falha no login do Antigravity";
"Antigravity login timed out" = "Tempo esgotado no login do Antigravity";
"Auth source" = "Fonte de autenticação";
"Automatic imports Chrome browser cookies from Xiaomi MiMo." = "Importa automaticamente cookies do Chrome do Xiaomi MiMo.";
"Automatic imports browser cookies from Xiaomi MiMo." = "Importa automaticamente cookies do navegador do Xiaomi MiMo.";
"Automatic imports Windsurf session data from Chromium browser localStorage." = "Importa automaticamente dados de sessão do Windsurf do localStorage do Chromium.";
"Automatic imports browser cookies from Bailian." = "Importa automaticamente cookies do navegador do Bailian.";
"Automatically imports browser cookies." = "Importa automaticamente cookies do navegador.";
Expand Down
2 changes: 1 addition & 1 deletion Sources/CodexBar/Resources/sv.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -1045,7 +1045,7 @@
"stale data" = "inaktuella data";
"Quota" = "Kvot";
"Auth source" = "Autentiseringskälla";
"Automatic imports Chrome browser cookies from Xiaomi MiMo." = "Importerar Chrome-cookies från Xiaomi MiMo automatiskt.";
"Automatic imports browser cookies from Xiaomi MiMo." = "Importerar webbläsarcookies från Xiaomi MiMo automatiskt.";
"No usage configured." = "Ingen användning konfigurerad.";
"Extra usage" = "Extra användning";
"That account is no longer available in CodexBar. Refresh the account list and try again." = "Kontot finns inte längre i CodexBar. Uppdatera kontolistan och försök igen.";
Expand Down
2 changes: 1 addition & 1 deletion Sources/CodexBar/Resources/uk.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -909,7 +909,7 @@
"Antigravity login failed" = "Помилка входу в Antigravity";
"Antigravity login timed out" = "Час очікування входу в антигравітацію минув";
"Auth source" = "Джерело авторизації";
"Automatic imports Chrome browser cookies from Xiaomi MiMo." = "Автоматично імпортує файли cookie браузера Chrome із Xiaomi MiMo.";
"Automatic imports browser cookies from Xiaomi MiMo." = "Автоматично імпортує файли cookie браузера з Xiaomi MiMo.";
"Automatic imports Windsurf session data from Chromium browser localStorage." = "Автоматично імпортує дані сесії Windsurf із локального сховища браузера Chromium.";
"Automatic imports browser cookies from Bailian." = "Автоматично імпортує файли cookie браузера з Bailian.";
"Automatically imports browser cookies." = "Автоматично імпортує файли cookie браузера.";
Expand Down
2 changes: 1 addition & 1 deletion Sources/CodexBar/Resources/vi.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -908,7 +908,7 @@
"Antigravity login failed" = "Đăng nhập chống trọng lực không thành công";
"Antigravity login timed out" = "Đã hết thời gian đăng nhập chống trọng lực";
"Auth source" = "Nguồn xác thực";
"Automatic imports Chrome browser cookies from Xiaomi MiMo." = "Tự động nhập cookie trình duyệt Chrome từ Xiaomi MiMo.";
"Automatic imports browser cookies from Xiaomi MiMo." = "Tự động nhập cookie trình duyệt từ Xiaomi MiMo.";
"Automatic imports Windsurf session data from Chromium browser localStorage." = "Tự động nhập dữ liệu phiên Windsurf từ localStorage của trình duyệt Chrome.";
"Automatic imports browser cookies from Bailian." = "Tự động nhập cookie trình duyệt từ Bailian.";
"Automatically imports browser cookies." = "Tự động nhập cookie trình duyệt.";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -882,7 +882,7 @@
"Antigravity login failed" = "Antigravity 登录失败";
"Antigravity login timed out" = "Antigravity 登录超时";
"Auth source" = "认证来源";
"Automatic imports Chrome browser cookies from Xiaomi MiMo." = "自动导入 Xiaomi MiMo 的 Chrome 浏览器 Cookie。";
"Automatic imports browser cookies from Xiaomi MiMo." = "自动导入 Xiaomi MiMo 的浏览器 Cookie。";
"Automatic imports Windsurf session data from Chromium browser localStorage." = "自动从 Chromium 浏览器 localStorage 导入 Windsurf 会话数据。";
"Automatic imports browser cookies from Bailian." = "自动导入 Bailian 的浏览器 Cookie。";
"Automatically imports browser cookies." = "自动导入浏览器 Cookie。";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -778,7 +778,7 @@
"Antigravity login failed" = "Antigravity 登入失敗";
"Antigravity login timed out" = "Antigravity 登入逾時";
"Auth source" = "認證來源";
"Automatic imports Chrome browser cookies from Xiaomi MiMo." = "自動匯入 Xiaomi MiMo 的 Chrome 瀏覽器 Cookie。";
"Automatic imports browser cookies from Xiaomi MiMo." = "自動匯入 Xiaomi MiMo 的瀏覽器 Cookie。";
"Automatic imports Windsurf session data from Chromium browser localStorage." = "自動從 Chromium 瀏覽器 localStorage 匯入 Windsurf 工作階段資料。";
"Automatic imports browser cookies from Bailian." = "自動匯入 Bailian 的瀏覽器 Cookie。";
"Automatically imports browser cookies." = "自動匯入瀏覽器 Cookie。";
Expand Down
33 changes: 29 additions & 4 deletions Sources/CodexBarCore/Providers/MiMo/MiMoCookieImporter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -129,26 +129,51 @@ public enum MiMoCookieImporter {
return try override(browserDetection, logger)
}

return try self.importSessions(
browserDetection: browserDetection,
logger: logger,
loadRecords: { browserSource, query, log in
try Self.cookieClient.records(
matching: query,
in: browserSource,
logger: log)
})
}

static func importSessions(
browserDetection: BrowserDetection,
logger: ((String) -> Void)? = nil,
loadRecords: (Browser, BrowserCookieQuery, ((String) -> Void)?) throws
-> [BrowserCookieStoreRecords]) throws -> [SessionInfo]
{
let log: (String) -> Void = { msg in logger?("[mimo-cookie] \(msg)") }
var sessions: [SessionInfo] = []
var accessDeniedHints: [String] = []
let installed = miMoCookieImportOrder.cookieImportCandidates(using: browserDetection)
let labels = installed.map(\.displayName).joined(separator: ", ")
log("Cookie import candidates: \(labels)")

for browserSource in installed {
do {
let query = BrowserCookieQuery(domains: self.cookieDomains)
let sources = try Self.cookieClient.records(
matching: query,
in: browserSource,
logger: log)
let sources = try loadRecords(browserSource, query, log)
sessions.append(contentsOf: self.sessionInfos(from: sources, origin: query.origin))
} catch let error as BrowserCookieError {
BrowserCookieAccessGate.recordIfNeeded(error)
if let hint = error.accessDeniedHint {
accessDeniedHints.append(hint)
}
log("\(browserSource.displayName) cookie import failed: \(error.localizedDescription)")
} catch {
BrowserCookieAccessGate.recordIfNeeded(error)
log("\(browserSource.displayName) cookie import failed: \(error.localizedDescription)")
}
}

if sessions.isEmpty, !accessDeniedHints.isEmpty {
let details = Array(Set(accessDeniedHints)).sorted().joined(separator: " ")
throw MiMoSettingsError.missingCookie(details: details)
}
return sessions
}

Expand Down
42 changes: 8 additions & 34 deletions Sources/CodexBarCore/Providers/MiMo/MiMoProviderDescriptor.swift
Original file line number Diff line number Diff line change
@@ -1,25 +1,11 @@
import CodexBarMacroSupport
import Foundation

#if os(macOS)
import SweetCookieKit
#endif

@ProviderDescriptorRegistration
@ProviderDescriptorDefinition
public enum MiMoProviderDescriptor {
static func makeDescriptor() -> ProviderDescriptor {
#if os(macOS)
let browserOrder: BrowserCookieImportOrder = [
.chrome,
.chromeBeta,
.chromeCanary,
]
#else
let browserOrder: BrowserCookieImportOrder? = nil
#endif

return ProviderDescriptor(
ProviderDescriptor(
id: .mimo,
metadata: ProviderMetadata(
id: .mimo,
Expand All @@ -35,7 +21,7 @@ public enum MiMoProviderDescriptor {
defaultEnabled: false,
isPrimaryProvider: false,
usesAccountFallback: false,
browserCookieOrder: browserOrder,
browserCookieOrder: ProviderBrowserCookieDefaults.mimoCookieImportOrder,
dashboardURL: "https://platform.xiaomimimo.com/#/console/balance",
statusPageURL: nil),
branding: ProviderBranding(
Expand Down Expand Up @@ -64,25 +50,13 @@ struct MiMoWebFetchStrategy: ProviderFetchStrategy {
if context.settings?.mimo?.cookieSource == .manual {
return Self.resolveManualCookieHeader(context: context) != nil
}
if Self.resolveManualCookieHeader(context: context) != nil {
return true
}

#if os(macOS)
if let cached = CookieHeaderCache.load(provider: .mimo),
MiMoCookieHeader.normalizedHeader(from: cached.cookieHeader) != nil
{
return true
}
return MiMoCookieImporter.hasSession(browserDetection: context.browserDetection)
#else
return false
#endif
// Fetch resolves the session so missing-cookie and browser-permission errors stay actionable.
return true
}

func fetch(_ context: ProviderFetchContext) async throws -> ProviderFetchResult {
guard context.settings?.mimo?.cookieSource != .off else {
throw MiMoSettingsError.missingCookie
throw MiMoSettingsError.missingCookie()
}
if context.settings?.mimo?.cookieSource == .manual {
guard let manualCookie = Self.resolveManualCookieHeader(context: context) else {
Expand Down Expand Up @@ -123,7 +97,7 @@ struct MiMoWebFetchStrategy: ProviderFetchStrategy {
let sessions = try MiMoCookieImporter.importSessions(browserDetection: context.browserDetection)
guard !sessions.isEmpty else {
if let lastError { throw lastError }
throw MiMoSettingsError.missingCookie
throw MiMoSettingsError.missingCookie()
}

for session in sessions {
Expand All @@ -146,9 +120,9 @@ struct MiMoWebFetchStrategy: ProviderFetchStrategy {
}

if let lastError { throw lastError }
throw MiMoSettingsError.missingCookie
throw MiMoSettingsError.missingCookie()
#else
throw MiMoSettingsError.missingCookie
throw MiMoSettingsError.missingCookie()
#endif
}

Expand Down
13 changes: 9 additions & 4 deletions Sources/CodexBarCore/Providers/MiMo/MiMoUsageFetcher.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,19 @@ import Foundation
import FoundationNetworking
#endif

public enum MiMoSettingsError: LocalizedError, Sendable {
case missingCookie
public enum MiMoSettingsError: LocalizedError, Sendable, Equatable {
case missingCookie(details: String? = nil)
case invalidCookie

public var errorDescription: String? {
switch self {
case .missingCookie:
"No Xiaomi MiMo browser session found. Log in at platform.xiaomimimo.com first."
case let .missingCookie(details):
[
"No Xiaomi MiMo browser session found. Log in at platform.xiaomimimo.com first.",
details,
]
.compactMap(\.self)
.joined(separator: " ")
case .invalidCookie:
"Xiaomi MiMo requires the api-platform_serviceToken and userId cookies."
}
Expand Down
11 changes: 11 additions & 0 deletions Sources/CodexBarCore/Providers/Providers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -220,4 +220,15 @@ public enum ProviderBrowserCookieDefaults {
nil
#endif
}

/// MiMo Auto: Safari first (no Keychain prompt), keep the existing Chrome-family
/// entries from main, and add Firefox/Edge per #1304. Other Chromium forks stay on
/// Manual import to avoid scanning the full SweetCookieKit default order.
public static var mimoCookieImportOrder: BrowserCookieImportOrder? {
#if os(macOS)
[.safari, .chrome, .chromeBeta, .chromeCanary, .firefox, .edge]
#else
nil
#endif
}
}
12 changes: 12 additions & 0 deletions Tests/CodexBarTests/BrowserCookieOrderLabelTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,17 @@ struct BrowserCookieOrderStatusStringTests {
func `opencode automatic cookies keep chrome only default`() {
#expect(OpenCodeWebCookieSupport.automaticImportOrder(provider: .opencode) == [.chrome])
}

@Test
func `mimo cookie import order supports safari firefox and edge`() {
let order = ProviderDefaults.metadata[.mimo]?.browserCookieOrder ?? Browser.defaultImportOrder
#expect(order == ProviderBrowserCookieDefaults.mimoCookieImportOrder)
#expect(order == [.safari, .chrome, .chromeBeta, .chromeCanary, .firefox, .edge])
#expect(order.first == .safari)
#expect(order.contains(.firefox))
#expect(order.contains(.edge))
#expect(!order.contains(.arc))
}

#endif
}
23 changes: 23 additions & 0 deletions Tests/CodexBarTests/MiMoProviderTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,29 @@ struct MiMoProviderTests {
}
}

@Test
func `mimo cookie importer surfaces safari access denial`() throws {
let detection = BrowserDetection(
homeDirectory: "/tmp/codexbar-mimo-browser-test",
cacheTTL: 0,
fileExists: { _ in false },
directoryContents: { _ in nil })

do {
_ = try MiMoCookieImporter.importSessions(
browserDetection: detection,
loadRecords: { browser, _, _ in
throw BrowserCookieError.accessDenied(
browser: browser,
details: "Grant CodexBar Full Disk Access to read Safari cookies.")
})
Issue.record("Expected Safari access denial")
} catch let error as MiMoSettingsError {
#expect(error.localizedDescription.contains("Full Disk Access"))
#expect(error.localizedDescription.contains("Safari"))
}
}

@Test
func `mimo web strategy retries imported sessions after decode failure`() async throws {
KeychainCacheStore.setTestStoreForTesting(true)
Expand Down
Loading