fix(inotes): read Notes via in-process NSAppleScript so the TCC grant applies
osascript runs out-of-process, so TCC attributes the Apple event to osascript rather than MacSync — every Notes read was denied even after the user granted MacSync → Notes Automation (the script works fine from Terminal). Send the event in-process via NSAppleScript on the main actor (tell-application events need a live run loop for their reply); the grant is then honored and notes sync. The read is infrequent (600s cycle) and brief enough for a menu agent. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
8597406898
commit
d03b9e3046
1 changed files with 22 additions and 28 deletions
|
|
@ -140,34 +140,28 @@ public final class NotesReader {
|
|||
return out
|
||||
}
|
||||
|
||||
/// Run AppleScript via /usr/bin/osascript subprocess. Returns
|
||||
/// `(success, stdout-or-stderr)`. We deliberately do NOT use
|
||||
/// `NSAppleScript` because it blocks the calling thread; `osascript`
|
||||
/// runs out-of-process and is awaitable.
|
||||
/// Run AppleScript IN-PROCESS via `NSAppleScript`. Returns
|
||||
/// `(success, output-or-error)`.
|
||||
///
|
||||
/// We must NOT shell out to `/usr/bin/osascript`: TCC attributes the Apple
|
||||
/// event a subprocess sends to that subprocess (`osascript`), not to MacSync,
|
||||
/// so the `MacSync → Notes` Automation grant doesn't apply and every read is
|
||||
/// denied even after the user allows it. NSAppleScript sends the event from
|
||||
/// MacSync itself, so the grant is honored.
|
||||
///
|
||||
/// Run on the main actor: `tell application` events need a live run loop to
|
||||
/// receive their reply, so a detached thread can hang. The Notes read is
|
||||
/// infrequent (the 600s sync cycle) and brief enough that a menu-bar agent
|
||||
/// can absorb it on the main thread.
|
||||
@MainActor
|
||||
private func runAppleScript(_ source: String) async -> (Bool, String) {
|
||||
await Task.detached {
|
||||
let process = Process()
|
||||
process.executableURL = URL(fileURLWithPath: "/usr/bin/osascript")
|
||||
process.arguments = ["-e", source]
|
||||
let outPipe = Pipe()
|
||||
let errPipe = Pipe()
|
||||
process.standardOutput = outPipe
|
||||
process.standardError = errPipe
|
||||
do {
|
||||
try process.run()
|
||||
process.waitUntilExit()
|
||||
let outData = outPipe.fileHandleForReading.readDataToEndOfFile()
|
||||
let errData = errPipe.fileHandleForReading.readDataToEndOfFile()
|
||||
let outStr = String(data: outData, encoding: .utf8) ?? ""
|
||||
let errStr = String(data: errData, encoding: .utf8) ?? ""
|
||||
if process.terminationStatus == 0 {
|
||||
return (true, outStr)
|
||||
} else {
|
||||
return (false, errStr.isEmpty ? outStr : errStr)
|
||||
}
|
||||
} catch {
|
||||
return (false, error.localizedDescription)
|
||||
}
|
||||
}.value
|
||||
var errorInfo: NSDictionary?
|
||||
let script = NSAppleScript(source: source)
|
||||
let result = script?.executeAndReturnError(&errorInfo)
|
||||
if let errorInfo {
|
||||
let msg = (errorInfo[NSAppleScript.errorMessage] as? String) ?? "\(errorInfo)"
|
||||
return (false, msg)
|
||||
}
|
||||
return (true, result?.stringValue ?? "")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue