chore: format macOS sources

This commit is contained in:
Peter Steinberger
2025-12-07 16:35:58 +01:00
parent 45398b7660
commit 040fe58693
14 changed files with 147 additions and 123 deletions

View File

@@ -21,7 +21,7 @@ actor AgentRPC {
deliver: Bool,
to: String?) async -> (ok: Bool, text: String?, error: String?)
{
if process?.isRunning != true {
if self.process?.isRunning != true {
do {
try await self.start()
} catch {
@@ -51,7 +51,8 @@ actor AgentRPC {
if let payloadDict = parsed["payload"] as? [String: Any],
let payloads = payloadDict["payloads"] as? [[String: Any]],
let first = payloads.first,
let txt = first["text"] as? String {
let txt = first["text"] as? String
{
return (true, txt, nil)
}
return (true, nil, nil)
@@ -62,14 +63,14 @@ actor AgentRPC {
}
return (false, nil, "rpc returned unexpected response: \(line)")
} catch {
logger.error("rpc send failed: \(error.localizedDescription, privacy: .public)")
await stop()
self.logger.error("rpc send failed: \(error.localizedDescription, privacy: .public)")
await self.stop()
return (false, nil, error.localizedDescription)
}
}
func status() async -> (ok: Bool, error: String?) {
if process?.isRunning != true {
if self.process?.isRunning != true {
do {
try await self.start()
} catch {
@@ -88,14 +89,14 @@ actor AgentRPC {
if let ok = parsed?["ok"] as? Bool, ok { return (true, nil) }
return (false, parsed?["error"] as? String ?? "rpc status failed: \(line)")
} catch {
logger.error("rpc status failed: \(error.localizedDescription, privacy: .public)")
await stop()
self.logger.error("rpc status failed: \(error.localizedDescription, privacy: .public)")
await self.stop()
return (false, error.localizedDescription)
}
}
func setHeartbeatsEnabled(_ enabled: Bool) async -> Bool {
guard process?.isRunning == true else { return false }
guard self.process?.isRunning == true else { return false }
do {
let payload: [String: Any] = ["type": "set-heartbeats", "enabled": enabled]
let data = try JSONSerialization.data(withJSONObject: payload)
@@ -108,8 +109,8 @@ actor AgentRPC {
if let ok = parsed?["ok"] as? Bool, ok { return true }
return false
} catch {
logger.error("rpc set-heartbeats failed: \(error.localizedDescription, privacy: .public)")
await stop()
self.logger.error("rpc set-heartbeats failed: \(error.localizedDescription, privacy: .public)")
await self.stop()
return false
}
}
@@ -155,12 +156,12 @@ actor AgentRPC {
}
private func stop() async {
stdoutHandle?.readabilityHandler = nil
process?.terminate()
process = nil
stdinHandle = nil
stdoutHandle = nil
buffer.removeAll(keepingCapacity: false)
self.stdoutHandle?.readabilityHandler = nil
self.process?.terminate()
self.process = nil
self.stdinHandle = nil
self.stdoutHandle = nil
self.buffer.removeAll(keepingCapacity: false)
let waiters = self.waiters
self.waiters.removeAll()
for waiter in waiters {
@@ -169,13 +170,13 @@ actor AgentRPC {
}
private func ingest(data: Data) {
buffer.append(data)
self.buffer.append(data)
while let range = buffer.firstRange(of: Data([0x0A])) {
let lineData = buffer.subdata(in: buffer.startIndex..<range.lowerBound)
buffer.removeSubrange(buffer.startIndex...range.lowerBound)
let lineData = self.buffer.subdata(in: self.buffer.startIndex..<range.lowerBound)
self.buffer.removeSubrange(self.buffer.startIndex...range.lowerBound)
guard let line = String(data: lineData, encoding: .utf8) else { continue }
if let waiter = waiters.first {
waiters.removeFirst()
self.waiters.removeFirst()
waiter.resume(returning: line)
}
}
@@ -183,7 +184,7 @@ actor AgentRPC {
private func nextLine() async throws -> String {
try await withCheckedThrowingContinuation { (cont: CheckedContinuation<String, Error>) in
waiters.append(cont)
self.waiters.append(cont)
}
}
}

View File

@@ -142,7 +142,7 @@ final class AppState: ObservableObject {
UserDefaults.standard.set(true, forKey: heartbeatsEnabledKey)
}
if self.swabbleEnabled && !PermissionManager.voiceWakePermissionsGranted() {
if self.swabbleEnabled, !PermissionManager.voiceWakePermissionsGranted() {
self.swabbleEnabled = false
}

View File

@@ -109,7 +109,9 @@ struct DebugSettings: View {
Button {
Task { await self.reloadModels() }
} label: {
Label(self.modelsLoading ? "Reloading…" : "Reload models", systemImage: "arrow.clockwise")
Label(
self.modelsLoading ? "Reloading…" : "Reload models",
systemImage: "arrow.clockwise")
}
.buttonStyle(.bordered)
.disabled(self.modelsLoading)
@@ -129,7 +131,9 @@ struct DebugSettings: View {
}
}
Button("Send Test Notification") {
Task { _ = await NotificationManager().send(title: "Clawdis", body: "Test notification", sound: nil) }
Task {
_ = await NotificationManager().send(title: "Clawdis", body: "Test notification", sound: nil)
}
}
.buttonStyle(.bordered)
VStack(alignment: .leading, spacing: 6) {
@@ -153,7 +157,8 @@ struct DebugSettings: View {
.font(.caption)
.foregroundStyle(.red)
} else {
Text("Uses the Voice Wake path: forwards over SSH when configured, otherwise runs locally via rpc.")
Text(
"Uses the Voice Wake path: forwards over SSH when configured, otherwise runs locally via rpc.")
.font(.caption)
.foregroundStyle(.secondary)
}

View File

@@ -172,7 +172,8 @@ struct GeneralSettings: View {
.font(.caption)
.foregroundStyle(.secondary)
if let recent = snap.sessions.recent.first {
Text("Last activity: \(recent.key) \(recent.updatedAt != nil ? relativeAge(from: Date(timeIntervalSince1970: (recent.updatedAt ?? 0) / 1000)) : "unknown")")
Text(
"Last activity: \(recent.key) \(recent.updatedAt != nil ? relativeAge(from: Date(timeIntervalSince1970: (recent.updatedAt ?? 0) / 1000)) : "unknown")")
.font(.caption)
.foregroundStyle(.secondary)
}
@@ -216,8 +217,8 @@ struct GeneralSettings: View {
}
}
private extension GeneralSettings {
func revealLogs() {
extension GeneralSettings {
private func revealLogs() {
let path = URL(fileURLWithPath: "/tmp/clawdis/clawdis.log")
if FileManager.default.fileExists(atPath: path.path) {
NSWorkspace.shared.selectFile(path.path, inFileViewerRootedAtPath: path.deletingLastPathComponent().path)

View File

@@ -1,11 +1,10 @@
import AppKit
import Darwin
import Foundation
import MenuBarExtraAccess
import SwiftUI
import OSLog
import Security
import OSLog
import OSLog
import Darwin
import SwiftUI
@main
struct ClawdisApp: App {
@@ -98,11 +97,11 @@ private struct MenuContent: View {
private func relayLabel(_ status: RelayProcessManager.Status) -> String {
switch status {
case .running: return "Running"
case .starting: return "Starting…"
case .restarting: return "Restarting…"
case let .failed(reason): return "Failed: \(reason)"
case .stopped: return "Stopped"
case .running: "Running"
case .starting: "Starting…"
case .restarting: "Restarting…"
case let .failed(reason): "Failed: \(reason)"
case .stopped: "Stopped"
}
}
@@ -497,7 +496,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSXPCListenerDelegate
// Developer/testing helper: auto-open WebChat when launched with --webchat
if CommandLine.arguments.contains("--webchat") {
webChatAutoLogger.debug("Auto-opening web chat via --webchat flag")
self.webChatAutoLogger.debug("Auto-opening web chat via --webchat flag")
WebChatManager.shared.show(sessionKey: "main")
}
}
@@ -581,7 +580,8 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSXPCListenerDelegate
var infoCF: CFDictionary?
guard SecCodeCopySigningInformation(sCode, SecCSFlags(), &infoCF) == errSecSuccess,
let info = infoCF as? [String: Any],
let teamID = info[kSecCodeInfoTeamIdentifier as String] as? String else {
let teamID = info[kSecCodeInfoTeamIdentifier as String] as? String
else {
return false
}

View File

@@ -167,7 +167,7 @@ enum AppleScriptPermission {
let result = appleScript?.executeAndReturnError(&error)
if let error, let code = error["NSAppleScriptErrorNumber"] as? Int {
if code == -1_743 { // errAEEventWouldRequireUserConsent
if code == -1743 { // errAEEventWouldRequireUserConsent
Self.logger.debug("AppleScript permission denied (-1743)")
return false
}
@@ -180,12 +180,12 @@ enum AppleScriptPermission {
/// Triggers the TCC prompt and opens System Settings Privacy & Security Automation.
@MainActor
static func requestAuthorization() async {
_ = isAuthorized() // first attempt triggers the dialog if not granted
_ = self.isAuthorized() // first attempt triggers the dialog if not granted
// Open the Automation pane to help the user if the prompt was dismissed.
let urlStrings = [
"x-apple.systempreferences:com.apple.preference.security?Privacy_Automation",
"x-apple.systempreferences:com.apple.preference.security"
"x-apple.systempreferences:com.apple.preference.security",
]
for candidate in urlStrings {

View File

@@ -1,5 +1,5 @@
import SwiftUI
import AppKit
import SwiftUI
// MARK: - Data models

View File

@@ -137,7 +137,7 @@ enum CLIInstaller {
let targetList = targets.map(self.shellEscape).joined(separator: " ")
let cmds = [
"mkdir -p /usr/local/bin /opt/homebrew/bin",
targets.map { "ln -sf \(escapedSource) \($0)" }.joined(separator: "; ")
targets.map { "ln -sf \(escapedSource) \($0)" }.joined(separator: "; "),
].joined(separator: "; ")
let script = """
@@ -180,7 +180,8 @@ enum CommandResolver {
static func projectRoot() -> URL {
if let stored = UserDefaults.standard.string(forKey: self.projectRootDefaultsKey),
let url = self.expandPath(stored) {
let url = self.expandPath(stored)
{
return url
}
let fallback = FileManager.default.homeDirectoryForCurrentUser

View File

@@ -50,7 +50,9 @@ enum VoiceWakeForwarder {
}
steps.append("if [ -z \"$CLI\" ]; then CLI=$(command -v clawdis-mac 2>/dev/null || true); fi")
steps.append("if [ -z \"$CLI\" ]; then for c in \(searchList); do [ -x \"$c\" ] && CLI=\"$c\" && break; done; fi")
steps
.append(
"if [ -z \"$CLI\" ]; then for c in \(searchList); do [ -x \"$c\" ] && CLI=\"$c\" && break; done; fi")
steps.append("if [ -z \"$CLI\" ]; then echo 'clawdis-mac missing'; exit 127; fi")
if echoPath {
@@ -61,17 +63,16 @@ enum VoiceWakeForwarder {
}
static func commandWithCliPath(_ command: String, target: String, echoCliPath: Bool = false) -> String {
let rewritten: String
if command.contains("clawdis-mac") {
rewritten = command.replacingOccurrences(of: "clawdis-mac", with: "\"$CLI\"")
let rewritten: String = if command.contains("clawdis-mac") {
command.replacingOccurrences(of: "clawdis-mac", with: "\"$CLI\"")
} else {
rewritten = "\"$CLI\" \(command)"
"\"$CLI\" \(command)"
}
return "\(self.cliLookupPrefix(target: target, echoPath: echoCliPath)); \(rewritten)"
}
#if DEBUG
#if DEBUG
// Test-only helpers
static func _testSetCliCache(target: String, path: String) {
self.cliCache.set((target: target, path: path))
@@ -80,7 +81,7 @@ enum VoiceWakeForwarder {
static func _testGetCliCache() -> (target: String, path: String)? {
self.cliCache.get()
}
#endif
#endif
enum VoiceWakeForwardError: LocalizedError, Equatable {
case invalidTarget
@@ -109,7 +110,10 @@ enum VoiceWakeForwarder {
}
@discardableResult
static func forward(transcript: String, config: VoiceWakeForwardConfig) async -> Result<Void, VoiceWakeForwardError> {
static func forward(
transcript: String,
config: VoiceWakeForwardConfig) async -> Result<Void, VoiceWakeForwardError>
{
guard config.enabled else { return .failure(.disabled) }
let destination = config.target.trimmingCharacters(in: .whitespacesAndNewlines)
guard let parsed = self.parse(target: destination) else {
@@ -236,7 +240,8 @@ enum VoiceWakeForwarder {
if checkProc.terminationStatus == 0 {
if let cliLine = statusOut
.split(separator: "\n")
.last(where: { $0.hasPrefix("__CLI:") }) {
.last(where: { $0.hasPrefix("__CLI:") })
{
let path = String(cliLine.dropFirst("__CLI:".count))
if !path.isEmpty {
self.cliCache.set((target: destination, path: path))

View File

@@ -34,32 +34,32 @@ actor VoiceWakeRuntime {
}
guard voiceWakeSupported, snapshot.0 else {
stop()
self.stop()
return
}
guard PermissionManager.voiceWakePermissionsGranted() else {
logger.debug("voicewake runtime not starting: permissions missing")
stop()
self.logger.debug("voicewake runtime not starting: permissions missing")
self.stop()
return
}
let config = snapshot.1
if config == currentConfig, recognitionTask != nil {
if config == self.currentConfig, self.recognitionTask != nil {
return
}
stop()
await start(with: config)
self.stop()
await self.start(with: config)
}
private func start(with config: RuntimeConfig) async {
do {
configureSession(localeID: config.localeID)
self.configureSession(localeID: config.localeID)
guard let recognizer, recognizer.isAvailable else {
logger.error("voicewake runtime: speech recognizer unavailable")
self.logger.error("voicewake runtime: speech recognizer unavailable")
return
}
@@ -67,19 +67,19 @@ actor VoiceWakeRuntime {
self.recognitionRequest?.shouldReportPartialResults = true
guard let request = self.recognitionRequest else { return }
let input = audioEngine.inputNode
let input = self.audioEngine.inputNode
let format = input.outputFormat(forBus: 0)
input.removeTap(onBus: 0)
input.installTap(onBus: 0, bufferSize: 2048, format: format) { [weak request] buffer, _ in
request?.append(buffer)
}
audioEngine.prepare()
try audioEngine.start()
self.audioEngine.prepare()
try self.audioEngine.start()
currentConfig = config
lastHeard = Date()
cooldownUntil = nil
self.currentConfig = config
self.lastHeard = Date()
self.cooldownUntil = nil
self.recognitionTask = recognizer.recognitionTask(with: request) { [weak self] result, error in
guard let self else { return }
@@ -87,22 +87,22 @@ actor VoiceWakeRuntime {
Task { await self.handleRecognition(transcript: transcript, error: error, config: config) }
}
logger.info("voicewake runtime started")
self.logger.info("voicewake runtime started")
} catch {
logger.error("voicewake runtime failed to start: \(error.localizedDescription, privacy: .public)")
stop()
self.logger.error("voicewake runtime failed to start: \(error.localizedDescription, privacy: .public)")
self.stop()
}
}
private func stop() {
recognitionTask?.cancel()
recognitionTask = nil
recognitionRequest?.endAudio()
recognitionRequest = nil
audioEngine.inputNode.removeTap(onBus: 0)
audioEngine.stop()
currentConfig = nil
logger.debug("voicewake runtime stopped")
self.recognitionTask?.cancel()
self.recognitionTask = nil
self.recognitionRequest?.endAudio()
self.recognitionRequest = nil
self.audioEngine.inputNode.removeTap(onBus: 0)
self.audioEngine.stop()
self.currentConfig = nil
self.logger.debug("voicewake runtime stopped")
}
private func configureSession(localeID: String?) {
@@ -113,20 +113,21 @@ actor VoiceWakeRuntime {
private func handleRecognition(
transcript: String?,
error: Error?,
config: RuntimeConfig) async {
config: RuntimeConfig) async
{
if let error {
logger.debug("voicewake recognition error: \(error.localizedDescription, privacy: .public)")
self.logger.debug("voicewake recognition error: \(error.localizedDescription, privacy: .public)")
}
guard let transcript else { return }
if !transcript.isEmpty { lastHeard = Date() }
if !transcript.isEmpty { self.lastHeard = Date() }
if Self.matches(text: transcript, triggers: config.triggers) {
let now = Date()
if let cooldown = cooldownUntil, now < cooldown {
return
}
cooldownUntil = now.addingTimeInterval(2.5)
self.cooldownUntil = now.addingTimeInterval(2.5)
await MainActor.run { AppStateStore.shared.triggerVoiceEars() }
let forwardConfig = await MainActor.run { AppStateStore.shared.voiceWakeForwardConfig }
if forwardConfig.enabled {
@@ -148,9 +149,9 @@ actor VoiceWakeRuntime {
return false
}
#if DEBUG
#if DEBUG
static func _testMatches(text: String, triggers: [String]) -> Bool {
Self.matches(text: text, triggers: triggers)
self.matches(text: text, triggers: triggers)
}
#endif
#endif
}

View File

@@ -188,7 +188,8 @@ final class VoiceWakeTester {
text: String,
isFinal: Bool,
errorMessage: String?,
onUpdate: @escaping @Sendable (VoiceWakeTestState) -> Void) async {
onUpdate: @escaping @Sendable (VoiceWakeTestState) -> Void) async
{
if !text.isEmpty {
self.lastHeard = Date()
}

View File

@@ -14,7 +14,7 @@ final class WebChatServer: @unchecked Sendable {
/// Start the local HTTP server if it isn't already running. Safe to call multiple times.
func start(root: URL) {
queue.async {
self.queue.async {
if self.listener != nil { return }
self.root = root
let params = NWParameters.tcp
@@ -26,8 +26,9 @@ final class WebChatServer: @unchecked Sendable {
case .ready:
self?.port = listener.port
webChatServerLogger.debug("WebChatServer ready on 127.0.0.1:\(listener.port?.rawValue ?? 0)")
case .failed(let error):
webChatServerLogger.error("WebChatServer failed: \(error.localizedDescription, privacy: .public)")
case let .failed(error):
webChatServerLogger
.error("WebChatServer failed: \(error.localizedDescription, privacy: .public)")
self?.listener = nil
default:
break
@@ -39,7 +40,8 @@ final class WebChatServer: @unchecked Sendable {
listener.start(queue: self.queue)
self.listener = listener
} catch {
webChatServerLogger.error("WebChatServer could not start: \(error.localizedDescription, privacy: .public)")
webChatServerLogger
.error("WebChatServer could not start: \(error.localizedDescription, privacy: .public)")
}
}
}
@@ -47,7 +49,7 @@ final class WebChatServer: @unchecked Sendable {
/// Returns the base URL once the server is ready, otherwise nil.
func baseURL() -> URL? {
var url: URL?
queue.sync {
self.queue.sync {
if let port {
url = URL(string: "http://127.0.0.1:\(port.rawValue)/webchat/")
}
@@ -60,14 +62,15 @@ final class WebChatServer: @unchecked Sendable {
switch state {
case .ready:
self.receive(on: connection)
case .failed(let error):
webChatServerLogger.error("WebChatServer connection failed: \(error.localizedDescription, privacy: .public)")
case let .failed(error):
webChatServerLogger
.error("WebChatServer connection failed: \(error.localizedDescription, privacy: .public)")
connection.cancel()
default:
break
}
}
connection.start(queue: queue)
connection.start(queue: self.queue)
}
private func receive(on connection: NWConnection) {
@@ -109,15 +112,15 @@ final class WebChatServer: @unchecked Sendable {
}
let fileURL = root.appendingPathComponent(path)
guard fileURL.path.hasPrefix(root.path) else {
send(status: 403, mime: "text/plain", body: Data("Forbidden".utf8), over: connection)
self.send(status: 403, mime: "text/plain", body: Data("Forbidden".utf8), over: connection)
return
}
guard let data = try? Data(contentsOf: fileURL) else {
send(status: 404, mime: "text/plain", body: Data("Not Found".utf8), over: connection)
self.send(status: 404, mime: "text/plain", body: Data("Not Found".utf8), over: connection)
return
}
let mime = mimeType(forExtension: fileURL.pathExtension)
send(status: 200, mime: mime, body: data, over: connection)
let mime = self.mimeType(forExtension: fileURL.pathExtension)
self.send(status: 200, mime: mime, body: data, over: connection)
}
private func send(status: Int, mime: String, body: Data, over connection: NWConnection) {
@@ -134,28 +137,28 @@ final class WebChatServer: @unchecked Sendable {
private func statusText(_ code: Int) -> String {
switch code {
case 200: return "OK"
case 403: return "Forbidden"
case 404: return "Not Found"
default: return "Error"
case 200: "OK"
case 403: "Forbidden"
case 404: "Not Found"
default: "Error"
}
}
private func mimeType(forExtension ext: String) -> String {
switch ext.lowercased() {
case "html", "htm": return "text/html; charset=utf-8"
case "js", "mjs": return "application/javascript; charset=utf-8"
case "css": return "text/css; charset=utf-8"
case "json": return "application/json; charset=utf-8"
case "map": return "application/json; charset=utf-8"
case "svg": return "image/svg+xml"
case "png": return "image/png"
case "jpg", "jpeg": return "image/jpeg"
case "gif": return "image/gif"
case "woff2": return "font/woff2"
case "woff": return "font/woff"
case "ttf": return "font/ttf"
default: return "application/octet-stream"
case "html", "htm": "text/html; charset=utf-8"
case "js", "mjs": "application/javascript; charset=utf-8"
case "css": "text/css; charset=utf-8"
case "json": "application/json; charset=utf-8"
case "map": "application/json; charset=utf-8"
case "svg": "image/svg+xml"
case "png": "image/png"
case "jpg", "jpeg": "image/jpeg"
case "gif": "image/gif"
case "woff2": "font/woff2"
case "woff": "font/woff"
case "ttf": "font/ttf"
default: "application/octet-stream"
}
}
}

View File

@@ -143,7 +143,11 @@ final class WebChatWindowController: NSWindowController, WKScriptMessageHandler,
webChatLogger.debug("didCommit url=\(webView.url?.absoluteString ?? "nil", privacy: .public)")
}
func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: any Error) {
func webView(
_ webView: WKWebView,
didFailProvisionalNavigation navigation: WKNavigation!,
withError error: any Error)
{
webChatLogger.error("didFailProvisional error=\(error.localizedDescription, privacy: .public)")
}

View File

@@ -32,10 +32,10 @@ struct ClawdisCLI {
FileHandle.standardOutput.write(Data([0x0A]))
exit(response.ok ? 0 : 1)
} catch CLIError.help {
printHelp()
self.printHelp()
exit(0)
} catch CLIError.version {
printVersion()
self.printVersion()
exit(0)
} catch {
fputs("clawdis-mac error: \(error)\n", stderr)
@@ -52,6 +52,7 @@ struct ClawdisCLI {
switch command {
case "--help", "-h", "help":
throw CLIError.help
case "--version", "-V", "version":
throw CLIError.version
@@ -192,7 +193,7 @@ struct ClawdisCLI {
}
private static func printVersion() {
let info = loadInfo()
let info = self.loadInfo()
let version = info["CFBundleShortVersionString"] as? String ?? "unknown"
let build = info["CFBundleVersion"] as? String ?? ""
let git = info["ClawdisGitCommit"] as? String ?? "unknown"
@@ -209,7 +210,8 @@ struct ClawdisCLI {
.deletingLastPathComponent() // Contents
.appendingPathComponent("Info.plist")
if let data = try? Data(contentsOf: url),
let dict = try? PropertyListSerialization.propertyList(from: data, options: [], format: nil) as? [String: Any]
let dict = try? PropertyListSerialization
.propertyList(from: data, options: [], format: nil) as? [String: Any]
{
return dict
}
@@ -217,7 +219,7 @@ struct ClawdisCLI {
}
private static func send(request: Request) async throws -> Response {
try await ensureAppRunning()
try await self.ensureAppRunning()
var lastError: Error?
for _ in 0..<10 {
@@ -246,7 +248,7 @@ struct ClawdisCLI {
}
private static func ensureAppRunning() async throws {
let appURL = URL(fileURLWithPath: (CommandLine.arguments.first ?? ""))
let appURL = URL(fileURLWithPath: CommandLine.arguments.first ?? "")
.resolvingSymlinksInPath()
.deletingLastPathComponent() // MacOS
.deletingLastPathComponent() // Contents