$npx -y skills add SpillwaveSolutions/automating-mac-apps-plugin --skill automating-messagesAutomates macOS Messages (iMessage/SMS) via JXA with reliable service→buddy resolution. Use when asked to "automate iMessage", "send Messages via script", "JXA Messages automation", or "read Messages history". Covers send-bug workarounds, UI scripting for attachments, chat.db for
| 1 | # Automating Messages (JXA-first with UI/DB fallbacks) |
| 2 | |
| 3 | ## Contents |
| 4 | - [Permissions and scope](#permissions-and-scope) |
| 5 | - [Default workflow](#default-workflow-happy-path) |
| 6 | - [Quick recipe](#quick-recipe-defensive-send) |
| 7 | - [Attachments and UI fallback](#attachments-and-ui-fallback) |
| 8 | - [Data access and forensics](#data-access-and-forensics) |
| 9 | - [Validation Checklist](#validation-checklist) |
| 10 | - [When Not to Use](#when-not-to-use) |
| 11 | - [What to load](#what-to-load) |
| 12 | |
| 13 | ## Permissions and scope |
| 14 | - Grants needed: Automation + Accessibility; Full Disk Access for any `chat.db` reads. |
| 15 | - Keep automation scoped and auditable; avoid unsolicited sends and DB writes. |
| 16 | - Pairs with `automating-mac-apps` for common setup (permissions, osascript invocation, UI scripting basics). |
| 17 | |
| 18 | ## Default workflow (happy path) |
| 19 | 1) [ ] Resolve transport: pick `serviceType` (`iMessage` or `SMS`) before targeting a buddy. |
| 20 | 2) [ ] Identify recipient: filter buddies by `handle` (phone/email). Avoid ambiguous names. |
| 21 | 3) [ ] Send via app-level `send`: pass Buddy object to `Messages.send()`. |
| 22 | 4) [ ] Verify window context: activate Messages when mixing with UI steps. |
| 23 | 5) [ ] Fallbacks: if send/attachments fail, use UI scripting; for history, use SQL. |
| 24 | |
| 25 | ## Quick recipe (defensive send) |
| 26 | ```javascript |
| 27 | const Messages = Application('Messages'); |
| 28 | Messages.includeStandardAdditions = true; |
| 29 | |
| 30 | function safeSend(text, handle, svcType = 'iMessage') { |
| 31 | const svc = Messages.services.whose({ serviceType: svcType })[0]; |
| 32 | if (!svc) throw new Error(`Service ${svcType} missing`); |
| 33 | const buddy = svc.buddies.whose({ handle })[0]; |
| 34 | if (!buddy) throw new Error(`Buddy ${handle} missing on ${svcType}`); |
| 35 | Messages.send(text, { to: buddy }); |
| 36 | } |
| 37 | ``` |
| 38 | - Wrap with `try/catch` and log; add small delays when activating UI. |
| 39 | - For groups, target an existing chat by GUID or fall back to UI scripting; array sends are unreliable. |
| 40 | |
| 41 | ## Attachments and UI fallback |
| 42 | - Messages lacks a stable JXA attachment API; use clipboard + System Events paste/send. |
| 43 | - Ensure Accessibility permission, bring app forward, paste file, press Enter. |
| 44 | - See `references/ui-scripting-attachments.md` for the full flow and ObjC pasteboard snippet. |
| 45 | |
| 46 | ## Data access and forensics |
| 47 | |
| 48 | **Reading messages limitation:** The AppleScript/JXA API for Messages is effectively **write-only**. While `send()` works reliably, reading messages via `chat.messages()` or similar methods is broken/unsupported in modern macOS. The only reliable way to read message history is via direct SQLite access to `~/Library/Messages/chat.db`. |
| 49 | |
| 50 | **Security consideration:** Reading `chat.db` requires **Full Disk Access** permission, which grants broad filesystem access beyond just Messages. This is a significant security trade-off - granting Full Disk Access to scripts or applications exposes all user data. Consider whether reading message history is truly necessary before enabling this permission. |
| 51 | |
| 52 | - Use SQL against `chat.db` for history; JXA `chat.messages()` is unreliable/non-functional. |
| 53 | - Requires: **System Settings > Privacy & Security > Full Disk Access** for your terminal/script. |
| 54 | - Remember Cocoa epoch conversion (nanoseconds since 2001-01-01); use `sqlite3 -json` for structured results. |
| 55 | - See `references/database-forensics.md` for schema notes, typedstream handling, and export tooling. |
| 56 | |
| 57 | **Example read query (requires Full Disk Access):** |
| 58 | ```sql |
| 59 | sqlite3 ~/Library/Messages/chat.db "SELECT |
| 60 | CASE WHEN m.is_from_me = 1 THEN 'Me' ELSE 'Them' END as sender, |
| 61 | m.text, |
| 62 | datetime(m.date/1000000000 + 978307200, 'unixepoch', 'localtime') as date |
| 63 | FROM message m |
| 64 | JOIN handle h ON m.handle_id = h.rowid |
| 65 | WHERE h.id LIKE '%PHONE_NUMBER%' |
| 66 | ORDER BY m.date DESC LIMIT 10;" |
| 67 | ``` |
| 68 | |
| 69 | ## Bots and monitoring |
| 70 | - Implement polling daemons with `launchd` now that on-receive handlers are gone. |
| 71 | - Track `rowid`, query diffs, dispatch actions, and persist state. |
| 72 | - See `references/monitoring-daemons.md` for the polling pattern and plist notes. |
| 73 | |
| 74 | ## Validation Checklist |
| 75 | - [ ] Automation + Accessibility permissions granted |
| 76 | - [ ] Service resolves: `Messages.services.whose({ serviceType: 'iMessage' })[0]` returns object |
| 77 | - [ ] Buddy lookup works: `svc.buddies.whose({ handle })[0]` returns target |
| 78 | - [ ] Test send completes without errors |
| 79 | - [ ] Full Disk Access granted if using `chat.db` reads |
| 80 | |
| 81 | ## When Not to Use |
| 82 | - For reading message history without Full Disk Access (AppleScript/JXA cannot read messages) |
| 83 | - For cross-platform messaging (use platform APIs or third-party services) |
| 84 | - For business SMS automation (use Twilio or similar APIs) |
| 85 | - When iMessage/SMS features are not available on the target system |
| 86 | - For bulk |