import CodexBarCoreimport Commanderimport Foundation struct TokenAccountCLISelection: Sendable { let label: String? let index: Int? let allAccounts: Bool var usesOverride: Bool { self.label != nil || self.index != nil || self.allAccounts }} enum TokenAccountCLIError: LocalizedError { case noAccounts(UsageProvider) case accountNotFound(UsageProvider, String) case indexOutOfRange(UsageProvider, Int, Int) var errorDescription: String? { switch self { case let .noAccounts(provider): "No token accounts configured for \(provider.rawValue)." case let .accountNotFound(provider, label): "No token account labeled '\(label)' for \(provider.rawValue)." case let .indexOutOfRange(provider, index, count): "Token account index \(index) out of range for \(provider.rawValue) (1-\(count))." } }} struct TokenAccountCLIContext { let selection: TokenAccountCLISelection let config: CodexBarConfig let accountsByProvider: [UsageProvider: ProviderTokenAccountData] init(selection: TokenAccountCLISelection, config: CodexBarConfig, verbose _: Bool) throws { self.selection = selection self.config = config self.accountsByProvider = Dictionary(uniqueKeysWithValues: config.providers.compactMap { provider in guard let accounts = provider.tokenAccounts else { return nil } return (provider.id, accounts) }) } func resolvedAccounts(for provider: UsageProvider) throws -> [ProviderTokenAccount] { guard TokenAccountSupportCatalog.support(for: provider) != nil else { return [] } guard let data = self.accountsByProvider[provider], !data.accounts.isEmpty else { if self.selection.usesOverride { throw TokenAccountCLIError.noAccounts(provider) } return [] } if self.selection.allAccounts { return data.accounts } if let label = self.selection.label?.trimmingCharacters(in: .whitespacesAndNewlines), !label.isEmpty { let normalized = label.lowercased() if let match = data.accounts.first(where: { $0.label.lowercased() == normalized }) { return [match] } throw TokenAccountCLIError.accountNotFound(provider, label) } if let index = self.selection.index { guard index >= 0, index < data.accounts.count else { throw TokenAccountCLIError.indexOutOfRange(provider, index + 1, data.accounts.count) } return [data.accounts[index]] } let clamped = data.clampedActiveIndex() return [data.accounts[clamped]] } func settingsSnapshot(for provider: UsageProvider, account: ProviderTokenAccount?) -> ProviderSettingsSnapshot? { let config = self.providerConfig(for: provider) let cookieHeader = self.manualCookieHeader(provider: provider, account: account, config: config) let cookieSource = self.cookieSource(provider: provider, account: account, config: config) switch provider { case .codex: return self.makeSnapshot( codex: ProviderSettingsSnapshot.CodexProviderSettings( usageDataSource: .auto, cookieSource: cookieSource, manualCookieHeader: cookieHeader)) case .claude: let claudeSource: ClaudeUsageDataSource = if provider == .claude, let account, TokenAccountSupportCatalog.isClaudeOAuthToken(account.token) { .oauth } else { .auto } let effectiveSource = (claudeSource == .oauth) ? ProviderCookieSource.off : cookieSource return self.makeSnapshot( claude: ProviderSettingsSnapshot.ClaudeProviderSettings( usageDataSource: claudeSource, webExtrasEnabled: false, cookieSource: effectiveSource, manualCookieHeader: claudeSource == .oauth ? nil : cookieHeader)) case .cursor: return self.makeSnapshot( cursor: ProviderSettingsSnapshot.CursorProviderSettings( cookieSource: cookieSource, manualCookieHeader: cookieHeader)) case .opencode: return self.makeSnapshot( opencode: ProviderSettingsSnapshot.OpenCodeProviderSettings( cookieSource: cookieSource, manualCookieHeader: cookieHeader, workspaceID: config?.workspaceID)) case .factory: return self.makeSnapshot( factory: ProviderSettingsSnapshot.FactoryProviderSettings( cookieSource: cookieSource, manualCookieHeader: cookieHeader)) case .minimax: return self.makeSnapshot( minimax: ProviderSettingsSnapshot.MiniMaxProviderSettings( cookieSource: cookieSource, manualCookieHeader: cookieHeader, apiRegion: self.resolveMiniMaxRegion(config))) case .augment: return self.makeSnapshot( augment: ProviderSettingsSnapshot.AugmentProviderSettings( cookieSource: cookieSource, manualCookieHeader: cookieHeader)) case .amp: return self.makeSnapshot( amp: ProviderSettingsSnapshot.AmpProviderSettings( cookieSource: cookieSource, manualCookieHeader: cookieHeader)) case .kimi: return self.makeSnapshot( kimi: ProviderSettingsSnapshot.KimiProviderSettings( cookieSource: cookieSource, manualCookieHeader: cookieHeader)) case .zai: return self.makeSnapshot( zai: ProviderSettingsSnapshot.ZaiProviderSettings(apiRegion: self.resolveZaiRegion(config))) case .jetbrains: return self.makeSnapshot( jetbrains: ProviderSettingsSnapshot.JetBrainsProviderSettings( ideBasePath: nil)) case .gemini, .antigravity, .copilot, .kiro, .vertexai, .kimik2, .synthetic: return nil } } private func makeSnapshot( codex: ProviderSettingsSnapshot.CodexProviderSettings? = nil, claude: ProviderSettingsSnapshot.ClaudeProviderSettings? = nil, cursor: ProviderSettingsSnapshot.CursorProviderSettings? = nil, opencode: ProviderSettingsSnapshot.OpenCodeProviderSettings? = nil, factory: ProviderSettingsSnapshot.FactoryProviderSettings? = nil, minimax: ProviderSettingsSnapshot.MiniMaxProviderSettings? = nil, zai: ProviderSettingsSnapshot.ZaiProviderSettings? = nil, kimi: ProviderSettingsSnapshot.KimiProviderSettings? = nil, augment: ProviderSettingsSnapshot.AugmentProviderSettings? = nil, amp: ProviderSettingsSnapshot.AmpProviderSettings? = nil, jetbrains: ProviderSettingsSnapshot.JetBrainsProviderSettings? = nil) -> ProviderSettingsSnapshot { ProviderSettingsSnapshot.make( codex: codex, claude: claude, cursor: cursor, opencode: opencode, factory: factory, minimax: minimax, zai: zai, kimi: kimi, augment: augment, amp: amp, jetbrains: jetbrains) } func environment( base: [String: String], provider: UsageProvider, account: ProviderTokenAccount?) -> [String: String] { var env = base if let account, let override = TokenAccountSupportCatalog.envOverride(for: provider, token: account.token) { for (key, value) in override { env[key] = value } } let providerConfig = self.providerConfig(for: provider) env = ProviderConfigEnvironment.applyAPIKeyOverride( base: env, provider: provider, config: providerConfig) return env } func applyAccountLabel( _ snapshot: UsageSnapshot, provider: UsageProvider, account: ProviderTokenAccount) -> UsageSnapshot { let label = account.label.trimmingCharacters(in: .whitespacesAndNewlines) guard !label.isEmpty else { return snapshot } let existing = snapshot.identity(for: provider) let email = existing?.accountEmail?.trimmingCharacters(in: .whitespacesAndNewlines) let resolvedEmail = (email?.isEmpty ?? true) ? label : email let identity = ProviderIdentitySnapshot( providerID: provider, accountEmail: resolvedEmail, accountOrganization: existing?.accountOrganization, loginMethod: existing?.loginMethod) return UsageSnapshot( primary: snapshot.primary, secondary: snapshot.secondary, tertiary: snapshot.tertiary, providerCost: snapshot.providerCost, zaiUsage: snapshot.zaiUsage, cursorRequests: snapshot.cursorRequests, updatedAt: snapshot.updatedAt, identity: identity) } func effectiveSourceMode( base: ProviderSourceMode, provider: UsageProvider, account: ProviderTokenAccount?) -> ProviderSourceMode { guard base == .auto, provider == .claude, let account, TokenAccountSupportCatalog.isClaudeOAuthToken(account.token) else { return base } return .oauth } func preferredSourceMode(for provider: UsageProvider) -> ProviderSourceMode { let config = self.providerConfig(for: provider) return config?.source ?? .auto } private func providerConfig(for provider: UsageProvider) -> ProviderConfig? { self.config.providerConfig(for: provider) } private func manualCookieHeader( provider: UsageProvider, account: ProviderTokenAccount?, config: ProviderConfig?) -> String? { if let account, let support = TokenAccountSupportCatalog.support(for: provider), case .cookieHeader = support.injection { if provider == .claude, TokenAccountSupportCatalog.isClaudeOAuthToken(account.token) { return nil } let header = TokenAccountSupportCatalog.normalizedCookieHeader(account.token, support: support) return header.isEmpty ? nil : header } return config?.sanitizedCookieHeader } private func cookieSource( provider: UsageProvider, account: ProviderTokenAccount?, config: ProviderConfig?) -> ProviderCookieSource { if let override = config?.cookieSource { return override } if let account, TokenAccountSupportCatalog.support(for: provider)?.requiresManualCookieSource == true { if provider == .claude, TokenAccountSupportCatalog.isClaudeOAuthToken(account.token) { return .off } return .manual } if config?.sanitizedCookieHeader != nil { return .manual } return .auto } private func resolveZaiRegion(_ config: ProviderConfig?) -> ZaiAPIRegion { guard let raw = config?.region?.trimmingCharacters(in: .whitespacesAndNewlines), !raw.isEmpty else { return .global } return ZaiAPIRegion(rawValue: raw) ?? .global } private func resolveMiniMaxRegion(_ config: ProviderConfig?) -> MiniMaxAPIRegion { guard let raw = config?.region?.trimmingCharacters(in: .whitespacesAndNewlines), !raw.isEmpty else { return .global } return MiniMaxAPIRegion(rawValue: raw) ?? .global }}
import CodexBarCore
import Commander
import Foundation
struct TokenAccountCLISelection: Sendable {
let label: String?
let index: Int?
let allAccounts: Bool
var usesOverride: Bool {
self.label != nil || self.index != nil || self.allAccounts
}
}
enum TokenAccountCLIError: LocalizedError {
case noAccounts(UsageProvider)
case accountNotFound(UsageProvider, String)
case indexOutOfRange(UsageProvider, Int, Int)
var errorDescription: String? {
switch self {
case let .noAccounts(provider):
"No token accounts configured for \(provider.rawValue)."
case let .accountNotFound(provider, label):
"No token account labeled '\(label)' for \(provider.rawValue)."
case let .indexOutOfRange(provider, index, count):
"Token account index \(index) out of range for \(provider.rawValue) (1-\(count))."
}
}
}
struct TokenAccountCLIContext {
let selection: TokenAccountCLISelection
let config: CodexBarConfig
let accountsByProvider: [UsageProvider: ProviderTokenAccountData]
init(selection: TokenAccountCLISelection, config: CodexBarConfig, verbose _: Bool) throws {
self.selection = selection
self.config = config
self.accountsByProvider = Dictionary(uniqueKeysWithValues: config.providers.compactMap { provider in
guard let accounts = provider.tokenAccounts else { return nil }
return (provider.id, accounts)
})
}
func resolvedAccounts(for provider: UsageProvider) throws -> [ProviderTokenAccount] {
guard TokenAccountSupportCatalog.support(for: provider) != nil else { return [] }
guard let data = self.accountsByProvider[provider], !data.accounts.isEmpty else {
if self.selection.usesOverride {
throw TokenAccountCLIError.noAccounts(provider)
}
return []
}
if self.selection.allAccounts {
return data.accounts
}
if let label = self.selection.label?.trimmingCharacters(in: .whitespacesAndNewlines), !label.isEmpty {
let normalized = label.lowercased()
if let match = data.accounts.first(where: { $0.label.lowercased() == normalized }) {
return [match]
}
throw TokenAccountCLIError.accountNotFound(provider, label)
}
if let index = self.selection.index {
guard index >= 0, index < data.accounts.count else {
throw TokenAccountCLIError.indexOutOfRange(provider, index + 1, data.accounts.count)
}
return [data.accounts[index]]
}
let clamped = data.clampedActiveIndex()
return [data.accounts[clamped]]
}
func settingsSnapshot(for provider: UsageProvider, account: ProviderTokenAccount?) -> ProviderSettingsSnapshot? {
let config = self.providerConfig(for: provider)
let cookieHeader = self.manualCookieHeader(provider: provider, account: account, config: config)
let cookieSource = self.cookieSource(provider: provider, account: account, config: config)
switch provider {
case .codex:
return self.makeSnapshot(
codex: ProviderSettingsSnapshot.CodexProviderSettings(
usageDataSource: .auto,
cookieSource: cookieSource,
manualCookieHeader: cookieHeader))
case .claude:
let claudeSource: ClaudeUsageDataSource = if provider == .claude,
let account,
TokenAccountSupportCatalog.isClaudeOAuthToken(account.token)
{
.oauth
} else {
.auto
}
let effectiveSource = (claudeSource == .oauth) ? ProviderCookieSource.off : cookieSource
return self.makeSnapshot(
claude: ProviderSettingsSnapshot.ClaudeProviderSettings(
usageDataSource: claudeSource,
webExtrasEnabled: false,
cookieSource: effectiveSource,
manualCookieHeader: claudeSource == .oauth ? nil : cookieHeader))
case .cursor:
return self.makeSnapshot(
cursor: ProviderSettingsSnapshot.CursorProviderSettings(
cookieSource: cookieSource,
manualCookieHeader: cookieHeader))
case .opencode:
return self.makeSnapshot(
opencode: ProviderSettingsSnapshot.OpenCodeProviderSettings(
cookieSource: cookieSource,
manualCookieHeader: cookieHeader,
workspaceID: config?.workspaceID))
case .factory:
return self.makeSnapshot(
factory: ProviderSettingsSnapshot.FactoryProviderSettings(
cookieSource: cookieSource,
manualCookieHeader: cookieHeader))
case .minimax:
return self.makeSnapshot(
minimax: ProviderSettingsSnapshot.MiniMaxProviderSettings(
cookieSource: cookieSource,
manualCookieHeader: cookieHeader,
apiRegion: self.resolveMiniMaxRegion(config)))
case .augment:
return self.makeSnapshot(
augment: ProviderSettingsSnapshot.AugmentProviderSettings(
cookieSource: cookieSource,
manualCookieHeader: cookieHeader))
case .amp:
return self.makeSnapshot(
amp: ProviderSettingsSnapshot.AmpProviderSettings(
cookieSource: cookieSource,
manualCookieHeader: cookieHeader))
case .kimi:
return self.makeSnapshot(
kimi: ProviderSettingsSnapshot.KimiProviderSettings(
cookieSource: cookieSource,
manualCookieHeader: cookieHeader))
case .zai:
return self.makeSnapshot(
zai: ProviderSettingsSnapshot.ZaiProviderSettings(apiRegion: self.resolveZaiRegion(config)))
case .jetbrains:
return self.makeSnapshot(
jetbrains: ProviderSettingsSnapshot.JetBrainsProviderSettings(
ideBasePath: nil))
case .gemini, .antigravity, .copilot, .kiro, .vertexai, .kimik2, .synthetic:
return nil
}
}
private func makeSnapshot(
codex: ProviderSettingsSnapshot.CodexProviderSettings? = nil,
claude: ProviderSettingsSnapshot.ClaudeProviderSettings? = nil,
cursor: ProviderSettingsSnapshot.CursorProviderSettings? = nil,
opencode: ProviderSettingsSnapshot.OpenCodeProviderSettings? = nil,
factory: ProviderSettingsSnapshot.FactoryProviderSettings? = nil,
minimax: ProviderSettingsSnapshot.MiniMaxProviderSettings? = nil,
zai: ProviderSettingsSnapshot.ZaiProviderSettings? = nil,
kimi: ProviderSettingsSnapshot.KimiProviderSettings? = nil,
augment: ProviderSettingsSnapshot.AugmentProviderSettings? = nil,
amp: ProviderSettingsSnapshot.AmpProviderSettings? = nil,
jetbrains: ProviderSettingsSnapshot.JetBrainsProviderSettings? = nil) -> ProviderSettingsSnapshot
{
ProviderSettingsSnapshot.make(
codex: codex,
claude: claude,
cursor: cursor,
opencode: opencode,
factory: factory,
minimax: minimax,
zai: zai,
kimi: kimi,
augment: augment,
amp: amp,
jetbrains: jetbrains)
}
func environment(
base: [String: String],
provider: UsageProvider,
account: ProviderTokenAccount?) -> [String: String]
{
var env = base
if let account,
let override = TokenAccountSupportCatalog.envOverride(for: provider, token: account.token)
{
for (key, value) in override {
env[key] = value
}
}
let providerConfig = self.providerConfig(for: provider)
env = ProviderConfigEnvironment.applyAPIKeyOverride(
base: env,
provider: provider,
config: providerConfig)
return env
}
func applyAccountLabel(
_ snapshot: UsageSnapshot,
provider: UsageProvider,
account: ProviderTokenAccount) -> UsageSnapshot
{
let label = account.label.trimmingCharacters(in: .whitespacesAndNewlines)
guard !label.isEmpty else { return snapshot }
let existing = snapshot.identity(for: provider)
let email = existing?.accountEmail?.trimmingCharacters(in: .whitespacesAndNewlines)
let resolvedEmail = (email?.isEmpty ?? true) ? label : email
let identity = ProviderIdentitySnapshot(
providerID: provider,
accountEmail: resolvedEmail,
accountOrganization: existing?.accountOrganization,
loginMethod: existing?.loginMethod)
return UsageSnapshot(
primary: snapshot.primary,
secondary: snapshot.secondary,
tertiary: snapshot.tertiary,
providerCost: snapshot.providerCost,
zaiUsage: snapshot.zaiUsage,
cursorRequests: snapshot.cursorRequests,
updatedAt: snapshot.updatedAt,
identity: identity)
}
func effectiveSourceMode(
base: ProviderSourceMode,
provider: UsageProvider,
account: ProviderTokenAccount?) -> ProviderSourceMode
{
guard base == .auto,
provider == .claude,
let account,
TokenAccountSupportCatalog.isClaudeOAuthToken(account.token)
else {
return base
}
return .oauth
}
func preferredSourceMode(for provider: UsageProvider) -> ProviderSourceMode {
let config = self.providerConfig(for: provider)
return config?.source ?? .auto
}
private func providerConfig(for provider: UsageProvider) -> ProviderConfig? {
self.config.providerConfig(for: provider)
}
private func manualCookieHeader(
provider: UsageProvider,
account: ProviderTokenAccount?,
config: ProviderConfig?) -> String?
{
if let account,
let support = TokenAccountSupportCatalog.support(for: provider),
case .cookieHeader = support.injection
{
if provider == .claude, TokenAccountSupportCatalog.isClaudeOAuthToken(account.token) {
return nil
}
let header = TokenAccountSupportCatalog.normalizedCookieHeader(account.token, support: support)
return header.isEmpty ? nil : header
}
return config?.sanitizedCookieHeader
}
private func cookieSource(
provider: UsageProvider,
account: ProviderTokenAccount?,
config: ProviderConfig?) -> ProviderCookieSource
{
if let override = config?.cookieSource { return override }
if let account, TokenAccountSupportCatalog.support(for: provider)?.requiresManualCookieSource == true {
if provider == .claude, TokenAccountSupportCatalog.isClaudeOAuthToken(account.token) {
return .off
}
return .manual
}
if config?.sanitizedCookieHeader != nil {
return .manual
}
return .auto
}
private func resolveZaiRegion(_ config: ProviderConfig?) -> ZaiAPIRegion {
guard let raw = config?.region?.trimmingCharacters(in: .whitespacesAndNewlines),
!raw.isEmpty
else {
return .global
}
return ZaiAPIRegion(rawValue: raw) ?? .global
}
private func resolveMiniMaxRegion(_ config: ProviderConfig?) -> MiniMaxAPIRegion {
guard let raw = config?.region?.trimmingCharacters(in: .whitespacesAndNewlines),
!raw.isEmpty
else {
return .global
}
return MiniMaxAPIRegion(rawValue: raw) ?? .global
}
}