slagyr/isaac-imessage

iMessage module for Isaac

โ˜… 0Forks 0ClojureGitHub โ†—Compare

README

๐Ÿ Isaac iMessage ๐Ÿ’ฌ

isaac-imessage

Bi-directional iMessage support for Isaac.

Runs as an Isaac module on a macOS host that is logged into the Messages app. Inbound messages are polled from ~/Library/Messages/chat.db and dispatched to the configured crew; outbound replies are sent through Messages via osascript.


CI Tests License: MIT Clojure Babashka Java


See:

  • PLAN.md โ€” architecture and MVP scope
  • ROADMAP.md โ€” phased delivery plan

Setup

1. macOS permissions

iMessage support requires two privacy grants on the host running Isaac. Both are configured under System Settings โ†’ Privacy & Security:

  • Full Disk Access โ€” needed to read ~/Library/Messages/chat.db. Grant to the binary that runs Isaac (your terminal, clojure, bb, or the Java executable the JVM resolves to).
  • Automation โ†’ Messages โ€” needed to send iMessages via osascript. Grant the same binary the ability to control Messages. The first time Isaac calls osascript, macOS may prompt; the prompt only appears in a GUI session.

The integration must run in a logged-in GUI session on macOS โ€” not a headless daemon โ€” because Messages.app must be running for sends and the chat.db is owned by the GUI user.

2. Isaac config

Add comms.imessage to your ~/.isaac/config/isaac.edn:

{:comms {:imessage {:imessage/db-path     "/Users/zane/Library/Messages/chat.db"
                    :imessage/bin         "/usr/local/bin/imsg"
                    :imessage/allow-from  ["+15551234567" "[email protected]"]}}}

All slice keys live in the :imessage/ keyword namespace so the comm config doesn't collide with anything Isaac (or another module) might inject into the same map.

  • :imessage/service โ€” transport imsg should use: "auto", "sms", or "imessage". Omit it unless you mean to pin the transport; Isaac then passes no service and imsg picks, which is the path that works. Do not set "imessage": through imsg's AppleScript transport an explicit imessage send reports -32001 "Delivery outcome unknown" and never arrives (measured 2026-09-24 against a live iMessage chat), while the same send with no service โ€” or "auto" โ€” delivers on the first attempt.

  • :imessage/db-path โ€” absolute path to the Messages chat database. Required to spawn the imsg subprocess; omitting it leaves the comm dormant (handy for non-Mac dev, and a guard so tests can't accidentally hit a real chat.db).

  • :imessage/bin โ€” path to the imsg binary. Defaults to whatever's on PATH; set explicitly when the process launching isaac doesn't see /usr/local/bin or /opt/homebrew/bin (common for headless processes like launchd jobs). Ignored when :imessage/command is set.

  • :imessage/command โ€” optional vector of strings forming the full launch prefix before rpc and --db. Use this to run imsg through a stdio wrapper (for example SSH to a remote Mac):

    :imessage/command ["ssh" "-T" "[email protected]" "/usr/local/bin/imsg"]

    Isaac appends rpc and, when configured, --db <path> to this prefix. With a wrapper, :imessage/db-path is the path on the machine where imsg runs (not checked for local existence); omit the wrapper to keep the default local chat.db readiness check.

  • :imessage/allow-from โ€” phone numbers / emails (string allowlist). Notifications from senders not in this list are dropped at debug log level (:imessage.intake/drop-sender). Fail-closed: an empty list drops everything; omit :imessage/allow-from to skip filtering entirely.

Remote Mac via SSH

If the machine running Isaac cannot reliably send Apple Events to Messages under launchd, move the imsg boundary onto a different logged-in Mac and tunnel stdio over SSH:

{:comms {:imessage {:imessage/command    ["ssh" "-T" "[email protected]" "/usr/local/bin/imsg"]
                    :imessage/db-path    "/Users/zane/Library/Messages/chat.db"
                    :imessage/allow-from ["[email protected]"]}}}

This does not "fix" TCC on the local Isaac host. It avoids the local permission boundary by running imsg on the remote Mac instead. In that setup:

  • the remote Mac must be logged in to Messages and hold the needed Full Disk Access / Automation grants
  • :imessage/db-path is the remote machine's chat.db path
  • the local Isaac host only needs SSH access to the remote imsg command
  • same-host self-SSH is possible in principle, but a separate Messages Mac is the cleaner deployment

Optional:

  • :imessage/message-cap โ€” split replies above this character count into multiple sends. Default 2000.
  • :imessage/max-chunks โ€” hard cap on how many chunks a single reply produces. Above the cap the tail is dropped with a truncation notice so a runaway LLM response can't flood Messages. Default 3 (โ‰ˆ6000 chars total at the default :imessage/message-cap).

3. Verify the host

Run the smoke check before bringing Isaac up:

bb smoke

Verifies osascript, sqlite3, and a successful read of chat.db. To also confirm Automation permission for Messages, pass a handle + body:

bb smoke +15551234567 "test from isaac-imessage"

A real iMessage should appear in your Messages window. If the send fails with Not authorized to send Apple events, you haven't granted Automation. If the chat.db read prints exit=1 err=unable to open database, you haven't granted Full Disk Access.

When using :imessage/command with an SSH wrapper, run the equivalent probe against the remote Mac instead of relying on the local bb smoke result. For example:

ssh -T [email protected] /usr/local/bin/imsg send --to +15551234567 --text "remote smoke" --service imessage --json

4. Start Isaac

isaac server

On startup, Isaac discovers this module via classpath manifest, registers the imessage Comm in the comm registry, and starts the poller. Look for these log events to confirm it's wired:

  • :comm/activated comm=imessage
  • :imessage.poller/started interval-ms=โ€ฆ

Inbound iMessages from allow-listed senders should now route to sessions named imessage:<chat-guid>, and crew responses should land back in the Messages thread.

Development

bb spec       # unit specs (66+ examples)
bb features   # gherclj feature suite

Both should be green before pushing. The pre-push hook (if installed) will re-run them.

License

MIT

Copyright (c) 2026 Micah Martin

See LICENSE.

Contributors

slagyrslagyr-assistant

Issues