import CodexBarCoreimport Commanderimport Foundation extension CodexBarCLI { static func runCookieRefresh(_ values: ParsedValues) async { let output = CLIOutputPreferences.from(values: values) let rawProvider = values.options["provider"]?.last let refreshAll = values.flags.contains("all") guard (rawProvider != nil) != refreshAll else { Self.exit( code: .failure, message: "Specify exactly one of --provider <name> or --all.", output: output, kind: .args) } #if os(macOS) let targets: [ProviderDescriptor] do { targets = try Self.cookieRefreshTargets(rawProvider: rawProvider, refreshAll: refreshAll) } catch { Self.exit( code: .failure, message: error.localizedDescription, output: output, kind: .args) } let config = Self.loadConfig(output: output) let tokenContext: TokenAccountCLIContext do { tokenContext = try TokenAccountCLIContext( selection: TokenAccountCLISelection(label: nil, index: nil, allAccounts: false), config: config, verbose: values.flags.contains("verbose")) } catch { Self.exit( code: .failure, message: "Could not prepare provider settings.", output: output, kind: .config) } let browserDetection = BrowserDetection() let allowKeychainPrompt = values.flags.contains("allowKeychainPrompt") let results = await Self.performCookieRefreshes( targets: targets, allowKeychainPrompt: allowKeychainPrompt, preflight: { descriptor in Self.cookieRefreshSkipResult(descriptor: descriptor, config: config) }, operation: { descriptor in await Self.refreshCookie( descriptor: descriptor, config: config, tokenContext: tokenContext, browserDetection: browserDetection) }) Self.printCookieRefreshResults(results, output: output) let hasErrors = results.contains(where: \.isFailure) Self.exit(code: hasErrors ? .failure : .success, output: output, kind: .runtime) #else Self.exit( code: .failure, message: "Cookie refresh is only supported on macOS.", output: output, kind: .args) #endif } #if os(macOS) static func cookieRefreshTargets( rawProvider: String?, refreshAll: Bool, descriptors: [ProviderDescriptor] = ProviderDescriptorRegistry.all) throws -> [ProviderDescriptor] { let supported = descriptors.filter { descriptor in descriptor.metadata.browserCookieOrder != nil && descriptor.fetchPlan.sourceModes.contains(.web) } if refreshAll { guard !supported.isEmpty else { throw CookieRefreshCommandError.noSupportedProviders } return supported } guard let rawProvider, let provider = ProviderDescriptorRegistry.cliNameMap[rawProvider.lowercased()] else { throw CookieRefreshCommandError.unknownProvider(rawProvider ?? "") } guard let descriptor = supported.first(where: { $0.id == provider }) else { throw CookieRefreshCommandError.unsupportedProvider(rawProvider) } return [descriptor] } static func performCookieRefreshes( targets: [ProviderDescriptor], allowKeychainPrompt: Bool, preflight: (ProviderDescriptor) -> CookieRefreshResult? = { _ in nil }, operation: (ProviderDescriptor) async -> CookieRefreshResult) async -> [CookieRefreshResult] { var results: [CookieRefreshResult] = [] results.reserveCapacity(targets.count) for descriptor in targets { if let result = preflight(descriptor) { results.append(result) continue } let browsers = descriptor.metadata.browserCookieOrder ?? [] let needsAcknowledgement = BrowserCookieAccessGate.requiresKeychainPromptAcknowledgement(for: browsers) guard !needsAcknowledgement || allowKeychainPrompt else { results.append(CookieRefreshResult( provider: descriptor.cli.name, status: .blocked, message: Self.keychainPromptAcknowledgementHint)) continue } let result: CookieRefreshResult = if allowKeychainPrompt { await BrowserCookieAccessGate.withExplicitRetry { await ProviderInteractionContext.$current.withValue(.userInitiated) { await operation(descriptor) } } } else { await ProviderInteractionContext.$current.withValue(.userInitiated) { await operation(descriptor) } } results.append(result) } return results } static func cookieRefreshFailure(provider: UsageProvider, error _: any Error) -> CookieRefreshResult { let descriptor = ProviderDescriptorRegistry.descriptor(for: provider) let promptCapableBrowsers = (descriptor.metadata.browserCookieOrder ?? []) .filter { BrowserCookieAccessGate.requiresKeychainPromptAcknowledgement(for: [$0]) } if let browser = promptCapableBrowsers.first, KeychainAccessGate.isDisabled { return CookieRefreshResult( provider: descriptor.cli.name, status: .failed, message: "\(browser.displayName) cookie decryption is disabled in CodexBar; " + "enable Keychain access and refresh.") } if let browser = promptCapableBrowsers.first(where: { BrowserCookieAccessGate.hasActiveDenial(for: $0) }) { return CookieRefreshResult( provider: descriptor.cli.name, status: .failed, message: "\(browser.displayName) cookie decryption was declined in Keychain; " + "retry with --allow-keychain-prompt.") } return CookieRefreshResult( provider: descriptor.cli.name, status: .failed, message: self.browserCookieAccessFailureHint) } static func cookieRefreshText(_ results: [CookieRefreshResult]) -> String { results.map { result in let marker = switch result.status { case .refreshed: "✅" case .skipped: "↷" case .blocked: "⚠️" case .failed: "❌" } return "\(result.provider): \(marker) \(result.message)" }.joined(separator: "\n") } private static let keychainPromptAcknowledgementHint = "Browser cookie decryption may open a macOS Keychain prompt. " + "Retry interactively with --allow-keychain-prompt to acknowledge it." private static let browserCookieAccessFailureHint = "No browser session cookie was refreshed. Sign in in a configured browser and retry. " + "If Keychain access was declined, CodexBar keeps the six-hour denial cooldown; " + "use --allow-keychain-prompt only for an explicit interactive retry." private static func refreshCookie( descriptor: ProviderDescriptor, config: CodexBarConfig, tokenContext: TokenAccountCLIContext, browserDetection: BrowserDetection) async -> CookieRefreshResult { let provider = descriptor.id if let result = Self.cookieRefreshSkipResult(descriptor: descriptor, config: config) { return result } return await Self.withCookieRefreshCacheSuppressed(provider: provider, providerName: descriptor.cli.name) { let environment = tokenContext.environment( base: ProcessInfo.processInfo.environment, provider: provider, account: nil) let context = ProviderFetchContext( runtime: .cli, sourceMode: .web, includeCredits: false, includeOptionalUsage: false, webTimeout: 60, webDebugDumpHTML: false, verbose: false, env: environment, settings: tokenContext.settingsSnapshot(for: provider, account: nil), fetcher: tokenContext.fetcher(base: UsageFetcher(), provider: provider, env: environment), claudeFetcher: ClaudeUsageFetcher(browserDetection: browserDetection), browserDetection: browserDetection) let outcome = await descriptor.fetchOutcome(context: context) return switch outcome.result { case .success: CookieRefreshResult( provider: descriptor.cli.name, status: .refreshed, message: "Browser cookie refreshed.") case let .failure(error): Self.cookieRefreshFailure(provider: provider, error: error) } } } static func withCookieRefreshCacheSuppressed( provider: UsageProvider, providerName: String, operation: () async -> CookieRefreshResult) async -> CookieRefreshResult { guard let gate = CookieHeaderCache.beginRefreshReadSuppression(provider: provider) else { return CookieRefreshResult( provider: providerName, status: .failed, message: "Cookie cache could not be read safely; no browser import was attempted.") } defer { CookieHeaderCache.endRefreshReadSuppression(gate) } let result = await operation() guard !result.isFailure else { return result } let commit = CookieHeaderCache.commitRefreshReadSuppression(gate) guard commit.stagedCount > 0, commit.committedCount == commit.stagedCount, commit.failedCount == 0 else { return CookieRefreshResult( provider: providerName, status: .failed, message: "Browser cookie validation succeeded, but the refreshed session could not be saved.") } return result } private static func cookieRefreshSkipResult( descriptor: ProviderDescriptor, config: CodexBarConfig) -> CookieRefreshResult? { switch config.providerConfig(for: descriptor.id)?.cookieSource ?? .auto { case .manual: CookieRefreshResult( provider: descriptor.cli.name, status: .skipped, message: "Browser refresh skipped because this provider uses a manual cookie.") case .off: CookieRefreshResult( provider: descriptor.cli.name, status: .skipped, message: "Browser refresh skipped because browser cookies are disabled for this provider.") case .auto: nil } } private static func printCookieRefreshResults( _ results: [CookieRefreshResult], output: CLIOutputPreferences) { switch output.format { case .text: if !output.jsonOnly { print(self.cookieRefreshText(results)) } case .json: printJSON(results, pretty: output.pretty) } } #endif} enum CookieRefreshStatus: String, Encodable { case refreshed case skipped case blocked case failed} struct CookieRefreshResult: Encodable { let provider: String let status: CookieRefreshStatus let message: String var isFailure: Bool { self.status == .blocked || self.status == .failed }} private enum CookieRefreshCommandError: LocalizedError { case noSupportedProviders case unknownProvider(String) case unsupportedProvider(String) var errorDescription: String? { switch self { case .noSupportedProviders: "No providers support browser cookie refresh on this platform." case let .unknownProvider(provider): "Unknown provider: \(provider)" case let .unsupportedProvider(provider): "\(provider) does not support browser cookie refresh." } }} struct CookieOptions: CommanderParsable { @Flag(names: [.short("v"), .long("verbose")], help: "Enable verbose logging") var verbose: Bool = false @Flag(name: .long("json-output"), help: "Emit machine-readable logs") var jsonOutput: Bool = false @Flag(name: .long("json"), help: "Output as JSON") var jsonShortcut: Bool = false @Flag(name: .long("json-only"), help: "Output as JSON only (no text)") var jsonOnly: Bool = false @Flag(name: .long("pretty"), help: "Pretty-print JSON output") var pretty: Bool = false @Option(name: .long("format"), help: "Output format: text | json") var format: OutputFormat? @Flag(name: .long("all"), help: "Refresh every browser-cookie provider") var all: Bool = false @Option(name: .long("provider"), help: "Refresh a specific browser-cookie provider") var provider: String? @Flag( name: .long("allow-keychain-prompt"), help: "Acknowledge that Chromium cookie decryption may open a macOS Keychain prompt") var allowKeychainPrompt: Bool = false}
import CodexBarCore
import Commander
import Foundation
extension CodexBarCLI {
static func runCookieRefresh(_ values: ParsedValues) async {
let output = CLIOutputPreferences.from(values: values)
let rawProvider = values.options["provider"]?.last
let refreshAll = values.flags.contains("all")
guard (rawProvider != nil) != refreshAll else {
Self.exit(
code: .failure,
message: "Specify exactly one of --provider <name> or --all.",
output: output,
kind: .args)
}
#if os(macOS)
let targets: [ProviderDescriptor]
do {
targets = try Self.cookieRefreshTargets(rawProvider: rawProvider, refreshAll: refreshAll)
} catch {
Self.exit(
code: .failure,
message: error.localizedDescription,
output: output,
kind: .args)
}
let config = Self.loadConfig(output: output)
let tokenContext: TokenAccountCLIContext
do {
tokenContext = try TokenAccountCLIContext(
selection: TokenAccountCLISelection(label: nil, index: nil, allAccounts: false),
config: config,
verbose: values.flags.contains("verbose"))
} catch {
Self.exit(
code: .failure,
message: "Could not prepare provider settings.",
output: output,
kind: .config)
}
let browserDetection = BrowserDetection()
let allowKeychainPrompt = values.flags.contains("allowKeychainPrompt")
let results = await Self.performCookieRefreshes(
targets: targets,
allowKeychainPrompt: allowKeychainPrompt,
preflight: { descriptor in
Self.cookieRefreshSkipResult(descriptor: descriptor, config: config)
},
operation: { descriptor in
await Self.refreshCookie(
descriptor: descriptor,
config: config,
tokenContext: tokenContext,
browserDetection: browserDetection)
})
Self.printCookieRefreshResults(results, output: output)
let hasErrors = results.contains(where: \.isFailure)
Self.exit(code: hasErrors ? .failure : .success, output: output, kind: .runtime)
#else
Self.exit(
code: .failure,
message: "Cookie refresh is only supported on macOS.",
output: output,
kind: .args)
#endif
}
#if os(macOS)
static func cookieRefreshTargets(
rawProvider: String?,
refreshAll: Bool,
descriptors: [ProviderDescriptor] = ProviderDescriptorRegistry.all) throws -> [ProviderDescriptor]
{
let supported = descriptors.filter { descriptor in
descriptor.metadata.browserCookieOrder != nil && descriptor.fetchPlan.sourceModes.contains(.web)
}
if refreshAll {
guard !supported.isEmpty else { throw CookieRefreshCommandError.noSupportedProviders }
return supported
}
guard let rawProvider,
let provider = ProviderDescriptorRegistry.cliNameMap[rawProvider.lowercased()]
else {
throw CookieRefreshCommandError.unknownProvider(rawProvider ?? "")
}
guard let descriptor = supported.first(where: { $0.id == provider }) else {
throw CookieRefreshCommandError.unsupportedProvider(rawProvider)
}
return [descriptor]
}
static func performCookieRefreshes(
targets: [ProviderDescriptor],
allowKeychainPrompt: Bool,
preflight: (ProviderDescriptor) -> CookieRefreshResult? = { _ in nil },
operation: (ProviderDescriptor) async -> CookieRefreshResult) async -> [CookieRefreshResult]
{
var results: [CookieRefreshResult] = []
results.reserveCapacity(targets.count)
for descriptor in targets {
if let result = preflight(descriptor) {
results.append(result)
continue
}
let browsers = descriptor.metadata.browserCookieOrder ?? []
let needsAcknowledgement = BrowserCookieAccessGate.requiresKeychainPromptAcknowledgement(for: browsers)
guard !needsAcknowledgement || allowKeychainPrompt else {
results.append(CookieRefreshResult(
provider: descriptor.cli.name,
status: .blocked,
message: Self.keychainPromptAcknowledgementHint))
continue
}
let result: CookieRefreshResult = if allowKeychainPrompt {
await BrowserCookieAccessGate.withExplicitRetry {
await ProviderInteractionContext.$current.withValue(.userInitiated) {
await operation(descriptor)
}
}
} else {
await ProviderInteractionContext.$current.withValue(.userInitiated) {
await operation(descriptor)
}
}
results.append(result)
}
return results
}
static func cookieRefreshFailure(provider: UsageProvider, error _: any Error) -> CookieRefreshResult {
let descriptor = ProviderDescriptorRegistry.descriptor(for: provider)
let promptCapableBrowsers = (descriptor.metadata.browserCookieOrder ?? [])
.filter { BrowserCookieAccessGate.requiresKeychainPromptAcknowledgement(for: [$0]) }
if let browser = promptCapableBrowsers.first, KeychainAccessGate.isDisabled {
return CookieRefreshResult(
provider: descriptor.cli.name,
status: .failed,
message: "\(browser.displayName) cookie decryption is disabled in CodexBar; " +
"enable Keychain access and refresh.")
}
if let browser = promptCapableBrowsers.first(where: { BrowserCookieAccessGate.hasActiveDenial(for: $0) }) {
return CookieRefreshResult(
provider: descriptor.cli.name,
status: .failed,
message: "\(browser.displayName) cookie decryption was declined in Keychain; " +
"retry with --allow-keychain-prompt.")
}
return CookieRefreshResult(
provider: descriptor.cli.name,
status: .failed,
message: self.browserCookieAccessFailureHint)
}
static func cookieRefreshText(_ results: [CookieRefreshResult]) -> String {
results.map { result in
let marker = switch result.status {
case .refreshed: "✅"
case .skipped: "↷"
case .blocked: "⚠️"
case .failed: "❌"
}
return "\(result.provider): \(marker) \(result.message)"
}.joined(separator: "\n")
}
private static let keychainPromptAcknowledgementHint =
"Browser cookie decryption may open a macOS Keychain prompt. " +
"Retry interactively with --allow-keychain-prompt to acknowledge it."
private static let browserCookieAccessFailureHint =
"No browser session cookie was refreshed. Sign in in a configured browser and retry. " +
"If Keychain access was declined, CodexBar keeps the six-hour denial cooldown; " +
"use --allow-keychain-prompt only for an explicit interactive retry."
private static func refreshCookie(
descriptor: ProviderDescriptor,
config: CodexBarConfig,
tokenContext: TokenAccountCLIContext,
browserDetection: BrowserDetection) async -> CookieRefreshResult
{
let provider = descriptor.id
if let result = Self.cookieRefreshSkipResult(descriptor: descriptor, config: config) {
return result
}
return await Self.withCookieRefreshCacheSuppressed(provider: provider, providerName: descriptor.cli.name) {
let environment = tokenContext.environment(
base: ProcessInfo.processInfo.environment,
provider: provider,
account: nil)
let context = ProviderFetchContext(
runtime: .cli,
sourceMode: .web,
includeCredits: false,
includeOptionalUsage: false,
webTimeout: 60,
webDebugDumpHTML: false,
verbose: false,
env: environment,
settings: tokenContext.settingsSnapshot(for: provider, account: nil),
fetcher: tokenContext.fetcher(base: UsageFetcher(), provider: provider, env: environment),
claudeFetcher: ClaudeUsageFetcher(browserDetection: browserDetection),
browserDetection: browserDetection)
let outcome = await descriptor.fetchOutcome(context: context)
return switch outcome.result {
case .success:
CookieRefreshResult(
provider: descriptor.cli.name,
status: .refreshed,
message: "Browser cookie refreshed.")
case let .failure(error):
Self.cookieRefreshFailure(provider: provider, error: error)
}
}
}
static func withCookieRefreshCacheSuppressed(
provider: UsageProvider,
providerName: String,
operation: () async -> CookieRefreshResult) async -> CookieRefreshResult
{
guard let gate = CookieHeaderCache.beginRefreshReadSuppression(provider: provider) else {
return CookieRefreshResult(
provider: providerName,
status: .failed,
message: "Cookie cache could not be read safely; no browser import was attempted.")
}
defer { CookieHeaderCache.endRefreshReadSuppression(gate) }
let result = await operation()
guard !result.isFailure else { return result }
let commit = CookieHeaderCache.commitRefreshReadSuppression(gate)
guard commit.stagedCount > 0,
commit.committedCount == commit.stagedCount,
commit.failedCount == 0
else {
return CookieRefreshResult(
provider: providerName,
status: .failed,
message: "Browser cookie validation succeeded, but the refreshed session could not be saved.")
}
return result
}
private static func cookieRefreshSkipResult(
descriptor: ProviderDescriptor,
config: CodexBarConfig) -> CookieRefreshResult?
{
switch config.providerConfig(for: descriptor.id)?.cookieSource ?? .auto {
case .manual:
CookieRefreshResult(
provider: descriptor.cli.name,
status: .skipped,
message: "Browser refresh skipped because this provider uses a manual cookie.")
case .off:
CookieRefreshResult(
provider: descriptor.cli.name,
status: .skipped,
message: "Browser refresh skipped because browser cookies are disabled for this provider.")
case .auto:
nil
}
}
private static func printCookieRefreshResults(
_ results: [CookieRefreshResult],
output: CLIOutputPreferences)
{
switch output.format {
case .text:
if !output.jsonOnly {
print(self.cookieRefreshText(results))
}
case .json:
printJSON(results, pretty: output.pretty)
}
}
#endif
}
enum CookieRefreshStatus: String, Encodable {
case refreshed
case skipped
case blocked
case failed
}
struct CookieRefreshResult: Encodable {
let provider: String
let status: CookieRefreshStatus
let message: String
var isFailure: Bool {
self.status == .blocked || self.status == .failed
}
}
private enum CookieRefreshCommandError: LocalizedError {
case noSupportedProviders
case unknownProvider(String)
case unsupportedProvider(String)
var errorDescription: String? {
switch self {
case .noSupportedProviders:
"No providers support browser cookie refresh on this platform."
case let .unknownProvider(provider):
"Unknown provider: \(provider)"
case let .unsupportedProvider(provider):
"\(provider) does not support browser cookie refresh."
}
}
}
struct CookieOptions: CommanderParsable {
@Flag(names: [.short("v"), .long("verbose")], help: "Enable verbose logging")
var verbose: Bool = false
@Flag(name: .long("json-output"), help: "Emit machine-readable logs")
var jsonOutput: Bool = false
@Flag(name: .long("json"), help: "Output as JSON")
var jsonShortcut: Bool = false
@Flag(name: .long("json-only"), help: "Output as JSON only (no text)")
var jsonOnly: Bool = false
@Flag(name: .long("pretty"), help: "Pretty-print JSON output")
var pretty: Bool = false
@Option(name: .long("format"), help: "Output format: text | json")
var format: OutputFormat?
@Flag(name: .long("all"), help: "Refresh every browser-cookie provider")
var all: Bool = false
@Option(name: .long("provider"), help: "Refresh a specific browser-cookie provider")
var provider: String?
@Flag(
name: .long("allow-keychain-prompt"),
help: "Acknowledge that Chromium cookie decryption may open a macOS Keychain prompt")
var allowKeychainPrompt: Bool = false
}