pi-imessage 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +84 -0
- package/dist/ACCESS.md +142 -0
- package/dist/DOCS.md +591 -0
- package/dist/README.md +84 -0
- package/dist/extensions/imessage.js +1 -0
- package/dist/skills/access/SKILL.md +140 -0
- package/dist/skills/configure/SKILL.md +82 -0
- package/package.json +49 -0
package/dist/DOCS.md
ADDED
|
@@ -0,0 +1,591 @@
|
|
|
1
|
+
# iMessage — Full Documentation
|
|
2
|
+
|
|
3
|
+
iMessage channel for Pi. Reads your Mac's Messages database directly, sends replies via AppleScript. No external server, no API keys, no background process to keep alive.
|
|
4
|
+
|
|
5
|
+
macOS only.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Table of Contents
|
|
10
|
+
|
|
11
|
+
1. [What It Does](#what-it-does)
|
|
12
|
+
2. [Installation](#installation)
|
|
13
|
+
3. [macOS Permissions](#macos-permissions)
|
|
14
|
+
4. [Quick Start](#quick-start)
|
|
15
|
+
5. [How It Works](#how-it-works)
|
|
16
|
+
6. [Environment Variables](#environment-variables)
|
|
17
|
+
7. [Access Control](#access-control)
|
|
18
|
+
- [Self-Chat](#self-chat)
|
|
19
|
+
- [DM Policies](#dm-policies)
|
|
20
|
+
- [Handle Addresses](#handle-addresses)
|
|
21
|
+
- [Allowlisting People](#allowlisting-people)
|
|
22
|
+
- [Pairing Mode](#pairing-mode)
|
|
23
|
+
- [Group Chats](#group-chats)
|
|
24
|
+
- [Mention Patterns](#mention-patterns)
|
|
25
|
+
8. [Slash Commands](#slash-commands)
|
|
26
|
+
- [/imessage:status](#imessagestatus)
|
|
27
|
+
- [/imessage:access](#imessageaccess)
|
|
28
|
+
9. [Tools Exposed to the Assistant](#tools-exposed-to-the-assistant)
|
|
29
|
+
10. [Configuration File](#configuration-file)
|
|
30
|
+
11. [Delivery Settings](#delivery-settings)
|
|
31
|
+
12. [Limitations](#limitations)
|
|
32
|
+
13. [Troubleshooting](#troubleshooting)
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## What It Does
|
|
37
|
+
|
|
38
|
+
iMessage connects your Pi coding assistant to iMessage. Once installed:
|
|
39
|
+
|
|
40
|
+
- **Inbound**: Polls `~/Library/Messages/chat.db` (your Messages database) once per second for new messages.
|
|
41
|
+
- **Outbound**: Sends replies via `osascript` (AppleScript) to Messages.app.
|
|
42
|
+
- **History**: Reads full conversation history directly from `chat.db` via SQLite — not just messages since the server started.
|
|
43
|
+
- **Attachments**: Inbound images are surfaced as local file paths. Outbound files send as separate messages.
|
|
44
|
+
|
|
45
|
+
No external server. No API token. No webhook. It all runs locally on your Mac.
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Installation
|
|
50
|
+
|
|
51
|
+
### Prerequisites
|
|
52
|
+
|
|
53
|
+
- macOS (iMessage / Messages.app)
|
|
54
|
+
- Pi installed (see below for different setup scenarios)
|
|
55
|
+
|
|
56
|
+
### Three ways to run Pi with iMessage
|
|
57
|
+
|
|
58
|
+
How you install and load iMessage depends on how you run Pi:
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
#### Option A — Running from the Pi monorepo (development)
|
|
63
|
+
|
|
64
|
+
If you cloned the [pi-mono](https://github.com/badlogic/pi-mono) repo and are running Pi from source, iMessage is already a package in the workspace. Pi discovers it automatically through workspace linking.
|
|
65
|
+
|
|
66
|
+
**No install command needed. No flags needed.** The extension, tools, and skills are available as soon as you start Pi from the monorepo root.
|
|
67
|
+
|
|
68
|
+
Just start Pi and verify:
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
/imessage:status
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
This should tab-complete and show the current state.
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
#### Option B — Running from a global npm install of Pi
|
|
79
|
+
|
|
80
|
+
If you installed Pi globally via npm (`npm install -g @mariozechner/pi-coding-agent`), iMessage is a separate npm package you install as a plugin.
|
|
81
|
+
|
|
82
|
+
**Step 1 — Install the plugin.**
|
|
83
|
+
|
|
84
|
+
Start a Pi session:
|
|
85
|
+
|
|
86
|
+
```sh
|
|
87
|
+
pi
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Inside the session, run:
|
|
91
|
+
|
|
92
|
+
```
|
|
93
|
+
/plugin install imessage
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
This fetches the package from npm and registers it in `~/.pi/agent/settings.json`. No environment variables required at install time.
|
|
97
|
+
|
|
98
|
+
**Step 2 — Relaunch with the channel flag.**
|
|
99
|
+
|
|
100
|
+
The iMessage server does not start unless you pass the `--channels` flag. Exit your session and relaunch:
|
|
101
|
+
|
|
102
|
+
```sh
|
|
103
|
+
pi --channels plugin:imessage
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
**Step 3 — Verify.**
|
|
107
|
+
|
|
108
|
+
Inside the new Pi session, type `/imessage:status` — it should tab-complete and show the current state (DM policy, allowed senders, self-chat addresses).
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
#### Option C — Running from a local build (no npm registry)
|
|
113
|
+
|
|
114
|
+
If you built Pi from source but are not using the monorepo workspace, or you have a custom build, you can load the extension directly by file path.
|
|
115
|
+
|
|
116
|
+
**Step 1 — Build iMessage.**
|
|
117
|
+
|
|
118
|
+
```sh
|
|
119
|
+
cd /path/to/imessage
|
|
120
|
+
npm run build
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
**Step 2 — Launch Pi with the extension flag.**
|
|
124
|
+
|
|
125
|
+
```sh
|
|
126
|
+
pi -e /path/to/imessage/dist/extensions/imessage.js
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Or use the `--extension` flag multiple times to load several extensions:
|
|
130
|
+
|
|
131
|
+
```sh
|
|
132
|
+
pi --extension /path/to/imessage/dist/extensions/imessage.js
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
**Step 3 — Verify.**
|
|
136
|
+
|
|
137
|
+
Inside the Pi session, type `/imessage:status` — it should tab-complete and show the current state.
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
### Quick comparison
|
|
142
|
+
|
|
143
|
+
| | Monorepo (A) | Global npm (B) | Local build (C) |
|
|
144
|
+
|---|---|---|---|
|
|
145
|
+
| Install command | None | `/plugin install imessage` | None (build manually) |
|
|
146
|
+
| Launch flag | None | `--channels plugin:imessage` | `-e /path/to/imessage.js` |
|
|
147
|
+
| npm registry needed | No | Yes | No |
|
|
148
|
+
| How Pi finds it | Workspace linking | Plugin registry | Direct file path |
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## macOS Permissions
|
|
153
|
+
|
|
154
|
+
iMessage needs two macOS permissions:
|
|
155
|
+
|
|
156
|
+
### Full Disk Access
|
|
157
|
+
|
|
158
|
+
`chat.db` is protected by macOS TCC (Transparency, Consent, and Control).
|
|
159
|
+
|
|
160
|
+
When Pi first tries to read `chat.db`, macOS shows a prompt asking if your terminal can access Messages. Click **Allow**.
|
|
161
|
+
|
|
162
|
+
If you dismissed the prompt or it never appeared, grant it manually:
|
|
163
|
+
|
|
164
|
+
**System Settings → Privacy & Security → Full Disk Access →** add your terminal app (Terminal.app, iTerm, Ghostty, your IDE, etc.)
|
|
165
|
+
|
|
166
|
+
Without this, the server exits immediately with `authorization denied`.
|
|
167
|
+
|
|
168
|
+
### Automation (for sending)
|
|
169
|
+
|
|
170
|
+
The first time iMessage sends a reply, macOS prompts: *"Terminal wants to control Messages"*. Click **OK**.
|
|
171
|
+
|
|
172
|
+
This prompt only appears once. If you denied it, go to **System Settings → Privacy & Security → Automation** and re-enable it for your terminal.
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## Quick Start
|
|
177
|
+
|
|
178
|
+
After installation, the fastest path to a working setup:
|
|
179
|
+
|
|
180
|
+
**1.** Launch Pi with the channel flag:
|
|
181
|
+
```sh
|
|
182
|
+
pi --channels plugin:imessage
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
**2.** Check status:
|
|
186
|
+
```
|
|
187
|
+
/imessage:status
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
**3.** Text yourself. Open Messages on any device signed into your Apple ID, start a conversation with yourself, and send a message. It reaches the assistant immediately — self-chat bypasses all access control.
|
|
191
|
+
|
|
192
|
+
**4.** (Optional) Allow other people. Nobody else's texts reach the assistant until you add their handle:
|
|
193
|
+
```
|
|
194
|
+
/imessage:access allow +14155551234
|
|
195
|
+
/imessage:access allow friend@icloud.com
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
That's it. The default policy (`allowlist`) silently drops messages from anyone not on the list.
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## How It Works
|
|
203
|
+
|
|
204
|
+
| Component | Mechanism |
|
|
205
|
+
|---|---|
|
|
206
|
+
| **Inbound** | Polls `chat.db` once a second for `ROWID > watermark`. The watermark initializes to `MAX(ROWID)` at boot — old messages are never replayed on restart. |
|
|
207
|
+
| **Outbound** | `osascript -e 'tell application "Messages" to send …'`. Text and chat GUID pass through `argv` to avoid shell escaping issues. |
|
|
208
|
+
| **History** | Direct SQLite queries against `chat.db`. Full history from the database, not limited to messages since server start. |
|
|
209
|
+
| **Attachments (inbound)** | `chat.db` stores absolute filesystem paths for attachments. The first image per message is surfaced to the assistant as a local path. |
|
|
210
|
+
| **Attachments (outbound)** | Files are sent as separate messages after the text, via AppleScript's `POSIX file` syntax. |
|
|
211
|
+
| **Echo suppression** | To prevent the assistant from processing its own replies (both appear in `chat.db` as "from me"), it maintains a 15-second window of recently sent text and matches incoming messages against it. |
|
|
212
|
+
| **State** | All access control state lives in `~/.pi/agent/imessage/access.json`. The server re-reads this file on every inbound message, so changes take effect without a restart. |
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
## Environment Variables
|
|
217
|
+
|
|
218
|
+
All optional. No environment variables are required.
|
|
219
|
+
|
|
220
|
+
| Variable | Default | Effect |
|
|
221
|
+
|---|---|---|
|
|
222
|
+
| `IMESSAGE_APPEND_SIGNATURE` | `true` | Appends `\nSent by iMessage` to outbound messages. Set to `false` to disable. |
|
|
223
|
+
| `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 bypass access control. Only enable if you understand the risk. |
|
|
224
|
+
| `IMESSAGE_ACCESS_MODE` | — | Set to `static` to disable runtime pairing and lock the access config to what was on disk at boot. Changes to `access.json` are ignored until restart. |
|
|
225
|
+
| `IMESSAGE_STATE_DIR` | `~/.pi/agent/imessage` | Override where `access.json` and pairing state live. |
|
|
226
|
+
| `IMESSAGE_DB_PATH` | `~/Library/Messages/chat.db` | Override the path to the Messages database. |
|
|
227
|
+
|
|
228
|
+
Set them before launching Pi:
|
|
229
|
+
|
|
230
|
+
```sh
|
|
231
|
+
IMESSAGE_APPEND_SIGNATURE=false pi --channels plugin:imessage
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
Or export them in your shell profile.
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
## Access Control
|
|
239
|
+
|
|
240
|
+
### Self-Chat
|
|
241
|
+
|
|
242
|
+
Texting yourself always works with zero configuration. The server learns your own addresses at boot by reading `message.account` and `chat.last_addressed_handle` from `chat.db`. Messages from those addresses skip the gate entirely.
|
|
243
|
+
|
|
244
|
+
Open Messages on any device signed into your Apple ID, start a conversation with yourself (just your name at the top), and text. The message reaches the assistant.
|
|
245
|
+
|
|
246
|
+
To distinguish your input from the assistant's own replies — both appear in `chat.db` as from-me — the server maintains a 15-second echo suppression window.
|
|
247
|
+
|
|
248
|
+
### DM Policies
|
|
249
|
+
|
|
250
|
+
`dmPolicy` controls how texts from senders **other than you** (not on the allowlist) are handled.
|
|
251
|
+
|
|
252
|
+
| Policy | Behavior |
|
|
253
|
+
|---|---|
|
|
254
|
+
| `allowlist` (default) | Drop silently. No auto-reply. The safest default for a personal Mac that reads your personal `chat.db`. |
|
|
255
|
+
| `pairing` | Auto-reply with a 6-character pairing code, then drop the message. The sender must tell you the code, and you run `/imessage:access pair <code>` to approve them. **Warning**: every contact who texts this Mac gets an auto-reply. Only use on a dedicated line with very few contacts. |
|
|
256
|
+
| `disabled` | Drop everything except self-chat. Nobody else gets through regardless of the allowlist. |
|
|
257
|
+
|
|
258
|
+
```
|
|
259
|
+
/imessage:access policy allowlist
|
|
260
|
+
/imessage:access policy pairing
|
|
261
|
+
/imessage:access policy disabled
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
### Handle Addresses
|
|
265
|
+
|
|
266
|
+
iMessage identifies senders by **handle addresses** — either a phone number or an Apple ID email. The format must match exactly what appears at the top of the conversation in Messages.app.
|
|
267
|
+
|
|
268
|
+
| Type | Format | Example |
|
|
269
|
+
|---|---|---|
|
|
270
|
+
| Phone number | `+countrycode` + number, no spaces or dashes | `+14155551234` |
|
|
271
|
+
| Email | Full email address | `friend@icloud.com` |
|
|
272
|
+
|
|
273
|
+
Keep the `+` prefix on phone numbers. No spaces, no dashes.
|
|
274
|
+
|
|
275
|
+
If you're unsure of the exact format, check the `chat_messages` tool output — it shows raw handle IDs from `chat.db`.
|
|
276
|
+
|
|
277
|
+
### Allowlisting People
|
|
278
|
+
|
|
279
|
+
Under the default `allowlist` policy, add anyone you want to reach the assistant:
|
|
280
|
+
|
|
281
|
+
```
|
|
282
|
+
/imessage:access allow +14155551234
|
|
283
|
+
/imessage:access allow partner@icloud.com
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
Remove them:
|
|
287
|
+
|
|
288
|
+
```
|
|
289
|
+
/imessage:access remove +14155551234
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
List everyone currently allowed:
|
|
293
|
+
|
|
294
|
+
```
|
|
295
|
+
/imessage:access
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
### Pairing Mode
|
|
299
|
+
|
|
300
|
+
Pairing is an alternative to manually allowlisting. When `dmPolicy` is `pairing`:
|
|
301
|
+
|
|
302
|
+
1. An unapproved sender texts you.
|
|
303
|
+
2. The server auto-replies with a 6-character code (e.g. `a4f91c`).
|
|
304
|
+
3. The sender tells you the code.
|
|
305
|
+
4. You run `/imessage:access pair a4f91c` inside Pi.
|
|
306
|
+
5. They're added to the allowlist and can now reach the assistant.
|
|
307
|
+
|
|
308
|
+
**Caveat**: Every contact who texts this Mac gets the pairing auto-reply. This is why `allowlist` is the default — most people use their personal Messages database and don't want random contacts getting bot replies.
|
|
309
|
+
|
|
310
|
+
Pairing codes expire after 1 hour. Maximum 3 pending codes at a time. Each sender gets at most 2 reminder replies.
|
|
311
|
+
|
|
312
|
+
### Group Chats
|
|
313
|
+
|
|
314
|
+
Group chats are **off by default**. Opt each one in individually.
|
|
315
|
+
|
|
316
|
+
Groups are identified by their **chat GUID**, which looks like `iMessage;+;chat123456789012345678`. These are not exposed in Messages.app. Get them from:
|
|
317
|
+
|
|
318
|
+
- The `chat_id` field in `chat_messages` tool output
|
|
319
|
+
- The server's stderr log when it drops a group message
|
|
320
|
+
- The `/imessage:access` status output
|
|
321
|
+
|
|
322
|
+
Enable a group:
|
|
323
|
+
|
|
324
|
+
```
|
|
325
|
+
/imessage:access group add "iMessage;+;chat123456789012345678"
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
Always quote the GUID — the semicolons are shell metacharacters.
|
|
329
|
+
|
|
330
|
+
Disable a group:
|
|
331
|
+
|
|
332
|
+
```
|
|
333
|
+
/imessage:access group rm "iMessage;+;chat123456789012345678"
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
By default, groups use `requireMention: true`, meaning the assistant only responds when a message matches a `mentionPatterns` regex. See [Mention Patterns](#mention-patterns).
|
|
337
|
+
|
|
338
|
+
Group flags:
|
|
339
|
+
|
|
340
|
+
| Flag | Effect |
|
|
341
|
+
|---|---|
|
|
342
|
+
| `--no-mention` | Respond to every message in the group (no mention trigger required) |
|
|
343
|
+
| `--allow addr1,addr2` | Only these group members can trigger the assistant |
|
|
344
|
+
|
|
345
|
+
```
|
|
346
|
+
/imessage:access group add "iMessage;+;chat123456789012345678" --no-mention
|
|
347
|
+
/imessage:access group add "iMessage;+;chat123456789012345678" --allow +14155551234,friend@icloud.com
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
### Mention Patterns
|
|
351
|
+
|
|
352
|
+
iMessage has **no structured @mentions**. The `@Name` highlight you see in group chats is purely visual — `chat.db` does not mark it as a mention.
|
|
353
|
+
|
|
354
|
+
With `requireMention: true` (the group default), the only trigger is a regex match against `mentionPatterns`. If no patterns are set, no group messages will ever reach the assistant.
|
|
355
|
+
|
|
356
|
+
Set mention patterns:
|
|
357
|
+
|
|
358
|
+
```
|
|
359
|
+
/imessage:access set mentionPatterns '["^pi\\b", "@assistant", "hey bot"]'
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
These are **case-insensitive** regexes. A message like "hey @assistant can you help?" would match `@assistant`.
|
|
363
|
+
|
|
364
|
+
---
|
|
365
|
+
|
|
366
|
+
## Slash Commands
|
|
367
|
+
|
|
368
|
+
Two commands are registered:
|
|
369
|
+
|
|
370
|
+
### /imessage:status
|
|
371
|
+
|
|
372
|
+
Shows the current state of the iMessage channel.
|
|
373
|
+
|
|
374
|
+
```
|
|
375
|
+
/imessage:status
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
Output includes:
|
|
379
|
+
- Whether the server is running
|
|
380
|
+
- DM policy (`allowlist`, `pairing`, or `disabled`)
|
|
381
|
+
- Allowed senders (handles)
|
|
382
|
+
- Number of enabled groups
|
|
383
|
+
- Pending pairings (if any)
|
|
384
|
+
- Your self-chat addresses
|
|
385
|
+
|
|
386
|
+
### /imessage:access
|
|
387
|
+
|
|
388
|
+
Manages access control. Supports multiple subcommands:
|
|
389
|
+
|
|
390
|
+
| Command | Effect |
|
|
391
|
+
|---|---|
|
|
392
|
+
| `/imessage:access` | Show current state: policy, allowlist, pending pairings, groups. |
|
|
393
|
+
| `/imessage:access allow +14155551234` | Add a handle to the allowlist. |
|
|
394
|
+
| `/imessage:access remove +14155551234` | Remove a handle from the allowlist. |
|
|
395
|
+
| `/imessage:access pair a4f91c` | Approve a pending pairing code. |
|
|
396
|
+
| `/imessage:access deny a4f91c` | Discard a pending pairing code. |
|
|
397
|
+
| `/imessage:access policy allowlist` | Set DM policy. Values: `allowlist`, `pairing`, `disabled`. |
|
|
398
|
+
| `/imessage:access group add "iMessage;+;chat…"` | Enable a group. Flags: `--no-mention`, `--allow a,b`. |
|
|
399
|
+
| `/imessage:access group rm "iMessage;+;chat…"` | Disable a group. |
|
|
400
|
+
| `/imessage:access set textChunkLimit 5000` | Set a delivery config key. |
|
|
401
|
+
|
|
402
|
+
All changes take effect immediately — the server re-reads `access.json` on every inbound message.
|
|
403
|
+
|
|
404
|
+
---
|
|
405
|
+
|
|
406
|
+
## Tools Exposed to the Assistant
|
|
407
|
+
|
|
408
|
+
iMessage registers two tools that the Pi assistant can use:
|
|
409
|
+
|
|
410
|
+
### imessage_reply
|
|
411
|
+
|
|
412
|
+
Send a message to an iMessage chat.
|
|
413
|
+
|
|
414
|
+
**Parameters:**
|
|
415
|
+
|
|
416
|
+
| Parameter | Required | Description |
|
|
417
|
+
|---|---|---|
|
|
418
|
+
| `chat_id` | Yes | The chat GUID from the inbound message |
|
|
419
|
+
| `text` | Yes | Message text to send |
|
|
420
|
+
| `files` | No | Array of absolute file paths to attach. Sent as separate messages after the text. Max 100MB per file. |
|
|
421
|
+
|
|
422
|
+
Text is auto-chunked based on `textChunkLimit` and `chunkMode` settings. Files over 100MB are rejected. The assistant cannot send files from the state directory (`~/.pi/agent/imessage/`).
|
|
423
|
+
|
|
424
|
+
### imessage_messages
|
|
425
|
+
|
|
426
|
+
Fetch recent iMessage history as conversation threads.
|
|
427
|
+
|
|
428
|
+
**Parameters:**
|
|
429
|
+
|
|
430
|
+
| Parameter | Required | Description |
|
|
431
|
+
|---|---|---|
|
|
432
|
+
| `chat_guid` | No | A specific chat GUID to read. Omit to read from every allowlisted chat. |
|
|
433
|
+
| `limit` | No | Max messages per chat. Default 100, max 500. |
|
|
434
|
+
|
|
435
|
+
Each thread is labelled **DM** or **Group** with its participant list, followed by timestamped messages (oldest-first). Reads `chat.db` directly — full native history, scoped to allowlisted chats only.
|
|
436
|
+
|
|
437
|
+
---
|
|
438
|
+
|
|
439
|
+
## Configuration File
|
|
440
|
+
|
|
441
|
+
All state lives in `~/.pi/agent/imessage/`. This is **outside** the npm package directory — npm packages can be updated or reinstalled, and you don't want to lose your access config. It follows Pi's standard convention:
|
|
442
|
+
|
|
443
|
+
| Path | What |
|
|
444
|
+
|---|---|
|
|
445
|
+
| `~/.pi/agent/settings.json` | Pi global settings |
|
|
446
|
+
| `~/.pi/agent/models.json` | Pi model config |
|
|
447
|
+
| `~/.pi/agent/sessions/` | Pi session history |
|
|
448
|
+
| `~/.pi/agent/imessage/` | iMessage channel state |
|
|
449
|
+
| `~/.pi/agent/imessage/access.json` | Access control (allowlist, policy, groups) |
|
|
450
|
+
| `~/.pi/agent/imessage/approved/` | Temporary files for pairing confirmations |
|
|
451
|
+
|
|
452
|
+
Nothing is written during installation (`/plugin install imessage`). The `~/.pi/agent/imessage/` directory is created at runtime — when you launch with `--channels` and either receive a message or run an access command.
|
|
453
|
+
|
|
454
|
+
Override the state directory with the `IMESSAGE_STATE_DIR` environment variable:
|
|
455
|
+
|
|
456
|
+
```sh
|
|
457
|
+
IMESSAGE_STATE_DIR=/some/other/path pi --channels plugin:imessage
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
If `access.json` is missing, defaults are used: `allowlist` policy with empty allowlists. Only self-chat passes.
|
|
461
|
+
|
|
462
|
+
### Schema
|
|
463
|
+
|
|
464
|
+
```jsonc
|
|
465
|
+
{
|
|
466
|
+
// How to handle texts from senders not in allowFrom.
|
|
467
|
+
// "allowlist" = drop silently (default, safest for personal accounts)
|
|
468
|
+
// "pairing" = auto-reply with a code
|
|
469
|
+
// "disabled" = drop everything except self-chat
|
|
470
|
+
"dmPolicy": "allowlist",
|
|
471
|
+
|
|
472
|
+
// Handle addresses allowed to reach the assistant.
|
|
473
|
+
// Phone numbers: +countrycode + digits, no spaces/dashes.
|
|
474
|
+
// Emails: full address.
|
|
475
|
+
"allowFrom": ["+14155551234", "partner@icloud.com"],
|
|
476
|
+
|
|
477
|
+
// Group chats the assistant participates in.
|
|
478
|
+
// Keyed by chat GUID. Empty object = DM-only (default).
|
|
479
|
+
"groups": {
|
|
480
|
+
"iMessage;+;chat123456789012345678": {
|
|
481
|
+
// true = only respond on mentionPatterns match.
|
|
482
|
+
// iMessage has no structured @mentions; regex is the only trigger.
|
|
483
|
+
"requireMention": true,
|
|
484
|
+
// Restrict triggers to these senders. Empty = any member.
|
|
485
|
+
"allowFrom": []
|
|
486
|
+
}
|
|
487
|
+
},
|
|
488
|
+
|
|
489
|
+
// Case-insensitive regexes that count as a mention in groups.
|
|
490
|
+
// Required when requireMention is true.
|
|
491
|
+
"mentionPatterns": ["^pi\\b", "@assistant"],
|
|
492
|
+
|
|
493
|
+
// Max characters per outbound message chunk.
|
|
494
|
+
// iMessage has no length cap; this is for readability.
|
|
495
|
+
"textChunkLimit": 10000,
|
|
496
|
+
|
|
497
|
+
// How to split long messages.
|
|
498
|
+
// "length" = cut exactly at the limit.
|
|
499
|
+
// "newline" = prefer paragraph/line boundaries near the limit.
|
|
500
|
+
"chunkMode": "newline"
|
|
501
|
+
}
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
Changes to this file take effect on the next inbound message — no restart needed (unless `IMESSAGE_ACCESS_MODE=static`).
|
|
505
|
+
|
|
506
|
+
---
|
|
507
|
+
|
|
508
|
+
## Delivery Settings
|
|
509
|
+
|
|
510
|
+
Configure via `/imessage:access set <key> <value>`:
|
|
511
|
+
|
|
512
|
+
| Key | Type | Default | Description |
|
|
513
|
+
|---|---|---|---|
|
|
514
|
+
| `textChunkLimit` | number | 10000 | Split outbound messages longer than this. Max 10000. |
|
|
515
|
+
| `chunkMode` | `"length"` or `"newline"` | `"length"` | `length` cuts at exactly the limit. `newline` prefers paragraph boundaries. |
|
|
516
|
+
| `mentionPatterns` | JSON array of regex strings | `[]` | Case-insensitive patterns that trigger the assistant in group chats. |
|
|
517
|
+
|
|
518
|
+
Examples:
|
|
519
|
+
|
|
520
|
+
```
|
|
521
|
+
/imessage:access set textChunkLimit 5000
|
|
522
|
+
/imessage:access set chunkMode newline
|
|
523
|
+
/imessage:access set mentionPatterns '["^pi\\b", "@assistant"]'
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
There is no tapback, edit, or thread-reply support — those require Apple's private API.
|
|
527
|
+
|
|
528
|
+
---
|
|
529
|
+
|
|
530
|
+
## Limitations
|
|
531
|
+
|
|
532
|
+
- **macOS only** — requires `chat.db` and AppleScript.
|
|
533
|
+
- **No tapback, edit, or thread replies** — AppleScript can send messages but cannot interact with these features. They require Apple's private API. If you need them, look at [BlueBubbles](https://bluebubbles.app) (requires disabling SIP).
|
|
534
|
+
- **No structured @mentions** — iMessage's `@Name` is visual only. Group mention triggers are regex-based.
|
|
535
|
+
- **SMS off by default** — SMS sender IDs can be spoofed. Enable with `IMESSAGE_ALLOW_SMS=true` only if you understand the risk.
|
|
536
|
+
- **Polling, not push** — checks `chat.db` once per second. There is a theoretical sub-second delay.
|
|
537
|
+
- **No message delete** — AppleScript cannot delete messages.
|
|
538
|
+
|
|
539
|
+
---
|
|
540
|
+
|
|
541
|
+
## Troubleshooting
|
|
542
|
+
|
|
543
|
+
### "authorization denied" on startup
|
|
544
|
+
|
|
545
|
+
Full Disk Access is not granted. Go to **System Settings → Privacy & Security → Full Disk Access** and add your terminal app.
|
|
546
|
+
|
|
547
|
+
### Messages are sent but the assistant doesn't see replies
|
|
548
|
+
|
|
549
|
+
The Automation permission was denied. Go to **System Settings → Privacy & Security → Automation** and re-enable Messages control for your terminal.
|
|
550
|
+
|
|
551
|
+
### Self-chat isn't working
|
|
552
|
+
|
|
553
|
+
Make sure you're texting from a device signed into the **same Apple ID**. The server detects your addresses from `message.account` and `chat.last_addressed_handle` in `chat.db`. Check `/imessage:status` — it lists detected self-chat addresses.
|
|
554
|
+
|
|
555
|
+
### Someone texted me but the assistant didn't respond
|
|
556
|
+
|
|
557
|
+
They're not on the allowlist. Under the default `allowlist` policy, messages from unknown senders are silently dropped. Add them:
|
|
558
|
+
|
|
559
|
+
```
|
|
560
|
+
/imessage:access allow +14155551234
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
Check who's currently allowed:
|
|
564
|
+
|
|
565
|
+
```
|
|
566
|
+
/imessage:access
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
### Group messages aren't reaching the assistant
|
|
570
|
+
|
|
571
|
+
Groups must be explicitly enabled. You also need mention patterns set (unless using `--no-mention`). Check:
|
|
572
|
+
|
|
573
|
+
```
|
|
574
|
+
/imessage:access
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
If the group is listed and `requireMention` is true, make sure `mentionPatterns` includes a pattern that matches how people address the assistant.
|
|
578
|
+
|
|
579
|
+
### The assistant is processing its own replies
|
|
580
|
+
|
|
581
|
+
The echo suppression window is 15 seconds. If the assistant sends a message and the same text appears back within that window, it's suppressed. This should be rare. If it happens, the duplicate is harmless — it just creates an extra processing cycle.
|
|
582
|
+
|
|
583
|
+
### Pairing codes aren't being sent
|
|
584
|
+
|
|
585
|
+
Make sure the DM policy is actually set to `pairing`:
|
|
586
|
+
|
|
587
|
+
```
|
|
588
|
+
/imessage:access
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
Also check that you haven't hit the limit of 3 pending codes. Expired codes (1 hour) are cleaned up automatically.
|
package/dist/README.md
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# iMessage
|
|
2
|
+
|
|
3
|
+
Connect iMessage to your Pi assistant. 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.
|
|
4
|
+
|
|
5
|
+
macOS only.
|
|
6
|
+
|
|
7
|
+
## Quick setup
|
|
8
|
+
> Default: text yourself. Other senders are dropped silently (no auto-reply) until you allowlist them. See [ACCESS.md](./ACCESS.md) for groups and multi-user setups.
|
|
9
|
+
|
|
10
|
+
**1. Grant Full Disk Access.**
|
|
11
|
+
|
|
12
|
+
`chat.db` is protected by macOS TCC. The first time the server reads it, macOS pops a prompt asking if your terminal can access Messages — click **Allow**. The prompt names whatever app launched bun (Terminal.app, iTerm, Ghostty, your IDE).
|
|
13
|
+
|
|
14
|
+
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 server exits immediately with `authorization denied`.
|
|
15
|
+
|
|
16
|
+
**2. Install the plugin.**
|
|
17
|
+
|
|
18
|
+
These are Pi commands — run `pi` to start a session first.
|
|
19
|
+
|
|
20
|
+
Install the plugin. No env vars required.
|
|
21
|
+
```
|
|
22
|
+
/plugin install imessage
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
**3. Relaunch with the channel flag.**
|
|
26
|
+
|
|
27
|
+
The server won't connect without this — exit your session and start a new one:
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
pi --channels plugin:imessage
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Check that `/imessage:status` tab-completes.
|
|
34
|
+
|
|
35
|
+
**4. Text yourself.**
|
|
36
|
+
|
|
37
|
+
iMessage yourself from any device. It reaches the assistant immediately — self-chat bypasses access control.
|
|
38
|
+
|
|
39
|
+
> The first outbound reply triggers an **Automation** permission prompt ("Terminal wants to control Messages"). Click OK.
|
|
40
|
+
|
|
41
|
+
**5. Decide who else gets in.**
|
|
42
|
+
|
|
43
|
+
Nobody else's texts reach the assistant until you add their handle:
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
/imessage:access allow +15551234567
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Handles are phone numbers (`+15551234567`) or Apple ID emails (`them@icloud.com`). If you're not sure what you want, ask iMessage to review your setup.
|
|
50
|
+
|
|
51
|
+
## How it works
|
|
52
|
+
|
|
53
|
+
| | |
|
|
54
|
+
| --- | --- |
|
|
55
|
+
| **Inbound** | Polls `chat.db` once a second for `ROWID > watermark`. Watermark initializes to `MAX(ROWID)` at boot — old messages aren't replayed on restart. |
|
|
56
|
+
| **Outbound** | `osascript` with `tell application "Messages" to send …`. Text and chat GUID pass through argv so there's no escaping footgun. |
|
|
57
|
+
| **History & search** | Direct SQLite queries against `chat.db`. Full history — not just messages since the server started. |
|
|
58
|
+
| **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. |
|
|
59
|
+
|
|
60
|
+
## Environment variables
|
|
61
|
+
|
|
62
|
+
| Variable | Default | Effect |
|
|
63
|
+
| --- | --- | --- |
|
|
64
|
+
| `IMESSAGE_APPEND_SIGNATURE` | `true` | Appends `\nSent by iMessage` to outbound messages. Set to `false` to disable. |
|
|
65
|
+
| `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. |
|
|
66
|
+
| `IMESSAGE_ACCESS_MODE` | — | Set to `static` to disable runtime pairing and read `access.json` only. |
|
|
67
|
+
| `IMESSAGE_STATE_DIR` | `~/.pi/agent/imessage` | Override where `access.json` and pairing state live. |
|
|
68
|
+
|
|
69
|
+
## Access control
|
|
70
|
+
|
|
71
|
+
See **[ACCESS.md](./ACCESS.md)** for DM policies, groups, self-chat, delivery config, skill commands, and the `access.json` schema.
|
|
72
|
+
|
|
73
|
+
Quick reference: IDs are **handle addresses** (`+15551234567` or `someone@icloud.com`). Default policy is `allowlist` — this reads your personal `chat.db`. Self-chat always bypasses the gate.
|
|
74
|
+
|
|
75
|
+
## Tools exposed to the assistant
|
|
76
|
+
|
|
77
|
+
| Tool | Purpose |
|
|
78
|
+
| --- | --- |
|
|
79
|
+
| `reply` | Send to a chat. `chat_id` + `text`, optional `files` (absolute paths). Auto-chunks text; files send as separate messages. |
|
|
80
|
+
| `chat_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. Reads `chat.db` directly — full native history. |
|
|
81
|
+
|
|
82
|
+
## What you don't get
|
|
83
|
+
|
|
84
|
+
AppleScript can send messages but not tapback, edit, or thread — those require Apple's private API. If you need them, look at [BlueBubbles](https://bluebubbles.app) (requires disabling SIP).
|