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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ Linux support via Omarchy: community Waybar module and TUI, driven by the `codex
- [Droid](docs/factory.md) — Browser cookies + WorkOS token flows for Factory usage + billing.
- [Copilot](docs/copilot.md) — GitHub device flow + Copilot internal usage API.
- [z.ai](docs/zai.md) — API token (Keychain) for quota + MCP windows.
- [Kimi](docs/kimi.md) — Auth token (JWT from `kimi-auth` cookie) for weekly quota + 5‑hour rate limit.
- [Kimi](docs/kimi.md) — Kimi Code OAuth or API-key usage for weekly quota + rolling rate limits.
- [Kimi K2](docs/kimi-k2.md) — API key for credit-based usage totals.
- [Kiro](docs/kiro.md) — CLI-based usage via `kiro-cli /usage` command; monthly credits + bonus credits.
- [Vertex AI](docs/vertexai.md) — Google Cloud gcloud OAuth with token cost tracking from local Claude logs.
Expand Down
18 changes: 0 additions & 18 deletions Sources/CodexBar/Config/CodexBarConfigMigrator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,6 @@ struct CodexBarConfigMigrator {
state: &state)

self.migrateMiniMax(userDefaults: userDefaults, stores: stores, config: &config, state: &state)
self.migrateKimi(userDefaults: userDefaults, stores: stores, config: &config, state: &state)
self.migrateOpenCode(userDefaults: userDefaults, stores: stores, config: &config, state: &state)
}

Expand All @@ -120,7 +119,6 @@ struct CodexBarConfigMigrator {
(.opencode, "opencodeCookieSource"),
(.factory, "factoryCookieSource"),
(.minimax, "minimaxCookieSource"),
(.kimi, "kimiCookieSource"),
(.augment, "augmentCookieSource"),
(.amp, "ampCookieSource"),
]
Expand Down Expand Up @@ -197,22 +195,6 @@ struct CodexBarConfigMigrator {
}
}

private static func migrateKimi(
userDefaults: UserDefaults,
stores: LegacyStores,
config: inout CodexBarConfig,
state: inout MigrationState)
{
var token = try? stores.kimiTokenStore.loadToken()
if token?.isEmpty ?? true {
token = userDefaults.string(forKey: "kimiManualCookieHeader")
}
if token != nil { state.sawLegacySecrets = true }
self.updateProvider(.kimi, config: &config, state: &state) { entry in
self.setIfEmpty(&entry.cookieHeader, token)
}
}

private static func migrateOpenCode(
userDefaults: UserDefaults,
stores: LegacyStores,
Expand Down
81 changes: 42 additions & 39 deletions Sources/CodexBar/Providers/Kimi/KimiProviderImplementation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,79 +8,82 @@ import SwiftUI
struct KimiProviderImplementation: ProviderImplementation {
let id: UsageProvider = .kimi

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

@MainActor
func observeSettings(_ settings: SettingsStore) {
_ = settings.kimiCookieSource
_ = settings.kimiManualCookieHeader
_ = settings.kimiUsageDataSource
_ = settings.kimiAPIToken
}

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

@MainActor
func defaultSourceLabel(context: ProviderSourceLabelContext) -> String? {
context.settings.kimiUsageDataSource.rawValue
}

@MainActor
func sourceMode(context: ProviderSourceModeContext) -> ProviderSourceMode {
switch context.settings.kimiUsageDataSource {
case .auto: .auto
case .oauth: .oauth
case .api: .api
}
}

@MainActor
func settingsPickers(context: ProviderSettingsContext) -> [ProviderSettingsPickerDescriptor] {
let cookieBinding = Binding(
get: { context.settings.kimiCookieSource.rawValue },
let usageBinding = Binding(
get: { context.settings.kimiUsageDataSource.rawValue },
set: { raw in
context.settings.kimiCookieSource = ProviderCookieSource(rawValue: raw) ?? .auto
context.settings.kimiUsageDataSource = KimiUsageDataSource(rawValue: raw) ?? .auto
})
let options = ProviderCookieSourceUI.options(
allowsOff: true,
keychainDisabled: context.settings.debugDisableKeychainAccess)

let subtitle: () -> String? = {
ProviderCookieSourceUI.subtitle(
source: context.settings.kimiCookieSource,
keychainDisabled: context.settings.debugDisableKeychainAccess,
auto: "Automatic imports browser cookies.",
manual: "Paste a cookie header or the kimi-auth token value.",
off: "Kimi cookies are disabled.")
}

return [
ProviderSettingsPickerDescriptor(
id: "kimi-cookie-source",
title: "Cookie source",
subtitle: "Automatic imports browser cookies.",
dynamicSubtitle: subtitle,
binding: cookieBinding,
options: options,
id: "kimi-usage-source",
title: "Usage source",
subtitle: "Auto prefers the official Kimi CLI OAuth session, then falls back to KIMI_API_KEY.",
binding: usageBinding,
options: KimiUsageDataSource.allCases.map {
ProviderSettingsPickerOption(id: $0.rawValue, title: $0.displayName)
},
isVisible: nil,
onChange: nil),
onChange: nil,
trailingText: {
guard context.settings.kimiUsageDataSource == .auto else { return nil }
let label = context.store.sourceLabel(for: .kimi)
return label == "auto" ? nil : label
}),
]
}

@MainActor
func settingsFields(context: ProviderSettingsContext) -> [ProviderSettingsFieldDescriptor] {
[
ProviderSettingsFieldDescriptor(
id: "kimi-cookie",
title: "",
subtitle: "",
id: "kimi-api-key",
title: "API key",
subtitle: "Optional. Auto mode uses ~/.kimi/credentials/kimi-code.json first, then this key.",
kind: .secure,
placeholder: "Cookie: \u{2026}\n\nor paste the kimi-auth token value",
binding: context.stringBinding(\.kimiManualCookieHeader),
placeholder: "sk-...",
binding: context.stringBinding(\.kimiAPIToken),
actions: [
ProviderSettingsActionDescriptor(
id: "kimi-open-console",
title: "Open Console",
id: "kimi-open-docs",
title: "Open Kimi Code Docs",
style: .link,
isVisible: nil,
perform: {
if let url = URL(string: "https://www.kimi.com/code/console") {
if let url = URL(string: "https://www.kimi.com/code/docs/en/") {
NSWorkspace.shared.open(url)
}
}),
],
isVisible: { context.settings.kimiCookieSource == .manual },
onActivate: { context.settings.ensureKimiAuthTokenLoaded() }),
isVisible: nil,
onActivate: { context.settings.ensureKimiAPIKeyLoaded() }),
]
}
}
46 changes: 31 additions & 15 deletions Sources/CodexBar/Providers/Kimi/KimiSettingsStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,51 @@ import CodexBarCore
import Foundation

extension SettingsStore {
var kimiManualCookieHeader: String {
get { self.configSnapshot.providerConfig(for: .kimi)?.sanitizedCookieHeader ?? "" }
var kimiUsageDataSource: KimiUsageDataSource {
get {
let source = self.configSnapshot.providerConfig(for: .kimi)?.source
return Self.kimiUsageDataSource(from: source)
}
set {
let source: ProviderSourceMode? = switch newValue {
case .auto: .auto
case .oauth: .oauth
case .api: .api
}
self.updateProviderConfig(provider: .kimi) { entry in
entry.cookieHeader = self.normalizedConfigValue(newValue)
entry.source = source
}
self.logSecretUpdate(provider: .kimi, field: "cookieHeader", value: newValue)
self.logProviderModeChange(provider: .kimi, field: "usageSource", value: newValue.rawValue)
}
}

var kimiCookieSource: ProviderCookieSource {
get { self.resolvedCookieSource(provider: .kimi, fallback: .auto) }
var kimiAPIToken: String {
get { self.configSnapshot.providerConfig(for: .kimi)?.sanitizedAPIKey ?? "" }
set {
self.updateProviderConfig(provider: .kimi) { entry in
entry.cookieSource = newValue
entry.apiKey = self.normalizedConfigValue(newValue)
}
self.logProviderModeChange(provider: .kimi, field: "cookieSource", value: newValue.rawValue)
self.logSecretUpdate(provider: .kimi, field: "apiKey", value: newValue)
}
}

func ensureKimiAuthTokenLoaded() {}
func ensureKimiAPIKeyLoaded() {}
}

extension SettingsStore {
func kimiSettingsSnapshot(tokenOverride: TokenAccountOverride?) -> ProviderSettingsSnapshot.KimiProviderSettings {
_ = tokenOverride
self.ensureKimiAuthTokenLoaded()
return ProviderSettingsSnapshot.KimiProviderSettings(
cookieSource: self.kimiCookieSource,
manualCookieHeader: self.kimiManualCookieHeader)
func kimiSettingsSnapshot(tokenOverride _: TokenAccountOverride?) -> ProviderSettingsSnapshot.KimiProviderSettings {
ProviderSettingsSnapshot.KimiProviderSettings(usageDataSource: self.kimiUsageDataSource)
}

private static func kimiUsageDataSource(from source: ProviderSourceMode?) -> KimiUsageDataSource {
guard let source else { return .auto }
switch source {
case .auto, .web, .cli:
return .auto
case .oauth:
return .oauth
case .api:
return .api
}
}
}
4 changes: 2 additions & 2 deletions Sources/CodexBar/SettingsStore+MenuObservation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ extension SettingsStore {
_ = self.factoryCookieSource
_ = self.minimaxCookieSource
_ = self.minimaxAPIRegion
_ = self.kimiCookieSource
_ = self.kimiUsageDataSource
_ = self.augmentCookieSource
_ = self.ampCookieSource
_ = self.ollamaCookieSource
Expand All @@ -61,7 +61,7 @@ extension SettingsStore {
_ = self.factoryCookieHeader
_ = self.minimaxCookieHeader
_ = self.minimaxAPIToken
_ = self.kimiManualCookieHeader
_ = self.kimiAPIToken
_ = self.kimiK2APIToken
_ = self.kiloAPIToken
_ = self.augmentCookieHeader
Expand Down
2 changes: 1 addition & 1 deletion Sources/CodexBar/UsageStore+Logging.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ extension UsageStore {
"opencodegoCookieSource": self.settings.opencodegoCookieSource.rawValue,
"factoryCookieSource": self.settings.factoryCookieSource.rawValue,
"minimaxCookieSource": self.settings.minimaxCookieSource.rawValue,
"kimiCookieSource": self.settings.kimiCookieSource.rawValue,
"kimiUsageSource": self.settings.kimiUsageDataSource.rawValue,
"augmentCookieSource": self.settings.augmentCookieSource.rawValue,
"ampCookieSource": self.settings.ampCookieSource.rawValue,
"ollamaCookieSource": self.settings.ollamaCookieSource.rawValue,
Expand Down
19 changes: 15 additions & 4 deletions Sources/CodexBarCLI/TokenAccountCLI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -161,12 +161,9 @@ struct TokenAccountCLIContext {
cookieSource: cookieSource,
manualCookieHeader: cookieHeader))
case .kimi:
let cookieHeader = self.manualCookieHeader(provider: provider, account: account, config: config)
let cookieSource = self.cookieSource(provider: provider, account: account, config: config)
return self.makeSnapshot(
kimi: ProviderSettingsSnapshot.KimiProviderSettings(
cookieSource: cookieSource,
manualCookieHeader: cookieHeader))
usageDataSource: Self.kimiUsageDataSource(from: config?.source)))
case .zai:
return self.makeSnapshot(
zai: ProviderSettingsSnapshot.ZaiProviderSettings(apiRegion: self.resolveZaiRegion(config)))
Expand Down Expand Up @@ -406,3 +403,17 @@ struct TokenAccountCLIContext {
manualCookieHeader: manualCookieHeader)
}
}

private extension TokenAccountCLIContext {
static func kimiUsageDataSource(from source: ProviderSourceMode?) -> KimiUsageDataSource {
guard let source else { return .auto }
switch source {
case .auto, .web, .cli:
return .auto
case .oauth:
return .oauth
case .api:
return .api
}
}
}
6 changes: 3 additions & 3 deletions Sources/CodexBarCore/Providers/Kimi/KimiAPIError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ public enum KimiAPIError: LocalizedError, Sendable, Equatable {
public var errorDescription: String? {
switch self {
case .missingToken:
"Kimi auth token is missing. Please add your JWT token from the Kimi console."
"Kimi Code credentials are missing. Sign into the Kimi CLI or provide KIMI_API_KEY."
case .invalidToken:
"Kimi auth token is invalid or expired. Please refresh your token."
"Kimi Code credentials are invalid or expired."
case let .invalidRequest(message):
"Invalid request: \(message)"
"Kimi request failed: \(message)"
case let .networkError(message):
"Kimi network error: \(message)"
case let .apiError(message):
Expand Down
Loading