cueward-adapter-macos 0.5.0

macOS adapter for Cueward with Safari, Notes, Messages, Reminders, Calendar, Screenshot, Clipboard, and OCR integrations.
import Darwin

alarm(30)
func attribute(_ element: AXUIElement, _ name: String) -> CFTypeRef? {
    var value: CFTypeRef?
    let status = AXUIElementCopyAttributeValue(element, name as CFString, &value)
    switch status {
    case .success: return value
    case .attributeUnsupported, .noValue: return nil
    default: fail("app AX read failed for \(name) (\(status.rawValue)); inspect again")
    }
}

func processIdentity(_ pid: pid_t) -> [String: Any] {
    var info = proc_bsdinfo()
    let size = Int32(MemoryLayout<proc_bsdinfo>.size)
    guard pid > 0, proc_pidinfo(pid, PROC_PIDTBSDINFO, 0, &info, size) == size else {
        fail("application process is unavailable; list apps again")
    }
    // PROC_PIDPATHINFO_MAXSIZE is a C expression macro unavailable to Swift.
    var buffer = [CChar](repeating: 0, count: 4 * Int(MAXPATHLEN))
    guard proc_pidpath(pid, &buffer, UInt32(buffer.count)) > 0 else { fail("application executable is unavailable") }
    return ["pid": pid, "start_seconds": info.pbi_start_tvsec,
            "start_micros": info.pbi_start_tvusec, "executable": String(cString: buffer)]
}

func elementPID(_ element: AXUIElement) -> pid_t {
    var result: pid_t = 0
    guard AXUIElementGetPid(element, &result) == .success, result > 0 else { fail("AX receiver process is unavailable") }
    return result
}

func appNode(_ element: AXUIElement) -> [String: Any] {
    var unavailable: [String] = []
    var node = describeElement(element) { element, name in
        var value: CFTypeRef?
        let status = AXUIElementCopyAttributeValue(element, name as CFString, &value)
        switch status {
        case .success:
            // Classify secure fields before describeElement can request AXValue.
            if name == kAXRoleAttribute || name == kAXSubroleAttribute {
                guard let role = value as? String, !role.isEmpty, role != "unknown" else {
                    fail("AX node has no readable \(name == kAXRoleAttribute ? "role" : "subrole")")
                }
            }
            return value
        case .attributeUnsupported, .noValue:
            guard name != kAXRoleAttribute else { fail("AX node has no readable role") }
            return nil
        // Other partial reads remain traversable; unreadable security metadata
        // and communication failures stop before any field value is requested.
        case .failure where name != kAXRoleAttribute && name != kAXSubroleAttribute:
            unavailable.append(name); return nil
        default: fail("app AX node read failed for \(name) (\(status.rawValue)); inspect again")
        }
    }
    guard node["role"] as? String != "unknown" else { fail("AX node has no readable role") }
    if !unavailable.isEmpty {
        node["unavailable_attributes"] = unavailable.sorted()
    }
    // Missing optional labels/identifiers do not invalidate independently read capabilities.
    // Keep its unavailable marker in the fingerprint so changed reads go stale.
    if unavailable.contains(where: { $0 != kAXDescriptionAttribute && $0 != kAXIdentifierAttribute }) {
        node["settable_value"] = false
        node["actions"] = [String]()
    }
    let receiver = elementPID(element)
    node["receiver_pid"] = receiver
    node["receiver_instance"] = processIdentity(receiver)
    return node
}

func ensureUnlocked() {
    guard let session = CGSessionCopyCurrentDictionary() as? [String: Any] else { fail("desktop session is unavailable") }
    guard session["CGSSessionScreenIsLocked"] as? Bool != true else {
        fail("desktop is locked; app AX exploration requires an unlocked session")
    }
}

let data = FileHandle.standardInput.readDataToEndOfFile()
guard let request = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
      let pid = request["pid"] as? Int32, pid > 0 else { fail("invalid app AX request") }
guard AXIsProcessTrusted() else { fail("Accessibility permission is required", code: 3) }
ensureUnlocked()
let instance = processIdentity(pid)
let application = AXUIElementCreateApplication(pid)
AXUIElementSetMessagingTimeout(application, 2)