From 7191cac2eb8e72551ce95d804fc6a2d94f8a7bc9 Mon Sep 17 00:00:00 2001 From: OfficialAbhinavSingh Date: Tue, 28 Jul 2026 10:29:07 +0530 Subject: [PATCH 1/3] fix: allow MiniMax usage on Linux with a configured API key The MiniMax descriptor advertises [.auto, .web, .api], so the CLI's web-support gate rejected the provider under Auto on non-macOS hosts even when an API key was configured. The MiniMax API fetch is plain HTTPS with Bearer auth, so only its web/cookie path needs macOS. Exempt Auto from the gate when an API key resolves, matching the existing Factory and Kimi credential exemptions. Explicit --source web and the no-credential case still require web support. Closes #2474 --- Sources/CodexBarCLI/CLIUsageCommand.swift | 8 ++++ TestsLinux/MiniMaxLinuxTests.swift | 48 +++++++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 TestsLinux/MiniMaxLinuxTests.swift diff --git a/Sources/CodexBarCLI/CLIUsageCommand.swift b/Sources/CodexBarCLI/CLIUsageCommand.swift index d3d3923a76..b37f2a4b47 100644 --- a/Sources/CodexBarCLI/CLIUsageCommand.swift +++ b/Sources/CodexBarCLI/CLIUsageCommand.swift @@ -759,6 +759,14 @@ extension CodexBarCLI { // Linux Auto/legacy-cli can use FACTORY_API_KEY without browser cookies. return false } + if provider == .minimax, + sourceMode == .auto, + environment.map({ MiniMaxAPISettingsReader.apiToken(environment: $0) != nil }) == true + { + // The MiniMax API fetch is plain HTTPS + Bearer auth; only its web/cookie path + // needs macOS, so a configured API key works off macOS too. + return false + } if provider == .mimo, sourceMode == .auto, let environment, diff --git a/TestsLinux/MiniMaxLinuxTests.swift b/TestsLinux/MiniMaxLinuxTests.swift new file mode 100644 index 0000000000..ad5c166ab8 --- /dev/null +++ b/TestsLinux/MiniMaxLinuxTests.swift @@ -0,0 +1,48 @@ +#if os(Linux) +import Foundation +import Testing +@testable import CodexBarCLI +@testable import CodexBarCore + +struct MiniMaxLinuxTests { + @Test + func `configured API key does not require macOS web support`() { + // The MiniMax API fetch is plain HTTPS + Bearer auth, so a configured API key must + // be usable off macOS (matches the Factory/Kimi credential exemptions). + #expect(!CodexBarCLI.sourceModeRequiresWebSupport( + .auto, + provider: .minimax, + environment: [MiniMaxAPISettingsReader.apiTokenKey: "sk-api-test"])) + } + + @Test + func `coding plan API key also skips the web-support gate`() { + // `config set-api-key --provider minimax` resolves through the same reader, which + // accepts the coding-plan key as well. + #expect(!CodexBarCLI.sourceModeRequiresWebSupport( + .auto, + provider: .minimax, + environment: [MiniMaxAPISettingsReader.codingPlanAPITokenKey: "sk-cp-test"])) + } + + @Test + func `auto without an API key still requires web support off macOS`() { + // Without a credential the only remaining Auto path is the web/cookie one, which + // genuinely needs macOS — the gate must still fire. + #expect(CodexBarCLI.sourceModeRequiresWebSupport( + .auto, + provider: .minimax, + environment: [:])) + } + + @Test + func `explicit web source still requires web support even with an API key`() { + // The exemption is scoped to Auto; asking for the web source explicitly must not + // be silently redirected to the API path. + #expect(CodexBarCLI.sourceModeRequiresWebSupport( + .web, + provider: .minimax, + environment: [MiniMaxAPISettingsReader.apiTokenKey: "sk-api-test"])) + } +} +#endif From 9e8a2a854df27b2479481666d37a597a494569a3 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Wed, 29 Jul 2026 07:29:45 -0700 Subject: [PATCH 2/3] Group CLI web-support exemptions into helpers to stay under complexity limit --- Sources/CodexBarCLI/CLIUsageCommand.swift | 180 ++++++++++++---------- 1 file changed, 96 insertions(+), 84 deletions(-) diff --git a/Sources/CodexBarCLI/CLIUsageCommand.swift b/Sources/CodexBarCLI/CLIUsageCommand.swift index b37f2a4b47..ca69302faa 100644 --- a/Sources/CodexBarCLI/CLIUsageCommand.swift +++ b/Sources/CodexBarCLI/CLIUsageCommand.swift @@ -254,7 +254,9 @@ extension CodexBarCLI { } private static func accountSelections(from accounts: [ProviderTokenAccount]) -> [ProviderTokenAccount?] { - if accounts.isEmpty { return [nil] } + if accounts.isEmpty { + return [nil] + } return accounts.map { Optional($0) } } @@ -674,113 +676,123 @@ extension CodexBarCLI { environment: [String: String]? = nil, settings: ProviderSettingsSnapshot? = nil) -> Bool { - guard provider != .grok, provider != .amp else { + if self.webSupportExempt( + sourceMode, + provider: provider, + environment: environment, + settings: settings) + { return false } - if provider == .codex, sourceMode == .auto { - return false + return switch sourceMode { + case .web: + true + case .auto: + ProviderDescriptorRegistry.descriptor(for: provider).fetchPlan.sourceModes.contains(.web) + case .cli, .oauth, .api: + false } - if provider == .claude, sourceMode == .auto { - // Claude's cross-platform planner skips its unavailable web step and falls back to the CLI. - return false + } + + /// Providers that can satisfy a source mode without the macOS-only web/browser path. + private static func webSupportExempt( + _ sourceMode: ProviderSourceMode, + provider: UsageProvider, + environment: [String: String]?, + settings: ProviderSettingsSnapshot?) -> Bool + { + if provider == .grok || provider == .amp { + return true } - if provider == .opencodego { - if sourceMode == .auto || settings?.opencodego?.cookieSource == .manual { - return false - } + if sourceMode == .auto, provider == .codex || provider == .claude { + // Claude's cross-platform planner skips its unavailable web step and falls back to the CLI. + return true } - if provider == .commandcode, - settings?.commandcode?.cookieSource == .manual - { - return false + if self.cookieSourceExempt(sourceMode, provider: provider, settings: settings) { + return true } - if provider == .alibabatokenplan, - settings?.alibabaTokenPlan?.cookieSource == .manual - { + return self.credentialExempt( + sourceMode, + provider: provider, + environment: environment, + settings: settings) + } + + /// Exemptions granted by a manual cookie header instead of browser auto-import. + private static func cookieSourceExempt( + _ sourceMode: ProviderSourceMode, + provider: UsageProvider, + settings: ProviderSettingsSnapshot?) -> Bool + { + switch provider { + case .opencodego: + return sourceMode == .auto || settings?.opencodego?.cookieSource == .manual + case .commandcode: + return settings?.commandcode?.cookieSource == .manual + case .alibabatokenplan: // The Alibaba/Qwen Token Plan fetch is plain URLSession + cookies; only browser // cookie auto-import needs macOS, so a manual cookie header works off macOS too. - return false - } - #if os(Linux) - if provider == .cursor, - settings?.cursor?.cookieSource != .off - { + return settings?.alibabaTokenPlan?.cookieSource == .manual + case .qoder: + return settings?.qoder?.cookieSource == .manual + case .cursor: + #if os(Linux) // Linux uses Cursor app auth and manual cookies; browser import remains macOS-only. + return settings?.cursor?.cookieSource != .off + #else return false - } - #endif - if provider == .sakana, - sourceMode == .auto || sourceMode == .web, - environment.map({ SakanaSettingsReader.cookieHeader(environment: $0) != nil }) == true - { + #endif + default: return false } - if provider == .qwencloud, - sourceMode == .auto || sourceMode == .web, - settings?.qwenCloud?.cookieSource != .off - { + } + + /// Exemptions granted by an already-configured credential (token, API key, or local cache). + private static func credentialExempt( + _ sourceMode: ProviderSourceMode, + provider: UsageProvider, + environment: [String: String]?, + settings: ProviderSettingsSnapshot?) -> Bool + { + switch provider { + case .sakana: + guard sourceMode == .auto || sourceMode == .web else { return false } + return environment.map { SakanaSettingsReader.cookieHeader(environment: $0) != nil } == true + case .qwencloud: + guard sourceMode == .auto || sourceMode == .web, + settings?.qwenCloud?.cookieSource != .off else { return false } let hasEnvironmentCookie = environment.map { QwenCloudSettingsReader.cookieHeader(environment: $0) != nil } == true let hasManualCookie = settings?.qwenCloud?.cookieSource == .manual && CookieHeaderNormalizer.normalize(settings?.qwenCloud?.manualCookieHeader) != nil - if hasEnvironmentCookie || hasManualCookie { - return false - } - } - if provider == .qoder, - settings?.qoder?.cookieSource == .manual - { - return false - } - if provider == .ollama, - sourceMode == .auto - { + return hasEnvironmentCookie || hasManualCookie + case .ollama: + guard sourceMode == .auto else { return false } let hasEnvironmentToken = environment.map { ProviderTokenResolver.ollamaToken(environment: $0) != nil } == true - if settings?.ollama?.cookieSource == .off || hasEnvironmentToken { - return false - } - } - if provider == .kimi, - sourceMode == .auto, - environment.map({ environment in - ProviderTokenResolver.kimiAPIToken(environment: environment) != nil || - KimiSettingsReader.hasKimiCodeCredential(environment: environment) - }) == true - { - return false - } - if provider == .factory, - sourceMode == .auto || sourceMode == .cli, - environment.map({ FactorySettingsReader.apiKey(environment: $0) != nil }) == true - { + return settings?.ollama?.cookieSource == .off || hasEnvironmentToken + case .kimi: + guard sourceMode == .auto else { return false } + return environment.map { environment in + ProviderTokenResolver.kimiAPIToken(environment: environment) != nil || + KimiSettingsReader.hasKimiCodeCredential(environment: environment) + } == true + case .factory: // Linux Auto/legacy-cli can use FACTORY_API_KEY without browser cookies. - return false - } - if provider == .minimax, - sourceMode == .auto, - environment.map({ MiniMaxAPISettingsReader.apiToken(environment: $0) != nil }) == true - { + guard sourceMode == .auto || sourceMode == .cli else { return false } + return environment.map { FactorySettingsReader.apiKey(environment: $0) != nil } == true + case .minimax: // The MiniMax API fetch is plain HTTPS + Bearer auth; only its web/cookie path // needs macOS, so a configured API key works off macOS too. + guard sourceMode == .auto else { return false } + return environment.map { MiniMaxAPISettingsReader.apiToken(environment: $0) != nil } == true + case .mimo: + guard sourceMode == .auto, let environment else { return false } + return MiMoLocalUsageFallback.cacheExists(environment: environment) + default: return false } - if provider == .mimo, - sourceMode == .auto, - let environment, - MiMoLocalUsageFallback.cacheExists(environment: environment) - { - return false - } - return switch sourceMode { - case .web: - true - case .auto: - ProviderDescriptorRegistry.descriptor(for: provider).fetchPlan.sourceModes.contains(.web) - case .cli, .oauth, .api: - false - } } } From 4783dafdf0427175854b60ed8ebd9619d2a50090 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Wed, 29 Jul 2026 07:38:21 -0700 Subject: [PATCH 3/3] Scope MiniMax Linux exemption to keys that resolve a Linux-capable strategy --- Sources/CodexBarCLI/CLIUsageCommand.swift | 10 +++-- TestsLinux/MiniMaxLinuxTests.swift | 46 ++++++++++++++++++----- 2 files changed, 42 insertions(+), 14 deletions(-) diff --git a/Sources/CodexBarCLI/CLIUsageCommand.swift b/Sources/CodexBarCLI/CLIUsageCommand.swift index ca69302faa..140cf40edf 100644 --- a/Sources/CodexBarCLI/CLIUsageCommand.swift +++ b/Sources/CodexBarCLI/CLIUsageCommand.swift @@ -784,10 +784,12 @@ extension CodexBarCLI { guard sourceMode == .auto || sourceMode == .cli else { return false } return environment.map { FactorySettingsReader.apiKey(environment: $0) != nil } == true case .minimax: - // The MiniMax API fetch is plain HTTPS + Bearer auth; only its web/cookie path - // needs macOS, so a configured API key works off macOS too. - guard sourceMode == .auto else { return false } - return environment.map { MiniMaxAPISettingsReader.apiToken(environment: $0) != nil } == true + // The MiniMax API fetch is plain HTTPS + Bearer auth, so a configured key works off + // macOS. Standard `sk-api-` keys are the exception: Auto resolves them to the Coding + // Plan web strategy, which still needs the macOS-only web path. + guard sourceMode == .auto, let environment else { return false } + guard MiniMaxAPISettingsReader.apiToken(environment: environment) != nil else { return false } + return MiniMaxAPISettingsReader.apiKeyKind(environment: environment) != .standard case .mimo: guard sourceMode == .auto, let environment else { return false } return MiMoLocalUsageFallback.cacheExists(environment: environment) diff --git a/TestsLinux/MiniMaxLinuxTests.swift b/TestsLinux/MiniMaxLinuxTests.swift index ad5c166ab8..610258cc09 100644 --- a/TestsLinux/MiniMaxLinuxTests.swift +++ b/TestsLinux/MiniMaxLinuxTests.swift @@ -6,23 +6,23 @@ import Testing struct MiniMaxLinuxTests { @Test - func `configured API key does not require macOS web support`() { - // The MiniMax API fetch is plain HTTPS + Bearer auth, so a configured API key must - // be usable off macOS (matches the Factory/Kimi credential exemptions). + func `coding plan API key does not require macOS web support`() { + // A coding-plan key resolves to the plain HTTPS + Bearer API strategy, so it must be + // usable off macOS (matches the Factory/Kimi credential exemptions). #expect(!CodexBarCLI.sourceModeRequiresWebSupport( .auto, provider: .minimax, - environment: [MiniMaxAPISettingsReader.apiTokenKey: "sk-api-test"])) + environment: [MiniMaxAPISettingsReader.codingPlanAPITokenKey: "sk-cp-test"])) } @Test - func `coding plan API key also skips the web-support gate`() { - // `config set-api-key --provider minimax` resolves through the same reader, which - // accepts the coding-plan key as well. - #expect(!CodexBarCLI.sourceModeRequiresWebSupport( + func `standard API key still requires web support because Auto resolves to the coding plan page`() { + // `MiniMaxAPIFetchStrategy` refuses standard `sk-api-` keys, so Auto falls back to the + // Coding Plan web strategy. Exempting it here would only produce `noAvailableStrategy`. + #expect(CodexBarCLI.sourceModeRequiresWebSupport( .auto, provider: .minimax, - environment: [MiniMaxAPISettingsReader.codingPlanAPITokenKey: "sk-cp-test"])) + environment: [MiniMaxAPISettingsReader.apiTokenKey: "sk-api-test"])) } @Test @@ -42,7 +42,33 @@ struct MiniMaxLinuxTests { #expect(CodexBarCLI.sourceModeRequiresWebSupport( .web, provider: .minimax, - environment: [MiniMaxAPISettingsReader.apiTokenKey: "sk-api-test"])) + environment: [MiniMaxAPISettingsReader.codingPlanAPITokenKey: "sk-cp-test"])) + } + + @Test + func `exempted coding plan key actually resolves a linux capable strategy`() async { + // Gate agreement is not enough: the Auto plan must contain a strategy that can run + // without the macOS web path, otherwise the exemption ends in `noAvailableStrategy`. + let env = [MiniMaxAPISettingsReader.codingPlanAPITokenKey: "sk-cp-test"] + let browserDetection = BrowserDetection(cacheTTL: 0) + let context = ProviderFetchContext( + runtime: .cli, + sourceMode: .auto, + includeCredits: false, + webTimeout: 1, + webDebugDumpHTML: false, + verbose: false, + env: env, + settings: ProviderSettingsSnapshot.make(), + fetcher: UsageFetcher(environment: env), + claudeFetcher: ClaudeUsageFetcher(browserDetection: browserDetection), + browserDetection: browserDetection) + let strategies = await ProviderDescriptorRegistry + .descriptor(for: .minimax) + .fetchPlan + .pipeline + .resolveStrategies(context) + #expect(strategies.contains { $0.id == "minimax.api" }) } } #endif