VoiceWake: log detection, hold to 1s silence, ssh log clarity

This commit is contained in:
Peter Steinberger
2025-12-07 02:24:18 +01:00
parent bac5ac18f7
commit e27690e894
3 changed files with 67 additions and 1 deletions

View File

@@ -4,6 +4,7 @@ import AVFoundation
import ClawdisIPC
import CoreGraphics
import Foundation
import OSLog
import Speech
import UserNotifications
@@ -41,6 +42,13 @@ enum PermissionManager {
results[cap] = false
}
case .appleScript:
let granted = await MainActor.run { AppleScriptPermission.isAuthorized() }
if interactive, !granted {
await AppleScriptPermission.requestAuthorization()
}
results[cap] = await MainActor.run { AppleScriptPermission.isAuthorized() }
case .accessibility:
let trusted = await MainActor.run { AXIsProcessTrusted() }
results[cap] = trusted
@@ -103,6 +111,9 @@ enum PermissionManager {
results[cap] = settings.authorizationStatus == .authorized
|| settings.authorizationStatus == .provisional
case .appleScript:
results[cap] = await MainActor.run { AppleScriptPermission.isAuthorized() }
case .accessibility:
results[cap] = await MainActor.run { AXIsProcessTrusted() }
@@ -139,6 +150,52 @@ enum NotificationPermissionHelper {
}
}
enum AppleScriptPermission {
private static let logger = Logger(subsystem: "com.steipete.clawdis", category: "AppleScriptPermission")
/// Sends a benign AppleScript to Terminal to verify Automation permission.
@MainActor
static func isAuthorized() -> Bool {
let script = """
tell application "Terminal"
return "clawdis-ok"
end tell
"""
var error: NSDictionary?
let appleScript = NSAppleScript(source: script)
let result = appleScript?.executeAndReturnError(&error)
if let error, let code = error["NSAppleScriptErrorNumber"] as? Int {
if code == -1_743 { // errAEEventWouldRequireUserConsent
Self.logger.debug("AppleScript permission denied (-1743)")
return false
}
Self.logger.debug("AppleScript check failed with code \(code)")
}
return result != nil
}
/// 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
// 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"
]
for candidate in urlStrings {
if let url = URL(string: candidate), NSWorkspace.shared.open(url) {
break
}
}
}
}
@MainActor
final class PermissionMonitor: ObservableObject {
static let shared = PermissionMonitor()

View File

@@ -53,6 +53,8 @@ enum VoiceWakeForwarder {
let rendered = self.renderedCommand(template: config.commandTemplate, transcript: transcript)
args.append(contentsOf: ["sh", "-c", rendered])
self.logger.info("voice wake forward starting host=\(userHost, privacy: .public)")
let process = Process()
process.executableURL = URL(fileURLWithPath: "/usr/bin/ssh")
process.arguments = args
@@ -75,7 +77,13 @@ enum VoiceWakeForwarder {
}
try? input.fileHandleForWriting.close()
_ = await self.wait(process, timeout: config.timeout)
let out = await self.wait(process, timeout: config.timeout)
if process.terminationStatus == 0 {
self.logger.info("voice wake forward ok host=\(userHost, privacy: .public)")
} else {
// swiftlint:disable:next line_length
self.logger.debug("voice wake forward exit=\(process.terminationStatus) host=\(userHost, privacy: .public) out=\(out, privacy: .public)")
}
}
static func checkConnection(config: VoiceWakeForwardConfig) async -> Result<Void, VoiceWakeForwardError> {

View File

@@ -1,4 +1,5 @@
import AVFoundation
import OSLog
import Speech
import SwiftUI