imsg
SkillCommunicationiMessage/SMS: local history, contacts, live watch, and requested sends.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the imsg skill
What this skill tells your AI
The instructions your AI receives, as published by openclaw/imsg in .agents/skills/imsg/SKILL.md and read by ahel’s review.
imsg reads ~/Library/Messages/chat.db directly and sends through Messages.app automation. Reading is local and safe. Sending, reacting, marking read, typing indicators, and any chat mutation require an explicit user request — confirm recipient, service, and content in your final summary.
Ground rules
- Every read command supports
--jsonand emits NDJSON (one object per line). Pipe tojq -sto get an array. Stdout carries only JSON; progress and warnings go to stderr. - Two capability tiers:
- Standard (normal permissions):
chats,group,history,watch,search,send,react,nickname --local,account --local,whois --local. - Bridge (SIP disabled +
imsg launchdylib injection):send-rich,send-multipart,send-attachment,send-sticker,tapback,poll,edit,unsend,delete-message,read,typing,notify-anyways,chat-*,name-photo, and default-modeaccount/whois/nickname.
- Standard (normal permissions):
- Check availability with
imsg status --jsonbefore using bridge commands. Stickers additionally requiresend.stickerinrpc_methodsandselectors.stickerSend; attaching one requiresselectors.stickerAttach. If the bridge is down, use a standard command only when it preserves the requested semantics; otherwise stop and explain. Never turn a reply/effect/subject into a plain send or a GUID-targeted tapback intoreact, and never suggest disabling SIP unprompted. - Full command and flag reference:
imsg completions llm.
Preconditions
imsg status --json # feature availability + setup hints
sqlite3 ~/Library/Messages/chat.db 'pragma quick_check;' # fails => terminal lacks Full Disk Access
Reading
Resolve a person visible in the Messages.app UI from chats, not search. The UI name usually surfaces as contact_name (Contacts permission); it does not appear in imsg search results, raw message.text, or the DB handle table. No search hits is not proof the contact doesn't exist.
imsg chats --limit 200 --json | jq -s '.[] | select((.contact_name // .display_name // .name // .identifier // "" | ascii_downcase) | contains("beatrix"))'
imsg chats --unread-only --json | jq -s
Then inspect and read the chat by rowid:
imsg group --chat-id ID --json # identity + participants; check before automating
imsg history --chat-id ID --limit 50 --json | jq -s
imsg history --chat-id ID --start 2025-01-01T00:00:00Z --end 2025-02-01T00:00:00Z --json
imsg stats --chat-id ID --time-zone UTC --media --json # logical message + media totals
imsg scheduled list --json # future Send Later rows; read-only
imsg chat-background status --chat-id ID --json # inspect local background state; read-only
imsg name-photo status --chat GUID --json # read-only Share Name & Photo eligibility
- Chat
idis thechat.dbrowid: stable on one machine, the preferred--chat-idhandle.identifierandguidare portable across machines. --startis inclusive,--endexclusive; both take ISO8601. Use absolute timestamps for date-scoped questions.--attachmentsadds attachment metadata;--convert-attachmentsconverts CAF→M4A / GIF→PNG for model consumption.imsg search --query "pizza tonight" --jsonsearches message bodies only (--match containsdefault,exactavailable).imsg stats [--chat-id ID] [--time-zone IANA] [--media] --jsonaggregates logical messages by chat, inbound sender, service, and local date; no bridge required.- Chat-list JSON includes
unread_count. Inbound message payloads includeis_readand, when read,date_read; outbound payloads omit both. - SIP-free lookups:
imsg whois --address "+15551234567" --type phone --local,imsg nickname --address "+15551234567" --local --json,imsg account --local --json. Notenickname --localreturns your AddressBook label for the handle; the iMessage-shared nickname needs default-modenicknamevia the bridge. - Direct
sqlite3queries are a last resort; thehandletable lacks the resolved namesimsg chatsprovides.
Streaming
imsg watch --chat-id ID --json # filesystem events with polling fallback
imsg watch --since-rowid N --json # resume from a message id cursor
Message id doubles as the watch cursor: persist the last-seen id and pass it back via --since-rowid. Add --reactions for tapback add/remove events and --attachments for attachment metadata.
Sending (explicit request only)
imsg send --to "+15551234567" --text "message" --service auto
imsg send --chat-id ID --text "message" # prefer for groups: no address ambiguity
imsg send --to "+15551234567" --file ~/Desktop/pic.jpg
imsg name-photo share --chat GUID # shares YOUR Name & Photo; explicit request only
--service autoprefers iMessage and falls back to SMS for text-only phone sends;--no-sms-fallbackdisables that.imsg react --chat-id ID --reaction like(AppleScript) only targets the most recent incoming message and needs Accessibility permission. To react to a specific message by GUID, use bridgetapback.name-photo shareis not a vCard send. It discloses the local Messages Name & Photo to every participant in the selected chat; confirm the destination and explicit user intent before invoking it.
Bridge extras
Only after imsg status confirms the bridge is loaded (imsg launch injects it; refuses when SIP is on; macOS 26 entitlement gates can block features even with SIP off):
imsg send-rich --chat 'iMessage;-;+15551234567' --text 'hi' --reply-to MSG_GUID # replies, effects, subjects
imsg send-sticker --chat GUID --file ~/Pictures/sticker.png --attach-to MSG_GUID --target-part 0
imsg send-rich --chat 'iMessage;-;+15551234567' --url https://imsg.sh
imsg poll send --chat GUID --question 'Dinner?' --option 'Pizza' --option 'Sushi' --comment 'Vote by 5pm'
imsg poll unvote --chat GUID --poll POLL_GUID --option-index 1
imsg edit --chat GUID --message MSG_GUID --new-text 'updated' # macOS 13+
imsg chat-create --addresses '+15551234567,+15559876543' --name 'Crew'
poll send echoes --question as a best-effort plain caption after the Polls balloon; --comment overrides that caption. Do not retry automatically when only the caption fails: the poll may already be delivered. history and watch backfill a title-less inbound native poll's poll.question from its clean caption row.
Destructive bridge commands — unsend, delete-message, chat-delete, chat-leave, chat-remove-member — need per-action user confirmation.
Verification
For repo edits:
make test
make build
./bin/imsg chats --limit 3 --json | jq -s # live read proof against the local DB
Signals
- GitHub stars
- 1k
- Forks
- 180
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
imsg-openclaw- Source
- github.com/openclaw/imsg