feat(privacy-host): add signed native calendar contacts and reminders host
Add the owner-only AF_UNIX Reyna CLI privacy host, strict signed-app installation, and typed native routing for Calendar, Contacts, and Reminders.\n\nAdd bounded system-status paths and config-only direct local-service wrappers. Preserve MacMiniMCP pending explicit cutover approval.\n\nApple Notes is intentionally deferred: no native Notes operations, Apple Events declaration, or Automation helper are included; legacy Notes handling remains untouched.
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
import Foundation
|
||||
|
||||
// Public entry point used by both SwiftPM executable and Xcode app target.
|
||||
// Preserves AF_UNIX privacy-host protocol exactly as before.
|
||||
// Headless design: socket-server or stdin JSON-lines mode only.
|
||||
|
||||
public func runReynaCLIHost(arguments: [String] = CommandLine.arguments) -> Never {
|
||||
if let idx = arguments.firstIndex(of: "--socket") {
|
||||
let nextIdx = idx + 1
|
||||
guard nextIdx < arguments.count else {
|
||||
fputs("error: --socket requires a path argument\n", stderr)
|
||||
Darwin.exit(2)
|
||||
}
|
||||
let socketPath = arguments[nextIdx]
|
||||
runSocketServer(socketPath: socketPath)
|
||||
} else {
|
||||
runStdinLoop()
|
||||
}
|
||||
}
|
||||
|
||||
func extractRecoverableId(from data: Data) -> String? {
|
||||
if let obj = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
|
||||
let id = obj["id"] as? String {
|
||||
return id
|
||||
}
|
||||
guard let str = String(data: data, encoding: .utf8) else { return nil }
|
||||
let pattern = "\"id\"\\s*:\\s*\"([^\"]*)\""
|
||||
guard let regex = try? NSRegularExpression(pattern: pattern, options: []),
|
||||
let match = regex.firstMatch(in: str, options: [], range: NSRange(str.startIndex..., in: str)),
|
||||
match.numberOfRanges >= 2,
|
||||
let r = Range(match.range(at: 1), in: str) else {
|
||||
return nil
|
||||
}
|
||||
return String(str[r])
|
||||
}
|
||||
|
||||
func writeResponse(_ response: Response) {
|
||||
guard let jsonData = try? JSONEncoder().encode(response),
|
||||
let jsonString = String(data: jsonData, encoding: .utf8),
|
||||
let outData = (jsonString + "\n").data(using: .utf8) else {
|
||||
return
|
||||
}
|
||||
FileHandle.standardOutput.write(outData)
|
||||
}
|
||||
|
||||
func handleLine(_ lineData: Data) {
|
||||
if lineData.isEmpty { return }
|
||||
if let s = String(data: lineData, encoding: .utf8),
|
||||
s.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
|
||||
return
|
||||
}
|
||||
if let request = try? JSONDecoder().decode(Request.self, from: lineData) {
|
||||
let response = dispatch(request: request)
|
||||
writeResponse(response)
|
||||
} else {
|
||||
let recoveredId = extractRecoverableId(from: lineData)
|
||||
let err = ErrorPayload(code: "invalid_request", message: "Invalid request JSON")
|
||||
let resp = Response(id: recoveredId ?? "", ok: false, result: nil, error: err)
|
||||
writeResponse(resp)
|
||||
}
|
||||
}
|
||||
|
||||
func runStdinLoop() -> Never {
|
||||
while let line = readLine() {
|
||||
if line.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { continue }
|
||||
if let data = line.data(using: .utf8) {
|
||||
handleLine(data)
|
||||
} else {
|
||||
let err = ErrorPayload(code: "invalid_request", message: "Invalid request encoding")
|
||||
let resp = Response(id: "", ok: false, result: nil, error: err)
|
||||
writeResponse(resp)
|
||||
}
|
||||
}
|
||||
Darwin.exit(0)
|
||||
}
|
||||
Reference in New Issue
Block a user