From d5038414b2fe55fc17d0f21afe1011d0c93bc8f8 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 8 May 2026 05:56:20 +0100 Subject: [PATCH] docs: refresh readme --- README.md | 261 +++++++++++++++++++++++++++--------------------------- 1 file changed, 131 insertions(+), 130 deletions(-) diff --git a/README.md b/README.md index f6bd73c..5cfe05a 100644 --- a/README.md +++ b/README.md @@ -1,53 +1,64 @@ # imsg -`imsg` is a macOS command-line tool for Messages.app. It reads your local -Messages database, streams new iMessage/SMS rows, sends messages through -Messages.app automation, and exposes the same surfaces over JSON and JSON-RPC. -Linux builds are read-only preview builds for inspecting an existing Messages -database copied from macOS. +Read, watch, and send iMessage / SMS from the macOS terminal — with stable JSON +and JSON-RPC surfaces designed for agents, scripts, and long-running +integrations. -Most read workflows need only Full Disk Access. Sending and standard tapbacks -also need macOS Automation permission for Messages.app. Advanced IMCore features -such as read receipts, typing indicators, and injection status are opt-in and -are increasingly limited by macOS 26. +`imsg` reads `~/Library/Messages/chat.db` directly, streams new rows over +filesystem events (with a polling fallback), and drives Messages.app through +its public AppleScript automation surface. Advanced IMCore controls (read +receipts, typing indicators, edit/unsend, group management, rich sends) are +opt-in behind a SIP-disabled dylib injection. Linux builds are a read-only +preview against a `chat.db` copied from macOS. + +Full docs: **[imsg.sh](https://imsg.sh)**. +[Quickstart](https://imsg.sh/quickstart) · +[JSON schema](https://imsg.sh/json) · +[JSON-RPC](https://imsg.sh/rpc) · +[Changelog](CHANGELOG.md) ## Highlights -- Read recent chats and message history without modifying `chat.db`. -- Stream new messages with `watch`, including a fallback poll when macOS misses - file events. -- Send text and files through Messages.app AppleScript, without private send - APIs. -- Inspect direct chats and groups, including participants, GUIDs, service, and - account routing hints. -- Emit newline-delimited JSON for automation, agents, and scripts. -- Resolve Contacts names when permission is granted, while keeping raw handles - in the output. -- Report attachment metadata, and optionally expose model-compatible converted - receive-side CAF/GIF files. -- Use JSON-RPC over stdio for long-running integrations. -- Run read-only `chats`, `group`, `history`, and `search` on Linux against a - copied `chat.db`. +- **Local-first reads.** Chats, history, attachments, and search query + `chat.db` directly — no daemon, no network round-trip. +- **Live streams.** `imsg watch` follows filesystem events on `chat.db` and + falls back to a lightweight poll when macOS drops the event. +- **Send through Messages.app.** Text, files, and standard tapbacks ride the + public AppleScript surface — no private send APIs required. +- **Group-aware.** Direct chats, group threads, participants, GUIDs, and + per-chat account routing hints all show up in JSON. +- **Built for agents.** Stable JSON-RPC over stdio, deterministic JSON + schemas, and `imsg completions llm` for in-context CLI help. +- **Contacts integration.** Resolves names from Address Book when permission + is granted, while keeping raw handles in the output. +- **Attachment-aware.** Filenames, UTIs, byte counts, resolved paths, and + optional CAF→M4A / GIF→PNG conversion for model consumers. +- **Advanced IMCore (opt-in).** Edit, unsend, delete, rich-text formatting, + effects, reply threading, group create/rename/photo, member add/remove, + read receipts, typing indicators, and live event streams via the bridge. +- **Linux read-only preview.** Inspect a copied Messages database from a Linux + host. No sending, no Messages.app integration. ## Requirements -- macOS 14 or newer. +- macOS 14 or newer (macOS 26 / Tahoe supported, with caveats noted below). - Messages.app signed in to iMessage and/or SMS relay. - Full Disk Access for the terminal or parent app that launches `imsg`. - Automation permission for Messages.app when using `send` or `react`. - Optional Contacts permission for name resolution. - Optional `ffmpeg` on `PATH` for receive-side attachment conversion. +For SMS, enable Text Message Forwarding on your iPhone for this Mac. + Linux support is read-only and requires an existing Messages database copied from macOS. It does not send, react, mark read, show typing, launch Messages.app, or access iMessage/SMS accounts on Linux. -For SMS, enable Text Message Forwarding on your iPhone for this Mac. - ## Install ```bash brew install steipete/tap/imsg +imsg --version ``` Build from source: @@ -57,80 +68,76 @@ make build ./bin/imsg --help ``` -## Common Workflows - -List recent chats: +## Quickstart ```bash -imsg chats --limit 10 -imsg chats --limit 10 --json -``` +# List recent chats. +imsg chats --limit 10 --json | jq -s -Inspect one chat before sending or wiring automation: - -```bash +# Inspect one chat before automating against it. imsg group --chat-id 42 --json -``` -Read history: - -```bash -imsg history --chat-id 42 --limit 20 +# Read history with attachment metadata. imsg history --chat-id 42 --limit 20 --attachments --json -imsg history --chat-id 42 --start 2026-05-01T00:00:00Z --end 2026-05-06T00:00:00Z --json -``` -Stream new messages: +# Stream new messages, including tapbacks. +imsg watch --chat-id 42 --reactions --json -```bash -imsg watch --chat-id 42 --json -imsg watch --chat-id 42 --since-rowid 9000 --attachments --reactions --debounce 250ms --json -``` +# Send a message — auto-pick iMessage or SMS. +imsg send --to "+14155551212" --text "on my way" -Send a message or file: +# Send a file (image, audio, document). +imsg send --to "Jane Appleseed" --file ~/Desktop/voice.m4a -```bash -imsg send --to "+14155551212" --text "hi" --service imessage -imsg send --to "Jane Appleseed" --text "voice note" --file ~/Desktop/voice.m4a -imsg send --chat-id 42 --text "same thread" -``` - -Send a standard tapback: - -```bash +# Send a standard tapback. imsg react --chat-id 42 --reaction like + +# Search local history. +imsg search --query "pizza" --match contains ``` -Generate integration help: - -```bash -imsg completions zsh -imsg completions llm -``` +`--json` emits one JSON object per line. Pipe to `jq -s` to materialize an +array, or stream it to whatever consumer you're wiring up. Human progress and +warnings always go to stderr so pipes stay parseable. ## Commands +Read, watch, and send (no special permissions beyond Full Disk Access and +Automation): + - `imsg chats [--limit 20] [--json]` - `imsg group --chat-id [--json]` - `imsg history --chat-id [--limit 50] [--attachments] [--convert-attachments] [--participants ] [--start ] [--end ] [--json]` - `imsg watch [--chat-id ] [--since-rowid ] [--debounce ] [--attachments] [--convert-attachments] [--reactions] [--participants ] [--start ] [--end ] [--json]` +- `imsg search --query [--match contains|exact] [--limit 50] [--json]` - `imsg send (--to | --chat-id | --chat-identifier | --chat-guid ) [--text ] [--file ] [--service imessage|sms|auto] [--region US] [--json]` - `imsg react --chat-id --reaction love|like|dislike|laugh|emphasis|question` -- `imsg read --to [--chat-id | --chat-identifier | --chat-guid ]` -- `imsg typing --to [--duration 5s] [--stop true] [--service imessage|sms|auto]` -- `imsg status [--json]` -- `imsg launch [--dylib ] [--kill-only] [--json]` - `imsg rpc` - `imsg completions bash|zsh|fish|llm` -`react` intentionally sends only the standard tapbacks that Messages.app exposes +Advanced IMCore (require `imsg launch` with SIP off — see +[Advanced IMCore](#advanced-imcore-features)): + +- `imsg read --to [--chat-id ]` +- `imsg typing --to [--duration 5s] [--stop true]` +- `imsg launch [--dylib ] [--kill-only] [--json]` +- `imsg status [--json]` +- `imsg send-rich`, `imsg send-multipart`, `imsg send-attachment`, + `imsg tapback` +- `imsg edit`, `imsg unsend`, `imsg delete-message`, `imsg notify-anyways` +- `imsg chat-create`, `imsg chat-name`, `imsg chat-photo`, + `imsg chat-add-member`, `imsg chat-remove-member`, `imsg chat-leave`, + `imsg chat-delete`, `imsg chat-mark` +- `imsg account`, `imsg whois`, `imsg nickname` + +`react` intentionally sends only the standard tapbacks Messages.app exposes reliably through automation. Custom emoji tapbacks can be read from -history/watch output, but are not sent by the CLI. +history/watch output, but are sent through the bridge `tapback` command. ## JSON Output -`--json` emits one JSON object per line, so consumers can stream it directly or -collect it with `jq -s`. +`--json` emits one JSON object per line, so consumers can stream it directly +or collect it with `jq -s`. Chat objects include: @@ -143,7 +150,7 @@ Message objects include: - `id`, `chat_id`, `chat_identifier`, `chat_guid`, `chat_name` - `participants`, `is_group` -- `guid`, `reply_to_guid`, `destination_caller_id` +- `guid`, `reply_to_guid`, `thread_originator_guid`, `destination_caller_id` - `sender`, `sender_name`, `is_from_me`, `text`, `created_at` - `attachments`, `reactions` @@ -153,27 +160,18 @@ and `reacted_to_guid`. Routing fields such as `destination_caller_id`, `account_id`, `account_login`, and `last_addressed_handle` are read-only diagnostics from -Messages. AppleScript does not expose a way for `imsg send` to force a specific -outgoing Apple ID phone number or inline reply target. +Messages. AppleScript does not expose a way for `imsg send` to force a +specific outgoing Apple ID phone number or inline reply target. ## JSON-RPC -`imsg rpc` speaks JSON-RPC 2.0 over stdin/stdout, one JSON object per line. It -is intended for agents and long-running integrations that want a single process -for chats, history, send, and watch. +`imsg rpc` speaks JSON-RPC 2.0 over stdin/stdout, one JSON object per line. +It is intended for agents and long-running integrations that want a single +process for chats, history, send, and watch. -Read methods: - -- `chats.list` -- `messages.history` -- `watch.subscribe` -- `watch.unsubscribe` - -Mutating method: - -- `send` - -See [docs/rpc.md](docs/rpc.md) for request and response shapes. +Read methods: `chats.list`, `messages.history`, `watch.subscribe`, +`watch.unsubscribe`. Mutating: `send`. See [docs/rpc.md](docs/rpc.md) for +request and response shapes. ## Attachments @@ -182,38 +180,39 @@ See [docs/rpc.md](docs/rpc.md) for request and response shapes. Attachment metadata includes filename, transfer name, UTI, MIME type, byte count, sticker flag, missing flag, and resolved original path. -`--convert-attachments` can expose cached, model-compatible receive-side +`--convert-attachments` exposes cached, model-compatible receive-side variants: -- CAF audio -> M4A -- GIF image -> first-frame PNG +- CAF audio → M4A +- GIF image → first-frame PNG -Conversion requires `ffmpeg` on `PATH`. Original Messages attachments are left -unchanged. Converted metadata is reported with `converted_path` and +Conversion requires `ffmpeg` on `PATH`. Original Messages attachments are +left unchanged. Converted metadata is reported with `converted_path` and `converted_mime_type`. -`send --file` sends regular files, including audio files, through Messages.app. +`send --file` sends regular files, including audio, through Messages.app. Before handing the file to Messages, `imsg` stages it under `~/Library/Messages/Attachments/imsg/` so Messages can read it reliably. ## Watch Behavior -`imsg watch` starts at the newest message by default and streams messages written -after it starts. Use `--since-rowid ` to resume from a stored cursor. +`imsg watch` starts at the newest message by default and streams messages +written after it starts. Use `--since-rowid ` to resume from a stored +cursor. The watcher listens for filesystem events on `chat.db`, `chat.db-wal`, and `chat.db-shm`, then backs that up with a lightweight poll. The poll keeps streams alive when macOS drops file events or rotates SQLite sidecar files. -RPC watch defaults to a 500ms debounce to reduce outbound echo races. CLI watch -can be tuned with `--debounce`. +RPC watch defaults to a 500ms debounce to reduce outbound echo races. CLI +watch can be tuned with `--debounce`. ## Permissions Troubleshooting If reads fail with `unable to open database file`, empty output, or `authorization denied`: -1. Open System Settings -> Privacy & Security -> Full Disk Access. +1. Open System Settings → Privacy & Security → Full Disk Access. 2. Add the terminal or parent app that launches `imsg`. 3. If launched from an editor, Node process, gateway, or shell wrapper, grant Full Disk Access to that parent app too. @@ -225,19 +224,19 @@ If reads fail with `unable to open database file`, empty output, or 6. Confirm Messages.app is signed in and `~/Library/Messages/chat.db` exists. For sends and tapbacks, allow the terminal or parent app under Privacy & -Security -> Automation -> Messages. +Security → Automation → Messages. `imsg` opens `chat.db` read-only. It does not use SQLite `immutable=1` by default because immutable reads can miss WAL-backed Messages updates. ## Advanced IMCore Features -Default `send`, `chats`, `history`, `watch`, and read-only `rpc` workflows do -not require IMCore injection. +Default `send`, `chats`, `history`, `watch`, `search`, and read-only `rpc` +workflows do not require IMCore injection. -Advanced features such as `read`, `typing`, `launch`, bridge-backed rich send, -message mutation, and chat management are opt-in. They require SIP to be -disabled and a helper dylib to be injected into Messages.app: +Advanced features such as `read`, `typing`, `launch`, bridge-backed rich +send, message mutation, and chat management are opt-in. They require SIP to +be disabled and a helper dylib to be injected into Messages.app: ```bash make build-dylib @@ -249,40 +248,37 @@ Important limits: - `imsg launch` refuses to inject when SIP is enabled. - `imsg status` is read-only and does not auto-launch or auto-inject. -- macOS 26/Tahoe can block injection through library validation. -- macOS 26/Tahoe can also reject direct IMCore clients through `imagent` +- macOS 26 / Tahoe can block injection through library validation. +- macOS 26 / Tahoe can also reject direct IMCore clients through `imagent` private-entitlement checks. -- These limits affect advanced IMCore features such as typing indicators, not - normal send/history/watch usage. +- These limits affect advanced IMCore features such as typing indicators, + not normal send/history/watch usage. -To revert after testing advanced features, re-enable SIP from Recovery mode with +To revert after testing, re-enable SIP from Recovery mode with `csrutil enable`. ### Bridge command surface The bridge implements a manual port of the BlueBubbles private-API surface -inspired by their Apache-2.0 helper, into our own dylib (no third-party -binary). Commands in this section require `imsg launch` first, which means -SIP-disabled DYLD injection into Messages.app. Most commands take a `--chat` -argument that is the chat guid (e.g. `iMessage;-;+15551234567` or -`iMessage;+;chat0000` for groups). Get a chat guid via `imsg chats --json`. +(inspired by their Apache-2.0 helper) into our own dylib — no third-party +binary. Most commands take a `--chat` argument that is the chat GUID +(e.g. `iMessage;-;+15551234567` for direct, `iMessage;+;chat0000` for +groups). Get a chat GUID via `imsg chats --json`. Messaging: + ```bash # Rich send with effect + reply imsg send-rich --chat 'iMessage;-;+15551234567' --text "boom" \ --effect com.apple.MobileSMS.expressivesend.impact \ --reply-to -# Text formatting (macOS 15+ Sequoia only): bold/italic/underline/strikethrough +# Text formatting (macOS 15+ Sequoia): bold/italic/underline/strikethrough # applied to specific ranges of the message body. imsg send-rich --chat ... --text 'hello world' \ --format '[{"start":0,"length":5,"styles":["bold"]}, {"start":6,"length":5,"styles":["italic","underline"]}]' -# Or load the ranges from a file -imsg send-rich --chat ... --text "$(cat msg.txt)" --format-file ranges.json - # Multipart send (text-only in v1; per-part textFormatting also supported) imsg send-multipart --chat 'iMessage;+;chat0000' \ --parts '[{"text":"hi"}, @@ -292,12 +288,13 @@ imsg send-multipart --chat 'iMessage;+;chat0000' \ imsg send-attachment --chat ... --file ~/Pictures/img.jpg imsg send-attachment --chat ... --file ~/audio.caf --audio -# Tapback (bridge-backed; `imsg react` remains the AppleScript variant) +# Bridge tapback (custom emoji + remove supported here, unlike `imsg react`) imsg tapback --chat ... --message --kind love imsg tapback --chat ... --message --kind love --remove ``` Mutate (macOS 13+ — selector availability surfaced in `imsg status`): + ```bash imsg edit --chat ... --message --new-text "actually..." imsg unsend --chat ... --message @@ -306,6 +303,7 @@ imsg notify-anyways --chat ... --message ``` Chat management: + ```bash imsg chat-create --addresses '+15551111111,+15552222222' --name 'Crew' --text 'gm' imsg chat-name --chat ... --name 'Renamed' @@ -317,10 +315,12 @@ imsg chat-leave --chat ... imsg chat-delete --chat ... imsg chat-mark --chat ... --read # or --unread ``` + `chat-create` currently creates iMessage chats only. SMS sending remains available through `imsg send --service sms`. Introspection: + ```bash imsg account # active iMessage account + aliases imsg whois --address +15551234567 --type phone @@ -328,12 +328,8 @@ imsg whois --address foo@bar.com --type email imsg nickname --address +15551234567 ``` -Local history search (does not require the bridge): -```bash -imsg search --query "pizza" --match contains -``` - Live events (typing indicators surfaced through the dylib): + ```bash imsg watch --bb-events # merge dylib events into stdout imsg watch --bb-events --json # one JSON object per event @@ -354,8 +350,8 @@ per-request UUID-keyed queue: ``` Set `IMSG_BRIDGE_LEGACY_IPC=1` to force the legacy single-file path for -debugging (existing v1 callers / un-rebuilt dylibs continue to work without -this). +debugging (existing v1 callers and un-rebuilt dylibs continue to work +without this). ## Development @@ -369,4 +365,9 @@ make build tests. The reusable Swift core lives in `Sources/IMsgCore`; the CLI target lives in -`Sources/imsg`. +`Sources/imsg`; the injected helper lives in `Sources/IMsgHelper`. + +## License + +MIT. Not affiliated with Apple. iMessage and SMS are trademarks of their +respective owners.