Skip to content
Open
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
7 changes: 7 additions & 0 deletions Sources/CodexBar/MenuCardView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -785,6 +785,13 @@ extension UsageMenuCardView.Model {
return notes
}

if input.provider == .mimo, input.snapshot != nil {
return [
"Balance updates in near-real time (up to 5 min lag)",
"Daily billing data finalizes at 07:00 UTC",
]
}

guard input.provider == .openrouter,
let openRouter = input.snapshot?.openRouterUsage
else {
Expand Down
13 changes: 12 additions & 1 deletion Sources/CodexBar/MenuDescriptor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,18 @@ struct MenuDescriptor {
entries.append(.text("Activity: \(detail)", .secondary))
}
} else if let loginMethodText, !loginMethodText.isEmpty {
entries.append(.text("Plan: \(AccountFormatter.plan(loginMethodText))", .secondary))
if provider == .openrouter || provider == .mimo {
let balanceValue = loginMethodText
.replacingOccurrences(
of: #"(?i)^\s*balance:\s*"#,
with: "",
options: [.regularExpression])
.trimmingCharacters(in: .whitespacesAndNewlines)
let value = balanceValue.isEmpty ? loginMethodText : balanceValue
entries.append(.text("Balance: \(AccountFormatter.plan(value))", .secondary))
} else {
entries.append(.text("Plan: \(AccountFormatter.plan(loginMethodText))", .secondary))
}
}

if metadata.usesAccountFallback {
Expand Down
2 changes: 1 addition & 1 deletion Sources/CodexBar/PreferencesProviderDetailView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ struct ProviderDetailView: View {
else {
return nil
}
guard provider == .openrouter else {
guard provider == .openrouter || provider == .mimo else {
return (label: "Plan", value: rawPlan)
}

Expand Down
102 changes: 102 additions & 0 deletions Sources/CodexBar/Providers/MiMo/MiMoProviderImplementation.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import AppKit
import CodexBarCore
import CodexBarMacroSupport
import Foundation
import SwiftUI

@ProviderImplementationRegistration
struct MiMoProviderImplementation: ProviderImplementation {
let id: UsageProvider = .mimo
let supportsLoginFlow: Bool = true

@MainActor
func presentation(context _: ProviderPresentationContext) -> ProviderPresentation {
ProviderPresentation { _ in "web" }
}

@MainActor
func observeSettings(_ settings: SettingsStore) {
_ = settings.miMoCookieSource
_ = settings.miMoCookieHeader
}

@MainActor
func settingsSnapshot(context: ProviderSettingsSnapshotContext) -> ProviderSettingsSnapshotContribution? {
.mimo(context.settings.miMoSettingsSnapshot(tokenOverride: context.tokenOverride))
}

@MainActor
func settingsPickers(context: ProviderSettingsContext) -> [ProviderSettingsPickerDescriptor] {
let cookieBinding = Binding(
get: { context.settings.miMoCookieSource.rawValue },
set: { raw in
context.settings.miMoCookieSource = ProviderCookieSource(rawValue: raw) ?? .auto
})
let cookieOptions = ProviderCookieSourceUI.options(
allowsOff: false,
keychainDisabled: context.settings.debugDisableKeychainAccess)
let cookieSubtitle: () -> String? = {
ProviderCookieSourceUI.subtitle(
source: context.settings.miMoCookieSource,
keychainDisabled: context.settings.debugDisableKeychainAccess,
auto: "Automatic imports Chrome browser cookies from Xiaomi MiMo.",
manual: "Paste a Cookie header from platform.xiaomimimo.com.",
off: "Xiaomi MiMo cookies are disabled.")
}

return [
ProviderSettingsPickerDescriptor(
id: "mimo-cookie-source",
title: "Cookie source",
subtitle: "Automatic imports Chrome browser cookies from Xiaomi MiMo.",
dynamicSubtitle: cookieSubtitle,
binding: cookieBinding,
options: cookieOptions,
isVisible: nil,
onChange: nil,
trailingText: {
guard let entry = CookieHeaderCache.load(provider: .mimo) else { return nil }
let when = entry.storedAt.relativeDescription()
return "Cached: \(entry.sourceLabel) • \(when)"
}),
]
}

@MainActor
func settingsFields(context: ProviderSettingsContext) -> [ProviderSettingsFieldDescriptor] {
[
ProviderSettingsFieldDescriptor(
id: "mimo-cookie",
title: "",
subtitle: "",
kind: .secure,
placeholder: "Cookie: ...",
binding: context.stringBinding(\.miMoCookieHeader),
actions: [
ProviderSettingsActionDescriptor(
id: "mimo-open-balance",
title: "Open MiMo Balance",
style: .link,
isVisible: nil,
perform: {
guard let url = URL(string: "https://platform.xiaomimimo.com/#/console/balance") else {
return
}
NSWorkspace.shared.open(url)
}),
],
isVisible: { context.settings.miMoCookieSource == .manual },
onActivate: { context.settings.ensureMiMoCookieLoaded() }),
]
}

@MainActor
func runLoginFlow(context _: ProviderLoginContext) async -> Bool {
let loginURL = "https://platform.xiaomimimo.com/api/v1/genLoginUrl?currentPath=%2F%23%2Fconsole%2Fbalance"
guard let url = URL(string: loginURL) else {
return false
}
NSWorkspace.shared.open(url)
return false
}
}
35 changes: 35 additions & 0 deletions Sources/CodexBar/Providers/MiMo/MiMoSettingsStore.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import CodexBarCore
import Foundation

extension SettingsStore {
var miMoCookieHeader: String {
get { self.configSnapshot.providerConfig(for: .mimo)?.sanitizedCookieHeader ?? "" }
set {
self.updateProviderConfig(provider: .mimo) { entry in
entry.cookieHeader = self.normalizedConfigValue(newValue)
}
self.logSecretUpdate(provider: .mimo, field: "cookieHeader", value: newValue)
}
}

var miMoCookieSource: ProviderCookieSource {
get { self.resolvedCookieSource(provider: .mimo, fallback: .auto) }
set {
self.updateProviderConfig(provider: .mimo) { entry in
entry.cookieSource = newValue
}
self.logProviderModeChange(provider: .mimo, field: "cookieSource", value: newValue.rawValue)
}
}

func ensureMiMoCookieLoaded() {}
}

extension SettingsStore {
func miMoSettingsSnapshot(tokenOverride: TokenAccountOverride?) -> ProviderSettingsSnapshot.MiMoProviderSettings {
_ = tokenOverride
return ProviderSettingsSnapshot.MiMoProviderSettings(
cookieSource: self.miMoCookieSource,
manualCookieHeader: self.miMoCookieHeader)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ enum ProviderImplementationRegistry {
case .synthetic: SyntheticProviderImplementation()
case .openrouter: OpenRouterProviderImplementation()
case .warp: WarpProviderImplementation()
case .mimo: MiMoProviderImplementation()
}
}

Expand Down
4 changes: 4 additions & 0 deletions Sources/CodexBar/Resources/ProviderIcon-mimo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 2 additions & 1 deletion Sources/CodexBar/UsageStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1158,6 +1158,7 @@ extension UsageStore {
.kimi: "Kimi debug log not yet implemented",
.kimik2: "Kimi K2 debug log not yet implemented",
.jetbrains: "JetBrains AI debug log not yet implemented",
.mimo: "Xiaomi MiMo debug log not yet implemented",
]
let buildText = {
switch provider {
Expand Down Expand Up @@ -1231,7 +1232,7 @@ extension UsageStore {
let source = resolution?.source.rawValue ?? "none"
return "WARP_API_KEY=\(hasAny ? "present" : "missing") source=\(source)"
case .gemini, .antigravity, .opencode, .factory, .copilot, .vertexai, .kilo, .kiro, .kimi,
.kimik2, .jetbrains:
.kimik2, .jetbrains, .mimo:
return unimplementedDebugLogMessages[provider] ?? "Debug log not yet implemented"
}
}
Expand Down
13 changes: 11 additions & 2 deletions Sources/CodexBarCLI/TokenAccountCLI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,13 @@ struct TokenAccountCLIContext {
kimi: ProviderSettingsSnapshot.KimiProviderSettings(
cookieSource: cookieSource,
manualCookieHeader: cookieHeader))
case .mimo:
let cookieHeader = self.manualCookieHeader(provider: provider, account: account, config: config)
let cookieSource = self.cookieSource(provider: provider, account: account, config: config)
return self.makeSnapshot(
mimo: ProviderSettingsSnapshot.MiMoProviderSettings(
cookieSource: cookieSource,
manualCookieHeader: cookieHeader))
case .zai:
return self.makeSnapshot(
zai: ProviderSettingsSnapshot.ZaiProviderSettings(apiRegion: self.resolveZaiRegion(config)))
Expand Down Expand Up @@ -196,7 +203,8 @@ struct TokenAccountCLIContext {
augment: ProviderSettingsSnapshot.AugmentProviderSettings? = nil,
amp: ProviderSettingsSnapshot.AmpProviderSettings? = nil,
ollama: ProviderSettingsSnapshot.OllamaProviderSettings? = nil,
jetbrains: ProviderSettingsSnapshot.JetBrainsProviderSettings? = nil) -> ProviderSettingsSnapshot
jetbrains: ProviderSettingsSnapshot.JetBrainsProviderSettings? = nil,
mimo: ProviderSettingsSnapshot.MiMoProviderSettings? = nil) -> ProviderSettingsSnapshot
{
ProviderSettingsSnapshot.make(
codex: codex,
Expand All @@ -212,7 +220,8 @@ struct TokenAccountCLIContext {
augment: augment,
amp: amp,
ollama: ollama,
jetbrains: jetbrains)
jetbrains: jetbrains,
mimo: mimo)
}

func environment(
Expand Down
Loading