chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,744 @@
|
||||
import CodexBarCore
|
||||
import Commander
|
||||
import Foundation
|
||||
|
||||
struct UsageCommandContext {
|
||||
let format: OutputFormat
|
||||
let includeCredits: Bool
|
||||
let sourceModeOverride: ProviderSourceMode?
|
||||
let antigravityPlanDebug: Bool
|
||||
let augmentDebug: Bool
|
||||
let webDebugDumpHTML: Bool
|
||||
let webTimeout: TimeInterval
|
||||
let verbose: Bool
|
||||
let useColor: Bool
|
||||
let resetStyle: ResetTimeDisplayStyle
|
||||
let weeklyWorkDays: Int?
|
||||
let jsonOnly: Bool
|
||||
let includeAllCodexAccounts: Bool
|
||||
let fetcher: UsageFetcher
|
||||
let claudeFetcher: ClaudeUsageFetcher
|
||||
let browserDetection: BrowserDetection
|
||||
/// True for long-lived hosts (`codexbar serve`) that keep warm provider
|
||||
/// helper sessions (such as the managed Antigravity `agy` process) alive
|
||||
/// between fetches instead of resetting after each one-shot fetch.
|
||||
var persistCLISessions: Bool = false
|
||||
var persistentCLISessionIdleWindow: TimeInterval?
|
||||
var cardsLayout: Bool = false
|
||||
}
|
||||
|
||||
struct UsageCommandOutput {
|
||||
var sections: [String] = []
|
||||
var payload: [ProviderPayload] = []
|
||||
var cards: [CLICardModel] = []
|
||||
var cardFailures: [CLICardFailure] = []
|
||||
var exitCode: ExitCode = .success
|
||||
}
|
||||
|
||||
private struct UsageSuccessRenderInput {
|
||||
let provider: UsageProvider
|
||||
let accountLabel: String?
|
||||
let cacheAccountKey: String?
|
||||
let version: String?
|
||||
let source: String
|
||||
let status: ProviderStatusPayload?
|
||||
let usage: UsageSnapshot
|
||||
let credits: CreditsSnapshot?
|
||||
let antigravityPlanInfo: AntigravityPlanInfoSummary?
|
||||
let dashboard: OpenAIDashboardSnapshot?
|
||||
let effectiveSourceMode: ProviderSourceMode
|
||||
let command: UsageCommandContext
|
||||
let notes: [String]
|
||||
}
|
||||
|
||||
extension UsageCommandOutput {
|
||||
mutating func merge(_ other: UsageCommandOutput) {
|
||||
self.sections.append(contentsOf: other.sections)
|
||||
self.payload.append(contentsOf: other.payload)
|
||||
self.cards.append(contentsOf: other.cards)
|
||||
self.cardFailures.append(contentsOf: other.cardFailures)
|
||||
if other.exitCode != .success {
|
||||
self.exitCode = other.exitCode
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension CodexBarCLI {
|
||||
static func runUsage(_ values: ParsedValues) async {
|
||||
let output = CLIOutputPreferences.from(values: values)
|
||||
let config = Self.loadConfig(output: output)
|
||||
let provider = Self.decodeProvider(from: values, config: config)
|
||||
let format = output.format
|
||||
let includeCredits = format == .json ? true : !values.flags.contains("noCredits")
|
||||
let includeStatus = values.flags.contains("status")
|
||||
let sourceModeRaw = values.options["source"]?.last
|
||||
let parsedSourceMode = Self.decodeSourceMode(from: values)
|
||||
if sourceModeRaw != nil, parsedSourceMode == nil {
|
||||
Self.exit(
|
||||
code: .failure,
|
||||
message: "Error: --source must be auto|web|cli|oauth|api.",
|
||||
output: output,
|
||||
kind: .args)
|
||||
}
|
||||
let antigravityPlanDebug = values.flags.contains("antigravityPlanDebug")
|
||||
let augmentDebug = values.flags.contains("augmentDebug")
|
||||
let webDebugDumpHTML = values.flags.contains("webDebugDumpHtml")
|
||||
let webTimeout: TimeInterval
|
||||
do {
|
||||
webTimeout = try Self.decodeWebTimeout(from: values) ?? 60
|
||||
} catch {
|
||||
Self.exit(code: .failure, message: "Error: \(error.localizedDescription)", output: output, kind: .args)
|
||||
}
|
||||
let verbose = values.flags.contains("verbose")
|
||||
let noColor = values.flags.contains("noColor")
|
||||
let useColor = Self.shouldUseColor(noColor: noColor, format: format)
|
||||
let resetStyle = Self.resetTimeDisplayStyleFromDefaults()
|
||||
let weeklyWorkDays = Self.weeklyProgressWorkDaysFromDefaults()
|
||||
let providerList = provider.asList
|
||||
|
||||
let tokenSelection: TokenAccountCLISelection
|
||||
do {
|
||||
tokenSelection = try Self.decodeTokenAccountSelection(from: values)
|
||||
} catch {
|
||||
Self.exit(code: .failure, message: "Error: \(error.localizedDescription)", output: output, kind: .args)
|
||||
}
|
||||
|
||||
if tokenSelection.allAccounts, tokenSelection.label != nil || tokenSelection.index != nil {
|
||||
Self.exit(
|
||||
code: .failure,
|
||||
message: "Error: --all-accounts cannot be combined with --account or --account-index.",
|
||||
output: output,
|
||||
kind: .args)
|
||||
}
|
||||
|
||||
if tokenSelection.usesOverride {
|
||||
guard providerList.count == 1 else {
|
||||
Self.exit(
|
||||
code: .failure,
|
||||
message: "Error: account selection requires a single provider.",
|
||||
output: output,
|
||||
kind: .args)
|
||||
}
|
||||
let supportsAllCodexAccounts = providerList[0] == .codex
|
||||
&& tokenSelection.allAccounts
|
||||
&& tokenSelection.label == nil
|
||||
&& tokenSelection.index == nil
|
||||
guard supportsAllCodexAccounts || TokenAccountSupportCatalog.support(for: providerList[0]) != nil else {
|
||||
Self.exit(
|
||||
code: .failure,
|
||||
message: "Error: \(providerList[0].rawValue) does not support token accounts.",
|
||||
output: output,
|
||||
kind: .args)
|
||||
}
|
||||
}
|
||||
|
||||
let browserDetection = BrowserDetection()
|
||||
let fetcher = UsageFetcher()
|
||||
let claudeFetcher = ClaudeUsageFetcher(browserDetection: browserDetection)
|
||||
let tokenContext: TokenAccountCLIContext
|
||||
do {
|
||||
tokenContext = try TokenAccountCLIContext(
|
||||
selection: tokenSelection,
|
||||
config: config,
|
||||
verbose: verbose)
|
||||
} catch {
|
||||
Self.exit(code: .failure, message: "Error: \(error.localizedDescription)", output: output, kind: .config)
|
||||
}
|
||||
|
||||
var sections: [String] = []
|
||||
var payload: [ProviderPayload] = []
|
||||
var exitCode: ExitCode = .success
|
||||
let command = UsageCommandContext(
|
||||
format: format,
|
||||
includeCredits: includeCredits,
|
||||
sourceModeOverride: parsedSourceMode,
|
||||
antigravityPlanDebug: antigravityPlanDebug,
|
||||
augmentDebug: augmentDebug,
|
||||
webDebugDumpHTML: webDebugDumpHTML,
|
||||
webTimeout: webTimeout,
|
||||
verbose: verbose,
|
||||
useColor: useColor,
|
||||
resetStyle: resetStyle,
|
||||
weeklyWorkDays: weeklyWorkDays,
|
||||
jsonOnly: output.jsonOnly,
|
||||
includeAllCodexAccounts: tokenSelection.allAccounts && providerList == [.codex],
|
||||
fetcher: fetcher,
|
||||
claudeFetcher: claudeFetcher,
|
||||
browserDetection: browserDetection)
|
||||
|
||||
for p in providerList {
|
||||
let status = includeStatus ? await Self.fetchStatus(for: p) : nil
|
||||
// CLI usage should not clear Keychain cooldowns or attempt interactive Keychain prompts.
|
||||
let output = await ProviderInteractionContext.$current.withValue(.background) {
|
||||
await Self.fetchUsageOutputs(
|
||||
provider: p,
|
||||
status: status,
|
||||
tokenContext: tokenContext,
|
||||
command: command)
|
||||
}
|
||||
if output.exitCode != .success {
|
||||
exitCode = output.exitCode
|
||||
}
|
||||
sections.append(contentsOf: output.sections)
|
||||
payload.append(contentsOf: output.payload)
|
||||
}
|
||||
|
||||
switch format {
|
||||
case .text:
|
||||
if !sections.isEmpty {
|
||||
print(sections.joined(separator: "\n\n"))
|
||||
}
|
||||
case .json:
|
||||
Self.printJSON(payload, pretty: output.pretty)
|
||||
}
|
||||
|
||||
Self.exit(code: exitCode, output: output, kind: exitCode == .success ? .runtime : .provider)
|
||||
}
|
||||
|
||||
static func fetchUsageOutputs(
|
||||
provider: UsageProvider,
|
||||
status: ProviderStatusPayload?,
|
||||
tokenContext: TokenAccountCLIContext,
|
||||
command: UsageCommandContext) async -> UsageCommandOutput
|
||||
{
|
||||
if provider == .codex, command.includeAllCodexAccounts {
|
||||
var output = UsageCommandOutput()
|
||||
let accounts = tokenContext.visibleCodexAccounts().visibleAccounts
|
||||
let selections: [CodexVisibleAccount?] = accounts.isEmpty ? [nil] : accounts.map { Optional($0) }
|
||||
for visibleAccount in selections {
|
||||
let result = await Self.fetchUsageOutput(
|
||||
provider: provider,
|
||||
account: nil,
|
||||
codexVisibleAccount: visibleAccount,
|
||||
status: status,
|
||||
tokenContext: tokenContext,
|
||||
command: command)
|
||||
output.merge(result)
|
||||
}
|
||||
return output
|
||||
}
|
||||
|
||||
let accounts: [ProviderTokenAccount]
|
||||
do {
|
||||
accounts = try tokenContext.resolvedAccounts(for: provider)
|
||||
} catch {
|
||||
return Self.usageOutputForAccountResolutionError(
|
||||
provider: provider,
|
||||
status: status,
|
||||
command: command,
|
||||
error: error)
|
||||
}
|
||||
|
||||
let selections = Self.accountSelections(from: accounts)
|
||||
var output = UsageCommandOutput()
|
||||
for account in selections {
|
||||
let result = await Self.fetchUsageOutput(
|
||||
provider: provider,
|
||||
account: account,
|
||||
status: status,
|
||||
tokenContext: tokenContext,
|
||||
command: command)
|
||||
output.merge(result)
|
||||
}
|
||||
return output
|
||||
}
|
||||
|
||||
private static func accountSelections(from accounts: [ProviderTokenAccount]) -> [ProviderTokenAccount?] {
|
||||
if accounts.isEmpty { return [nil] }
|
||||
return accounts.map { Optional($0) }
|
||||
}
|
||||
|
||||
private static func usageOutputForAccountResolutionError(
|
||||
provider: UsageProvider,
|
||||
status: ProviderStatusPayload?,
|
||||
command: UsageCommandContext,
|
||||
error: Error) -> UsageCommandOutput
|
||||
{
|
||||
var output = UsageCommandOutput()
|
||||
output.exitCode = .failure
|
||||
if command.format == .json {
|
||||
output.payload.append(Self.makeProviderErrorPayload(
|
||||
provider: provider,
|
||||
account: nil,
|
||||
source: command.sourceModeOverride?.rawValue ?? "auto",
|
||||
status: status,
|
||||
error: error,
|
||||
kind: .provider))
|
||||
} else if command.cardsLayout {
|
||||
output.cardFailures.append(CLICardFailure(
|
||||
provider: provider,
|
||||
accountLabel: nil,
|
||||
message: error.localizedDescription))
|
||||
} else if !command.jsonOnly {
|
||||
Self.writeStderr("Error: \(error.localizedDescription)\n")
|
||||
}
|
||||
return output
|
||||
}
|
||||
|
||||
// swiftlint:disable:next function_parameter_count
|
||||
private static func makeUsagePayload(
|
||||
provider: UsageProvider,
|
||||
accountLabel: String?,
|
||||
cacheAccountKey: String?,
|
||||
version: String?,
|
||||
source: String,
|
||||
status: ProviderStatusPayload?,
|
||||
usage: UsageSnapshot,
|
||||
credits: CreditsSnapshot?,
|
||||
antigravityPlanInfo: AntigravityPlanInfoSummary?,
|
||||
dashboard: OpenAIDashboardSnapshot?,
|
||||
weeklyWorkDays: Int?) -> ProviderPayload
|
||||
{
|
||||
ProviderPayload(
|
||||
provider: provider,
|
||||
account: accountLabel,
|
||||
cacheAccountKey: cacheAccountKey,
|
||||
version: version,
|
||||
source: source,
|
||||
status: status,
|
||||
usage: usage,
|
||||
credits: credits,
|
||||
antigravityPlanInfo: antigravityPlanInfo,
|
||||
openaiDashboard: dashboard,
|
||||
error: nil,
|
||||
pace: CLIRenderer.providerPacePayload(provider: provider, snapshot: usage, weeklyWorkDays: weeklyWorkDays))
|
||||
}
|
||||
|
||||
private static func appendSuccessRenderOutput(
|
||||
_ input: UsageSuccessRenderInput,
|
||||
output: inout UsageCommandOutput)
|
||||
{
|
||||
switch input.command.format {
|
||||
case .text:
|
||||
if input.command.cardsLayout {
|
||||
output.cards.append(CLICardsRenderer.makeCard(CLICardBuildInput(
|
||||
provider: input.provider,
|
||||
snapshot: input.usage,
|
||||
credits: input.credits,
|
||||
source: input.source,
|
||||
status: input.status,
|
||||
notes: input.notes,
|
||||
useColor: input.command.useColor,
|
||||
resetStyle: input.command.resetStyle,
|
||||
weeklyWorkDays: input.command.weeklyWorkDays,
|
||||
now: Date())))
|
||||
} else {
|
||||
var text = CLIRenderer.renderText(
|
||||
provider: input.provider,
|
||||
snapshot: input.usage,
|
||||
credits: input.credits,
|
||||
context: RenderContext(
|
||||
header: Self.makeHeader(
|
||||
provider: input.provider,
|
||||
version: input.version,
|
||||
source: input.source),
|
||||
status: input.status,
|
||||
useColor: input.command.useColor,
|
||||
resetStyle: input.command.resetStyle,
|
||||
weeklyWorkDays: input.command.weeklyWorkDays,
|
||||
notes: input.notes))
|
||||
if let dashboard = input.dashboard, input.provider == .codex, input.effectiveSourceMode.usesWeb {
|
||||
text += "\n" + Self.renderOpenAIWebDashboardText(dashboard)
|
||||
}
|
||||
output.sections.append(text)
|
||||
}
|
||||
case .json:
|
||||
output.payload.append(self.makeUsagePayload(
|
||||
provider: input.provider,
|
||||
accountLabel: input.accountLabel,
|
||||
cacheAccountKey: input.cacheAccountKey,
|
||||
version: input.version,
|
||||
source: input.source,
|
||||
status: input.status,
|
||||
usage: input.usage,
|
||||
credits: input.credits,
|
||||
antigravityPlanInfo: input.antigravityPlanInfo,
|
||||
dashboard: input.dashboard,
|
||||
weeklyWorkDays: input.command.weeklyWorkDays))
|
||||
}
|
||||
}
|
||||
|
||||
private static func fetchUsageOutput(
|
||||
provider: UsageProvider,
|
||||
account: ProviderTokenAccount?,
|
||||
codexVisibleAccount: CodexVisibleAccount? = nil,
|
||||
status: ProviderStatusPayload?,
|
||||
tokenContext: TokenAccountCLIContext,
|
||||
command: UsageCommandContext) async -> UsageCommandOutput
|
||||
{
|
||||
var output = UsageCommandOutput()
|
||||
let env = tokenContext.environment(
|
||||
base: ProcessInfo.processInfo.environment,
|
||||
provider: provider,
|
||||
account: account,
|
||||
codexActiveSourceOverride: codexVisibleAccount?.selectionSource)
|
||||
let settings = tokenContext.settingsSnapshot(
|
||||
for: provider,
|
||||
account: account,
|
||||
codexActiveSourceOverride: codexVisibleAccount?.selectionSource)
|
||||
let configSource = tokenContext.preferredSourceMode(for: provider)
|
||||
let baseSource = command.sourceModeOverride ?? configSource
|
||||
let effectiveSourceMode = tokenContext.effectiveSourceMode(
|
||||
base: baseSource,
|
||||
provider: provider,
|
||||
account: account)
|
||||
let cacheAccountKey = Self.usageCacheAccountKey(
|
||||
provider: provider,
|
||||
account: account,
|
||||
codexVisibleAccount: codexVisibleAccount)
|
||||
|
||||
#if !os(macOS)
|
||||
if Self.sourceModeRequiresWebSupport(
|
||||
effectiveSourceMode,
|
||||
provider: provider,
|
||||
environment: env,
|
||||
settings: settings)
|
||||
{
|
||||
return Self.webSourceUnsupportedOutput(
|
||||
provider: provider,
|
||||
account: (
|
||||
label: account?.label ?? codexVisibleAccount?.menuDisplayName,
|
||||
cacheKey: cacheAccountKey),
|
||||
source: effectiveSourceMode.rawValue,
|
||||
status: status,
|
||||
command: command)
|
||||
}
|
||||
#endif
|
||||
|
||||
let fetchContext = ProviderFetchContext(
|
||||
runtime: .cli,
|
||||
sourceMode: effectiveSourceMode,
|
||||
includeCredits: command.includeCredits,
|
||||
webTimeout: command.webTimeout,
|
||||
webDebugDumpHTML: command.webDebugDumpHTML,
|
||||
verbose: command.verbose,
|
||||
env: env,
|
||||
settings: settings,
|
||||
fetcher: tokenContext.fetcher(base: command.fetcher, provider: provider, env: env),
|
||||
claudeFetcher: command.claudeFetcher,
|
||||
browserDetection: command.browserDetection,
|
||||
selectedTokenAccountID: account?.id,
|
||||
tokenAccountTokenUpdater: tokenContext.tokenUpdater(for: account),
|
||||
providerManualTokenUpdater: tokenContext.manualTokenUpdater(),
|
||||
persistsCLISessions: Self.persistsCLISessions(provider: provider, command: command),
|
||||
persistentCLISessionIdleWindow: command.persistentCLISessionIdleWindow)
|
||||
let outcome = await Self.fetchProviderUsage(provider: provider, context: fetchContext)
|
||||
if command.verbose, !command.jsonOnly {
|
||||
Self.printFetchAttempts(provider: provider, attempts: outcome.attempts)
|
||||
}
|
||||
|
||||
switch outcome.result {
|
||||
case let .success(result):
|
||||
let antigravityPlanInfo = await Self.fetchAntigravityPlanInfoIfNeeded(
|
||||
provider: provider,
|
||||
command: command)
|
||||
await Self.emitAugmentDebugIfNeeded(provider: provider, command: command)
|
||||
|
||||
var usage = result.usage.scoped(to: provider)
|
||||
if let account {
|
||||
usage = tokenContext.applyAccountLabel(usage, provider: provider, account: account)
|
||||
} else if let codexVisibleAccount {
|
||||
usage = tokenContext.applyCodexVisibleAccountLabel(usage, account: codexVisibleAccount)
|
||||
}
|
||||
|
||||
var dashboard = result.dashboard
|
||||
if dashboard == nil, command.format == .json, provider == .codex {
|
||||
dashboard = Self.loadOpenAIDashboardIfAvailable(
|
||||
usage: usage,
|
||||
sourceLabel: result.sourceLabel,
|
||||
context: fetchContext)
|
||||
}
|
||||
|
||||
let shouldDetectVersion = Self.shouldDetectVersion(provider: provider, result: result)
|
||||
let version = Self.normalizeVersion(
|
||||
raw: shouldDetectVersion
|
||||
? Self.detectVersion(for: provider, browserDetection: command.browserDetection)
|
||||
: nil)
|
||||
let source = result.sourceLabel
|
||||
let notes = Self.usageTextNotes(
|
||||
provider: provider,
|
||||
sourceMode: effectiveSourceMode,
|
||||
resolvedSourceLabel: source)
|
||||
|
||||
Self.appendSuccessRenderOutput(
|
||||
UsageSuccessRenderInput(
|
||||
provider: provider,
|
||||
accountLabel: account?.label ?? codexVisibleAccount?.menuDisplayName,
|
||||
cacheAccountKey: cacheAccountKey,
|
||||
version: version,
|
||||
source: source,
|
||||
status: status,
|
||||
usage: usage,
|
||||
credits: result.credits,
|
||||
antigravityPlanInfo: antigravityPlanInfo,
|
||||
dashboard: dashboard,
|
||||
effectiveSourceMode: effectiveSourceMode,
|
||||
command: command,
|
||||
notes: notes),
|
||||
output: &output)
|
||||
case let .failure(error):
|
||||
output.exitCode = Self.mapError(error)
|
||||
if command.format == .json {
|
||||
output.payload.append(Self.makeProviderErrorPayload(
|
||||
provider: provider,
|
||||
account: account?.label ?? codexVisibleAccount?.menuDisplayName,
|
||||
cacheAccountKey: cacheAccountKey,
|
||||
source: effectiveSourceMode.rawValue,
|
||||
status: status,
|
||||
error: error,
|
||||
kind: .provider))
|
||||
} else if command.cardsLayout {
|
||||
output.cardFailures.append(CLICardFailure(
|
||||
provider: provider,
|
||||
accountLabel: account?.label ?? codexVisibleAccount?.menuDisplayName,
|
||||
message: error.localizedDescription))
|
||||
} else if !command.jsonOnly {
|
||||
if let accountLabel = account?.label ?? codexVisibleAccount?.menuDisplayName {
|
||||
Self.writeStderr(
|
||||
"Error (\(provider.rawValue) - \(accountLabel)): \(error.localizedDescription)\n")
|
||||
} else {
|
||||
Self.writeStderr("Error: \(error.localizedDescription)\n")
|
||||
}
|
||||
if let summary = Self.kiloAutoFallbackSummary(
|
||||
provider: provider,
|
||||
sourceMode: effectiveSourceMode,
|
||||
attempts: outcome.attempts)
|
||||
{
|
||||
Self.writeStderr("\(summary)\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return await Self.finishUsageOutput(output, provider: provider, command: command)
|
||||
}
|
||||
|
||||
static func shouldDetectVersion(provider: UsageProvider, result: ProviderFetchResult) -> Bool {
|
||||
let descriptor = ProviderDescriptorRegistry.descriptor(for: provider)
|
||||
guard descriptor.cli.versionDetector != nil else { return false }
|
||||
guard result.strategyKind != .webDashboard else { return false }
|
||||
return !(provider == .claude && result.strategyKind == .oauth)
|
||||
}
|
||||
|
||||
private static func holdsAntigravitySession(
|
||||
provider: UsageProvider,
|
||||
command: UsageCommandContext) -> Bool
|
||||
{
|
||||
self.holdsAntigravityCLISessionForPlanDebug(
|
||||
provider: provider,
|
||||
planDebugEnabled: command.antigravityPlanDebug,
|
||||
jsonOnly: command.jsonOnly,
|
||||
persistsCLISessions: command.persistCLISessions)
|
||||
}
|
||||
|
||||
private static func persistsCLISessions(
|
||||
provider: UsageProvider,
|
||||
command: UsageCommandContext) -> Bool
|
||||
{
|
||||
command.persistCLISessions || self.holdsAntigravitySession(provider: provider, command: command)
|
||||
}
|
||||
|
||||
static func holdsAntigravityCLISessionForPlanDebug(
|
||||
provider: UsageProvider,
|
||||
planDebugEnabled: Bool,
|
||||
jsonOnly: Bool,
|
||||
persistsCLISessions: Bool) -> Bool
|
||||
{
|
||||
provider == .antigravity
|
||||
&& planDebugEnabled
|
||||
&& !jsonOnly
|
||||
&& !persistsCLISessions
|
||||
}
|
||||
|
||||
private static func finishUsageOutput(
|
||||
_ output: UsageCommandOutput,
|
||||
provider: UsageProvider,
|
||||
command: UsageCommandContext) async -> UsageCommandOutput
|
||||
{
|
||||
if self.holdsAntigravitySession(provider: provider, command: command) {
|
||||
await ProviderCLISessionLifecycle.shutdownPersistentSessions()
|
||||
}
|
||||
return output
|
||||
}
|
||||
|
||||
private static func fetchAntigravityPlanInfoIfNeeded(
|
||||
provider: UsageProvider,
|
||||
command: UsageCommandContext) async -> AntigravityPlanInfoSummary?
|
||||
{
|
||||
guard command.antigravityPlanDebug,
|
||||
provider == .antigravity,
|
||||
!command.jsonOnly
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
let info = try? await AntigravityStatusProbe().fetchPlanInfoSummary()
|
||||
if command.format == .text, let info {
|
||||
Self.printAntigravityPlanInfo(info)
|
||||
}
|
||||
return info
|
||||
}
|
||||
|
||||
private static func emitAugmentDebugIfNeeded(
|
||||
provider: UsageProvider,
|
||||
command: UsageCommandContext) async
|
||||
{
|
||||
guard command.augmentDebug, provider == .augment else { return }
|
||||
#if os(macOS)
|
||||
let dump = await AugmentStatusProbe.latestDumps()
|
||||
if command.format == .text, !dump.isEmpty, !command.jsonOnly {
|
||||
Self.writeStderr("Augment API responses:\n\(dump)\n")
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
private static func webSourceUnsupportedOutput(
|
||||
provider: UsageProvider,
|
||||
account: (label: String?, cacheKey: String?),
|
||||
source: String,
|
||||
status: ProviderStatusPayload?,
|
||||
command: UsageCommandContext) -> UsageCommandOutput
|
||||
{
|
||||
var output = UsageCommandOutput()
|
||||
let error = NSError(
|
||||
domain: "CodexBarCLI",
|
||||
code: 1,
|
||||
userInfo: [NSLocalizedDescriptionKey:
|
||||
"Error: selected source requires web support and is only supported on macOS."])
|
||||
output.exitCode = .failure
|
||||
if command.format == .json {
|
||||
output.payload.append(Self.makeProviderErrorPayload(
|
||||
provider: provider,
|
||||
account: account.label,
|
||||
cacheAccountKey: account.cacheKey,
|
||||
source: source,
|
||||
status: status,
|
||||
error: error,
|
||||
kind: .runtime))
|
||||
} else if command.cardsLayout {
|
||||
output.cardFailures.append(CLICardFailure(
|
||||
provider: provider,
|
||||
accountLabel: account.label,
|
||||
message: error.localizedDescription))
|
||||
} else if !command.jsonOnly {
|
||||
Self.writeStderr("Error: \(error.localizedDescription)\n")
|
||||
}
|
||||
return output
|
||||
}
|
||||
|
||||
static func usageCacheAccountKey(
|
||||
provider _: UsageProvider,
|
||||
account: ProviderTokenAccount?,
|
||||
codexVisibleAccount: CodexVisibleAccount?) -> String?
|
||||
{
|
||||
if let account {
|
||||
return "token:\(account.id.uuidString.lowercased())"
|
||||
}
|
||||
if let codexVisibleAccount {
|
||||
if let workspaceAccountID = codexVisibleAccount.workspaceAccountID?
|
||||
.trimmingCharacters(in: .whitespacesAndNewlines),
|
||||
!workspaceAccountID.isEmpty,
|
||||
!codexVisibleAccount.email.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
|
||||
{
|
||||
let email = codexVisibleAccount.email
|
||||
.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
.lowercased()
|
||||
return "codex:workspace:\(workspaceAccountID):email:\(email)"
|
||||
}
|
||||
if let storedAccountID = codexVisibleAccount.storedAccountID {
|
||||
return "codex:stored:\(storedAccountID.uuidString.lowercased())"
|
||||
}
|
||||
if let authFingerprint = codexVisibleAccount.authFingerprint {
|
||||
return "codex:auth:\(authFingerprint)"
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
static func sourceModeRequiresWebSupport(
|
||||
_ sourceMode: ProviderSourceMode,
|
||||
provider: UsageProvider,
|
||||
environment: [String: String]? = nil,
|
||||
settings: ProviderSettingsSnapshot? = nil) -> Bool
|
||||
{
|
||||
guard provider != .grok, provider != .amp else {
|
||||
return false
|
||||
}
|
||||
if provider == .codex, sourceMode == .auto {
|
||||
return false
|
||||
}
|
||||
if provider == .claude, sourceMode == .auto {
|
||||
// Claude's cross-platform planner skips its unavailable web step and falls back to the CLI.
|
||||
return false
|
||||
}
|
||||
if provider == .opencodego {
|
||||
if sourceMode == .auto || settings?.opencodego?.cookieSource == .manual {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if provider == .commandcode,
|
||||
settings?.commandcode?.cookieSource == .manual
|
||||
{
|
||||
return false
|
||||
}
|
||||
#if os(Linux)
|
||||
if provider == .cursor,
|
||||
settings?.cursor?.cookieSource != .off
|
||||
{
|
||||
// Linux uses Cursor app auth and manual cookies; browser import remains macOS-only.
|
||||
return false
|
||||
}
|
||||
#endif
|
||||
if provider == .sakana,
|
||||
sourceMode == .auto || sourceMode == .web,
|
||||
environment.map({ SakanaSettingsReader.cookieHeader(environment: $0) != nil }) == true
|
||||
{
|
||||
return false
|
||||
}
|
||||
if provider == .qoder,
|
||||
settings?.qoder?.cookieSource == .manual
|
||||
{
|
||||
return false
|
||||
}
|
||||
if provider == .ollama,
|
||||
sourceMode == .auto
|
||||
{
|
||||
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
|
||||
{
|
||||
// Linux Auto/legacy-cli can use FACTORY_API_KEY without browser cookies.
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user