1099 lines · 40.1 KB · Swift
RawDownload
import Foundation#if canImport(Darwin)import Darwin#elseif canImport(Glibc)import Glibc#elseif canImport(Musl)import Musl#endif public enum PathPurpose: Hashable, Sendable {    case rpc    case tty    case nodeTooling} public struct PathDebugSnapshot: Equatable, Sendable {    public let codexBinary: String?    public let claudeBinary: String?    public let geminiBinary: String?    public let effectivePATH: String    public let loginShellPATH: String?     public static let empty = PathDebugSnapshot(        codexBinary: nil,        claudeBinary: nil,        geminiBinary: nil,        effectivePATH: "",        loginShellPATH: nil)     public init(        codexBinary: String?,        claudeBinary: String?,        geminiBinary: String? = nil,        effectivePATH: String,        loginShellPATH: String?)    {        self.codexBinary = codexBinary        self.claudeBinary = claudeBinary        self.geminiBinary = geminiBinary        self.effectivePATH = effectivePATH        self.loginShellPATH = loginShellPATH    }} public enum BinaryLocator {    public static func resolveClaudeBinary(        env: [String: String] = ProcessInfo.processInfo.environment,        loginPATH: [String]? = LoginShellPathCache.shared.current,        commandV: (String, String?, TimeInterval, FileManager) -> String? = ShellCommandLocator.commandV,        aliasResolver: (String, String?, TimeInterval, FileManager, String) -> String? = ShellCommandLocator            .resolveAlias,        fileManager: FileManager = .default,        home: String = NSHomeDirectory()) -> String?    {        self.resolveBinary(            name: "claude",            overrideKey: "CLAUDE_CLI_PATH",            env: env,            loginPATH: loginPATH,            commandV: commandV,            aliasResolver: aliasResolver,            wellKnownPaths: self.claudeWellKnownPaths(home: home),            fileManager: fileManager,            home: home)    }     /// Well-known installation paths for the Claude CLI binary.    /// Covers Anthropic's native installer (`~/.local/bin`), the `claude migrate-installer`    /// self-updating location (`~/.claude/local`), the legacy per-user installer    /// (`~/.claude/bin`), Homebrew, and the macOS Terminal installer (cmux.app).    static func claudeWellKnownPaths(home: String) -> [String] {        [            "\(home)/.local/bin/claude",            "\(home)/.claude/local/claude",            "\(home)/.claude/bin/claude",            "/opt/homebrew/bin/claude",            "/usr/local/bin/claude",            "/Applications/cmux.app/Contents/Resources/bin/claude",        ]    }     public static func resolveAntigravityBinary(        env: [String: String] = ProcessInfo.processInfo.environment,        loginPATH: [String]? = LoginShellPathCache.shared.current,        commandV: (String, String?, TimeInterval, FileManager) -> String? = ShellCommandLocator.commandV,        aliasResolver: (String, String?, TimeInterval, FileManager, String) -> String? = ShellCommandLocator            .resolveAlias,        fileManager: FileManager = .default,        home: String = NSHomeDirectory()) -> String?    {        self.resolveBinary(            name: "agy",            overrideKey: "ANTIGRAVITY_CLI_PATH",            env: env,            loginPATH: loginPATH,            commandV: commandV,            aliasResolver: aliasResolver,            wellKnownPaths: [                "\(home)/.local/bin/agy",                "/opt/homebrew/bin/agy",                "/usr/local/bin/agy",            ],            fileManager: fileManager,            home: home)    }     public static func resolveCodexBinary(        env: [String: String] = ProcessInfo.processInfo.environment,        loginPATH: [String]? = LoginShellPathCache.shared.current,        commandV: (String, String?, TimeInterval, FileManager) -> String? = ShellCommandLocator.commandV,        aliasResolver: (String, String?, TimeInterval, FileManager, String) -> String? = ShellCommandLocator            .resolveAlias,        launchCandidateFilter: (String, FileManager) -> Bool = CodexLaunchPreflight.isLaunchCandidateAllowed,        fileManager: FileManager = .default,        home: String = NSHomeDirectory()) -> String?    {        self.resolveBinary(            name: "codex",            overrideKey: "CODEX_CLI_PATH",            env: env,            loginPATH: loginPATH,            commandV: commandV,            aliasResolver: aliasResolver,            wellKnownPaths: self.codexWellKnownPaths(home: home),            launchCandidateFilter: launchCandidateFilter,            fileManager: fileManager,            home: home)    }     /// Well-known installation paths for the signed Codex desktop app CLI.    /// Keep these after PATH lookups, but use them as a safe fallback when a PATH shim is blocked.    static func codexWellKnownPaths(home: String) -> [String] {        #if os(macOS)        [            "\(home)/Applications/Codex.app/Contents/Resources/codex",            "/Applications/Codex.app/Contents/Resources/codex",        ]        #else        []        #endif    }     public static func resolveGeminiBinary(        env: [String: String] = ProcessInfo.processInfo.environment,        loginPATH: [String]? = LoginShellPathCache.shared.current,        commandV: (String, String?, TimeInterval, FileManager) -> String? = ShellCommandLocator.commandV,        aliasResolver: (String, String?, TimeInterval, FileManager, String) -> String? = ShellCommandLocator            .resolveAlias,        fileManager: FileManager = .default,        home: String = NSHomeDirectory()) -> String?    {        self.resolveBinary(            name: "gemini",            overrideKey: "GEMINI_CLI_PATH",            env: env,            loginPATH: loginPATH,            commandV: commandV,            aliasResolver: aliasResolver,            fileManager: fileManager,            home: home)    }     public static func resolveGrokBinary(        env: [String: String] = ProcessInfo.processInfo.environment,        loginPATH: [String]? = LoginShellPathCache.shared.current,        commandV: (String, String?, TimeInterval, FileManager) -> String? = ShellCommandLocator.commandV,        aliasResolver: (String, String?, TimeInterval, FileManager, String) -> String? = ShellCommandLocator            .resolveAlias,        fileManager: FileManager = .default,        home: String = NSHomeDirectory()) -> String?    {        self.resolveBinary(            name: "grok",            overrideKey: "GROK_CLI_PATH",            env: env,            loginPATH: loginPATH,            commandV: commandV,            aliasResolver: aliasResolver,            wellKnownPaths: self.grokWellKnownPaths(home: home),            fileManager: fileManager,            home: home)    }     public static func resolveAmpBinary(        env: [String: String] = ProcessInfo.processInfo.environment,        loginPATH: [String]? = LoginShellPathCache.shared.current,        commandV: (String, String?, TimeInterval, FileManager) -> String? = ShellCommandLocator.commandV,        aliasResolver: (String, String?, TimeInterval, FileManager, String) -> String? = ShellCommandLocator            .resolveAlias,        fileManager: FileManager = .default,        home: String = NSHomeDirectory()) -> String?    {        self.resolveBinary(            name: "amp",            overrideKey: "AMP_CLI_PATH",            env: env,            loginPATH: loginPATH,            commandV: commandV,            aliasResolver: aliasResolver,            wellKnownPaths: self.ampWellKnownPaths(home: home),            fileManager: fileManager,            home: home)    }     static func ampWellKnownPaths(home: String) -> [String] {        [            "\(home)/.local/bin/amp",            "\(home)/.amp/bin/amp",            "/opt/homebrew/bin/amp",            "/usr/local/bin/amp",        ]    }     /// Well-known install locations for the Grok Build CLI binary.    /// Covers the installer's default (`~/.grok/bin/grok`) and the symlinks it sometimes    /// creates into `~/.local/bin` and `/usr/local/bin`.    static func grokWellKnownPaths(home: String) -> [String] {        [            "\(home)/.grok/bin/grok",            "\(home)/.local/bin/grok",            "/usr/local/bin/grok",            "/opt/homebrew/bin/grok",        ]    }     public static func resolveAWSBinary(        env: [String: String] = ProcessInfo.processInfo.environment,        loginPATH: [String]? = LoginShellPathCache.shared.current,        commandV: (String, String?, TimeInterval, FileManager) -> String? = ShellCommandLocator.commandV,        aliasResolver: (String, String?, TimeInterval, FileManager, String) -> String? = ShellCommandLocator            .resolveAlias,        fileManager: FileManager = .default,        home: String = NSHomeDirectory()) -> String?    {        self.resolveBinary(            name: "aws",            overrideKey: "AWS_CLI_PATH",            env: env,            loginPATH: loginPATH,            commandV: commandV,            aliasResolver: aliasResolver,            wellKnownPaths: self.awsWellKnownPaths(home: home),            fileManager: fileManager,            home: home)    }     /// Well-known install locations for the AWS CLI v2 (`aws`).    /// Covers Homebrew (Apple Silicon + Intel) and the per-user pip/uv install path.    static func awsWellKnownPaths(home: String) -> [String] {        [            "/opt/homebrew/bin/aws",            "/usr/local/bin/aws",            "\(home)/.local/bin/aws",        ]    }     public static func resolveAuggieBinary(        env: [String: String] = ProcessInfo.processInfo.environment,        loginPATH: [String]? = LoginShellPathCache.shared.current,        commandV: (String, String?, TimeInterval, FileManager) -> String? = ShellCommandLocator.commandV,        aliasResolver: (String, String?, TimeInterval, FileManager, String) -> String? = ShellCommandLocator            .resolveAlias,        fileManager: FileManager = .default,        home: String = NSHomeDirectory()) -> String?    {        self.resolveBinary(            name: "auggie",            overrideKey: "AUGGIE_CLI_PATH",            env: env,            loginPATH: loginPATH,            commandV: commandV,            aliasResolver: aliasResolver,            fileManager: fileManager,            home: home)    }     // swiftlint:disable function_parameter_count    private static func resolveBinary(        name: String,        overrideKey: String,        env: [String: String],        loginPATH: [String]?,        commandV: (String, String?, TimeInterval, FileManager) -> String?,        aliasResolver: (String, String?, TimeInterval, FileManager, String) -> String?,        wellKnownPaths: [String] = [],        launchCandidateFilter: (String, FileManager) -> Bool = { _, _ in true },        fileManager: FileManager,        home: String) -> String?    {        // swiftlint:enable function_parameter_count        // 1) Explicit override        if let override = env[overrideKey], fileManager.isExecutableFile(atPath: override) {            return override        }         // 2) Login-shell PATH (captured once per launch)        if let loginPATH,           let pathHit = self.find(               name,               in: loginPATH,               fileManager: fileManager,               launchCandidateFilter: launchCandidateFilter)        {            return pathHit        }         // 3) Existing PATH        if let existingPATH = env["PATH"],           let pathHit = self.find(               name,               in: existingPATH.split(separator: ":").map(String.init),               fileManager: fileManager,               launchCandidateFilter: launchCandidateFilter)        {            return pathHit        }         // 4) Well-known installation paths (e.g. Homebrew, cmux.app bundle, ~/.claude/bin).        // Prefer these before shell probing to avoid running interactive shell init for common installs.        for candidate in wellKnownPaths            where fileManager.isExecutableFile(atPath: candidate) && launchCandidateFilter(candidate, fileManager)        {            return candidate        }         // 5) Interactive login shell lookup (captures nvm/fnm/mise paths from .zshrc/.bashrc)        if let shellHit = commandV(name, env["SHELL"], 2.0, fileManager),           fileManager.isExecutableFile(atPath: shellHit),           launchCandidateFilter(shellHit, fileManager)        {            return shellHit        }         // 5b) Alias fallback (login shell); only attempt after all standard lookups fail.        if let aliasHit = aliasResolver(name, env["SHELL"], 2.0, fileManager, home),           fileManager.isExecutableFile(atPath: aliasHit),           launchCandidateFilter(aliasHit, fileManager)        {            return aliasHit        }         // 6) Minimal fallback        let fallback = ["/usr/bin", "/bin", "/usr/sbin", "/sbin"]        if let pathHit = self.find(            name,            in: fallback,            fileManager: fileManager,            launchCandidateFilter: launchCandidateFilter)        {            return pathHit        }         return nil    }     private static func find(        _ binary: String,        in paths: [String],        fileManager: FileManager,        launchCandidateFilter: (String, FileManager) -> Bool = { _, _ in true }) -> String?    {        for path in paths where !path.isEmpty {            let candidate = "\(path.hasSuffix("/") ? String(path.dropLast()) : path)/\(binary)"            if fileManager.isExecutableFile(atPath: candidate), launchCandidateFilter(candidate, fileManager) {                return candidate            }        }        return nil    }} public enum CodexLaunchPreflight {    public static func isLaunchCandidateAllowed(path: String, fileManager: FileManager = .default) -> Bool {        #if os(macOS)        self.isLaunchCandidateAllowed(            path: path,            fileManager: fileManager,            hasExtendedAttribute: self.hasExtendedAttribute,            spctlAssessment: { self.spctlAssessment(path: $0) },            isMachOExecutable: self.isMachOExecutable)        #else        _ = path        _ = fileManager        return true        #endif    }     #if os(macOS)    static func isLaunchCandidateAllowed(        path: String,        fileManager: FileManager,        hasExtendedAttribute: (String, String) -> Bool,        spctlAssessment: (String) -> String?,        isMachOExecutable: (String) -> Bool) -> Bool    {        let realPath = URL(fileURLWithPath: path).resolvingSymlinksInPath().path        let pathsToCheck = [path, realPath] + self.nativeCodexExecutableCandidates(            for: realPath,            fileManager: fileManager)         for candidate in Set(pathsToCheck) where hasExtendedAttribute(candidate, "com.apple.malware") {            return false        }         let hasQuarantine = Set(pathsToCheck).contains { hasExtendedAttribute($0, "com.apple.quarantine") }        guard let native = pathsToCheck.first(where: isMachOExecutable) else {            return !hasQuarantine        }         guard let assessment = spctlAssessment(native)        else {            return !hasQuarantine        }         return !self.isExplicitlyBlockedAssessment(assessment, path: native)    }     private static func nativeCodexExecutableCandidates(for path: String, fileManager: FileManager) -> [String] {        let url = URL(fileURLWithPath: path)        guard url.lastPathComponent == "codex.js" else { return [] }         let packageRoot = url.deletingLastPathComponent().deletingLastPathComponent()        return self.npmNativeCodexCandidates(packageRoot: packageRoot)            .map(\.path)            .filter { fileManager.isExecutableFile(atPath: $0) }    }     private static func npmNativeCodexCandidates(packageRoot: URL) -> [URL] {        guard let target = self.darwinCodexTarget else { return [] }        let optionalPackage = packageRoot            .appendingPathComponent("node_modules")            .appendingPathComponent("@openai")            .appendingPathComponent(target.packageName)         return [            optionalPackage,            packageRoot,        ].map {            $0.appendingPathComponent("vendor")                .appendingPathComponent(target.triple)                .appendingPathComponent("codex")                .appendingPathComponent("codex")        }    }     private static var darwinCodexTarget: (packageName: String, triple: String)? {        #if arch(arm64)        ("codex-darwin-arm64", "aarch64-apple-darwin")        #elseif arch(x86_64)        ("codex-darwin-x64", "x86_64-apple-darwin")        #else        nil        #endif    }     private static func hasExtendedAttribute(path: String, name: String) -> Bool {        path.withCString { pathPointer in            name.withCString { namePointer in                getxattr(pathPointer, namePointer, nil, 0, 0, 0) >= 0            }        }    }     private static func isMachOExecutable(path: String) -> Bool {        guard let handle = try? FileHandle(forReadingFrom: URL(fileURLWithPath: path)) else { return false }        defer { try? handle.close() }         guard let data = try? handle.read(upToCount: 4), data.count == 4 else { return false }        let bytes = [UInt8](data)        return bytes == [0xFE, 0xED, 0xFA, 0xCE] ||            bytes == [0xCE, 0xFA, 0xED, 0xFE] ||            bytes == [0xFE, 0xED, 0xFA, 0xCF] ||            bytes == [0xCF, 0xFA, 0xED, 0xFE] ||            bytes == [0xCA, 0xFE, 0xBA, 0xBE] ||            bytes == [0xCA, 0xFE, 0xBA, 0xBF]    }     private static func spctlAssessment(path: String, timeout: TimeInterval = 2.0) -> String? {        let spctlPath = "/usr/sbin/spctl"        guard FileManager.default.isExecutableFile(atPath: spctlPath) else { return nil }         let process = Process()        process.executableURL = URL(fileURLWithPath: spctlPath)        process.arguments = ["--assess", "--type", "execute", "--verbose=4", path]         let output = Pipe()        process.standardOutput = output        process.standardError = output         let finished = DispatchSemaphore(value: 0)        process.terminationHandler = { _ in finished.signal() }         do {            try process.run()        } catch {            return nil        }         if finished.wait(timeout: .now() + timeout) != .success {            process.terminate()

Lines 1–500 of 1099.