spb/forge-studio Public
The Instruments of LLM training — a native macOS cockpit for Forge. Train language models from scratch on Apple Silicon without a terminal.
Swift 95.7%
Shell 4.3%
1// Author: Simon-Pierre Boucher — contact@spboucher.ai2//3// Finds and validates the forge binary: a candidate is accepted only if4// `forge info` runs and reports a device — the same probe surfaces the GPU5// name for the header. Paths persist in UserDefaults.6import Foundation78struct ForgeValidation: Equatable, Sendable {9 var version: String // device line, e.g. "Apple M5 Max"10 var binaryPath: String11}1213enum ForgeBinaryLocator {14 static let defaultsKey = "forge.binary.path"15 static let workspaceKey = "forge.workspace.path"1617 static var savedBinaryURL: URL? {18 UserDefaults.standard.string(forKey: defaultsKey).map { URL(fileURLWithPath: $0) }19 }2021 static var savedWorkspaceURL: URL? {22 UserDefaults.standard.string(forKey: workspaceKey).map { URL(fileURLWithPath: $0) }23 }2425 static func save(binary: URL) {26 UserDefaults.standard.set(binary.path, forKey: defaultsKey)27 }2829 static func save(workspace: URL) {30 UserDefaults.standard.set(workspace.path, forKey: workspaceKey)31 }3233 /// Candidate locations, most specific first.34 static func candidates() -> [URL] {35 var urls: [URL] = []36 if let saved = savedBinaryURL { urls.append(saved) }37 let home = FileManager.default.homeDirectoryForCurrentUser38 urls.append(home.appendingPathComponent("Desktop/forge/build/forge"))39 urls.append(URL(fileURLWithPath: "/usr/local/bin/forge"))40 return urls.filter { FileManager.default.isExecutableFile(atPath: $0.path) }41 }4243 /// Runs `forge info` and parses the device line.44 static func validate(binary: URL) async -> Result<ForgeValidation, Error> {45 let runner = ProcessRunner()46 do {47 let (lines, exit) = try await runner.launch(48 executable: binary, arguments: ["info"],49 currentDirectory: binary.deletingLastPathComponent())50 var device = ""51 for await (line, _) in lines where line.hasPrefix("device: ") {52 device = String(line.dropFirst("device: ".count))53 }54 let status = await exit.value55 guard status.code == 0, !device.isEmpty else {56 throw NSError(domain: "ForgeStudio", code: 1, userInfo: [57 NSLocalizedDescriptionKey:58 "forge info a échoué (code \(status.code)). Vérifiez que forge.metallib est à côté du binaire.",59 ])60 }61 return .success(ForgeValidation(version: device, binaryPath: binary.path))62 } catch {63 return .failure(error)64 }65 }66}67