174 lines · 7.4 KB · Swift
RawDownload
import AppKitimport CodexBarCoreimport SweetCookieKit private enum KeychainPromptMessage {    static let browserCookie =        "CodexBar will ask macOS Keychain for “%@” so it can decrypt browser cookies " +        "and authenticate your account. Click OK to continue."     static let claudeOAuth =        "CodexBar will ask macOS Keychain for the Claude Code OAuth token " +        "so it can fetch your Claude usage. Click OK to continue."    static let codexCookie =        "CodexBar will ask macOS Keychain for your OpenAI cookie header " +        "so it can fetch Codex dashboard extras. Click OK to continue."    static let claudeCookie =        "CodexBar will ask macOS Keychain for your Claude cookie header " +        "so it can fetch Claude web usage. Click OK to continue."    static let cursorCookie =        "CodexBar will ask macOS Keychain for your Cursor cookie header " +        "so it can fetch usage. Click OK to continue."    static let openCodeCookie =        "CodexBar will ask macOS Keychain for your OpenCode cookie header " +        "so it can fetch usage. Click OK to continue."    static let factoryCookie =        "CodexBar will ask macOS Keychain for your Factory cookie header " +        "so it can fetch usage. Click OK to continue."    static let zaiToken =        "CodexBar will ask macOS Keychain for your z.ai API token " +        "so it can fetch usage. Click OK to continue."    static let syntheticToken =        "CodexBar will ask macOS Keychain for your Synthetic API key " +        "so it can fetch usage. Click OK to continue."    static let copilotToken =        "CodexBar will ask macOS Keychain for your GitHub Copilot token " +        "so it can fetch usage. Click OK to continue."    static let kimiToken =        "CodexBar will ask macOS Keychain for your Kimi auth token " +        "so it can fetch usage. Click OK to continue."    static let kimiK2Token =        "CodexBar will ask macOS Keychain for your Kimi K2 API key " +        "so it can fetch usage. Click OK to continue."    static let minimaxCookie =        "CodexBar will ask macOS Keychain for your MiniMax cookie header " +        "so it can fetch usage. Click OK to continue."    static let minimaxToken =        "CodexBar will ask macOS Keychain for your MiniMax API token " +        "so it can fetch usage. Click OK to continue."    static let augmentCookie =        "CodexBar will ask macOS Keychain for your Augment cookie header " +        "so it can fetch usage. Click OK to continue."    static let ampCookie =        "CodexBar will ask macOS Keychain for your Amp cookie header " +        "so it can fetch usage. Click OK to continue."} enum KeychainPromptCoordinator {    private static let promptLock = NSLock()    private static let log = CodexBarLog.logger(LogCategories.keychainPrompt)     static func install() {        KeychainPromptHandler.handler = { context in            self.presentKeychainPrompt(context)        }        BrowserCookieKeychainPromptHandler.handler = { context in            self.presentBrowserCookiePrompt(context)        }        self.disableKeychainForUnbundledExecutableIfNeeded()    }     private static let unbundledExecutableCheckLock = NSLock()    private nonisolated(unsafe) static var didCheckUnbundledExecutable = false     static func disableKeychainForUnbundledExecutableIfNeeded() {        self.unbundledExecutableCheckLock.lock()        guard !self.didCheckUnbundledExecutable else {            self.unbundledExecutableCheckLock.unlock()            return        }        self.didCheckUnbundledExecutable = true        self.unbundledExecutableCheckLock.unlock()         let executablePath = Bundle.main.executableURL?.path ?? ""        guard Self.isUnbundledCodexBarExecutable(executablePath) else { return }        KeychainAccessGate.forceDisabledForProcess(reason: "unbundled-executable")        Self.log.warning(            "Unbundled CodexBar executable detected; disabling keychain access to avoid repeated prompts",            metadata: ["doc": "docs/DEVELOPMENT_SETUP.md"])    }     static func isUnbundledCodexBarExecutable(_ executablePath: String) -> Bool {        guard executablePath.hasPrefix("/") else { return false }        let executableURL = URL(fileURLWithPath: executablePath).standardizedFileURL        return executableURL.lastPathComponent == "CodexBar"            && !executableURL.pathComponents.contains(where: { $0.hasSuffix(".app") })    }     private static func presentKeychainPrompt(_ context: KeychainPromptContext) {        let (title, message) = self.keychainCopy(for: context)        self.log.info("Keychain prompt requested", metadata: ["kind": "\(context.kind)"])        self.presentAlert(title: title, message: message)    }     private static func presentBrowserCookiePrompt(_ context: BrowserCookieKeychainPromptContext) {        let title = L("Keychain Access Required")        let message = L(            KeychainPromptMessage.browserCookie,            context.label)        self.log.info("Browser cookie keychain prompt requested", metadata: ["label": context.label])        self.presentAlert(title: title, message: message)    }     private static func keychainCopy(for context: KeychainPromptContext) -> (title: String, message: String) {        let title = L("Keychain Access Required")        switch context.kind {        case .claudeOAuth:            return (title, L(KeychainPromptMessage.claudeOAuth))        case .codexCookie:            return (title, L(KeychainPromptMessage.codexCookie))        case .claudeCookie:            return (title, L(KeychainPromptMessage.claudeCookie))        case .cursorCookie:            return (title, L(KeychainPromptMessage.cursorCookie))        case .opencodeCookie:            return (title, L(KeychainPromptMessage.openCodeCookie))        case .factoryCookie:            return (title, L(KeychainPromptMessage.factoryCookie))        case .zaiToken:            return (title, L(KeychainPromptMessage.zaiToken))        case .syntheticToken:            return (title, L(KeychainPromptMessage.syntheticToken))        case .copilotToken:            return (title, L(KeychainPromptMessage.copilotToken))        case .kimiToken:            return (title, L(KeychainPromptMessage.kimiToken))        case .kimiK2Token:            return (title, L(KeychainPromptMessage.kimiK2Token))        case .minimaxCookie:            return (title, L(KeychainPromptMessage.minimaxCookie))        case .minimaxToken:            return (title, L(KeychainPromptMessage.minimaxToken))        case .augmentCookie:            return (title, L(KeychainPromptMessage.augmentCookie))        case .ampCookie:            return (title, L(KeychainPromptMessage.ampCookie))        }    }     private static func presentAlert(title: String, message: String) {        self.promptLock.lock()        defer { self.promptLock.unlock() }         if Thread.isMainThread {            MainActor.assumeIsolated {                self.showAlert(title: title, message: message)            }            return        }        DispatchQueue.main.sync {            MainActor.assumeIsolated {                self.showAlert(title: title, message: message)            }        }    }     @MainActor    private static func showAlert(title: String, message: String) {        let alert = NSAlert()        alert.messageText = L(title)        alert.informativeText = L(message)        alert.addButton(withTitle: L("OK"))        _ = alert.runModal()    }}