68 lines · 2.1 KB · Swift
RawDownload
import Foundation public enum ProviderVersionDetector {    public static func codexVersion() -> String? {        guard let path = TTYCommandRunner.which("codex") else { return nil }        let candidates = [            ["--version"],            ["version"],            ["-v"],        ]        for args in candidates {            if let version = Self.run(path: path, args: args) { return version }        }        return nil    }     public static func geminiVersion() -> String? {        let env = ProcessInfo.processInfo.environment        guard let path = BinaryLocator.resolveGeminiBinary(env: env, loginPATH: nil)            ?? TTYCommandRunner.which("gemini") else { return nil }        let candidates = [            ["--version"],            ["-v"],        ]        for args in candidates {            if let version = Self.run(path: path, args: args) { return version }        }        return nil    }     private static func run(path: String, args: [String]) -> String? {        let proc = Process()        proc.executableURL = URL(fileURLWithPath: path)        proc.arguments = args        let out = Pipe()        proc.standardOutput = out        proc.standardError = Pipe()         do {            try proc.run()        } catch {            return nil        }         let deadline = Date().addingTimeInterval(2.0)        while proc.isRunning, Date() < deadline {            usleep(50000)        }        if proc.isRunning {            proc.terminate()            let killDeadline = Date().addingTimeInterval(0.5)            while proc.isRunning, Date() < killDeadline {                usleep(20000)            }            if proc.isRunning {                kill(proc.processIdentifier, SIGKILL)            }        }         let data = out.fileHandleForReading.readDataToEndOfFile()        guard proc.terminationStatus == 0,              let text = String(data: data, encoding: .utf8)?                  .split(whereSeparator: \.isNewline).first        else { return nil }        let trimmed = text.trimmingCharacters(in: .whitespacesAndNewlines)        return trimmed.isEmpty ? nil : trimmed    }}