@treentity/pi-imessage

iMessage channel for Pi

Packages

Package details

extensionskill

Install @treentity/pi-imessage from npm and Pi will load the resources declared by the package manifest.

$ pi install npm:@treentity/pi-imessage
Package
@treentity/pi-imessage
Version
0.1.1
Published
Apr 15, 2026
Downloads
24/mo · 7/wk
Author
treentity
License
Apache-2.0
Types
extension, skill
Size
356 KB
Dependencies
1 dependency · 2 peers
Pi manifest JSON
{
  "skills": [
    "dist/skills"
  ],
  "extensions": [
    "dist/extensions"
  ]
}

Security note

Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.

README

@treentity/pi-imessage

iMessage channel for Pi. Reads ~/Library/Messages/chat.db directly for history, search, and new-message detection; sends via AppleScript to Messages.app. No external server, no background process to keep alive.

macOS only.

Install

pi install npm:@treentity/pi-imessage

Requirements

  • macOS with Messages.app signed into iMessage
  • Full Disk Access granted to your terminal
  • Pi installed and configured with an LLM provider

Quick setup

Default: text yourself. Other senders are dropped silently (no auto-reply) until you allowlist them. See ACCESS.md for groups and multi-user setups.

1. Grant Full Disk Access

chat.db is protected by macOS TCC. The first time the extension reads it, macOS pops a prompt asking if your terminal can access Messages — click Allow. The prompt names whatever app launched Pi (Terminal.app, iTerm, Ghostty, your IDE).

If you click Don't Allow, or the prompt never appears, grant it manually: System Settings → Privacy & Security → Full Disk Access → add your terminal. Without this the extension exits immediately with authorization denied.

2. Text yourself

iMessage yourself from any device. It reaches the assistant immediately — self-chat bypasses access control.

The first outbound reply triggers an Automation permission prompt ("Terminal wants to control Messages"). Click OK.

3. Decide who else gets in

Nobody else's texts reach the assistant until you add their handle:

/imessage:access allow +15551234567

Handles are phone numbers (+15551234567) or Apple ID emails (user@icloud.com).

How it works

Inbound Polls chat.db once a second for ROWID > watermark. Watermark initializes to MAX(ROWID) at boot — old messages aren't replayed on restart.
Outbound osascript with tell application "Messages" to send …. Text and chat GUID pass through argv so there's no escaping footgun.
History & search Direct SQLite queries against chat.db. Full history — not just messages since the extension started.
Attachments chat.db stores absolute filesystem paths. The first inbound image per message is surfaced to the assistant as a local path it can Read. Outbound attachments send as separate messages after the text.
Self-chat detection Learns your own addresses (phone + email) at boot. Messages from yourself are recognized and processed without polluting the assistant's context.

Multi-terminal support

Multiple Pi terminals can run on the same Mac. Terminals are discovered and registered automatically — no manual coordination needed.

Role Behavior
Main First terminal to start. Receives all untagged messages.
Agent Additional terminals. Only receive messages tagged with their name.

Routing examples:

Message Who gets it
fix the auth bug Main terminal
@frontend fix the button Terminal named "frontend"
@backend check the API Terminal named "backend"

Terminal management via iMessage self-chat:

@terminal-name rename @new-name     Change a terminal's name
@terminal-name on                    Start polling on a terminal
@terminal-name off                   Stop polling on a terminal

Terminal management via Pi slash commands:

/imessage:access                     Show status: role, polling state, all terminals
/imessage:access on                  Enable polling on current terminal
/imessage:access off                 Disable polling on current terminal
/imessage:access main                Claim main role for current terminal
/imessage:access name my-name       Set a display name for current terminal

Terminal names are auto-assigned based on the project directory (projectname-1, projectname-2, etc). Names and aliases are preserved across restarts.

Access control

See ACCESS.md for the full reference.

DM policies

Policy Behavior
allowlist (default) Only handles you explicitly add. Safe for personal chat.db.
pairing New senders get a pairing code. You approve with /imessage:access pair <code>.
disabled All DMs reach the assistant. Use with caution.

Groups

Groups are off by default. Enable per-group:

/imessage:access group add "iMessage;+;chat123456789012345678"

Allowlisting people

/imessage:access allow +15551234567
/imessage:access allow user@icloud.com
/imessage:access remove +15551234567

Pairing mode

When dmPolicy is pairing, unknown senders receive a code:

/imessage:access pair ABC123

Slash commands

Command Description
/imessage:status Show connection status, polling state, registered terminals
/imessage:access Show full access control panel
/imessage:access allow <handle> Allow a sender
/imessage:access remove <handle> Remove a sender
/imessage:access pair <code> Approve a pairing request
/imessage:access deny <handle> Deny a pending pairing
/imessage:access on Enable polling on this terminal
/imessage:access off Disable polling on this terminal
/imessage:access main Claim main terminal role
/imessage:access name <name> Set terminal display name
/imessage:access policy <policy> Set DM policy: allowlist, pairing, or disabled

Tools exposed to the assistant

Tool Purpose
imessage_reply Send to a chat. chat_id + text, optional files (absolute paths). Auto-chunks long messages; files send as separate messages after the text. One reply per inbound — duplicate calls are blocked.
imessage_messages Fetch recent history as conversation threads. Each thread is labelled DM or Group with its participant list, then timestamped messages (oldest-first). Omit chat_guid to see every allowlisted chat at once, or pass one to drill in. Default 100 messages per chat, max 500. Reads chat.db directly — full native history, not limited to messages since startup.

Environment variables

Variable Default Effect
IMESSAGE_APPEND_SIGNATURE true Appends \nSent by Pi [terminal-name: project-dir] to outbound messages. Set to false to disable.
IMESSAGE_ALLOW_SMS false Accept inbound SMS/RCS in addition to iMessage. Off by default because SMS sender IDs are spoofable — a forged SMS from your own number would otherwise bypass access control. Only enable if you understand the risk.
IMESSAGE_ACCESS_MODE Set to static to disable runtime pairing and read access.json only.
IMESSAGE_STATE_DIR ~/.pi/agent/imessage Override where access.json, terminal state, and pairing data live.

File locations

File Path Purpose
Access config ~/.pi/agent/imessage/access.json DM policy, allowlists, group rules, pairing state
Terminal state ~/.pi/agent/imessage/terminals.json Registered terminals, names, roles
Pairing approvals ~/.pi/agent/imessage/approved/ Approved sender IDs
Messages database ~/Library/Messages/chat.db macOS iMessage database (read-only access)

Limitations

  • AppleScript can send messages but not tapback, edit, or thread — those require Apple's private API
  • No delivery receipts or read receipts
  • SMS support is off by default due to sender ID spoofing risk
  • macOS only (requires Messages.app and chat.db)

License

Apache-2.0