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/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).
|
package/dist/ACCESS.md
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# iMessage — Access & Delivery
|
|
2
|
+
|
|
3
|
+
This channel reads your Messages database (`~/Library/Messages/chat.db`) directly. Every text to this Mac — from any contact, in any chat — reaches the gate. Access control selects which conversations the assistant should see.
|
|
4
|
+
|
|
5
|
+
Texting yourself always works. **Self-chat bypasses the gate** with no setup: the server learns your own addresses at boot and lets them through unconditionally. For other senders, the default policy is **`allowlist`**: nothing passes until you add the handle with `/imessage:access allow <address>`.
|
|
6
|
+
|
|
7
|
+
All state lives in `~/.pi/agent/imessage/access.json`. The `/imessage:access` skill commands edit this file; the server re-reads it on every inbound message, so changes take effect without a restart. Set `IMESSAGE_ACCESS_MODE=static` to pin config to what was on disk at boot.
|
|
8
|
+
|
|
9
|
+
## At a glance
|
|
10
|
+
|
|
11
|
+
| | |
|
|
12
|
+
| --- | --- |
|
|
13
|
+
| Default policy | `allowlist` |
|
|
14
|
+
| Self-chat | Bypasses the gate; no config needed |
|
|
15
|
+
| Sender ID | Handle address: `+15551234567` or `someone@icloud.com` |
|
|
16
|
+
| Group key | Chat GUID: `iMessage;+;chat…` |
|
|
17
|
+
| Mention quirk | Regex only; iMessage has no structured @mentions |
|
|
18
|
+
| Config file | `~/.pi/agent/imessage/access.json` |
|
|
19
|
+
|
|
20
|
+
## Self-chat
|
|
21
|
+
|
|
22
|
+
Open Messages on any device signed into your Apple ID, start a conversation with yourself, and text. It reaches the assistant.
|
|
23
|
+
|
|
24
|
+
The server identifies your addresses at boot by reading `message.account` and `chat.last_addressed_handle` from `chat.db`. Messages from those addresses skip the gate entirely. To distinguish your input from its own replies — both appear in `chat.db` as from-me — it maintains a 15-second window of recently sent text and matches against it.
|
|
25
|
+
|
|
26
|
+
## DM policies
|
|
27
|
+
|
|
28
|
+
`dmPolicy` controls how texts from senders other than you, not on the allowlist, are handled.
|
|
29
|
+
|
|
30
|
+
| Policy | Behavior |
|
|
31
|
+
| --- | --- |
|
|
32
|
+
| `allowlist` (default) | Drop silently. Safe default for a personal account. |
|
|
33
|
+
| `pairing` | Reply with a pairing code, drop the message. Every contact who texts this Mac will receive one; only use this if very few people have the number. |
|
|
34
|
+
| `disabled` | Drop everything except self-chat, which always bypasses. |
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
/imessage:access policy pairing
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Handle addresses
|
|
41
|
+
|
|
42
|
+
iMessage identifies senders by **handle addresses**: either a phone number in `+country` format or the Apple ID email. The form matches what appears at the top of the conversation in Messages.app.
|
|
43
|
+
|
|
44
|
+
| Contact shown as | Handle address |
|
|
45
|
+
| --- | --- |
|
|
46
|
+
| Phone number | `+15551234567` (keep the `+`, no spaces or dashes) |
|
|
47
|
+
| Email | `someone@icloud.com` |
|
|
48
|
+
|
|
49
|
+
If the exact form is unclear, check the `chat_messages` tool output or (under `pairing` policy) the pending entry in `access.json`.
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
/imessage:access allow +15551234567
|
|
53
|
+
/imessage:access allow friend@icloud.com
|
|
54
|
+
/imessage:access remove +15551234567
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Groups
|
|
58
|
+
|
|
59
|
+
Groups are off by default. Opt each one in individually, keyed on the chat GUID.
|
|
60
|
+
|
|
61
|
+
Chat GUIDs look like `iMessage;+;chat123456789012345678`. They're not exposed in Messages.app; get them from the `chat_id` field in `chat_messages` tool output or from the server's stderr log when it drops a group message.
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
/imessage:access group add "iMessage;+;chat123456789012345678"
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Quote the GUID; the semicolons are shell metacharacters.
|
|
68
|
+
|
|
69
|
+
iMessage has **no structured @mentions**. The `@Name` highlight in group chats is presentational styling — nothing in `chat.db` marks it as a mention. With the default `requireMention: true`, the only trigger is a `mentionPatterns` regex match. Set at least one pattern before opting a group in, or no message will ever match.
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
/imessage:access set mentionPatterns '["^pi\\b", "@assistant"]'
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Pass `--no-mention` to process every message in the group, or `--allow addr1,addr2` to restrict which members can trigger it.
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
/imessage:access group add "iMessage;+;chat123456789012345678" --no-mention
|
|
79
|
+
/imessage:access group add "iMessage;+;chat123456789012345678" --allow +15551234567,friend@icloud.com
|
|
80
|
+
/imessage:access group rm "iMessage;+;chat123456789012345678"
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Delivery
|
|
84
|
+
|
|
85
|
+
AppleScript can send messages but cannot tapback, edit, or thread-reply; those require private API. Delivery config is correspondingly limited. Set with `/imessage:access set <key> <value>`.
|
|
86
|
+
|
|
87
|
+
**`textChunkLimit`** sets the split threshold. iMessage has no length cap; chunking is for readability. Defaults to 10000.
|
|
88
|
+
|
|
89
|
+
**`chunkMode`** chooses the split strategy: `length` cuts exactly at the limit; `newline` prefers paragraph boundaries.
|
|
90
|
+
|
|
91
|
+
There is no `ackReaction` or `replyToMode` on this channel.
|
|
92
|
+
|
|
93
|
+
## Skill reference
|
|
94
|
+
|
|
95
|
+
| Command | Effect |
|
|
96
|
+
| --- | --- |
|
|
97
|
+
| `/imessage:access` | Print current state: policy, allowlist, pending pairings, enabled groups. |
|
|
98
|
+
| `/imessage:access pair a4f91c` | Approve a pending code (relevant only under `pairing` policy). |
|
|
99
|
+
| `/imessage:access deny a4f91c` | Discard a pending code. |
|
|
100
|
+
| `/imessage:access allow +15551234567` | Add a handle. The primary entry point under the default `allowlist` policy. |
|
|
101
|
+
| `/imessage:access remove +15551234567` | Remove from the allowlist. |
|
|
102
|
+
| `/imessage:access policy pairing` | Set `dmPolicy`. Values: `pairing`, `allowlist`, `disabled`. |
|
|
103
|
+
| `/imessage:access group add "iMessage;+;chat…"` | Enable a group. Quote the GUID. Flags: `--no-mention`, `--allow a,b`. |
|
|
104
|
+
| `/imessage:access group rm "iMessage;+;chat…"` | Disable a group. |
|
|
105
|
+
| `/imessage:access set textChunkLimit 5000` | Set a config key: `textChunkLimit`, `chunkMode`, `mentionPatterns`. |
|
|
106
|
+
|
|
107
|
+
## Config file
|
|
108
|
+
|
|
109
|
+
`~/.pi/agent/imessage/access.json`. Absent file is equivalent to `allowlist` policy with empty lists: only self-chat passes.
|
|
110
|
+
|
|
111
|
+
```jsonc
|
|
112
|
+
{
|
|
113
|
+
// Handling for texts from senders not in allowFrom.
|
|
114
|
+
// Defaults to allowlist since this reads your personal chat.db.
|
|
115
|
+
// Self-chat bypasses regardless.
|
|
116
|
+
"dmPolicy": "allowlist",
|
|
117
|
+
|
|
118
|
+
// Handle addresses allowed to reach the assistant.
|
|
119
|
+
"allowFrom": ["+15551234567", "friend@icloud.com"],
|
|
120
|
+
|
|
121
|
+
// Group chats the assistant participates in. Empty object = DM-only.
|
|
122
|
+
"groups": {
|
|
123
|
+
"iMessage;+;chat123456789012345678": {
|
|
124
|
+
// true: respond only on mentionPatterns match.
|
|
125
|
+
// iMessage has no structured @mentions; regex is the only trigger.
|
|
126
|
+
"requireMention": true,
|
|
127
|
+
// Restrict triggers to these senders. Empty = any member (subject to requireMention).
|
|
128
|
+
"allowFrom": []
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
|
|
132
|
+
// Case-insensitive regexes that count as a mention.
|
|
133
|
+
// Required for groups with requireMention, since there are no structured mentions.
|
|
134
|
+
"mentionPatterns": ["^pi\\b", "@assistant"],
|
|
135
|
+
|
|
136
|
+
// Split threshold. No length cap; this is about readability.
|
|
137
|
+
"textChunkLimit": 10000,
|
|
138
|
+
|
|
139
|
+
// length = cut at limit. newline = prefer paragraph boundaries.
|
|
140
|
+
"chunkMode": "newline"
|
|
141
|
+
}
|
|
142
|
+
```
|