Skip to main content
CodexBar uses a flexible strategy pattern to fetch usage data from different sources. Each provider defines a fetch plan with an ordered pipeline of strategies that are tried in sequence until one succeeds.

Overview

The fetch system has three layers:
  1. Fetch Plan: Declares which source modes are supported
  2. Fetch Pipeline: Resolves and executes strategies in priority order
  3. Fetch Strategies: Individual implementations that fetch from specific sources

Source Modes

public enum ProviderSourceMode: String, CaseIterable, Sendable, Codable {
    case auto   // Try all available strategies in order
    case web    // Web scraping or browser cookies
    case cli    // Command-line interface
    case oauth  // OAuth API access
    case api    // API token access
}

Source Mode Filtering

  • App runtime: Always uses .auto mode (tries all strategies)
  • CLI runtime: Respects --source flag to filter strategies
  • Strategies check context.sourceMode to determine availability

ProviderFetchPlan

sourceModes
Set<ProviderSourceMode>
required
Set of source modes this provider supports
pipeline
ProviderFetchPipeline
required
The strategy resolution and execution pipeline
public struct ProviderFetchPlan: Sendable {
    public let sourceModes: Set<ProviderSourceMode>
    public let pipeline: ProviderFetchPipeline
    
    public func fetchOutcome(
        context: ProviderFetchContext,
        provider: UsageProvider
    ) async -> ProviderFetchOutcome
}

ProviderFetchPipeline

The pipeline resolves strategies dynamically based on the fetch context:
public struct ProviderFetchPipeline: Sendable {
    public let resolveStrategies: @Sendable (ProviderFetchContext) async -> [any ProviderFetchStrategy]
    
    public func fetch(
        context: ProviderFetchContext,
        provider: UsageProvider
    ) async -> ProviderFetchOutcome
}

Pipeline Execution

  1. Resolve strategies for the current context
  2. For each strategy in order:
    • Check if strategy is available
    • If available, attempt to fetch
    • If successful, return result immediately
    • If error occurs and shouldFallback returns true, continue to next strategy
    • If error occurs and shouldFallback returns false, return error
  3. If no strategies succeed, return ProviderFetchError.noAvailableStrategy

ProviderFetchStrategy Protocol

id
String
required
Unique identifier for this strategy (e.g., “claude.cli”, “codex.web”)
kind
ProviderFetchKind
required
The kind of fetch this strategy performs

Required Methods

isAvailable

func isAvailable(_ context: ProviderFetchContext) async -> Bool
Determines if this strategy can be used in the given context. Check for:
  • Installed CLI binaries
  • Available browser cookies
  • API tokens in settings
  • Source mode compatibility

fetch

func fetch(_ context: ProviderFetchContext) async throws -> ProviderFetchResult
Executes the fetch and returns usage data. Should be timeout-bounded.

shouldFallback

func shouldFallback(on error: Error, context: ProviderFetchContext) -> Bool
Determines if the pipeline should try the next strategy after this error. Return:
  • true: Try next strategy (soft error like “CLI not found”)
  • false: Stop immediately (hard error like “authentication failed”)

ProviderFetchKind

public enum ProviderFetchKind: Sendable {
    case cli           // Command-line interface
    case web           // Browser cookies
    case oauth         // OAuth API
    case apiToken      // API token
    case localProbe    // Local process inspection
    case webDashboard  // Web scraping with WebView
}

ProviderFetchContext

Context passed to all strategies containing runtime configuration:
runtime
ProviderRuntime
Whether running in app or CLI mode
sourceMode
ProviderSourceMode
Requested source mode (auto, web, cli, oauth, api)
includeCredits
Bool
Whether to fetch credit information
webTimeout
TimeInterval
Timeout for web operations
verbose
Bool
Whether to log verbose debug info
settings
ProviderSettingsSnapshot?
Provider-specific settings
fetcher
UsageFetcher
Shared fetcher for Codex-specific operations
browserDetection
BrowserDetection
Browser cookie detection helper

ProviderFetchResult

usage
UsageSnapshot
required
The fetched usage data
credits
CreditsSnapshot?
Credit information if available
dashboard
OpenAIDashboardSnapshot?
Dashboard data for OpenAI providers
sourceLabel
String
required
Human-readable label for data source (e.g., “cli”, “web”)
strategyID
String
required
ID of strategy that produced this result
strategyKind
ProviderFetchKind
required
Kind of strategy that produced this result

ProviderFetchOutcome

Complete result of a fetch attempt including all strategy attempts:
result
Result<ProviderFetchResult, Error>
required
Success or failure result
attempts
[ProviderFetchAttempt]
required
History of all strategy attempts

ProviderFetchAttempt

strategyID
String
ID of attempted strategy
kind
ProviderFetchKind
Kind of strategy attempted
wasAvailable
Bool
Whether strategy reported itself as available
errorDescription
String?
Error message if fetch failed

Example: Implementing a Strategy

struct ClaudeCLIStrategy: ProviderFetchStrategy {
    let id: String = "claude.cli"
    let kind: ProviderFetchKind = .cli
    
    func isAvailable(_ context: ProviderFetchContext) async -> Bool {
        // Check if source mode allows CLI
        guard context.sourceMode == .auto || context.sourceMode == .cli else {
            return false
        }
        
        // Check if claude binary is installed
        guard let binary = TTYCommandRunner.which("claude") else {
            return false
        }
        
        return true
    }
    
    func fetch(_ context: ProviderFetchContext) async throws -> ProviderFetchResult {
        // Execute CLI command with timeout
        let runner = TTYCommandRunner(
            command: "claude status --json",
            timeout: context.webTimeout
        )
        
        let output = try await runner.run()
        let data = output.data(using: .utf8)!
        let parsed = try JSONDecoder().decode(ClaudeStatusResponse.self, from: data)
        
        // Convert to UsageSnapshot
        let usage = UsageSnapshot(
            primary: RateWindow(
                usedPercent: parsed.usedPercent,
                windowMinutes: 300,
                resetsAt: parsed.resetsAt,
                resetDescription: nil
            ),
            secondary: nil,
            updatedAt: Date(),
            identity: ProviderIdentitySnapshot(
                providerID: .claude,
                accountEmail: parsed.email,
                accountOrganization: nil,
                loginMethod: nil
            )
        )
        
        return makeResult(
            usage: usage,
            sourceLabel: "cli"
        )
    }
    
    func shouldFallback(on error: Error, context: ProviderFetchContext) -> Bool {
        // Fallback to web strategy if CLI fails
        return true
    }
}

Example: Multi-Strategy Pipeline

ProviderFetchPlan(
    sourceModes: [.auto, .web, .cli, .api],
    pipeline: ProviderFetchPipeline(
        resolveStrategies: { context in
            var strategies: [any ProviderFetchStrategy] = []
            
            // Prefer API if token available
            if context.settings?.hasAPIToken == true {
                strategies.append(ProviderAPIStrategy())
            }
            
            // Try CLI if installed
            strategies.append(ProviderCLIStrategy())
            
            // Fall back to web scraping
            strategies.append(ProviderWebStrategy())
            
            return strategies
        }
    )
)

Best Practices

Order strategies by reliability and speed: API tokens > CLI > Web scraping
  • Implement timeout bounds for all network and subprocess operations
  • Check context.sourceMode in isAvailable to respect CLI flags
  • Return true from shouldFallback for recoverable errors (missing binary, no cookies)
  • Return false from shouldFallback for auth failures (don’t spam other strategies)
  • Use descriptive strategyID values for debugging (e.g., “claude.cli.status”)
  • Provide helpful error messages that guide users to fix the issue

Build docs developers (and LLMs) love