import Foundation @MainActor enum CLIInstaller { static func installedLocation() -> String? { self.installedLocation( searchPaths: CommandResolver.preferredPaths(), fileManager: .default) } static func installedLocation( searchPaths: [String], fileManager: FileManager) -> String? { for basePath in searchPaths { let candidate = URL(fileURLWithPath: basePath).appendingPathComponent("clawdbot").path var isDirectory: ObjCBool = false guard fileManager.fileExists(atPath: candidate, isDirectory: &isDirectory), !isDirectory.boolValue else { continue } guard fileManager.isExecutableFile(atPath: candidate) else { continue } return candidate } return nil } static func isInstalled() -> Bool { self.installedLocation() != nil } static func install(statusHandler: @escaping @Sendable (String) async -> Void) async { let expected = GatewayEnvironment.expectedGatewayVersion() await GatewayEnvironment.installGlobal(version: expected) { message in Task { @MainActor in await statusHandler(message) } } } }