← back to Exo
fmt: add swift formatting
55463a980623e25117e17f00b969546ea8b78037 · 2026-01-08 12:48:00 +0000 · Jake Hillion
Swift code currently has no auto formatting. Add `swift-format` to the
`treefmt-nix` config to get this formatted.
As our existing Swift code uses 4-space formatting instead of the
default 2-space, also adds a custom `.swift-format
Test plan:
- CI
Files touched
A .swift-formatM app/EXO/EXO/ContentView.swiftM app/EXO/EXO/EXOApp.swiftM app/EXO/EXO/ExoProcessController.swiftM app/EXO/EXO/Models/ClusterState.swiftM app/EXO/EXO/Services/BugReportService.swiftM app/EXO/EXO/Services/ClusterStateService.swiftM app/EXO/EXO/Services/LocalNetworkChecker.swiftM app/EXO/EXO/Services/NetworkSetupHelper.swiftM app/EXO/EXO/Services/NetworkStatusService.swiftM app/EXO/EXO/ViewModels/InstanceViewModel.swiftM app/EXO/EXO/ViewModels/NodeViewModel.swiftM app/EXO/EXO/Views/InstanceRowView.swiftM app/EXO/EXO/Views/NodeDetailView.swiftM app/EXO/EXO/Views/NodeRowView.swiftM app/EXO/EXO/Views/TopologyMiniView.swiftM app/EXO/EXOTests/EXOTests.swiftM flake.nix
Diff
commit 55463a980623e25117e17f00b969546ea8b78037
Author: Jake Hillion <jake@hillion.co.uk>
Date: Thu Jan 8 12:48:00 2026 +0000
fmt: add swift formatting
Swift code currently has no auto formatting. Add `swift-format` to the
`treefmt-nix` config to get this formatted.
As our existing Swift code uses 4-space formatting instead of the
default 2-space, also adds a custom `.swift-format
Test plan:
- CI
---
.swift-format | 6 +
app/EXO/EXO/ContentView.swift | 38 +++--
app/EXO/EXO/EXOApp.swift | 12 +-
app/EXO/EXO/ExoProcessController.swift | 7 +-
app/EXO/EXO/Models/ClusterState.swift | 19 ++-
app/EXO/EXO/Services/BugReportService.swift | 79 ++++++----
app/EXO/EXO/Services/ClusterStateService.swift | 14 +-
app/EXO/EXO/Services/LocalNetworkChecker.swift | 5 +-
app/EXO/EXO/Services/NetworkSetupHelper.swift | 193 ++++++++++++------------
app/EXO/EXO/Services/NetworkStatusService.swift | 10 +-
app/EXO/EXO/ViewModels/InstanceViewModel.swift | 12 +-
app/EXO/EXO/ViewModels/NodeViewModel.swift | 24 ++-
app/EXO/EXO/Views/InstanceRowView.swift | 21 ++-
app/EXO/EXO/Views/NodeDetailView.swift | 1 -
app/EXO/EXO/Views/NodeRowView.swift | 1 -
app/EXO/EXO/Views/TopologyMiniView.swift | 17 ++-
app/EXO/EXOTests/EXOTests.swift | 1 +
flake.nix | 17 ++-
18 files changed, 279 insertions(+), 198 deletions(-)
diff --git a/.swift-format b/.swift-format
new file mode 100644
index 00000000..71b901ac
--- /dev/null
+++ b/.swift-format
@@ -0,0 +1,6 @@
+{
+ "version": 1,
+ "indentation": {
+ "spaces": 4
+ }
+}
diff --git a/app/EXO/EXO/ContentView.swift b/app/EXO/EXO/ContentView.swift
index 2125f0f9..4107a938 100644
--- a/app/EXO/EXO/ContentView.swift
+++ b/app/EXO/EXO/ContentView.swift
@@ -70,10 +70,12 @@ struct ContentView: View {
.font(.caption)
.fontWeight(.semibold)
}
- Text("Device discovery won't work. To fix:\n1. Quit EXO\n2. Open System Settings → Privacy & Security → Local Network\n3. Toggle EXO off, then back on\n4. Relaunch EXO")
- .font(.caption2)
- .foregroundColor(.secondary)
- .fixedSize(horizontal: false, vertical: true)
+ Text(
+ "Device discovery won't work. To fix:\n1. Quit EXO\n2. Open System Settings → Privacy & Security → Local Network\n3. Toggle EXO off, then back on\n4. Relaunch EXO"
+ )
+ .font(.caption2)
+ .foregroundColor(.secondary)
+ .fixedSize(horizontal: false, vertical: true)
Button {
openLocalNetworkSettings()
} label: {
@@ -96,14 +98,18 @@ struct ContentView: View {
private func openLocalNetworkSettings() {
// Open Privacy & Security settings - Local Network section
- if let url = URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_LocalNetwork") {
+ if let url = URL(
+ string: "x-apple.systempreferences:com.apple.preference.security?Privacy_LocalNetwork")
+ {
NSWorkspace.shared.open(url)
}
}
private var topologySection: some View {
Group {
- if let topology = stateService.latestSnapshot?.topologyViewModel(localNodeId: stateService.localNodeId), !topology.nodes.isEmpty {
+ if let topology = stateService.latestSnapshot?.topologyViewModel(
+ localNodeId: stateService.localNodeId), !topology.nodes.isEmpty
+ {
TopologyMiniView(topology: topology)
}
}
@@ -137,8 +143,10 @@ struct ContentView: View {
VStack(alignment: .leading, spacing: 4) {
HStack {
VStack(alignment: .leading) {
- Text("\(overview.usedRam, specifier: "%.0f") / \(overview.totalRam, specifier: "%.0f") GB")
- .font(.headline)
+ Text(
+ "\(overview.usedRam, specifier: "%.0f") / \(overview.totalRam, specifier: "%.0f") GB"
+ )
+ .font(.headline)
Text("Memory")
.font(.caption)
.foregroundColor(.secondary)
@@ -262,7 +270,9 @@ struct ContentView: View {
}
}
- private func controlButton(title: String, tint: Color = .primary, action: @escaping () -> Void) -> some View {
+ private func controlButton(title: String, tint: Color = .primary, action: @escaping () -> Void)
+ -> some View
+ {
HoverButton(title: title, tint: tint, trailingSystemImage: nil, action: action)
}
@@ -293,9 +303,12 @@ struct ContentView: View {
Button {
isExpanded.wrappedValue.toggle()
} label: {
- Label(isExpanded.wrappedValue ? "Hide" : "Show All", systemImage: isExpanded.wrappedValue ? "chevron.up" : "chevron.down")
- .labelStyle(.titleAndIcon)
- .contentTransition(.symbolEffect(.replace))
+ Label(
+ isExpanded.wrappedValue ? "Hide" : "Show All",
+ systemImage: isExpanded.wrappedValue ? "chevron.up" : "chevron.down"
+ )
+ .labelStyle(.titleAndIcon)
+ .contentTransition(.symbolEffect(.replace))
}
.buttonStyle(.plain)
.font(.caption2)
@@ -588,4 +601,3 @@ private struct HoverButton: View {
.onHover { isHovering = $0 }
}
}
-
diff --git a/app/EXO/EXO/EXOApp.swift b/app/EXO/EXO/EXOApp.swift
index af1be1b5..82bb5c7b 100644
--- a/app/EXO/EXO/EXOApp.swift
+++ b/app/EXO/EXO/EXOApp.swift
@@ -8,9 +8,9 @@
import AppKit
import CoreImage
import CoreImage.CIFilterBuiltins
+import ServiceManagement
import Sparkle
import SwiftUI
-import ServiceManagement
import UserNotifications
import os.log
@@ -113,7 +113,7 @@ struct EXOApp: App {
filter.contrast = 0.9
guard let output = filter.outputImage,
- let rendered = ciContext.createCGImage(output, from: output.extent)
+ let rendered = ciContext.createCGImage(output, from: output.extent)
else {
return nil
}
@@ -126,7 +126,8 @@ struct EXOApp: App {
do {
try SMAppService.mainApp.register()
} catch {
- Logger().error("Failed to register EXO for launch at login: \(error.localizedDescription)")
+ Logger().error(
+ "Failed to register EXO for launch at login: \(error.localizedDescription)")
}
}
}
@@ -151,7 +152,7 @@ final class SparkleUpdater: NSObject, ObservableObject {
center.requestAuthorization(options: [.alert, .sound]) { _, _ in }
controller.updater.automaticallyChecksForUpdates = true
controller.updater.automaticallyDownloadsUpdates = false
- controller.updater.updateCheckInterval = 900 // 15 minutes
+ controller.updater.updateCheckInterval = 900 // 15 minutes
DispatchQueue.main.asyncAfter(deadline: .now() + 5) { [weak controller] in
controller?.updater.checkForUpdatesInBackground()
}
@@ -218,7 +219,8 @@ private final class ExoNotificationDelegate: NSObject, UNUserNotificationCenterD
func userNotificationCenter(
_ center: UNUserNotificationCenter,
willPresent notification: UNNotification,
- withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void
+ withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) ->
+ Void
) {
completionHandler([.banner, .list, .sound])
}
diff --git a/app/EXO/EXO/ExoProcessController.swift b/app/EXO/EXO/ExoProcessController.swift
index 431a6d68..69d8e02a 100644
--- a/app/EXO/EXO/ExoProcessController.swift
+++ b/app/EXO/EXO/ExoProcessController.swift
@@ -31,7 +31,8 @@ final class ExoProcessController: ObservableObject {
@Published private(set) var launchCountdownSeconds: Int?
@Published var customNamespace: String = {
return UserDefaults.standard.string(forKey: customNamespaceKey) ?? ""
- }() {
+ }()
+ {
didSet {
UserDefaults.standard.set(customNamespace, forKey: customNamespaceKey)
}
@@ -221,7 +222,9 @@ final class ExoProcessController: ObservableObject {
if let tag = Bundle.main.infoDictionary?["EXOBuildTag"] as? String, !tag.isEmpty {
return tag
}
- if let short = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String, !short.isEmpty {
+ if let short = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String,
+ !short.isEmpty
+ {
return short
}
return "dev"
diff --git a/app/EXO/EXO/Models/ClusterState.swift b/app/EXO/EXO/Models/ClusterState.swift
index 8f750e7e..b82bf7ea 100644
--- a/app/EXO/EXO/Models/ClusterState.swift
+++ b/app/EXO/EXO/Models/ClusterState.swift
@@ -16,10 +16,13 @@ struct ClusterState: Decodable {
self.instances = rawInstances.mapValues(\.instance)
self.runners = try container.decode([String: RunnerStatusSummary].self, forKey: .runners)
self.nodeProfiles = try container.decode([String: NodeProfile].self, forKey: .nodeProfiles)
- let rawTasks = try container.decodeIfPresent([String: TaggedTask].self, forKey: .tasks) ?? [:]
+ let rawTasks =
+ try container.decodeIfPresent([String: TaggedTask].self, forKey: .tasks) ?? [:]
self.tasks = rawTasks.compactMapValues(\.task)
self.topology = try container.decodeIfPresent(Topology.self, forKey: .topology)
- let rawDownloads = try container.decodeIfPresent([String: [TaggedNodeDownload]].self, forKey: .downloads) ?? [:]
+ let rawDownloads =
+ try container.decodeIfPresent([String: [TaggedNodeDownload]].self, forKey: .downloads)
+ ?? [:]
self.downloads = rawDownloads.mapValues { $0.compactMap(\.status) }
}
@@ -41,7 +44,8 @@ private struct TaggedInstance: Decodable {
let payloads = try container.decode([String: ClusterInstancePayload].self)
guard let entry = payloads.first else {
throw DecodingError.dataCorrupted(
- DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Empty instance payload")
+ DecodingError.Context(
+ codingPath: decoder.codingPath, debugDescription: "Empty instance payload")
)
}
self.instance = ClusterInstance(
@@ -77,7 +81,8 @@ struct RunnerStatusSummary: Decodable {
let payloads = try container.decode([String: RunnerStatusDetail].self)
guard let entry = payloads.first else {
throw DecodingError.dataCorrupted(
- DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Empty runner status payload")
+ DecodingError.Context(
+ codingPath: decoder.codingPath, debugDescription: "Empty runner status payload")
)
}
self.status = entry.key
@@ -257,7 +262,9 @@ struct ChatCompletionTaskParameters: Decodable, Equatable {
func promptPreview() -> String? {
guard let messages else { return nil }
- if let userMessage = messages.last(where: { $0.role?.lowercased() == "user" && ($0.content?.isEmpty == false) }) {
+ if let userMessage = messages.last(where: {
+ $0.role?.lowercased() == "user" && ($0.content?.isEmpty == false)
+ }) {
return userMessage.content
}
return messages.last?.content
@@ -365,5 +372,3 @@ extension ClusterState {
func availableModels() -> [ModelOption] { [] }
}
-
-
diff --git a/app/EXO/EXO/Services/BugReportService.swift b/app/EXO/EXO/Services/BugReportService.swift
index b578deb9..e1ee9031 100644
--- a/app/EXO/EXO/Services/BugReportService.swift
+++ b/app/EXO/EXO/Services/BugReportService.swift
@@ -66,7 +66,7 @@ struct BugReportService {
("\(prefix)exo.log", logData),
("\(prefix)state.json", stateData),
("\(prefix)events.json", eventsData),
- ("\(prefix)report.json", reportJSON)
+ ("\(prefix)report.json", reportJSON),
]
let uploader = try S3Uploader(config: credentials)
@@ -78,7 +78,8 @@ struct BugReportService {
)
}
- return BugReportOutcome(success: true, message: "Bug Report sent. Thank you for helping to improve EXO 1.0.")
+ return BugReportOutcome(
+ success: true, message: "Bug Report sent. Thank you for helping to improve EXO 1.0.")
}
private func loadCredentials() throws -> AWSConfig {
@@ -100,7 +101,8 @@ struct BugReportService {
private func captureIfconfig() async throws -> String {
let result = runCommand(["/sbin/ifconfig"])
guard result.exitCode == 0 else {
- throw BugReportError.collectFailed(result.error.isEmpty ? "ifconfig failed" : result.error)
+ throw BugReportError.collectFailed(
+ result.error.isEmpty ? "ifconfig failed" : result.error)
}
return result.output
}
@@ -113,7 +115,9 @@ struct BugReportService {
}
private func readThunderboltBridgeDisabled() -> Bool? {
- let result = runCommand(["/usr/sbin/networksetup", "-getnetworkserviceenabled", "Thunderbolt Bridge"])
+ let result = runCommand([
+ "/usr/sbin/networksetup", "-getnetworkserviceenabled", "Thunderbolt Bridge",
+ ])
guard result.exitCode == 0 else { return nil }
let output = result.output.lowercased()
if output.contains("enabled") {
@@ -156,7 +160,8 @@ struct BugReportService {
request.timeoutInterval = 5
do {
let (data, response) = try await URLSession.shared.data(for: request)
- guard let http = response as? HTTPURLResponse, (200..<300).contains(http.statusCode) else {
+ guard let http = response as? HTTPURLResponse, (200..<300).contains(http.statusCode)
+ else {
return nil
}
return data
@@ -182,7 +187,7 @@ struct BugReportService {
"system": system,
"exo_version": exo.version as Any,
"exo_commit": exo.commit as Any,
- "report_type": isManual ? "manual" : "automated"
+ "report_type": isManual ? "manual" : "automated",
]
return try? JSONSerialization.data(withJSONObject: payload, options: [.prettyPrinted])
}
@@ -213,10 +218,13 @@ struct BugReportService {
let user = safeRunCommand(["/usr/bin/whoami"])
let consoleUser = safeRunCommand(["/usr/bin/stat", "-f%Su", "/dev/console"])
let uptime = safeRunCommand(["/usr/bin/uptime"])
- let diskRoot = safeRunCommand(["/bin/sh", "-c", "/bin/df -h / | awk 'NR==2 {print $1, $2, $3, $4, $5}'"])
+ let diskRoot = safeRunCommand([
+ "/bin/sh", "-c", "/bin/df -h / | awk 'NR==2 {print $1, $2, $3, $4, $5}'",
+ ])
let interfacesList = safeRunCommand(["/usr/sbin/ipconfig", "getiflist"])
- let interfacesAndIPs = interfacesList?
+ let interfacesAndIPs =
+ interfacesList?
.split(whereSeparator: { $0 == " " || $0 == "\n" })
.compactMap { iface -> [String: Any]? in
let name = String(iface)
@@ -227,7 +235,8 @@ struct BugReportService {
} ?? []
let wifiSSID: String?
- let airportPath = "/System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport"
+ let airportPath =
+ "/System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport"
if FileManager.default.isExecutableFile(atPath: airportPath) {
wifiSSID = safeRunCommand([airportPath, "-I"]).flatMap(parseWifiSSID)
} else {
@@ -255,7 +264,7 @@ struct BugReportService {
"disk_root": diskRoot as Any,
"interfaces_and_ips": interfacesAndIPs,
"ipconfig_getiflist": interfacesList as Any,
- "wifi_ssid": wifiSSID as Any
+ "wifi_ssid": wifiSSID as Any,
]
}
@@ -313,7 +322,8 @@ struct BugReportService {
for line in airportOutput.split(separator: "\n") {
let trimmed = line.trimmingCharacters(in: .whitespaces)
if trimmed.hasPrefix("SSID:") {
- return trimmed.replacingOccurrences(of: "SSID:", with: "").trimmingCharacters(in: .whitespaces)
+ return trimmed.replacingOccurrences(of: "SSID:", with: "").trimmingCharacters(
+ in: .whitespaces)
}
}
return nil
@@ -358,7 +368,7 @@ private struct DebugInfo {
func toDictionary() -> [String: Any] {
[
"name": name,
- "ip": ip as Any
+ "ip": ip as Any,
]
}
}
@@ -366,7 +376,7 @@ private struct DebugInfo {
func toDictionary() -> [String: Any] {
[
"thunderbolt_bridge_disabled": thunderboltBridgeDisabled as Any,
- "interfaces": interfaces.map { $0.toDictionary() }
+ "interfaces": interfaces.map { $0.toDictionary() },
]
}
}
@@ -398,7 +408,7 @@ private struct S3Uploader {
let headers = [
"host": host,
"x-amz-content-sha256": payloadHash,
- "x-amz-date": amzDate
+ "x-amz-date": amzDate,
]
let canonicalRequest = buildCanonicalRequest(
@@ -414,18 +424,20 @@ private struct S3Uploader {
canonicalRequestHash: sha256Hex(canonicalRequest.data(using: .utf8) ?? Data())
)
- let signingKey = deriveKey(secret: config.secretKey, dateStamp: dateStamp, region: config.region, service: "s3")
+ let signingKey = deriveKey(
+ secret: config.secretKey, dateStamp: dateStamp, region: config.region, service: "s3")
let signature = hmacHex(key: signingKey, data: Data(stringToSign.utf8))
let signedHeaders = "host;x-amz-content-sha256;x-amz-date"
let authorization = """
-AWS4-HMAC-SHA256 Credential=\(config.accessKey)/\(dateStamp)/\(config.region)/s3/aws4_request, SignedHeaders=\(signedHeaders), Signature=\(signature)
-"""
+ AWS4-HMAC-SHA256 Credential=\(config.accessKey)/\(dateStamp)/\(config.region)/s3/aws4_request, SignedHeaders=\(signedHeaders), Signature=\(signature)
+ """
var request = URLRequest(url: url)
request.httpMethod = "PUT"
request.httpBody = body
- request.setValue(headers["x-amz-content-sha256"], forHTTPHeaderField: "x-amz-content-sha256")
+ request.setValue(
+ headers["x-amz-content-sha256"], forHTTPHeaderField: "x-amz-content-sha256")
request.setValue(headers["x-amz-date"], forHTTPHeaderField: "x-amz-date")
request.setValue(host, forHTTPHeaderField: "Host")
request.setValue(authorization, forHTTPHeaderField: "Authorization")
@@ -433,7 +445,7 @@ AWS4-HMAC-SHA256 Credential=\(config.accessKey)/\(dateStamp)/\(config.region)/s3
let (data, response) = try await URLSession.shared.data(for: request)
guard let http = response as? HTTPURLResponse, (200..<300).contains(http.statusCode) else {
let statusText = (response as? HTTPURLResponse)?.statusCode ?? -1
- _ = data // ignore response body for UX
+ _ = data // ignore response body for UX
throw BugReportError.uploadFailed("HTTP status \(statusText)")
}
}
@@ -447,7 +459,8 @@ AWS4-HMAC-SHA256 Credential=\(config.accessKey)/\(dateStamp)/\(config.region)/s3
let canonicalURI = encodePath(url.path)
let canonicalQuery = url.query ?? ""
let sortedHeaders = headers.sorted { $0.key < $1.key }
- let canonicalHeaders = sortedHeaders
+ let canonicalHeaders =
+ sortedHeaders
.map { "\($0.key.lowercased()):\($0.value)\n" }
.joined()
let signedHeaders = sortedHeaders.map { $0.key.lowercased() }.joined(separator: ";")
@@ -458,15 +471,17 @@ AWS4-HMAC-SHA256 Credential=\(config.accessKey)/\(dateStamp)/\(config.region)/s3
canonicalQuery,
canonicalHeaders,
signedHeaders,
- payloadHash
+ payloadHash,
].joined(separator: "\n")
}
private func encodePath(_ path: String) -> String {
- return path
+ return
+ path
.split(separator: "/")
.map { segment in
- segment.addingPercentEncoding(withAllowedCharacters: Self.rfc3986) ?? String(segment)
+ segment.addingPercentEncoding(withAllowedCharacters: Self.rfc3986)
+ ?? String(segment)
}
.joined(separator: "/")
.prependSlashIfNeeded()
@@ -478,14 +493,16 @@ AWS4-HMAC-SHA256 Credential=\(config.accessKey)/\(dateStamp)/\(config.region)/s3
canonicalRequestHash: String
) -> String {
"""
-AWS4-HMAC-SHA256
-\(amzDate)
-\(dateStamp)/\(config.region)/s3/aws4_request
-\(canonicalRequestHash)
-"""
+ AWS4-HMAC-SHA256
+ \(amzDate)
+ \(dateStamp)/\(config.region)/s3/aws4_request
+ \(canonicalRequestHash)
+ """
}
- private func deriveKey(secret: String, dateStamp: String, region: String, service: String) -> Data {
+ private func deriveKey(secret: String, dateStamp: String, region: String, service: String)
+ -> Data
+ {
let kDate = hmac(key: Data(("AWS4" + secret).utf8), data: Data(dateStamp.utf8))
let kRegion = hmac(key: kDate, data: Data(region.utf8))
let kService = hmac(key: kRegion, data: Data(service.utf8))
@@ -528,8 +545,8 @@ AWS4-HMAC-SHA256
}()
}
-private extension String {
- func prependSlashIfNeeded() -> String {
+extension String {
+ fileprivate func prependSlashIfNeeded() -> String {
if hasPrefix("/") {
return self
}
diff --git a/app/EXO/EXO/Services/ClusterStateService.swift b/app/EXO/EXO/Services/ClusterStateService.swift
index 65097e8c..27c7ffdf 100644
--- a/app/EXO/EXO/Services/ClusterStateService.swift
+++ b/app/EXO/EXO/Services/ClusterStateService.swift
@@ -57,7 +57,9 @@ final class ClusterStateService: ObservableObject {
var request = URLRequest(url: url)
request.cachePolicy = .reloadIgnoringLocalCacheData
let (data, response) = try await session.data(for: request)
- guard let httpResponse = response as? HTTPURLResponse, (200..<300).contains(httpResponse.statusCode) else {
+ guard let httpResponse = response as? HTTPURLResponse,
+ (200..<300).contains(httpResponse.statusCode)
+ else {
return
}
if let nodeId = try? decoder.decode(String.self, from: data) {
@@ -113,7 +115,9 @@ final class ClusterStateService: ObservableObject {
}
}
- func launchInstance(modelId: String, sharding: String, instanceMeta: String, minNodes: Int) async {
+ func launchInstance(modelId: String, sharding: String, instanceMeta: String, minNodes: Int)
+ async
+ {
do {
var request = URLRequest(url: baseURL.appendingPathComponent("instance"))
request.httpMethod = "POST"
@@ -122,7 +126,7 @@ final class ClusterStateService: ObservableObject {
"model_id": modelId,
"sharding": sharding,
"instance_meta": instanceMeta,
- "min_nodes": minNodes
+ "min_nodes": minNodes,
]
request.httpBody = try JSONSerialization.data(withJSONObject: payload, options: [])
let (_, response) = try await session.data(for: request)
@@ -143,7 +147,9 @@ final class ClusterStateService: ObservableObject {
do {
let url = baseURL.appendingPathComponent("models")
let (data, response) = try await session.data(from: url)
- guard let httpResponse = response as? HTTPURLResponse, (200..<300).contains(httpResponse.statusCode) else {
+ guard let httpResponse = response as? HTTPURLResponse,
+ (200..<300).contains(httpResponse.statusCode)
+ else {
throw URLError(.badServerResponse)
}
let list = try decoder.decode(ModelListResponse.self, from: data)
diff --git a/app/EXO/EXO/Services/LocalNetworkChecker.swift b/app/EXO/EXO/Services/LocalNetworkChecker.swift
index 5b3a48d2..20dfd4d8 100644
--- a/app/EXO/EXO/Services/LocalNetworkChecker.swift
+++ b/app/EXO/EXO/Services/LocalNetworkChecker.swift
@@ -110,8 +110,9 @@ final class LocalNetworkChecker: ObservableObject {
}
case .failed(let error):
let errorStr = "\(error)"
- if errorStr.contains("65") || errorStr.contains("EHOSTUNREACH") ||
- errorStr.contains("permission") || errorStr.contains("denied") {
+ if errorStr.contains("65") || errorStr.contains("EHOSTUNREACH")
+ || errorStr.contains("permission") || errorStr.contains("denied")
+ {
resumeOnce(.notWorking(reason: "Permission denied"))
} else {
resumeOnce(.notWorking(reason: "Failed: \(error.localizedDescription)"))
diff --git a/app/EXO/EXO/Services/NetworkSetupHelper.swift b/app/EXO/EXO/Services/NetworkSetupHelper.swift
index f62d44b1..c73e744b 100644
--- a/app/EXO/EXO/Services/NetworkSetupHelper.swift
+++ b/app/EXO/EXO/Services/NetworkSetupHelper.swift
@@ -5,61 +5,62 @@ import os.log
enum NetworkSetupHelper {
private static let logger = Logger(subsystem: "io.exo.EXO", category: "NetworkSetup")
private static let daemonLabel = "io.exo.networksetup"
- private static let scriptDestination = "/Library/Application Support/EXO/disable_bridge_enable_dhcp.sh"
+ private static let scriptDestination =
+ "/Library/Application Support/EXO/disable_bridge_enable_dhcp.sh"
private static let plistDestination = "/Library/LaunchDaemons/io.exo.networksetup.plist"
private static let requiredStartInterval: Int = 1791
private static let setupScript = """
-#!/usr/bin/env bash
+ #!/usr/bin/env bash
-set -euo pipefail
+ set -euo pipefail
-PREFS="/Library/Preferences/SystemConfiguration/preferences.plist"
+ PREFS="/Library/Preferences/SystemConfiguration/preferences.plist"
-# Remove bridge0 interface
-ifconfig bridge0 &>/dev/null && {
- ifconfig bridge0 | grep -q 'member' && {
- ifconfig bridge0 | awk '/member/ {print $2}' | xargs -n1 ifconfig bridge0 deletem 2>/dev/null || true
- }
- ifconfig bridge0 destroy 2>/dev/null || true
-}
+ # Remove bridge0 interface
+ ifconfig bridge0 &>/dev/null && {
+ ifconfig bridge0 | grep -q 'member' && {
+ ifconfig bridge0 | awk '/member/ {print $2}' | xargs -n1 ifconfig bridge0 deletem 2>/dev/null || true
+ }
+ ifconfig bridge0 destroy 2>/dev/null || true
+ }
-# Remove Thunderbolt Bridge from VirtualNetworkInterfaces in preferences.plist
-/usr/libexec/PlistBuddy -c "Delete :VirtualNetworkInterfaces:Bridge:bridge0" "$PREFS" 2>/dev/null || true
+ # Remove Thunderbolt Bridge from VirtualNetworkInterfaces in preferences.plist
+ /usr/libexec/PlistBuddy -c "Delete :VirtualNetworkInterfaces:Bridge:bridge0" "$PREFS" 2>/dev/null || true
-networksetup -listlocations | grep -q exo || {
- networksetup -createlocation exo
-}
+ networksetup -listlocations | grep -q exo || {
+ networksetup -createlocation exo
+ }
-networksetup -switchtolocation exo
-networksetup -listallhardwareports \\
- | awk -F': ' '/Hardware Port: / {print $2}' \\
- | while IFS=":" read -r name; do
- case "$name" in
- "Ethernet Adapter"*)
- ;;
- "Thunderbolt Bridge")
- ;;
- "Thunderbolt "*)
- networksetup -listallnetworkservices \\
- | grep -q "EXO $name" \\
- || networksetup -createnetworkservice "EXO $name" "$name" 2>/dev/null \\
- || continue
- networksetup -setdhcp "EXO $name"
- ;;
- *)
- networksetup -listallnetworkservices \\
- | grep -q "$name" \\
- || networksetup -createnetworkservice "$name" "$name" 2>/dev/null \\
- || continue
- ;;
- esac
- done
-
-networksetup -listnetworkservices | grep -q "Thunderbolt Bridge" && {
- networksetup -setnetworkserviceenabled "Thunderbolt Bridge" off
-} || true
-"""
+ networksetup -switchtolocation exo
+ networksetup -listallhardwareports \\
+ | awk -F': ' '/Hardware Port: / {print $2}' \\
+ | while IFS=":" read -r name; do
+ case "$name" in
+ "Ethernet Adapter"*)
+ ;;
+ "Thunderbolt Bridge")
+ ;;
+ "Thunderbolt "*)
+ networksetup -listallnetworkservices \\
+ | grep -q "EXO $name" \\
+ || networksetup -createnetworkservice "EXO $name" "$name" 2>/dev/null \\
+ || continue
+ networksetup -setdhcp "EXO $name"
+ ;;
+ *)
+ networksetup -listallnetworkservices \\
+ | grep -q "$name" \\
+ || networksetup -createnetworkservice "$name" "$name" 2>/dev/null \\
+ || continue
+ ;;
+ esac
+ done
+
+ networksetup -listnetworkservices | grep -q "Thunderbolt Bridge" && {
+ networksetup -setnetworkserviceenabled "Thunderbolt Bridge" off
+ } || true
+ """
static func ensureLaunchDaemonInstalled() {
Task.detached {
@@ -70,7 +71,9 @@ networksetup -listnetworkservices | grep -q "Thunderbolt Bridge" && {
try await installLaunchDaemon()
logger.info("Network setup launch daemon installed and started")
} catch {
- logger.error("Network setup launch daemon failed: \(error.localizedDescription, privacy: .public)")
+ logger.error(
+ "Network setup launch daemon failed: \(error.localizedDescription, privacy: .public)"
+ )
}
}
}
@@ -82,7 +85,8 @@ networksetup -listnetworkservices | grep -q "Thunderbolt Bridge" && {
guard scriptExists, plistExists else { return false }
guard
let data = try? Data(contentsOf: URL(fileURLWithPath: plistDestination)),
- let plist = try? PropertyListSerialization.propertyList(from: data, options: [], format: nil) as? [String: Any]
+ let plist = try? PropertyListSerialization.propertyList(
+ from: data, options: [], format: nil) as? [String: Any]
else {
return false
}
@@ -92,7 +96,9 @@ networksetup -listnetworkservices | grep -q "Thunderbolt Bridge" && {
else {
return false
}
- if let programArgs = plist["ProgramArguments"] as? [String], programArgs.contains(scriptDestination) == false {
+ if let programArgs = plist["ProgramArguments"] as? [String],
+ programArgs.contains(scriptDestination) == false
+ {
return false
}
return true
@@ -105,58 +111,59 @@ networksetup -listnetworkservices | grep -q "Thunderbolt Bridge" && {
private static func makeInstallerScript() -> String {
"""
-set -euo pipefail
-
-LABEL="\(daemonLabel)"
-SCRIPT_DEST="\(scriptDestination)"
-PLIST_DEST="\(plistDestination)"
-
-mkdir -p "$(dirname "$SCRIPT_DEST")"
-
-cat > "$SCRIPT_DEST" <<'EOF_SCRIPT'
-\(setupScript)
-EOF_SCRIPT
-chmod 755 "$SCRIPT_DEST"
-
-cat > "$PLIST_DEST" <<'EOF_PLIST'
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-<plist version="1.0">
-<dict>
- <key>Label</key>
- <string>\(daemonLabel)</string>
- <key>ProgramArguments</key>
- <array>
- <string>/bin/bash</string>
- <string>\(scriptDestination)</string>
- </array>
- <key>StartInterval</key>
- <integer>\(requiredStartInterval)</integer>
- <key>RunAtLoad</key>
- <true/>
- <key>StandardOutPath</key>
- <string>/var/log/\(daemonLabel).log</string>
- <key>StandardErrorPath</key>
- <string>/var/log/\(daemonLabel).err.log</string>
-</dict>
-</plist>
-EOF_PLIST
-
-launchctl bootout system/"$LABEL" >/dev/null 2>&1 || true
-launchctl bootstrap system "$PLIST_DEST"
-launchctl enable system/"$LABEL"
-launchctl kickstart -k system/"$LABEL"
-"""
+ set -euo pipefail
+
+ LABEL="\(daemonLabel)"
+ SCRIPT_DEST="\(scriptDestination)"
+ PLIST_DEST="\(plistDestination)"
+
+ mkdir -p "$(dirname "$SCRIPT_DEST")"
+
+ cat > "$SCRIPT_DEST" <<'EOF_SCRIPT'
+ \(setupScript)
+ EOF_SCRIPT
+ chmod 755 "$SCRIPT_DEST"
+
+ cat > "$PLIST_DEST" <<'EOF_PLIST'
+ <?xml version="1.0" encoding="UTF-8"?>
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+ <plist version="1.0">
+ <dict>
+ <key>Label</key>
+ <string>\(daemonLabel)</string>
+ <key>ProgramArguments</key>
+ <array>
+ <string>/bin/bash</string>
+ <string>\(scriptDestination)</string>
+ </array>
+ <key>StartInterval</key>
+ <integer>\(requiredStartInterval)</integer>
+ <key>RunAtLoad</key>
+ <true/>
+ <key>StandardOutPath</key>
+ <string>/var/log/\(daemonLabel).log</string>
+ <key>StandardErrorPath</key>
+ <string>/var/log/\(daemonLabel).err.log</string>
+ </dict>
+ </plist>
+ EOF_PLIST
+
+ launchctl bootout system/"$LABEL" >/dev/null 2>&1 || true
+ launchctl bootstrap system "$PLIST_DEST"
+ launchctl enable system/"$LABEL"
+ launchctl kickstart -k system/"$LABEL"
+ """
}
private static func runShellAsAdmin(_ script: String) throws {
- let escapedScript = script
+ let escapedScript =
+ script
.replacingOccurrences(of: "\\", with: "\\\\")
.replacingOccurrences(of: "\"", with: "\\\"")
let appleScriptSource = """
-do shell script "\(escapedScript)" with administrator privileges
-"""
+ do shell script "\(escapedScript)" with administrator privileges
+ """
guard let appleScript = NSAppleScript(source: appleScriptSource) else {
throw NetworkSetupError.scriptCreationFailed
diff --git a/app/EXO/EXO/Services/NetworkStatusService.swift b/app/EXO/EXO/Services/NetworkStatusService.swift
index 16e09667..1c390121 100644
--- a/app/EXO/EXO/Services/NetworkStatusService.swift
+++ b/app/EXO/EXO/Services/NetworkStatusService.swift
@@ -85,10 +85,11 @@ private struct NetworkStatusFetcher {
private func readBridgeInactive() -> Bool? {
let result = runCommand(["ifconfig", "bridge0"])
guard result.exitCode == 0 else { return nil }
- guard let statusLine = result.output
- .components(separatedBy: .newlines)
- .first(where: { $0.contains("status:") })?
- .lowercased()
+ guard
+ let statusLine = result.output
+ .components(separatedBy: .newlines)
+ .first(where: { $0.contains("status:") })?
+ .lowercased()
else {
return nil
}
@@ -171,4 +172,3 @@ private struct NetworkStatusFetcher {
)
}
}
-
diff --git a/app/EXO/EXO/ViewModels/InstanceViewModel.swift b/app/EXO/EXO/ViewModels/InstanceViewModel.swift
index c472a162..510e85fe 100644
--- a/app/EXO/EXO/ViewModels/InstanceViewModel.swift
+++ b/app/EXO/EXO/ViewModels/InstanceViewModel.swift
@@ -107,10 +107,13 @@ extension ClusterState {
let nodeToRunner = instance.shardAssignments.nodeToRunner
let nodeIds = Array(nodeToRunner.keys)
let runnerIds = Array(nodeToRunner.values)
- let nodeNames = nodeIds.compactMap { nodeProfiles[$0]?.friendlyName ?? nodeProfiles[$0]?.modelId ?? $0 }
+ let nodeNames = nodeIds.compactMap {
+ nodeProfiles[$0]?.friendlyName ?? nodeProfiles[$0]?.modelId ?? $0
+ }
let statuses = runnerIds.compactMap { runners[$0]?.status.lowercased() }
let downloadProgress = aggregateDownloadProgress(for: nodeIds)
- let state = InstanceViewModel.State(statuses: statuses, hasActiveDownload: downloadProgress != nil)
+ let state = InstanceViewModel.State(
+ statuses: statuses, hasActiveDownload: downloadProgress != nil)
let chatTasks = (chatTasksByInstance[entry.key] ?? [])
.sorted(by: { $0.sortPriority < $1.sortPriority })
.map { InstanceTaskViewModel(task: $0) }
@@ -165,8 +168,8 @@ extension ClusterState {
}
}
-private extension InstanceViewModel.State {
- init(statuses: [String], hasActiveDownload: Bool = false) {
+extension InstanceViewModel.State {
+ fileprivate init(statuses: [String], hasActiveDownload: Bool = false) {
if statuses.contains(where: { $0.contains("failed") }) {
self = .failed
} else if hasActiveDownload || statuses.contains(where: { $0.contains("downloading") }) {
@@ -243,4 +246,3 @@ extension InstanceTaskViewModel {
self.parameters = task.parameters
}
}
-
diff --git a/app/EXO/EXO/ViewModels/NodeViewModel.swift b/app/EXO/EXO/ViewModels/NodeViewModel.swift
index 3c72155d..e52670f0 100644
--- a/app/EXO/EXO/ViewModels/NodeViewModel.swift
+++ b/app/EXO/EXO/ViewModels/NodeViewModel.swift
@@ -87,7 +87,9 @@ struct TopologyViewModel {
extension ClusterState {
func topologyViewModel(localNodeId: String?) -> TopologyViewModel? {
let topologyNodeIds = Set(topology?.nodes.map(\.nodeId) ?? [])
- let allNodes = nodeViewModels().filter { topologyNodeIds.isEmpty || topologyNodeIds.contains($0.id) }
+ let allNodes = nodeViewModels().filter {
+ topologyNodeIds.isEmpty || topologyNodeIds.contains($0.id)
+ }
guard !allNodes.isEmpty else { return nil }
let nodesById = Dictionary(uniqueKeysWithValues: allNodes.map { ($0.id, $0) })
@@ -106,18 +108,24 @@ extension ClusterState {
}
// Rotate so the local node (from /node_id API) is first
- if let localId = localNodeId, let index = orderedNodes.firstIndex(where: { $0.id == localId }) {
+ if let localId = localNodeId,
+ let index = orderedNodes.firstIndex(where: { $0.id == localId })
+ {
orderedNodes = Array(orderedNodes[index...]) + Array(orderedNodes[..<index])
}
let nodeIds = Set(orderedNodes.map(\.id))
- let edgesArray: [TopologyEdgeViewModel] = topology?.connections?.compactMap { connection in
- guard nodeIds.contains(connection.localNodeId), nodeIds.contains(connection.sendBackNodeId) else { return nil }
- return TopologyEdgeViewModel(sourceId: connection.localNodeId, targetId: connection.sendBackNodeId)
- } ?? []
+ let edgesArray: [TopologyEdgeViewModel] =
+ topology?.connections?.compactMap { connection in
+ guard nodeIds.contains(connection.localNodeId),
+ nodeIds.contains(connection.sendBackNodeId)
+ else { return nil }
+ return TopologyEdgeViewModel(
+ sourceId: connection.localNodeId, targetId: connection.sendBackNodeId)
+ } ?? []
let edges = Set(edgesArray)
- return TopologyViewModel(nodes: orderedNodes, edges: Array(edges), currentNodeId: localNodeId)
+ return TopologyViewModel(
+ nodes: orderedNodes, edges: Array(edges), currentNodeId: localNodeId)
}
}
-
diff --git a/app/EXO/EXO/Views/InstanceRowView.swift b/app/EXO/EXO/Views/InstanceRowView.swift
index 92217525..64e61ea2 100644
--- a/app/EXO/EXO/Views/InstanceRowView.swift
+++ b/app/EXO/EXO/Views/InstanceRowView.swift
@@ -20,8 +20,8 @@ struct InstanceRowView: View {
if let progress = instance.downloadProgress {
downloadStatusView(progress: progress)
} else {
- statusChip(label: instance.state.label.uppercased(), color: statusColor)
- }
+ statusChip(label: instance.state.label.uppercased(), color: statusColor)
+ }
}
if let progress = instance.downloadProgress {
GeometryReader { geometry in
@@ -97,7 +97,8 @@ struct InstanceRowView: View {
.font(.caption)
.fontWeight(.semibold)
if let subtitle = task.subtitle,
- subtitle.caseInsensitiveCompare(parentModelName) != .orderedSame {
+ subtitle.caseInsensitiveCompare(parentModelName) != .orderedSame
+ {
Text(subtitle)
.font(.caption2)
.foregroundColor(.secondary)
@@ -234,9 +235,12 @@ struct InstanceRowView: View {
Button {
isExpanded.wrappedValue.toggle()
} label: {
- Label(isExpanded.wrappedValue ? "Hide" : "Show", systemImage: isExpanded.wrappedValue ? "chevron.up" : "chevron.down")
- .labelStyle(.titleAndIcon)
- .contentTransition(.symbolEffect(.replace))
+ Label(
+ isExpanded.wrappedValue ? "Hide" : "Show",
+ systemImage: isExpanded.wrappedValue ? "chevron.up" : "chevron.down"
+ )
+ .labelStyle(.titleAndIcon)
+ .contentTransition(.symbolEffect(.replace))
}
.buttonStyle(.plain)
.font(.caption2)
@@ -311,7 +315,9 @@ struct InstanceRowView: View {
}
@ViewBuilder
- private func detailRow(icon: String? = nil, title: String, value: String, tint: Color = .secondary) -> some View {
+ private func detailRow(
+ icon: String? = nil, title: String, value: String, tint: Color = .secondary
+ ) -> some View {
HStack(alignment: .firstTextBaseline, spacing: 6) {
if let icon {
Image(systemName: icon)
@@ -329,4 +335,3 @@ struct InstanceRowView: View {
}
}
}
-
diff --git a/app/EXO/EXO/Views/NodeDetailView.swift b/app/EXO/EXO/Views/NodeDetailView.swift
index 5a38a65b..5e330e2e 100644
--- a/app/EXO/EXO/Views/NodeDetailView.swift
+++ b/app/EXO/EXO/Views/NodeDetailView.swift
@@ -32,4 +32,3 @@ struct NodeDetailView: View {
}
}
}
-
diff --git a/app/EXO/EXO/Views/NodeRowView.swift b/app/EXO/EXO/Views/NodeRowView.swift
index 730e9ad4..324958d4 100644
--- a/app/EXO/EXO/Views/NodeRowView.swift
+++ b/app/EXO/EXO/Views/NodeRowView.swift
@@ -28,4 +28,3 @@ struct NodeRowView: View {
.padding(.vertical, 4)
}
}
-
diff --git a/app/EXO/EXO/Views/TopologyMiniView.swift b/app/EXO/EXO/Views/TopologyMiniView.swift
index eccab35b..b7179112 100644
--- a/app/EXO/EXO/Views/TopologyMiniView.swift
+++ b/app/EXO/EXO/Views/TopologyMiniView.swift
@@ -76,30 +76,33 @@ struct TopologyMiniView: View {
private func connectionLines(in size: CGSize) -> some View {
let positions = positionedNodes(in: size)
- let positionById = Dictionary(uniqueKeysWithValues: positions.map { ($0.node.id, $0.point) })
+ let positionById = Dictionary(
+ uniqueKeysWithValues: positions.map { ($0.node.id, $0.point) })
return Canvas { context, _ in
guard !topology.edges.isEmpty else { return }
let nodeRadius: CGFloat = 32
let arrowLength: CGFloat = 10
let arrowSpread: CGFloat = .pi / 7
for edge in topology.edges {
- guard let start = positionById[edge.sourceId], let end = positionById[edge.targetId] else { continue }
+ guard let start = positionById[edge.sourceId], let end = positionById[edge.targetId]
+ else { continue }
let dx = end.x - start.x
let dy = end.y - start.y
let distance = max(CGFloat(hypot(dx, dy)), 1)
let ux = dx / distance
let uy = dy / distance
- let adjustedStart = CGPoint(x: start.x + ux * nodeRadius, y: start.y + uy * nodeRadius)
+ let adjustedStart = CGPoint(
+ x: start.x + ux * nodeRadius, y: start.y + uy * nodeRadius)
let adjustedEnd = CGPoint(x: end.x - ux * nodeRadius, y: end.y - uy * nodeRadius)
var linePath = Path()
linePath.move(to: adjustedStart)
linePath.addLine(to: adjustedEnd)
- context.stroke(
+ context.stroke(
linePath,
with: .color(.secondary.opacity(0.3)),
- style: StrokeStyle(lineWidth: 1, dash: [4, 4])
- )
+ style: StrokeStyle(lineWidth: 1, dash: [4, 4])
+ )
let angle = atan2(uy, ux)
let tip = adjustedEnd
@@ -168,5 +171,3 @@ private struct NodeGlyphView: View {
.frame(width: 95)
}
}
-
-
diff --git a/app/EXO/EXOTests/EXOTests.swift b/app/EXO/EXOTests/EXOTests.swift
index d8595d48..2e587804 100644
--- a/app/EXO/EXOTests/EXOTests.swift
+++ b/app/EXO/EXOTests/EXOTests.swift
@@ -6,6 +6,7 @@
//
import Testing
+
@testable import EXO
struct EXOTests {
diff --git a/flake.nix b/flake.nix
index f1de5df1..94d43688 100644
--- a/flake.nix
+++ b/flake.nix
@@ -42,11 +42,18 @@
};
treefmtEval = inputs.treefmt-nix.lib.evalModule pkgs {
projectRootFile = "flake.nix";
- programs.ruff-format.enable = true;
- programs.ruff-format.excludes = [ "rust/exo_pyo3_bindings/exo_pyo3_bindings.pyi" ];
- programs.rustfmt.enable = true;
- programs.rustfmt.package = (fenixToolchain system).rustfmt;
- programs.nixpkgs-fmt.enable = true;
+ programs = {
+ nixpkgs-fmt.enable = true;
+ ruff-format = {
+ enable = true;
+ excludes = [ "rust/exo_pyo3_bindings/exo_pyo3_bindings.pyi" ];
+ };
+ rustfmt = {
+ enable = true;
+ package = (fenixToolchain system).rustfmt;
+ };
+ swift-format.enable = true;
+ };
};
in
{
← 56af61fa add a server for distributed testing in /tests until we work
·
back to Exo
·
fmt: add typescript formatting 383309e2 →