diff --git a/CHANGELOG.md b/CHANGELOG.md index 4aa1888..2db1ab4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## 0.14.0 - Unreleased +### Documentation +- docs: rewrite the README as a concise front door to installation, core workflows, and the full documentation site. + ### JSON-RPC - feat: add bounded `messages.after` pagination with authoritative database-instance-scoped ROWID cursors, cross-chat catchup, and optional standalone reaction events (#200, #201, thanks @vincentkoc). diff --git a/README.md b/README.md index e50b290..4b06247 100644 --- a/README.md +++ b/README.md @@ -1,429 +1,92 @@ -# imsg +# imsg ๐Ÿ’ฌ โ€” Messages, piped. -![imsg banner](docs/assets/readme-banner.jpg) - -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. - -`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 - -- **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 events or rotates SQLite - WAL sidecars. -- **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, native stickers, group create/rename/photo, - member add/remove, Name & Photo sharing, 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 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. - -## Install +[![CI](https://img.shields.io/github/actions/workflow/status/openclaw/imsg/ci.yml?branch=main&style=flat-square&label=ci)](https://github.com/openclaw/imsg/actions/workflows/ci.yml) +[![GitHub release](https://img.shields.io/github/v/release/openclaw/imsg?style=flat-square)](https://github.com/openclaw/imsg/releases/latest) +[![macOS 14+](https://img.shields.io/badge/platform-macOS%2014%2B-lightgrey?style=flat-square)](docs/install.md) +[![Swift 6](https://img.shields.io/badge/Swift-6-F05138?style=flat-square&logo=swift&logoColor=white)](https://www.swift.org) +[![License](https://img.shields.io/github/license/openclaw/imsg?style=flat-square)](LICENSE) +[![Homebrew](https://img.shields.io/badge/Homebrew-steipete%2Ftap-FBB040?style=flat-square&logo=homebrew&logoColor=black)](https://github.com/steipete/homebrew-tap) +[![Docs](https://img.shields.io/badge/docs-imsg.sh-4B5563?style=flat-square)](https://imsg.sh) -```bash -brew install steipete/tap/imsg -imsg --version -``` - -Build from source: - -```bash -make build -./bin/imsg --help -``` +![imsg banner](docs/assets/readme-banner.jpg) -## Quickstart +`imsg` is a Swift CLI for reading, watching, and sending iMessage and SMS from macOS. It reads the local Messages database, sends through Messages.app automation, and exposes NDJSON and JSON-RPC for scripts and agents. ```bash -# List recent chats. imsg chats --limit 10 --json | jq -s - -# Inspect one chat before automating against it. -imsg group --chat-id 42 --json - -# Read history with attachment metadata. -imsg history --chat-id 42 --limit 20 --attachments --json - -# Stream new messages, including tapbacks. +imsg history --chat-id 42 --limit 20 --attachments --json | jq -s imsg watch --chat-id 42 --reactions --json - -# Send a message โ€” auto-pick iMessage or SMS. imsg send --to "+14155551212" --text "on my way" - -# Send a file (image, audio, document). -imsg send --to "Jane Appleseed" --file ~/Desktop/voice.m4a - -# Send a standard tapback. -imsg react --chat-id 42 --reaction like - -# Search local history. -imsg search --query "pizza" --match contains - -# Summarize logical messages in your local timezone. -imsg stats --media --json - -# List future Send Later rows without launching Messages. -imsg scheduled list --json - -# Inspect a chat's local background metadata and cache state. -imsg chat-background status --chat-id 42 --json ``` -`--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 stats [--chat-id ] [--time-zone ] [--media] [--json]` -- `imsg scheduled list [--limit 50] [--json]` -- `imsg chat-background status --chat-id [--json]` -- `imsg send (--to | --chat-id | --chat-identifier | --chat-guid ) [--text ] [--file ] [--service imessage|sms|auto] [--no-sms-fallback] [--region US] [--json]` -- `imsg react --chat-id --reaction love|like|dislike|laugh|emphasis|question` -- `imsg rpc` -- `imsg completions bash|zsh|fish|llm` - -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 [--reply-to ] [--file ] [--url ]`, - `imsg send-multipart`, `imsg send-attachment [--reply-to ]`, - `imsg send-sticker [--attach-to ] [--target-part ]`, `imsg tapback` -- `imsg poll send (--chat | --chat-id ) --question [--comment | --no-comment] --option --option [--reply-to ]` -- `imsg poll vote (--chat | --chat-id ) --poll (--option-id | --option-index | --option )` -- `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` -- `imsg name-photo status|share --chat ` - -`imsg status --json` reports native bridge selector capabilities. Poll creation -requires `selectors.pollPayloadMessage`; poll voting requires -`selectors.pollVoteMessage` plus `poll.vote` in `rpc_methods`. Standalone -stickers require `send.sticker` in `rpc_methods` and `selectors.stickerSend`; -attaching one to a bubble also requires `selectors.stickerAttach`. -Messages does not render the poll payload title on the balloon, so `poll send` -also sends a best-effort plain caption message right after the poll. The -caption defaults to `--question`; use `--comment` when the visible text should -be different from the stored question, or `--no-comment` when the caller -already sent its own visible context and needs only the poll balloon. - -`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 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`. - -Chat objects include: - -- `id`, `name`, `identifier`, `guid`, `service`, `last_message_at` -- `display_name`, `contact_name` -- `is_group`, `participants` -- `account_id`, `account_login`, `last_addressed_handle` - -Message objects include: - -- `id`, `chat_id`, `chat_identifier`, `chat_guid`, `chat_name` -- `participants`, `is_group` -- `guid`, `reply_to_guid`, `thread_originator_guid`, `destination_caller_id` -- `reply_to_text`, `reply_to_sender` (parent body + handle for threaded - replies and non-reaction associations, when the parent row is still in - chat.db) -- `balloon_bundle_id`, `url_preview` -- `sender`, `sender_name`, `is_from_me`, `text`, `created_at` -- `attachments`, `reactions` -- `poll` for native Apple Messages poll creation and vote rows - -When `watch --reactions --json` sees a tapback event, the message object also -includes `is_reaction`, `reaction_type`, `reaction_emoji`, `is_reaction_add`, -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. - -## 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. - -Read methods: `chats.list`, `messages.history`, `messages.after`, `messages.stats`, `messages.scheduled`, `watch.subscribe`, -`watch.unsubscribe`, `message.send_status`. Mutating: `send`, `poll.send`. -Bridge introspection: `handles.check`. See [docs/rpc.md](docs/rpc.md) for -request and response shapes. - -## Attachments - -`--attachments` reports metadata only. It does not copy or upload files. - -Attachment metadata includes filename, transfer name, UTI, MIME type, byte -count, sticker flag, missing flag, and resolved original path. - -`--convert-attachments` exposes cached, model-compatible receive-side -variants: - -- 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 -`converted_mime_type`. - -`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. - -The watcher listens for filesystem events on `chat.db`, `chat.db-wal`, -`chat.db-shm`, and the containing Messages directory, then backs that up with -a lightweight poll. The poll also refreshes the file watches, keeping streams -alive when macOS drops file events or SQLite rotates sidecar files. - -If Messages writes a row before its chat metadata is joined, watch retries that -row briefly and then drops it fail-closed instead of emitting an empty -`chat_id=0` payload that could be mistaken for a direct message. - -RPC watch defaults to a 500ms debounce to reduce outbound echo races. CLI -watch can be tuned with `--debounce`. - -## Permissions Troubleshooting +That's the whole pitch: read directly, stream updates, and ask Messages.app to send. -If reads fail with `unable to open database file`, empty output, or -`authorization denied`: - -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. -4. Also add the built-in Terminal.app at - `/System/Applications/Utilities/Terminal.app`; macOS can still consult the - default terminal grant. -5. Toggle stale Full Disk Access entries off and on after terminal, Homebrew, - Node, or app updates. -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. - -`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`, `search`, and read-only `rpc` -workflows do not require IMCore injection. +## Install -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. Homebrew -installs the helper from macOS release archives; source builds can run -`make build-dylib` first. +Homebrew is the smallest path on macOS: ```bash -imsg launch -imsg status +brew install steipete/tap/imsg +imsg --version ``` -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` - private-entitlement checks. -- These limits affect advanced IMCore features such as typing indicators, - not normal send/history/watch usage. - -To revert after testing, re-enable SIP from Recovery mode with -`csrutil enable`. +`imsg` requires macOS 14 or newer. Signed macOS builds and Linux x86_64 read-only builds are also available from [GitHub Releases](https://github.com/openclaw/imsg/releases/latest). Linux reads a `chat.db` copied from macOS; it does not connect to iMessage or send messages. See the [Linux guide](docs/linux.md). -### Bridge command surface +## Quick start -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. 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: +Grant your terminal **Full Disk Access** in **System Settings โ†’ Privacy & Security**, then reopen it. `imsg` needs that permission to read `~/Library/Messages/chat.db`. ```bash -# Apple URL preview (URL-only; incompatible with text/effects/replies/files) -imsg send-rich --chat 'iMessage;-;+15551234567' --url https://imsg.sh - -# Rich send with effect + reply -imsg send-rich --chat 'iMessage;-;+15551234567' --text "boom" \ - --effect com.apple.MobileSMS.expressivesend.impact \ - --reply-to - -# Threaded reply with an attachment in one message -imsg send-rich --chat 'iMessage;-;+15551234567' \ - --reply-to --text "here it is" --file ~/Pictures/img.jpg - -# 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"]}]' - -# Multipart send (text-only in v1; per-part textFormatting also supported) -imsg send-multipart --chat 'iMessage;+;chat0000' \ - --parts '[{"text":"hi"}, - {"text":"there","textFormatting":[{"start":0,"length":5,"styles":["bold"]}]}]' - -# Attachment (file or audio) -imsg send-attachment --chat ... --file ~/Pictures/img.jpg --transport auto -imsg send-attachment --chat ... --reply-to --file ~/Pictures/img.jpg -imsg send-attachment --chat ... --file ~/audio.caf --audio - -# Validated iMessage sticker, standalone or attached to an exact bubble part -imsg send-sticker --chat ... --file ~/Pictures/sticker.png -imsg send-sticker --chat ... --file ~/Pictures/sticker.png \ - --attach-to --target-part 0 - -# 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`): +# Find a chat and note its id. +imsg chats --limit 3 -```bash -imsg edit --chat ... --message --new-text "actually..." -imsg unsend --chat ... --message -imsg delete-message --chat ... --message -imsg notify-anyways --chat ... --message +# Read its ten most recent messages. +imsg history --chat-id 42 --limit 10 ``` -Chat management: +Use an id from the first command in place of `42`. The [five-minute quickstart](docs/quickstart.md) continues with live watching and sending. -```bash -imsg chat-create --addresses '+15551111111,+15552222222' --name 'Crew' --text 'gm' -imsg chat-name --chat ... --name 'Renamed' -imsg chat-photo --chat ... --file ~/Downloads/g.jpg # set -imsg chat-photo --chat ... # clear -imsg chat-add-member --chat ... --address +15553333333 -imsg chat-remove-member --chat ... --address +15553333333 -imsg chat-leave --chat ... -imsg chat-delete --chat ... -imsg chat-mark --chat ... --read # or --unread -``` +## Core workflows -`chat-create` currently creates iMessage chats only. SMS sending remains -available through `imsg send --service sms`. +| Goal | Start here | +| --- | --- | +| List chats and inspect their identifiers | [Chats](docs/chats.md) and [groups](docs/groups.md) | +| Read or search local history | [History](docs/history.md) | +| Stream new messages and tapbacks | [Watch](docs/watch.md) | +| Send text, files, and standard tapbacks | [Send](docs/send.md) and [attachments](docs/attachments.md) | +| Count messages and media | [Statistics](docs/stats.md) | +| Consume stable NDJSON or a long-running stdio API | [JSON schema](docs/json.md) and [JSON-RPC](docs/rpc.md) | +| Generate shell completions or model-ready CLI help | [Completions](docs/completions.md) | -Introspection: +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. -```bash -imsg account # active iMessage account + aliases -imsg account --local # accounts observed in local history -imsg whois --address +15551234567 --type phone -imsg whois --address +15551234567 --local -imsg whois --address foo@bar.com --type email -imsg nickname --address +15551234567 -imsg nickname --address +15551234567 --local -``` +## Permissions -Messages Name & Photo: +Full Disk Access is required for local database reads. Sending and standard tapbacks also require **Automation โ†’ Messages**; Contacts access is optional and only adds resolved names. The [permissions guide](docs/permissions.md) covers parent-process grants and stale TCC entries, while [troubleshooting](docs/troubleshooting.md) maps common failures to their likely gate. -```bash -imsg name-photo status --chat 'iMessage;-;+15551234567' # read-only offer eligibility -imsg name-photo share --chat 'iMessage;-;+15551234567' # explicitly share with participants -``` +For SMS, enable Text Message Forwarding on the paired iPhone. `imsg send` uses Messages.app's AppleScript surface and cannot force a particular outgoing number when several numbers share one Apple ID. -`status` reports whether Messages would currently offer its native Share Name -& Photo action; it is advisory, not a durable record of prior sharing. `share` -submits an explicit private-API send request and reports `requested: true`, not -a delivery receipt. Because it discloses your personal profile to every chat -participant, agents must only invoke it after an explicit user request. +## JSON and automation -Live events (typing indicators surfaced through the dylib): +`--json` emits one JSON object per line. Human progress and warnings stay on stderr, so stdout remains safe to stream. Pipe finite commands through `jq -s` when you want one array. ```bash -imsg watch --bb-events # merge dylib events into stdout -imsg watch --bb-events --json # one JSON object per event +imsg chats --json | jq -s +imsg rpc +imsg completions llm ``` -### v2 IPC under the hood +The [JSON schema](docs/json.md) documents chats, messages, attachments, reactions, polls, scheduled messages, and statistics. The [JSON-RPC reference](docs/rpc.md) covers the long-running stdio transport used by agents and gateways. -The dylib v1 used a single overwriting `.imsg-command.json` polled at 100ms, -which races when multiple CLI invocations run concurrently. v2 uses a -per-request UUID-keyed queue: +## Advanced IMCore -``` -~/Library/Containers/com.apple.MobileSMS/Data/ - .imsg-bridge-ready PID lock โ€” set when injection is live - .imsg-rpc/in/.json requests dropped here by the CLI (atomic rename) - .imsg-rpc/out/.json responses written by the dylib (atomic rename) - .imsg-events.jsonl inbound async events (typing, alias-removed) -``` +Normal `chats`, `history`, `watch`, `send`, `react`, and read-only RPC workflows do not use private frameworks or process injection. -Set `IMSG_BRIDGE_LEGACY_IPC=1` to force the legacy single-file path for -debugging (existing v1 callers and un-rebuilt dylibs continue to work -without this). +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 on current macOS releases. Start with [Advanced IMCore](docs/advanced-imcore.md), then use the [bridge command reference](docs/bridge.md) for the full CLI surface and IPC layout. + +## Documentation + +The complete guide lives at **[imsg.sh](https://imsg.sh)**. Useful entry points include [install](docs/install.md), [permissions](docs/permissions.md), [history](docs/history.md), [watch](docs/watch.md), [send](docs/send.md), [attachments](docs/attachments.md), [Linux](docs/linux.md), and [troubleshooting](docs/troubleshooting.md). ## Development @@ -433,13 +96,8 @@ make test make build ``` -`make test` applies the repository's SQLite.swift patch before running Swift -tests. - -The reusable Swift core lives in `Sources/IMsgCore`; the CLI target lives in -`Sources/imsg`; the injected helper lives in `Sources/IMsgHelper`. +`IMsgCore` contains the reusable Swift core, `imsg` contains the CLI, and `IMsgHelper` contains the optional injected helper. The package uses Swift 6 and targets macOS 14 or newer. ## License -MIT. Not affiliated with Apple. iMessage and SMS are trademarks of their -respective owners. +MIT. See [LICENSE](LICENSE). Not affiliated with Apple; iMessage and SMS are trademarks of their respective owners. diff --git a/docs/bridge.md b/docs/bridge.md new file mode 100644 index 0000000..0233c62 --- /dev/null +++ b/docs/bridge.md @@ -0,0 +1,161 @@ +--- +title: Bridge command reference +description: "CLI commands and IPC details for imsg's optional injected IMCore bridge." +--- + +This page is the command reference for `imsg` features that run through the injected IMCore bridge. Read [Advanced IMCore](advanced-imcore.md) first for the SIP, library-validation, entitlement, and privacy boundaries. + +Most commands take `--chat `, where a direct chat looks like `iMessage;-;+15551234567` and a group looks like `iMessage;+;chat0000`. Get the exact GUID from `imsg chats --json`, and run `imsg status --json` to inspect the selectors and RPC methods available on the current macOS version. + +## Messaging + +Send an Apple URL preview. URL mode cannot be combined with text, effects, replies, or files. + +```bash +imsg send-rich --chat 'iMessage;-;+15551234567' --url https://imsg.sh +``` + +Send rich text, a reply, or an attachment: + +```bash +imsg send-rich --chat 'iMessage;-;+15551234567' --text "boom" \ + --effect com.apple.MobileSMS.expressivesend.impact \ + --reply-to + +imsg send-rich --chat 'iMessage;-;+15551234567' \ + --reply-to --text "here it is" --file ~/Pictures/image.jpg + +imsg send-rich --chat 'iMessage;-;+15551234567' --text 'hello world' \ + --format '[{"start":0,"length":5,"styles":["bold"]}]' +``` + +Formatting requires macOS 15 or newer. Multipart messages accept a JSON array of text parts: + +```bash +imsg send-multipart --chat 'iMessage;+;chat0000' \ + --parts '[{"text":"hi"},{"text":"there"}]' +``` + +Send regular or audio attachments: + +```bash +imsg send-attachment --chat 'iMessage;-;+15551234567' \ + --file ~/Pictures/image.jpg --transport auto +imsg send-attachment --chat 'iMessage;-;+15551234567' \ + --reply-to --file ~/Pictures/image.jpg +imsg send-attachment --chat 'iMessage;-;+15551234567' \ + --file ~/Desktop/audio.caf --audio +``` + +With `--transport auto`, a normal file can fall back to AppleScript when the bridge is unavailable. `--audio` and `--reply-to` remain bridge-only. + +Send a validated sticker on its own or attach it to an existing bubble part: + +```bash +imsg send-sticker --chat 'iMessage;-;+15551234567' \ + --file ~/Pictures/sticker.png +imsg send-sticker --chat 'iMessage;-;+15551234567' \ + --file ~/Pictures/sticker.png --attach-to --target-part 0 +``` + +Stickers are iMessage-only. They accept PNG/APNG, GIF, or JPEG images up to 500 KiB, 618ร—618 pixels, 100 frames, and 25 million decoded pixels. Standalone sends require `selectors.stickerSend`; attached stickers also require `selectors.stickerAttach`. + +Bridge tapbacks support removal and custom emoji in addition to the standard reactions exposed by `imsg react`: + +```bash +imsg tapback --chat 'iMessage;-;+15551234567' \ + --message --kind love +imsg tapback --chat 'iMessage;-;+15551234567' \ + --message --kind love --remove +``` + +## Native polls + +Create a poll with a visible caption: + +```bash +imsg poll send --chat 'iMessage;-;+15551234567' \ + --question 'Dinner?' --option 'Pizza' --option 'Sushi' +``` + +Messages does not render the payload title on the poll balloon, so `poll send` follows it with a best-effort caption. Use `--comment` to choose different visible text or `--no-comment` when the caller already sent the context. + +Vote or remove a vote with one option selector: + +```bash +imsg poll vote --chat-id 42 --poll --option-index 2 +imsg poll unvote --chat-id 42 --poll --option-index 2 +``` + +Poll creation requires `selectors.pollPayloadMessage`. Voting requires `selectors.pollVoteMessage` and the matching RPC capability reported by `imsg status --json`. + +## Message and chat mutation + +Mutate an existing message: + +```bash +imsg edit --chat 'iMessage;-;+15551234567' \ + --message --new-text "actually..." +imsg unsend --chat 'iMessage;-;+15551234567' --message +imsg delete-message --chat 'iMessage;-;+15551234567' --message +imsg notify-anyways --chat 'iMessage;-;+15551234567' --message +``` + +Manage chats and participants: + +```bash +imsg chat-create --addresses '+15551111111,+15552222222' --name 'Crew' --text 'gm' +imsg chat-name --chat 'iMessage;+;chat0000' --name 'Renamed' +imsg chat-photo --chat 'iMessage;+;chat0000' --file ~/Pictures/group.jpg +imsg chat-add-member --chat 'iMessage;+;chat0000' --address +15553333333 +imsg chat-remove-member --chat 'iMessage;+;chat0000' --address +15553333333 +imsg chat-leave --chat 'iMessage;+;chat0000' +imsg chat-delete --chat 'iMessage;+;chat0000' +imsg chat-mark --chat 'iMessage;+;chat0000' --read +``` + +`chat-photo` clears the photo when `--file` is omitted. `chat-mark` also accepts `--unread`. `chat-create` creates iMessage chats; SMS sending remains available through the standard `imsg send --service sms` path. + +## Account and identity + +Inspect the active account, local history, and address capabilities: + +```bash +imsg account +imsg account --local +imsg whois --address +15551234567 --type phone +imsg whois --address +15551234567 --local +imsg nickname --address +15551234567 +imsg nickname --address +15551234567 --local +``` + +Inspect or explicitly share the local Messages Name & Photo: + +```bash +imsg name-photo status --chat 'iMessage;-;+15551234567' +imsg name-photo share --chat 'iMessage;-;+15551234567' +``` + +`status` reports whether Messages would offer its native sharing action; it is not a durable record of prior sharing. `share` discloses the local profile to every chat participant and reports a request, not a delivery receipt. Call it only after explicit user confirmation of the destination. + +## Live bridge events + +Merge bridge-pushed typing and alias events into the normal watch stream: + +```bash +imsg watch --bb-events --json +``` + +## IPC layout + +The bridge uses a UUID-keyed request queue so concurrent CLI invocations cannot overwrite one another: + +```text +~/Library/Containers/com.apple.MobileSMS/Data/ + .imsg-bridge-ready PID lock set while injection is live + .imsg-rpc/in/.json atomically published requests + .imsg-rpc/out/.json per-request responses + .imsg-events.jsonl inbound asynchronous events +``` + +Set `IMSG_BRIDGE_LEGACY_IPC=1` only when debugging against an older, unrebuilt helper that still uses the single-file IPC path.