[BREAKGLASS] CLI for Apple's Messages.app so your agent can send and receive text messages/iMessages. https://imsg.to
Go to file
2026-05-05 06:05:18 +01:00
.agents/skills/imsg feat: add advanced message controls 2026-04-27 02:11:23 +01:00
.github/workflows ci: publish universal macOS release binary 2026-05-04 06:20:04 +01:00
docs feat: expose chat routing hints 2026-05-05 02:00:56 +01:00
Resources feat: resolve contact names 2026-05-04 09:16:44 +01:00
scripts docs: integrate Homebrew tap update into local release flow 2026-05-04 08:58:49 +01:00
Sources chore: prepare 0.6.0 release 2026-05-05 06:05:18 +01:00
Tests refactor: split message store query layers 2026-05-05 05:31:04 +01:00
.gitignore chore: remove slides ignore 2026-04-27 02:13:30 +01:00
.swiftlint.yml feat: swift 6 rewrite 2025-12-28 17:17:40 +01:00
AGENTS.md chore: replace pnpm with make 2026-01-03 06:31:55 +01:00
CHANGELOG.md chore: prepare 0.6.0 release 2026-05-05 06:05:18 +01:00
LICENSE chore: fix copyright header 2026-04-27 11:26:47 +01:00
Makefile feat: add advanced message controls 2026-04-27 02:11:23 +01:00
Package.swift feat: resolve contact names 2026-05-04 09:16:44 +01:00
README.md chore: prepare 0.6.0 release 2026-05-05 06:05:18 +01:00
version.env chore: prepare 0.6.0 release 2026-05-05 06:05:18 +01:00

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.

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.

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.

Requirements

  • macOS 14 or newer.
  • 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.

Install

brew install steipete/tap/imsg

Build from source:

make build
./bin/imsg --help

Common Workflows

List recent chats:

imsg chats --limit 10
imsg chats --limit 10 --json

Inspect one chat before sending or wiring automation:

imsg group --chat-id 42 --json

Read history:

imsg history --chat-id 42 --limit 20
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:

imsg watch --chat-id 42 --json
imsg watch --chat-id 42 --since-rowid 9000 --attachments --reactions --debounce 250ms --json

Send a message or file:

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:

imsg react --chat-id 42 --reaction like

Generate integration help:

imsg completions zsh
imsg completions llm

Commands

  • 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 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 reliably through automation. Custom emoji tapbacks can be read from history/watch output, but are not sent by the CLI.

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, destination_caller_id
  • sender, sender_name, is_from_me, text, created_at
  • attachments, reactions

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
  • watch.subscribe
  • watch.unsubscribe

Mutating method:

  • send

See 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 can expose 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 files, 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.

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.

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.
  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, and read-only rpc workflows do not require IMCore injection.

Advanced features such as read, typing, launch, and IMCore bridge status are opt-in. They require SIP to be disabled and a helper dylib to be injected into Messages.app:

make build-dylib
imsg launch
imsg status

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 advanced features, re-enable SIP from Recovery mode with csrutil enable.

Development

make lint
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.