docs: refresh readme
This commit is contained in:
parent
23c5892688
commit
d5038414b2
261
README.md
261
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 <id> [--json]`
|
||||
- `imsg history --chat-id <id> [--limit 50] [--attachments] [--convert-attachments] [--participants <handles>] [--start <iso>] [--end <iso>] [--json]`
|
||||
- `imsg watch [--chat-id <id>] [--since-rowid <id>] [--debounce <duration>] [--attachments] [--convert-attachments] [--reactions] [--participants <handles>] [--start <iso>] [--end <iso>] [--json]`
|
||||
- `imsg search --query <text> [--match contains|exact] [--limit 50] [--json]`
|
||||
- `imsg send (--to <handle-or-contact-name> | --chat-id <id> | --chat-identifier <id> | --chat-guid <guid>) [--text <text>] [--file <path>] [--service imessage|sms|auto] [--region US] [--json]`
|
||||
- `imsg react --chat-id <id> --reaction love|like|dislike|laugh|emphasis|question`
|
||||
- `imsg read --to <handle> [--chat-id <id> | --chat-identifier <id> | --chat-guid <guid>]`
|
||||
- `imsg typing --to <handle> [--duration 5s] [--stop true] [--service imessage|sms|auto]`
|
||||
- `imsg status [--json]`
|
||||
- `imsg launch [--dylib <path>] [--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 <handle> [--chat-id <id>]`
|
||||
- `imsg typing --to <handle> [--duration 5s] [--stop true]`
|
||||
- `imsg launch [--dylib <path>] [--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 <id>` 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 <id>` 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 <messageGuid>
|
||||
|
||||
# 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 <guid> --kind love
|
||||
imsg tapback --chat ... --message <guid> --kind love --remove
|
||||
```
|
||||
|
||||
Mutate (macOS 13+ — selector availability surfaced in `imsg status`):
|
||||
|
||||
```bash
|
||||
imsg edit --chat ... --message <guid> --new-text "actually..."
|
||||
imsg unsend --chat ... --message <guid>
|
||||
@ -306,6 +303,7 @@ imsg notify-anyways --chat ... --message <guid>
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
Loading…
Reference in New Issue
Block a user