imsg: a Swift command line that reads, streams and sends iMessage from a Mac
imsg is an open-source command-line program for a Mac that reads, streams and sends iMessage and SMS through the Messages app already signed in on that Mac. Its README states the scope in one line: it "is a Swift CLI for reading, watching,
imsg is an open-source command-line program for a Mac that reads, streams and sends iMessage and SMS through the Messages app already signed in on that Mac. Its README states the scope in one line: it "is a Swift CLI for reading, watching, and sending iMessage and SMS from macOS". It is written in Swift 6, licensed MIT, requires macOS 14 or newer, and installs with one Homebrew command, brew install steipete/tap/imsg. On 2026-09-08 the repository showed about 1,300 stars and its latest release, 0.15.3, was published the day before.
Where the messages come from. Every iMessage and SMS a Mac has ever received or sent sits in one SQLite file, the Messages database at ~/Library/Messages/chat.db. imsg opens that file directly. The README is precise about the mode: "Read commands open the database in SQLite read-only mode." Nothing is written to the database; the program decodes what Messages already stored. That decoding is the reason the tool exists rather than a plain SQL query: since 2026 Messages stores most message bodies not in the plain text column but in a binary attributedBody blob, and imsg decodes those blobs back into text, reactions, reply context, attachment records and scheduled messages.
Streaming. The watch command emits one JSON object per new message as it arrives. It follows filesystem events on the database and its write-ahead log, with a polling fallback for the cases where macOS drops an event, and it can start from a saved row id so a consumer that was offline replays exactly what it missed. Output discipline is explicit: the flag "emits one JSON object per line. Human progress and warnings stay on stderr, so stdout remains safe to stream."
Sending.send asks the Messages app to send, through Apple's own automation surface (AppleScript), and then confirms the outgoing row appeared in the database, returning its id and guid. A separate message.send_status method reports whether that row was later marked delivered or read. Text, files and audio go through the same path. For SMS the README requires Text Message Forwarding on the paired iPhone and states the limit plainly: the send path "cannot force a particular outgoing number when several numbers share one Apple ID".
A long-running interface for programs.imsg rpc speaks JSON-RPC 2.0 over standard input and output, with no network port: initialize, chats.list, messages.history, messages.search, messages.after (a cursor over everything newer than a row id), messages.stats, watch.subscribe, send, send.tracked (idempotent, caller-supplied id), message.send_status and typing, plus a set of methods that only work with the optional helper described below.
Permissions. Three macOS grants govern what works. "Full Disk Access is required for local database reads." Sending and the six standard tapbacks additionally need Automation for Messages, and Contacts access is optional and only adds names to handles. Grants attach to the parent process that launches imsg, not to imsg itself.
The two tiers. Everything above runs with System Integrity Protection on. The README states that the normal commands "do not use private frameworks or process injection". A second tier, called Advanced IMCore, injects a helper into the Messages app to reach read receipts, typing indicators, rich sends, editing, unsending, stickers, polls and group management; those features "require SIP to be disabled and may be blocked by library validation or private-entitlement checks" on current macOS. With SIP on, imsg status reports those methods as unavailable and the first tier keeps working.
What it does not do. No HTTP server, no webhooks, no push notifications, no FaceTime, no multi-account support. Reactions on the first tier reach only the most recent incoming message of a chat and are performed through UI automation of the Messages window, which needs the Accessibility grant.
Verdict. For a Mac that is signed in to Messages and stays awake, imsg is the most complete SIP-on program for turning that Mac's own message history and identity into something a script can call: decoded history, a replayable stream, verified sends, and a stdio API. Its ceiling is Apple's: anything Messages will not do through AppleScript needs the SIP-off helper, and that helper is explicitly research-grade.
References (2)
openclaw/imsg README“Read commands open the database in SQLite read-only mode. `watch` follows database and WAL filesystem events, with a polling fallback when macOS drops an event or rotates a sidecar file.”
openclaw/imsg README, Permissions and Advanced IMCore“Read receipts, typing indicators, rich sends, message mutation, stickers, polls, and chat management use an injected helper inside Messages.app. They require SIP to be disabled and may be blocked by library validation or private-entitlement checks”
the whole object this page is drawn from, as JSON
{
"schema":"https://miscsubjects.com/schemas/jarticle.json",
"status":"published",
"slug":"imsg",
"title":"imsg: a Swift command line that reads, streams and sends iMessage from a Mac",
"kind":"reference",
"summary":"imsg is an open-source command-line program for a Mac that reads, streams and sends iMessage and SMS through the Messages app already signed in on that Mac. Its README states the scope in one line: it \"is a Swift CLI for reading, watching,",
"created":"2026-09-08T20:13:06.636Z",
"body": [
"imsg is an open-source command-line program for a Mac that reads, streams and sends iMessage and SMS through the Messages app already signed in on that Mac. Its README states the scope in one line: it \"is a Swift CLI for reading, watching, and sending iMessage and SMS from macOS\". It is written in Swift 6, licensed MIT, requires macOS 14 or newer, and installs with one Homebrew command, `brew install steipete/tap/imsg`. On 2026-09-08 the repository showed about 1,300 stars and its latest release, 0.15.3, was published the day before.\n\n**Where the messages come from.** Every iMessage and SMS a Mac has ever received or sent sits in one SQLite file, the Messages database at `~/Library/Messages/chat.db`. imsg opens that file directly. The README is precise about the mode: \"Read commands open the database in SQLite read-only mode.\" Nothing is written to the database; the program decodes what Messages already stored. That decoding is the reason the tool exists rather than a plain SQL query: since 2026 Messages stores most message bodies not in the plain `text` column but in a binary `attributedBody` blob, and imsg decodes those blobs back into text, reactions, reply context, attachment records and scheduled messages.\n\n**Streaming.** The `watch` command emits one JSON object per new message as it arrives. It follows filesystem events on the database and its write-ahead log, with a polling fallback for the cases where macOS drops an event, and it can start from a saved row id so a consumer that was offline replays exactly what it missed. Output discipline is explicit: the flag \"emits one JSON object per line. Human progress and warnings stay on stderr, so stdout remains safe to stream.\"\n\n**Sending.** `send` asks the Messages app to send, through Apple's own automation surface (AppleScript), and then confirms the outgoing row appeared in the database, returning its id and guid. A separate `message.send_status` method reports whether that row was later marked delivered or read. Text, files and audio go through the same path. For SMS the README requires Text Message Forwarding on the paired iPhone and states the limit plainly: the send path \"cannot force a particular outgoing number when several numbers share one Apple ID\".\n\n**A long-running interface for programs.** `imsg rpc` speaks JSON-RPC 2.0 over standard input and output, with no network port: initialize, chats.list, messages.history, messages.search, messages.after (a cursor over everything newer than a row id), messages.stats, watch.subscribe, send, send.tracked (idempotent, caller-supplied id), message.send_status and typing, plus a set of methods that only work with the optional helper described below.\n\n**Permissions.** Three macOS grants govern what works. \"Full Disk Access is required for local database reads.\" Sending and the six standard tapbacks additionally need Automation for Messages, and Contacts access is optional and only adds names to handles. Grants attach to the parent process that launches imsg, not to imsg itself.\n\n**The two tiers.** Everything above runs with System Integrity Protection on. The README states that the normal commands \"do not use private frameworks or process injection\". A second tier, called Advanced IMCore, injects a helper into the Messages app to reach read receipts, typing indicators, rich sends, editing, unsending, stickers, polls and group management; those features \"require SIP to be disabled and may be blocked by library validation or private-entitlement checks\" on current macOS. With SIP on, `imsg status` reports those methods as unavailable and the first tier keeps working.\n\n**What it does not do.** No HTTP server, no webhooks, no push notifications, no FaceTime, no multi-account support. Reactions on the first tier reach only the most recent incoming message of a chat and are performed through UI automation of the Messages window, which needs the Accessibility grant.\n\n**Verdict.** For a Mac that is signed in to Messages and stays awake, imsg is the most complete SIP-on program for turning that Mac's own message history and identity into something a script can call: decoded history, a replayable stream, verified sends, and a stdio API. Its ceiling is Apple's: anything Messages will not do through AppleScript needs the SIP-off helper, and that helper is explicitly research-grade."
],
"facts": {
"converted_from":"markdown",
"original_updated":"2026-09-08T19:52:40.713Z",
"register":null,
"category":"reference"
},
"model":"unattributed",
"tags": [
"reference",
"imessage",
"mac",
"messaging"
],
"sources": [
{
"id":"s1",
"url":"https://github.com/openclaw/imsg",
"title":"openclaw/imsg README",
"quote":"Read commands open the database in SQLite read-only mode. `watch` follows database and WAL filesystem events, with a polling fallback when macOS drops an event or rotates a sidecar file.",
"accessed_at":"2026-09-08T19:52:40.482Z",
"prev":"genesis",
"hash":"7dba210c307df4e4fb40a0fd7176495fe73440987359a51abf153a63ace54128",
"quote_sha256":"a5d49e367cde956d9c1b1c472fb77c9ebb0abd1cd30bb3b75c0fc40fa5cbf93e"
},
{
"id":"s2",
"url":"https://github.com/openclaw/imsg#permissions",
"title":"openclaw/imsg README, Permissions and Advanced IMCore",
"quote":"Read receipts, typing indicators, rich sends, message mutation, stickers, polls, and chat management use an injected helper inside Messages.app. They require SIP to be disabled and may be blocked by library validation or private-entitlement checks",
"accessed_at":"2026-09-08T19:52:40.482Z",
"prev":"7dba210c307df4e4fb40a0fd7176495fe73440987359a51abf153a63ace54128",
"hash":"2551ac6b0d2259c4700ad9a6d790dd03da708af14a254d84aa495e991b310e18",
"quote_sha256":"269d8482e23d67d30bdc0e3e4c953592e134eb4a531e972606c7b7dda8d20bab"
}
],
"claims": [
{
"id":"c1",
"text":"imsg is an open-source command-line program for a Mac that reads, streams and sends iMessage and SMS through the Messages app already signed in on that Mac.",
"section":"imsg: a Swift command line that reads, streams and sends iMessage from a Mac",
"tier":"definition",
"source_ids": [
"s1"
],
"why_material":"identifies the system and what it is"
},
{
"id":"c2",
"text":"Read commands open the database in SQLite read-only mode.",
"section":"Where the messages come from",
"tier":"definition",
"source_ids": [
"s1"
],
"why_material":"the mechanism, in the source's own words: it reads, never writes, the Messages store"
},
{
"id":"c3",
"text":"Everything above runs with System Integrity Protection on. A second tier, called Advanced IMCore, injects a helper into the Messages app; those features require SIP to be disabled.",
"section":"The two tiers",
"tier":"definition",
"source_ids": [
"s1",
"s2"
],
"why_material":"decides which features are safe to rely on"
},
{
"id":"c4",
"text":"Full Disk Access is required for local database reads. Sending and the six standard tapbacks additionally need Automation for Messages.",
"section":"Permissions",
"tier":"regulatory",
"source_ids": [
"s2"
],
"why_material":"the grants an operator must hold before any of it works"
}
],
"type":"article",
"updated":"2026-10-08T11:29:37.691Z",
"version":1,
"hash":"729d7e44af6bac9e27ea4376aff42e7ee18aa4dc76f932ab5a038fb6921ca0ae",
"styling": {
"scope":"shared",
"how":"no value on the page is set by hand: each comes from this theme, and each theme value becomes the CSS variable named beside it. PATCH https://miscsubjects.com/api/jarticles/theme changes them.",
"widget_style": {
"what":"every widget may carry style: {…}; these are the only keys it reads",
"rotate":"degrees, a number",
"offset_x":"pixels, a number",
"offset_y":"pixels, a number",
"pulse":"true to pulse"
},
"theme": {
"color": {
"page_bg":"#f5f5f7",
"sheet_bg":"#ffffff",
"ink":"#1d1d1f",
"soft":"#424245",
"muted":"#6e6e73",
"dim":"#86868b",
"line":"#d2d2d7",
"accent":"#0071e3",
"accent_soft":"rgba(0,113,227,0.08)",
"link":"#0066cc",
"selection":"rgba(0,113,227,0.18)",
"widget_bg":"#f5f5f7",
"widget_border":"#d2d2d7",
"card_bg":"#ffffff",
"code_bg":"#f5f5f7",
"json_bg":"#161b19",
"json_ink":"#e6e1d4",
"json_key":"#7fd3c3",
"json_string":"#e9c46a",
"json_number":"#f4a261",
"json_bool":"#c792ea",
"json_null":"#928d7e",
"json_punct":"#6b6759",
"json_bar":"#0e1512",
"kind_badge_bg":"#0e6e60",
"kind_badge_ink":"#ffffff"
},
"color_dark": {
"page_bg":"#000000",
"sheet_bg":"#1d1d1f",
"ink":"#f5f5f7",
"soft":"#d2d2d7",
"muted":"#a1a1a6",
"dim":"#86868b",
"line":"#424245",
"accent":"#2997ff",
"accent_soft":"rgba(41,151,255,0.12)",
"link":"#2997ff",
"selection":"rgba(41,151,255,0.25)",
"widget_bg":"#2c2c2e",
"widget_border":"#424245",
"card_bg":"#1d1d1f",
"code_bg":"#2c2c2e",
"json_bg":"#0a0d0c",
"json_ink":"#e6e1d4",
"json_key":"#7fd3c3",
"json_string":"#e9c46a",
"json_number":"#f4a261",
"json_bool":"#c792ea",
"json_null":"#757062",
"json_punct":"#5d665f",
"json_bar":"#060807",
"kind_badge_bg":"#4fc1ab",
"kind_badge_ink":"#0b0f0d"
},
"font": {
"display":"-apple-system,BlinkMacSystemFont,\"SF Pro Display\",\"SF Pro Text\",\"Helvetica Neue\",Helvetica,Arial,sans-serif",
"body":"-apple-system,BlinkMacSystemFont,\"SF Pro Display\",\"SF Pro Text\",\"Helvetica Neue\",Helvetica,Arial,sans-serif",
"mono":"ui-monospace,SFMono-Regular,\"SF Mono\",Menlo,Consolas,monospace",
"google_fonts":""
},
"size": {
"body":"17px",
"small":"17px",
"eye":"17px",
"lead":"17px",
"h1":"52px",
"h2":"34px",
"h3":"22px",
"byline":"17px",
"h1_measure":"none",
"code":"17px",
"json":"17px",
"widget_title":"17px"
},
"leading": {
"body":"1.7",
"head":"1.18",
"lead":"1.5",
"json":"1.6"
},
"weight": {
"display":"700",
"head":"700",
"body":"400",
"strong":"700",
"eye":"700"
},
"tracking": {
"eye":"0.14em",
"display":"-0.03em",
"body":"-0.006em",
"badge":"0.08em"
},
"layout": {
"sheet_width":"1000px",
"text_width":"880px",
"gutter":"16px",
"sheet_padding":"56px 64px 64px",
"sheet_padding_mobile":"24px 18px 40px",
"sheet_margin":"28px auto 72px",
"block_gap":"1.15em",
"widget_gap":"28px",
"grid_gap":"14px"
},
"border": {
"sheet":"0px solid",
"sheet_radius":"30px",
"widget":"1px solid",
"widget_radius":"24px",
"card_radius":"24px",
"badge_radius":"999px",
"rule":"1px solid",
"code_radius":"18px",
"accent_bar":"3px"
},
"shadow": {
"sheet":"0 1px 1px rgba(0,0,0,.02),0 6px 26px rgba(0,0,0,.05)",
"widget":"none",
"card_hover":"0 10px 28px -16px rgba(28,27,23,0.35)"
},
"motion": {
"hover":"160ms ease"
}
}
},
"page":"https://miscsubjects.com/a/imsg",
"json":"https://miscsubjects.com/a/imsg.json",
"markdown":"https://miscsubjects.com/a/imsg.md",
"theme":"https://miscsubjects.com/article-theme.json",
"calls": [
{
"what":"this article as a page (no key)",
"method":"GET",
"url":"https://miscsubjects.com/a/imsg",
"headers": {}
},
{
"what":"this article as its object (no key)",
"method":"GET",
"url":"https://miscsubjects.com/a/imsg.json",
"headers": {}
},
{
"what":"this article as Markdown (no key)",
"method":"GET",
"url":"https://miscsubjects.com/a/imsg.md",
"headers": {}
},
{
"what":"the object with its hash, drafts too",
"method":"GET",
"url":"https://miscsubjects.com/api/jarticles/imsg",
"headers": {
"x-sheets-key":"$(cat /Users/cyrusmassoumi/.misc-sheets-key)"
}
},
{
"what":"one piece and that piece’s hash",
"method":"GET",
"url":"https://miscsubjects.com/api/jarticles/imsg/at?path=facts",
"headers": {
"x-sheets-key":"$(cat /Users/cyrusmassoumi/.misc-sheets-key)"
}
},
{
"what":"grep: every key or value that matches, with the path to change it",
"method":"GET",
"url":"https://miscsubjects.com/api/jarticles/imsg/grep?q=owner",
"headers": {
"x-sheets-key":"$(cat /Users/cyrusmassoumi/.misc-sheets-key)"
}
},
{
"what":"change pieces (set, merge, delete, append, insert, move); if-match is the hash",
"method":"PATCH",
"url":"https://miscsubjects.com/api/jarticles/imsg",
"headers": {
"x-sheets-key":"$(cat /Users/cyrusmassoumi/.misc-sheets-key)",
"content-type":"application/json",
"if-match":"<hash from the read>"
},
"body": {
"ops": [
{
"op":"set",
"path":"facts.example",
"value":1
}
]
}
},
{
"what":"replace the whole object",
"method":"PUT",
"url":"https://miscsubjects.com/api/jarticles/imsg",
"headers": {
"x-sheets-key":"$(cat /Users/cyrusmassoumi/.misc-sheets-key)",
"content-type":"application/json",
"if-match":"<hash>"
},
"body": {
"slug":"imsg",
"title":"…"
}
},
{
"what":"fetch every source, hash what came back and check the quote is in it",
"method":"POST",
"url":"https://miscsubjects.com/api/jarticles/imsg/verify",
"headers": {
"x-sheets-key":"$(cat /Users/cyrusmassoumi/.misc-sheets-key)",
"content-type":"application/json"
},
"body": {}
},
{
"what":"an Obsidian-style command",
"method":"POST",
"url":"https://miscsubjects.com/api/jarticles/cli",
"headers": {
"x-sheets-key":"$(cat /Users/cyrusmassoumi/.misc-sheets-key)",
"content-type":"application/json"
},
"body": {
"command":"backlinks",
"file":"imsg"
}
}
]
}