import Foundation // MARK: - System info data models (read-only, no TCC) struct SystemInfoItem: Codable, Equatable, Sendable { let macos_version: String let build: String? let uname: String? let hw_model: String? let cpu_brand: String? let is_macos_26_plus: Bool? let speech_analyzer_expected: String? } struct SpeechApiStatusItem: Codable, Equatable, Sendable { let system: SystemInfoItem let swift_availability: [String: String]? // minimal stub let conclusion: String } enum SystemProviderError: Error, Equatable, Sendable { case unavailable(String) } protocol SystemInfoProviding: Sendable { func getSystemInfo() throws -> SystemInfoItem func getSpeechApiStatus() throws -> SpeechApiStatusItem } // Production provider - reads sw_vers / uname / sysctl, no permission needed struct ProductionSystemInfoProvider: SystemInfoProviding { func getSystemInfo() throws -> SystemInfoItem { let ver = ProcessInfo.processInfo.operatingSystemVersion let verString = "\(ver.majorVersion).\(ver.minorVersion).\(ver.patchVersion)" // Best-effort hw model / cpu / uname without spawning processes in Swift? Use sysctl/mib. var hwModel: String? = nil var cpuBrand: String? = nil var unameStr: String? = nil // Use ProcessInfo hostName as fallback for minimal // For hw.model, use sysctlbyname where possible - but keep simple fallback to avoid C interop complexity // We'll try reading via sysctl nametable via Foundation #if os(macOS) hwModel = sysctlString("hw.model") cpuBrand = sysctlString("machdep.cpu.brand_string") #endif let m = ver.majorVersion let is26 = m >= 26 let expected = is26 ? "likely available (macOS 26+)" : "not available - requires macOS 26+" return SystemInfoItem( macos_version: verString, build: nil, uname: unameStr, hw_model: hwModel, cpu_brand: cpuBrand, is_macos_26_plus: is26, speech_analyzer_expected: expected ) } func getSpeechApiStatus() throws -> SpeechApiStatusItem { let info = try getSystemInfo() let major = ProcessInfo.processInfo.operatingSystemVersion.majorVersion let conclusion: String if major >= 26 { conclusion = "macOS 26+ detected — SpeechAnalyzer/SpeechTranscriber should be available per Apple docs." } else { conclusion = "macOS \(info.macos_version) detected — SpeechAnalyzer requires macOS 26+." } return SpeechApiStatusItem(system: info, swift_availability: nil, conclusion: conclusion) } private func sysctlString(_ name: String) -> String? { var size = 0 let rc1 = sysctlbyname(name, nil, &size, nil, 0) if rc1 != 0 { return nil } var buffer = [CChar](repeating: 0, count: size) let rc2 = sysctlbyname(name, &buffer, &size, nil, 0) if rc2 != 0 { return nil } return String(cString: buffer) } }