import CodexBarCore import Commander #if canImport(Darwin) import Darwin #elseif canImport(Glibc) import Glibc #elseif canImport(Musl) import Musl #endif import Foundation #if canImport(FoundationNetworking) import FoundationNetworking #endif @main enum CodexBarCLI { static func main() async { let rawArgv = Array(CommandLine.arguments.dropFirst()) let argv = Self.effectiveArgv(rawArgv) let outputPreferences = CLIOutputPreferences.from(argv: argv) // Fast path: global help/version before building descriptors. if let helpIndex = argv.firstIndex(where: { $0 == "-h" || $0 == "--help" }) { let command = helpIndex == 0 ? argv.dropFirst().first : argv.first Self.printHelp(for: command) } if argv.contains("-V") || argv.contains("--version") { Self.printVersion() } let program = Program(descriptors: Self.commandDescriptors()) do { let invocation = try program.resolve(argv: argv) Self.bootstrapLogging(path: invocation.path, values: invocation.parsedValues) switch invocation.path { case ["cards"]: let signalMonitor = CLITerminationSignalMonitor { signalNumber in CLITerminationSignalMonitor.terminateActiveHelpersAndReraise(signalNumber) } defer { signalMonitor.cancel() } await self.runCards(invocation.parsedValues) case ["usage"]: let signalMonitor = CLITerminationSignalMonitor { signalNumber in CLITerminationSignalMonitor.terminateActiveHelpersAndReraise(signalNumber) } defer { signalMonitor.cancel() } await self.runUsage(invocation.parsedValues) case ["cost"]: await self.runCost(invocation.parsedValues) case ["sessions", "list"]: await self.runSessions(invocation.parsedValues) case ["sessions", "focus"]: await self.runSessionsFocus(invocation.parsedValues) case ["serve"]: await self.runServe(invocation.parsedValues) case ["config", "validate"]: self.runConfigValidate(invocation.parsedValues) case ["config", "dump"]: self.runConfigDump(invocation.parsedValues) case ["config", "providers"]: self.runConfigProviders(invocation.parsedValues) case ["config", "enable"]: self.runConfigSetProviderEnabled(invocation.parsedValues, enabled: true) case ["config", "disable"]: self.runConfigSetProviderEnabled(invocation.parsedValues, enabled: false) case ["config", "set-api-key"]: self.runConfigSetAPIKey(invocation.parsedValues) case ["cache", "clear"]: self.runCacheClear(invocation.parsedValues) case ["diagnose"]: let signalMonitor = CLITerminationSignalMonitor { signalNumber in CLITerminationSignalMonitor.terminateActiveHelpersAndReraise(signalNumber) } defer { signalMonitor.cancel() } await self.runDiagnose(invocation.parsedValues) default: Self.exit( code: .failure, message: "Unknown command", output: outputPreferences, kind: .args) } } catch let error as CommanderProgramError { Self.exit(code: .failure, message: error.description, output: outputPreferences, kind: .args) } catch { Self.exit(code: .failure, message: error.localizedDescription, output: outputPreferences, kind: .runtime) } } private static func commandDescriptors() -> [CommandDescriptor] { let cardsSignature = CommandSignature.describe(CardsOptions()) let usageSignature = CommandSignature.describe(UsageOptions()) let costSignature = CommandSignature.describe(CostOptions()) let sessionsSignature = CommandSignature.describe(SessionsOptions()) let sessionsFocusSignature = CommandSignature.describe(SessionsFocusOptions()) let serveSignature = CommandSignature.describe(ServeOptions()) let configSignature = CommandSignature.describe(ConfigOptions()) let configProviderToggleSignature = CommandSignature.describe(ConfigProviderToggleOptions()) let configSetAPIKeySignature = CommandSignature.describe(ConfigSetAPIKeyOptions()) let cacheSignature = CommandSignature.describe(CacheOptions()) let diagnoseSignature = CommandSignature.describe(DiagnoseOptions()) return [ CommandDescriptor( name: "cards", abstract: "Print usage as a terminal card grid", discussion: nil, signature: cardsSignature), CommandDescriptor( name: "usage", abstract: "Print usage as text or JSON", discussion: nil, signature: usageSignature), CommandDescriptor( name: "cost", abstract: "Print local cost usage as text or JSON", discussion: nil, signature: costSignature), CommandDescriptor( name: "sessions", abstract: "List live Codex and Claude Code sessions", discussion: nil, signature: CommandSignature(), subcommands: [ CommandDescriptor( name: "list", abstract: "List live Codex and Claude Code sessions", discussion: nil, signature: sessionsSignature), CommandDescriptor( name: "focus", abstract: "Focus the window for a session", discussion: nil, signature: sessionsFocusSignature), ], defaultSubcommandName: "list"), CommandDescriptor( name: "serve", abstract: "Serve usage and cost JSON over localhost HTTP", discussion: nil, signature: serveSignature), CommandDescriptor( name: "config", abstract: "Config utilities", discussion: nil, signature: CommandSignature(), subcommands: [ CommandDescriptor( name: "validate", abstract: "Validate config file", discussion: nil, signature: configSignature), CommandDescriptor( name: "dump", abstract: "Print normalized config JSON", discussion: nil, signature: configSignature), CommandDescriptor( name: "providers", abstract: "List provider enablement", discussion: nil, signature: configSignature), CommandDescriptor( name: "enable", abstract: "Enable a provider", discussion: nil, signature: configProviderToggleSignature), CommandDescriptor( name: "disable", abstract: "Disable a provider", discussion: nil, signature: configProviderToggleSignature), CommandDescriptor( name: "set-api-key", abstract: "Store a provider API key", discussion: nil, signature: configSetAPIKeySignature), ], defaultSubcommandName: "validate"), CommandDescriptor( name: "cache", abstract: "Cache management", discussion: nil, signature: CommandSignature(), subcommands: [ CommandDescriptor( name: "clear", abstract: "Clear cached data (cookies, cost, or all)", discussion: nil, signature: cacheSignature), ], defaultSubcommandName: "clear"), CommandDescriptor( name: "diagnose", abstract: "Run provider diagnostic and emit safe JSON export", discussion: nil, signature: diagnoseSignature), ] } // MARK: - Helpers private static func bootstrapLogging(path: [String], values: ParsedValues) { CodexBarLog.bootstrapIfNeeded(self.loggingConfiguration(path: path, values: values)) } static func loggingConfiguration(path: [String], values: ParsedValues) -> CodexBarLog.Configuration { let isJSON = values.flags.contains("jsonOutput") || values.flags.contains("jsonOnly") let verbose = values.flags.contains("verbose") let rawLevel = values.options["logLevel"]?.last let level = Self.resolvedLogLevel(verbose: verbose, rawLevel: rawLevel) let destination: CodexBarLog.Destination = path == ["diagnose"] ? .discard : .stderr return .init(destination: destination, level: level, json: isJSON) } static func resolvedLogLevel(verbose: Bool, rawLevel: String?) -> CodexBarLog.Level { CodexBarLog.parseLevel(rawLevel) ?? (verbose ? .debug : .error) } static func effectiveArgv(_ argv: [String]) -> [String] { guard let first = argv.first else { return ["usage"] } if first.hasPrefix("-") { return ["usage"] + argv } return argv } }