152 lines · 5.7 KB · Swift
RawDownload
import CodexBarCoreimport Commanderimport Foundation extension CodexBarCLI {    static func runCacheClear(_ values: ParsedValues) {        let output = CLIOutputPreferences.from(values: values)        let cookies = values.flags.contains("cookies")        let cost = values.flags.contains("cost")        let all = values.flags.contains("all")        let rawProvider = values.options["provider"]?.last         let clearCookies = cookies || all        let clearCost = cost || all         if !clearCookies, !clearCost {            Self.exit(                code: .failure,                message: "Specify --cookies, --cost, or --all.",                output: output,                kind: .args)        }        if let error = Self.cacheClearProviderScopeError(rawProvider: rawProvider, clearCost: clearCost) {            Self.exit(code: .failure, message: error, output: output, kind: .args)        }         var results: [CacheClearResult] = []         if clearCookies {            if let rawProvider {                if let provider = ProviderDescriptorRegistry.cliNameMap[rawProvider.lowercased()] {                    let summary = CookieHeaderCache.clearAllScopesDetailed(provider: provider)                    results.append(CacheClearResult(                        cache: "cookies",                        provider: provider.rawValue,                        cleared: summary.clearedCount,                        error: Self.cookieClearError(failedCount: summary.failedCount)))                } else {                    Self.exit(                        code: .failure,                        message: "Unknown provider: \(rawProvider)",                        output: output,                        kind: .args)                }            } else {                let summary = CookieHeaderCache.clearAllDetailed()                results.append(CacheClearResult(                    cache: "cookies",                    provider: nil,                    cleared: summary.clearedCount,                    error: Self.cookieClearError(failedCount: summary.failedCount)))            }        }         if clearCost {            let fm = FileManager.default            let cacheDir = Self.costUsageCacheDirectory(fileManager: fm)            var cleared = 0            var costError: String?            if fm.fileExists(atPath: cacheDir.path) {                do {                    try fm.removeItem(at: cacheDir)                    cleared = 1                } catch {                    costError = error.localizedDescription                }            }            results.append(CacheClearResult(cache: "cost", provider: nil, cleared: cleared, error: costError))        }         switch output.format {        case .text:            for result in results {                let scope = result.provider ?? "all providers"                if let error = result.error {                    print("\(result.cache): failed to clear (\(scope)) - \(error)")                } else if result.cleared > 0 {                    print("\(result.cache): cleared (\(scope))")                } else {                    print("\(result.cache): nothing to clear (\(scope))")                }            }        case .json:            Self.printJSON(results, pretty: output.pretty)        }         let hasErrors = results.contains(where: { $0.error != nil })        Self.exit(code: hasErrors ? .failure : .success, output: output, kind: .runtime)    }     static func cacheClearProviderScopeError(rawProvider: String?, clearCost: Bool) -> String? {        guard rawProvider != nil, clearCost else { return nil }        return "--provider only scopes cookie caches. Use --cookies --provider <name>, or omit --provider."    }     private static func cookieClearError(failedCount: Int) -> String? {        guard failedCount > 0 else { return nil }        return "Cookie cache cleanup failed for \(failedCount) operation\(failedCount == 1 ? "" : "s")"    }} struct CacheOptions: 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     @Option(name: .long("log-level"), help: "Set log level (trace|verbose|debug|info|warning|error|critical)")    var logLevel: String?     @Option(name: .long("format"), help: "Output format: text | json")    var format: OutputFormat?     @Flag(name: .long("json"), help: "")    var jsonShortcut: Bool = false     @Flag(name: .long("json-only"), help: "Emit JSON only (suppress non-JSON output)")    var jsonOnly: Bool = false     @Flag(name: .long("pretty"), help: "Pretty-print JSON output")    var pretty: Bool = false     @Flag(name: .long("cookies"), help: "Clear browser cookie caches")    var cookies: Bool = false     @Flag(name: .long("cost"), help: "Clear cost usage caches")    var cost: Bool = false     @Flag(name: .long("all"), help: "Clear all caches")    var all: Bool = false     @Option(name: .long("provider"), help: "Clear cache for a specific provider only")    var provider: String?} private struct CacheClearResult: Encodable {    let cache: String    let provider: String?    let cleared: Int    var error: String?} extension CodexBarCLI {    /// Mirrors the cost usage cache directory used by the app (UsageStore.costUsageCacheDirectory).    static func costUsageCacheDirectory(fileManager: FileManager = .default) -> URL {        let root = fileManager.urls(for: .cachesDirectory, in: .userDomainMask).first!        return root            .appendingPathComponent("CodexBar", isDirectory: true)            .appendingPathComponent("cost-usage", isDirectory: true)    }}