A minimal and self-managing iMessage bot β powered by pi.
- Minimal: No BlueBubble, no webhooks, no extra dependencies
- Self-managing: Turn the agent into whatever you need. He builds his own tools without pre-built assumptions
- Transparent: tool calls and reasoning are sent to your iMessage chat, so you can see exactly what it's doing and why
- iMessage Integration: Responds to DMs, SMS, and group chats; identifies who sent each message; understands quoted/reply-to messages
- Web UI: browse chat history, toggle replies on/off per chat, live updates β disable with WEB_ENABLED=false and let the agent build your own web UI
β οΈ Security note
- Replies are off for all chats by default (
blacklist: ["*"]) β only explicitly whitelisted chats get a response- The agent runs with Full Disk Access and can read/write your filesystem as part of its tool use
- The web UI has no authentication and is accessible to anyone on your local network; set
WEB_ENABLED=falseif that's a concern
Prerequisites: macOS with Messages.app, Full Disk Access for the terminal, Pi Coding Agent authenticated
npm install -g @kingcrab/pi-imessage
pi-imessage # run in foreground
pi-imessage install # install as launchd service (auto-start on boot, restart on crash)Available at http://localhost:7750 (configurable via WEB_HOST and WEB_PORT).
- Chat history with live updates
- Logs (tail -f style)
- Memory (global & per-chat)
P.S. Disable with WEB_ENABLED=false and let the agent build your own web UI
Durable memories are stored as namespaced JSONL records under
WORKING_DIR/skills/file-memory/namespaces/. Each record includes a stable ID,
creation timestamp, factual event date when known, source, kind, subjects,
importance, confidence, and status. This keeps every memory traceable without
inventing dates for older facts.
The agent uses typed tools to load complete namespaces, search for specific
records, and append validated memories. Corrections append a new record that
supersedes the old ID, preserving an auditable history instead of silently
rewriting it. Legacy global and per-chat MEMORY.md files remain read-only
archives. See the structured memory flow
for migration, retrieval, and write behavior.
The agent is aware of these endpoints via its system prompt and can use them as tools (e.g., scheduling a cron job that calls /prompt).
| Endpoint | Description | Example |
|---|---|---|
POST /send |
Send text and/or a local file attachment to a chat (bypasses the agent) | curl -X POST localhost:7750/send -d '{"chatGuid": "iMessage;-;+11234567890", "text": "hello"}'β {"ok": true} |
POST /prompt |
Feed a prompt to the agent asynchronously; replies are sent to the chat when ready | curl -X POST localhost:7750/prompt -d '{"chatGuid": "iMessage;-;+11234567890", "prompt": "say hello"}'β {"ok": true} |
GET /health/model |
Make a live request to the configured default AI model; returns HTTP 200 when healthy or 503 on failure | curl localhost:7750/health/modelβ {"ok":true,"model":"openai/gpt-5","latencyMs":842,"checkedAt":"..."} |
Send these as iMessage to interact with the bot:
| Command | Description | Example Reply |
|---|---|---|
/help |
List available slash commands | Commands:/help β list commands |
/new |
Reset the session, starting a fresh conversation | β New session started |
/status |
Show session stats: tokens, context, model | π¬ 3 msgs - β7.2k β505 1.1%/128kπ€ anthropic/claude-sonnet-4 β’ π minimal |
/compact |
Compress session context to free up token space | β Compacted: 15.2k β 2.1k tokens |
/stop |
Steer the agent to stop after current tool calls finish, then process the next queued message | |
/reload |
Reload models and clear all sessions | β Models reloaded |
All fields are optional.
{
"chatAllowlist": {
"whitelist": ["iMessage;-;+11234567890"],
"blacklist": ["*"]
},
"richText": {
"enabled": false,
"markdown": true
}
}Chat allowlist controls which chats receive replies (messages are always logged). By default, replies are off for all chats (blacklist: ["*"]) β opt in specific chats via the web UI or by adding their guid to whitelist. Resolution priority: blacklist[guid] > whitelist[guid] > blacklist["*"] > whitelist["*"].
Rich text is optional and disabled by default. When enabled, pi-imessage uses a UI automation fallback to open the target conversation, paste an RTF payload, and send it. Currently this is intended for direct-message iMessage chats. With markdown: true, pi-imessage interprets **bold** spans and renders them as actual bold text in Messages.
| Variable | Required | Default | Description |
|---|---|---|---|
WEB_ENABLED |
no | true |
Set to false to disable the built-in web UI |
WEB_HOST |
no | localhost |
Web UI host |
WEB_PORT |
no | 7750 |
Web UI port |
WORKING_DIR |
no | ~/.pi/imessage |
Workspace directory |
AGENT_IDLE_TIMEOUT_MS |
no | 120000 |
Abort only after this much continuous agent inactivity; model and tool events reset the timer |
AGENT_MAX_PROMPT_DURATION_MS |
no | 1800000 |
Absolute ceiling for one prompt, independent of activity |
npm run check # typecheck + lint (run after code changes)
npm test # run tests ~/Library/Messages/chat.db
β
(poll every 2s for new rows)
β
βΌ
ββββββββββββββββββββββββββββββββββββββββββββββββββββ
β pi-imessage β
β β
β Watcher (chat.db polling) β
β β β
β ββ Filter: is_from_me=0, no reactions β
β ββ Deduplicate via seenRowIds β
β ββ Read attachments from local disk β
β β β
β βΌ β
β AsyncQueue<IncomingMessage> β
β β β
β βΌ β
β SessionManager (pi-coding-agent) β
β β per chatGuid, persistent on disk β
β β ββ data/<chatGuid>/ β
β β ββ log.jsonl (full history) β
β β ββ context.jsonl (LLM context) β
β β β
β βΌ β
β Agent loop (pi-agent-core) β
β β β
β β ββ outer: follow-up messages βββββ β
β β β ββ inner: tool calls + β β β
β β β β steering messages β β β
β β β βββββββββββββββββββββββββββββ β β
β β ββββββββββββββββββββββββββββββββββ β
β β β
β βΌ β
β Collect assistant reply text β
β β β
β ββ sendMessage (AppleScript β Messages.app) β
β ββ save logs (messages, digests) β
β β
ββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
iMessage (user receives reply via Messages.app)
