| .agents/skills/imsg | ||
| .github/workflows | ||
| docs | ||
| Resources | ||
| scripts | ||
| Sources | ||
| Tests | ||
| .gitignore | ||
| .swiftlint.yml | ||
| AGENTS.md | ||
| CHANGELOG.md | ||
| LICENSE | ||
| Makefile | ||
| Package.swift | ||
| README.md | ||
| version.env | ||
💬 imsg — Send, read, stream iMessage & SMS
A macOS Messages.app CLI to send, read, and stream iMessage/SMS (with attachment metadata). Read-only for receives; send uses AppleScript (no private APIs).
Features
- List chats, view history, or stream new messages (
watch). - Send text and attachments via iMessage or SMS (AppleScript, no private APIs).
- Phone normalization to E.164 for reliable buddy lookup (
--region, default US). - Optional attachment metadata output (mime, name, path, missing flag).
- Filters: participants, start/end time, JSON output for tooling.
- Read-only DB access (
mode=ro), no DB writes. - Event-driven watch via filesystem events.
- Optional advanced IMCore features (
typing,launch,status) behind explicit SIP-off setup.
Requirements
- macOS 14+ with Messages.app signed in.
- Full Disk Access for your terminal to read
~/Library/Messages/chat.db. - Automation permission for your terminal to control Messages.app (for sending).
- For SMS relay, enable “Text Message Forwarding” on your iPhone to this Mac.
Install
Homebrew
brew install steipete/tap/imsg
Build from source
make build
# binary at ./bin/imsg
Commands
imsg chats [--limit 20] [--json]— list recent conversations.imsg group --chat-id <id> [--json]— show identity and participants for one chat.imsg history --chat-id <id> [--limit 50] [--attachments] [--participants +15551234567,...] [--start 2025-01-01T00:00:00Z] [--end 2025-02-01T00:00:00Z] [--json]imsg watch [--chat-id <id>] [--since-rowid <n>] [--debounce 250ms] [--attachments] [--participants …] [--start …] [--end …] [--json]imsg send --to <handle> [--text "hi"] [--file /path/img.jpg] [--service imessage|sms|auto] [--region US]imsg read --to <handle> [--chat-id <id> | --chat-identifier <id> | --chat-guid <guid>]imsg typing --to <handle> [--duration 5s] [--stop true] [--service imessage|sms|auto]imsg status [--json]— advanced feature and SIP statusimsg launch [--dylib <path>] [--kill-only] [--json]
Quick samples
# list 5 chats
imsg chats --limit 5
# list chats as JSON
imsg chats --limit 5 --json
# show one chat's identity + participants
imsg group --chat-id 1 --json
# last 10 messages in chat 1 with attachments
imsg history --chat-id 1 --limit 10 --attachments
# filter by date and emit JSON
imsg history --chat-id 1 --start 2025-01-01T00:00:00Z --json
# live stream a chat
imsg watch --chat-id 1 --attachments --debounce 250ms
# send a picture
imsg send --to "+14155551212" --text "hi" --file ~/Desktop/pic.jpg --service imessage
# mark a chat as read
imsg read --to "+14155551212"
# advanced status check
imsg status
# launch Messages with injection (SIP must be disabled first)
imsg launch
# show typing indicator for 5s
imsg typing --to "+14155551212" --duration 5s
Attachment notes
--attachments prints per-attachment lines with name, MIME, missing flag, and resolved path (tilde expanded). Only metadata is shown; files aren’t copied.
JSON output
imsg chats --json emits one JSON object per chat with fields: id, name, identifier, service, last_message_at, guid, display_name, is_group, participants.
imsg history --json and imsg watch --json emit one JSON object per message with fields: id, chat_id, chat_identifier, chat_guid, chat_name, participants, is_group, guid, reply_to_guid, destination_caller_id, sender, is_from_me, text, created_at, attachments (array of metadata with filename, transfer_name, uti, mime_type, total_bytes, is_sticker, original_path, missing), reactions.
Note: reply_to_guid, destination_caller_id, and reactions are read-only metadata.
Permissions troubleshooting
If you see “unable to open database file” or empty output:
- Grant Full Disk Access: System Settings → Privacy & Security → Full Disk Access → add your terminal.
- If you launch
imsgfrom an editor, Node process, gateway, or shell wrapper, grant Full Disk Access to that parent app too. - Also add the built-in Terminal.app (
/System/Applications/Utilities/Terminal.app). macOS can require the default terminal even when you normally use iTerm, VS Code, or another launcher. - Toggle the Full Disk Access entry off and on after terminal, Homebrew, Node, or app updates; stale TCC grants can look enabled but still produce
authorization denied (code: 23). - Ensure Messages.app is signed in and
~/Library/Messages/chat.dbexists. - For send, allow the terminal under System Settings → Privacy & Security → Automation → Messages.
imsg opens chat.db read-only. It does not use SQLite immutable=1 by default because immutable reads can miss new Messages rows and WAL-backed updates.
Advanced Features (SIP-Off Only)
Advanced features (typing, launch, IMCore bridge) require injecting a helper dylib into Messages.app.
Important:
- This is opt-in only. Default send/history/watch flows do not need injection.
imsg launchrefuses to inject when SIP is enabled.imsg statusis read-only and does not auto-launch or auto-inject.
Setup:
- Disable SIP from Recovery mode:
csrutil disable - Grant Full Disk Access to your terminal
- Build helper dylib:
make build-dylib - Launch with injection:
imsg launch - Verify:
imsg status
To revert after testing, re-enable SIP in Recovery mode: csrutil enable.
Testing
make test
Note: make test applies a small patch to SQLite.swift to silence a SwiftPM warning about PrivacyInfo.xcprivacy.
Linting & formatting
make lint
make format
Core library
The reusable Swift core lives in Sources/IMsgCore and is consumed by the CLI target. Apps can depend on the IMsgCore library target directly.