Peter Steinbergerfeat: add config-backed cli output8a8517310 months agoHistory
306 lines · 11.9 KB · Swift
RawDownload
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    }}