227 lines
9.9 KiB
Swift
227 lines
9.9 KiB
Swift
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
|
|
}
|
|
}
|