@treentity/pi-imessage
iMessage channel for Pi
Package details
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)