VoiceWake: log detection, hold to 1s silence, ssh log clarity
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import AVFoundation
|
||||
import OSLog
|
||||
import Speech
|
||||
import SwiftUI
|
||||
|
||||
|
||||
Reference in New Issue
Block a user