imsg reads chat.db directly and sends through Messages.app, with one path that needs SIP off
CLI for Apple's Messages.app so your agent can send and receive text messages/iMessages.
At a glance
- What is it?
- imsg is a Swift command line tool for reading, watching and sending iMessage and SMS on macOS, built for scripts and agents through NDJSON and JSON-RPC. Its advanced half injects a helper into Messages.app and asks you to disable a system security setting.
- Who is it for?
- imsg suits a macOS 14 or newer user who wants messages in a pipeline, with Full Disk Access granted to the terminal and Automation granted for Messages. It does not suit a Linux user expecting to send, a team that cannot hand an agent Full Disk Access on a Mac, or anyone who needs read receipts, typing indicators or message mutation on a locked-down machine.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 9 days ago.
- What is it written in?
- Mainly Swift, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on November 13, 2026, and from our analysis. They are not legal advice.
Star history
- openclaw/imsg1,409 ★ · +141
| Repository | 2026-07-28 | 2026-11-13 |
|---|---|---|
| openclaw/imsg | 1.3k | 1.4k |
141 Stars in the last 108 days, measured from the daily GitHub snapshot. Compare with other projects
Editorial analysis
Sending goes through Messages.app, which is why Linux is read-only
The tool has two halves with different reach. Reading opens the local Messages database at `~/Library/Messages/chat.db`, and read commands open it in SQLite read-only mode. Sending does not talk to iMessage at all: it asks Messages.app to send, through that app's AppleScript surface, which is why sending needs an Automation grant. The platform split follows from the same fact. macOS 14 or newer is required, signed builds exist for macOS, and Linux has x86_64 read-only builds that read a `chat.db` copied from a Mac. The Linux build does not connect to iMessage and cannot send, because there is no Messages.app on the other end to ask. The pitch fits in four lines:
imsg chats --limit 10 --json | jq -s
imsg history --chat-id 42 --limit 20 --attachments --json | jq -s
imsg watch --chat-id 42 --reactions --json
imsg send --to "+14155551212" --text "on my way"For SMS, Text Message Forwarding has to be enabled on the paired iPhone first. And because sending goes through the app, `imsg send` cannot force a particular outgoing number when several numbers share one Apple ID.
Watching follows filesystem events, then quietly falls back to polling
Live streaming is not a socket and not a loop over the database. The `watch` command follows database and WAL filesystem events, with a polling fallback when macOS drops an event or rotates a sidecar file. That fallback is the design's own escape hatch, and it is exactly the condition you cannot see from the outside. A dropped filesystem event and a rotated sidecar file both produce the same output, and nothing on this page offers a flag that reports which mode is currently serving your stream. For an agent that is waiting on a reply, the practical consequence is that a stalled stream is ambiguous by construction: it may mean no new message, or it may mean the fallback engaged after a rotation. Numeric validation elsewhere in the tool is deliberately strict, which makes the silence here stand out as the one place where the tool cannot tell you what state it is in.
Zero is a valid message part index and an invalid chat id
Numeric arguments are checked before any work starts, and the rules are specific enough to matter to a caller. Chat ids, limits and poll option indices must be positive integers. Message part indices may also be zero, which is the one place a zero passes. Anything malformed or overflowing produces an error rather than falling back to a default target, and defaults apply only when an option is omitted entirely. So a script that computes a limit of zero, or passes a chat id of 0 because it treated the field as unset, gets an error instead of a chat. That is the right choice for a tool that reads a personal database and can send messages, and it is worth knowing before writing a retry loop: the failure arrives as an error at parse time, not as an empty result set halfway through a query.
Warnings go to stderr so stdout stays streamable
The output contract is designed for a pipe. The `--json` flag emits one JSON object per line, human progress and warnings stay on stderr, and stdout therefore remains safe to stream without filtering. Finite commands are meant to be closed into a single array by piping through `jq -s`, which is why every example in the file ends that way. Two more surfaces exist for machine callers: `imsg rpc` starts the long-running stdio transport described as the one used by agents and gateways, and `imsg completions llm` emits model-ready CLI help rather than ordinary shell completions. The JSON schema covers chats, messages, attachments, reactions, polls, scheduled messages and statistics.
imsg chats --json | jq -s
imsg rpc
imsg completions llmThe advanced half needs an injected helper and SIP disabled
The tool splits its own surface in two, and the split is a security boundary. Normal `chats`, `history`, `watch`, `send`, `react` and read-only RPC workflows do not use private frameworks or process injection. Read receipts, typing indicators, rich sends, message mutation, stickers, polls and chat management do, through a helper injected inside Messages.app, which the package builds as a separate artifact with its own make target for an injectable dylib. Those features require SIP to be disabled, and they may be blocked by library validation or private entitlement checks on current macOS releases. Two consequences follow. The half that needs a system security setting lowered is also the half that can stop working when Apple ships a release, so its availability is not something you can assume from a version number. And the project's own test story reflects that caution: one make target exists to probe native IMCore reply rendering without sending anything.
Full Disk Access attaches to the parent process, not to the binary
Reading the database needs Full Disk Access, granted in System Settings under Privacy and Security, and the terminal has to be reopened afterwards for the grant to take. Sending and standard tapbacks additionally need Automation for Messages. Contacts access is optional and only adds resolved names, so a run without it still works and merely shows handles. The parent-process detail is the one that bites: because the grant attaches to the process that owns the terminal rather than to the imsg binary, replacing that terminal, or running the script from a different host process such as a service, means the grant has to be made again. The permissions guide covers parent-process grants and stale TCC entries for exactly that case, and the troubleshooting page maps common failures to the gate that produces them. There is a further fallback for SSH sessions, which resolve contact names through the Mac's read-only AddressBook database when the SSH service holds Full Disk Access and Contacts.framework is unavailable.
chat-create reaches people you have never contacted
One command reaches further than the rest. `imsg chat-create` accepts phone numbers and email addresses you have never contacted, creates the missing handles through the active iMessage account, and checks every recipient with IDS before creating the chat, so there is no need to type the recipients into Messages first. Read from a documentation angle that is a convenience, since it removes a manual step. Read from an automation angle it is the widest door in the tool: the same command that would create a chat with a colleague can create one with a stranger, and an agent that composes recipient lists has no field in that command that asks it not to. The recipient check happens before creation rather than instead of it, so it verifies delivery handles, not permission. Deciding whether that command should be reachable from an agent at all is a decision the tool deliberately leaves open.
Every helper test is compiled into its own binary and executed
The make file shows how this project tests the part that cannot be unit tested normally. On Darwin, the test-helper target walks `Tests/IMsgHelperTests/*Tests.m`, compiles each file separately with clang under ARC, links four frameworks (Foundation, AppKit, ImageIO and LinkPresentation), suppresses two warnings, runs the resulting binary and stops at the first failure. One more file, BridgeOwnershipHost.m, is compiled the same way but not run in the loop. On any other system the whole block collapses to a single echoed line saying native bridge tests are macOS only, which is why the repository carries a separate `TestsLinux/` directory. The main test target runs the docs site tests under Node, then the helper tests, then a generated version file, a package resolve, a dependency patch script and finally `swift test`. CI pins Xcode 26.6, Swift 6.4.0 on Linux, Node 26.10.0 and SwiftLint 0.65.1, and the recipe for the reply-rendering target opens with a silent mkdir line cut off after the dash.
Editorial conclusion
imsg suits a macOS 14 or newer user who wants messages in a pipeline, with Full Disk Access granted to the terminal and Automation granted for Messages. It does not suit a Linux user expecting to send, a team that cannot hand an agent Full Disk Access on a Mac, or anyone who needs read receipts, typing indicators or message mutation on a locked-down machine. Verify four things before wiring it into anything: whether the parent process holding Full Disk Access is the one that will run the script, whether your use needs anything beyond the clean path, since the advanced half asks for SIP disabled and can be blocked by entitlement checks, which number an outgoing SMS will actually use when several share one Apple ID, and whether `imsg chat-create` should be reachable by the agent at all.
Frequently asked questions
Can OpenClaw work with iMessage?
Through this command line tool rather than through the agent itself. imsg reads the local Messages database at `~/Library/Messages/chat.db`, which requires Full Disk Access, and sends through Messages.app automation, which requires an Automation grant for Messages. It needs macOS 14 or newer.
Can OpenClaw send text Messages?
Yes. The send command uses the AppleScript surface of Messages.app, taking a recipient and a text argument. Sending needs Automation permission for Messages, and for SMS you also enable Text Message Forwarding on the paired iPhone. It cannot force a particular outgoing number when several numbers share one Apple ID.
Does imsg send messages from Linux?
No. Linux x86_64 read-only builds are published and they read a chat.db copied from macOS, but they do not connect to iMessage and cannot send. Sending goes through Messages.app automation, which requires macOS 14 or newer.
Does imsg need private frameworks or process injection?
Not for normal work. The chats, history, watch, send, react and read-only RPC workflows avoid private frameworks and process injection. Read receipts, typing indicators, rich sends, message mutation, stickers, polls and chat management use an injected helper inside Messages.app, which requires SIP to be disabled and may be blocked by entitlement checks on current macOS releases.
Can imsg start a conversation with someone I have never messaged?
Yes. The chat-create command accepts phone numbers and email addresses you have never contacted, creates the missing handles through the active iMessage account, and checks every recipient with IDS before creating the chat, so recipients do not have to be typed into Messages first.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/openclaw-imsg)Star history chart for your README
[](https://hysenlabs.com/projects/openclaw-imsg)Was this useful?