@elizaos/plugin-imessage 2.0.0-alpha.9 → 2.0.0-beta.1
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 +184 -0
- package/auto-enable.ts +21 -0
- package/dist/api/bluebubbles-routes.d.ts +10 -0
- package/dist/api/bluebubbles-routes.d.ts.map +1 -0
- package/dist/api/bluebubbles-routes.js +132 -0
- package/dist/api/bluebubbles-routes.js.map +1 -0
- package/dist/api/imessage-routes.d.ts +80 -0
- package/dist/api/imessage-routes.d.ts.map +1 -0
- package/dist/api/imessage-routes.js +230 -0
- package/dist/api/imessage-routes.js.map +1 -0
- package/dist/chatdb-reader.d.ts +240 -0
- package/dist/chatdb-reader.d.ts.map +1 -0
- package/dist/chatdb-reader.js +647 -0
- package/dist/chatdb-reader.js.map +1 -0
- package/dist/connector-account-provider.d.ts +18 -0
- package/dist/connector-account-provider.d.ts.map +1 -0
- package/dist/connector-account-provider.js +83 -0
- package/dist/connector-account-provider.js.map +1 -0
- package/dist/contacts-reader.d.ts +147 -0
- package/dist/contacts-reader.d.ts.map +1 -0
- package/dist/contacts-reader.js +481 -0
- package/dist/contacts-reader.js.map +1 -0
- package/dist/index.d.ts +11 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +48 -16
- package/dist/index.js.map +1 -1
- package/dist/providers/index.d.ts +1 -2
- package/dist/providers/index.d.ts.map +1 -1
- package/dist/providers/index.js +2 -2
- package/dist/providers/index.js.map +1 -1
- package/dist/rpc.d.ts +16 -4
- package/dist/rpc.d.ts.map +1 -1
- package/dist/rpc.js +103 -11
- package/dist/rpc.js.map +1 -1
- package/dist/service.d.ts +203 -8
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +1368 -107
- package/dist/service.js.map +1 -1
- package/dist/setup-routes.d.ts +38 -0
- package/dist/setup-routes.d.ts.map +1 -0
- package/dist/setup-routes.js +322 -0
- package/dist/setup-routes.js.map +1 -0
- package/dist/types.d.ts +29 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -1
- package/package.json +19 -10
- package/dist/actions/index.d.ts +0 -5
- package/dist/actions/index.d.ts.map +0 -1
- package/dist/actions/index.js +0 -5
- package/dist/actions/index.js.map +0 -1
- package/dist/actions/sendMessage.d.ts +0 -6
- package/dist/actions/sendMessage.d.ts.map +0 -1
- package/dist/actions/sendMessage.js +0 -178
- package/dist/actions/sendMessage.js.map +0 -1
- package/dist/providers/chatContext.d.ts +0 -6
- package/dist/providers/chatContext.d.ts.map +0 -1
- package/dist/providers/chatContext.js +0 -60
- package/dist/providers/chatContext.js.map +0 -1
package/README.md
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# @elizaos/plugin-imessage
|
|
2
|
+
|
|
3
|
+
iMessage plugin for ElizaOS agents. Enables chat integration with Apple's iMessage on macOS.
|
|
4
|
+
|
|
5
|
+
**Note: This plugin only works on macOS systems.**
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Send Messages**: Send text messages via iMessage
|
|
10
|
+
- **Direct & Group Chats**: Support for direct messages and group conversations
|
|
11
|
+
- **Attachments**: Send media attachments (via CLI tool)
|
|
12
|
+
- **Message Polling**: Receive incoming messages via polling
|
|
13
|
+
- **Policy Controls**: Configure DM and group policies
|
|
14
|
+
|
|
15
|
+
## Requirements
|
|
16
|
+
|
|
17
|
+
- **macOS**: This plugin only works on macOS
|
|
18
|
+
- **Messages App Access**: Full Disk Access permission may be required
|
|
19
|
+
- **Optional CLI Tool**: For enhanced functionality, use an iMessage CLI tool
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
# npm
|
|
25
|
+
npm install @elizaos/plugin-imessage
|
|
26
|
+
|
|
27
|
+
# bun
|
|
28
|
+
bun add @elizaos/plugin-imessage
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Configuration
|
|
32
|
+
|
|
33
|
+
### Environment Variables
|
|
34
|
+
|
|
35
|
+
| Variable | Description | Required |
|
|
36
|
+
|----------|-------------|----------|
|
|
37
|
+
| `IMESSAGE_CLI_PATH` | Path to iMessage CLI tool | No |
|
|
38
|
+
| `IMESSAGE_DB_PATH` | Path to iMessage database | No |
|
|
39
|
+
| `IMESSAGE_POLL_INTERVAL_MS` | Polling interval in ms | No |
|
|
40
|
+
| `IMESSAGE_DM_POLICY` | DM policy: open, pairing, allowlist, disabled | No |
|
|
41
|
+
| `IMESSAGE_GROUP_POLICY` | Group policy: open, allowlist, disabled | No |
|
|
42
|
+
| `IMESSAGE_ALLOW_FROM` | Comma-separated handles for allowlist | No |
|
|
43
|
+
| `IMESSAGE_ENABLED` | Enable/disable the plugin | No |
|
|
44
|
+
|
|
45
|
+
### Agent Configuration
|
|
46
|
+
|
|
47
|
+
```json
|
|
48
|
+
{
|
|
49
|
+
"plugins": ["@elizaos/plugin-imessage"],
|
|
50
|
+
"pluginParameters": {
|
|
51
|
+
"IMESSAGE_DM_POLICY": "pairing",
|
|
52
|
+
"IMESSAGE_GROUP_POLICY": "allowlist",
|
|
53
|
+
"IMESSAGE_POLL_INTERVAL_MS": "5000"
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Setup
|
|
59
|
+
|
|
60
|
+
### Permissions
|
|
61
|
+
|
|
62
|
+
1. Open System Preferences > Security & Privacy > Privacy
|
|
63
|
+
2. Grant Full Disk Access to:
|
|
64
|
+
- Terminal (or your terminal app)
|
|
65
|
+
- Your Node.js/Python executable
|
|
66
|
+
3. Allow Messages app to be controlled via AppleScript
|
|
67
|
+
|
|
68
|
+
### CLI Tool (Optional)
|
|
69
|
+
|
|
70
|
+
For enhanced functionality, you can use an iMessage CLI tool like `imsg`:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
# Install a CLI tool (example)
|
|
74
|
+
brew install imessage-cli
|
|
75
|
+
|
|
76
|
+
# Configure the path
|
|
77
|
+
IMESSAGE_CLI_PATH=/usr/local/bin/imsg
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Usage
|
|
81
|
+
|
|
82
|
+
### Actions
|
|
83
|
+
|
|
84
|
+
iMessage sending is exposed through the canonical message connector action. Use
|
|
85
|
+
`source: "imessage"` when a request needs to target iMessage explicitly.
|
|
86
|
+
|
|
87
|
+
| Primary action | Operation | Description |
|
|
88
|
+
|----------------|-----------|-------------|
|
|
89
|
+
| `MESSAGE` | `send` | Send a text message to a phone number, email, contact, or chat |
|
|
90
|
+
|
|
91
|
+
### Providers
|
|
92
|
+
|
|
93
|
+
iMessage does not register standalone planner providers. Chat and contact
|
|
94
|
+
context is exposed through the iMessage message connector hooks.
|
|
95
|
+
|
|
96
|
+
## How It Works
|
|
97
|
+
|
|
98
|
+
The plugin uses two methods to interact with iMessage:
|
|
99
|
+
|
|
100
|
+
1. **AppleScript** (default): Uses macOS's built-in scripting support to send messages through the Messages app
|
|
101
|
+
2. **CLI Tool** (optional): Uses a command-line tool for more features
|
|
102
|
+
|
|
103
|
+
### AppleScript Method
|
|
104
|
+
|
|
105
|
+
```applescript
|
|
106
|
+
tell application "Messages"
|
|
107
|
+
set targetService to 1st account whose service type = iMessage
|
|
108
|
+
set targetBuddy to participant "+1234567890" of targetService
|
|
109
|
+
send "Hello!" to targetBuddy
|
|
110
|
+
end tell
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Message Targets
|
|
114
|
+
|
|
115
|
+
iMessage supports multiple target types:
|
|
116
|
+
|
|
117
|
+
- **Phone Numbers**: `+1234567890`, `1234567890`
|
|
118
|
+
- **Email Addresses**: `user@example.com`
|
|
119
|
+
- **Chat IDs**: `chat_id:UUID` (for existing chats)
|
|
120
|
+
|
|
121
|
+
## Policies
|
|
122
|
+
|
|
123
|
+
### DM Policies
|
|
124
|
+
|
|
125
|
+
| Policy | Description |
|
|
126
|
+
|--------|-------------|
|
|
127
|
+
| `open` | Accept DMs from anyone |
|
|
128
|
+
| `pairing` | Accept DMs and remember senders |
|
|
129
|
+
| `allowlist` | Only accept from IMESSAGE_ALLOW_FROM list |
|
|
130
|
+
| `disabled` | Don't accept any DMs |
|
|
131
|
+
|
|
132
|
+
### Group Policies
|
|
133
|
+
|
|
134
|
+
| Policy | Description |
|
|
135
|
+
|--------|-------------|
|
|
136
|
+
| `open` | Respond to anyone in groups |
|
|
137
|
+
| `allowlist` | Only respond to allowed users |
|
|
138
|
+
| `disabled` | Don't respond in groups |
|
|
139
|
+
|
|
140
|
+
## Limitations
|
|
141
|
+
|
|
142
|
+
- **macOS Only**: iMessage doesn't have an official API and only works on macOS
|
|
143
|
+
- **No Official API**: Relies on AppleScript or CLI tools
|
|
144
|
+
- **Permissions**: Requires Full Disk Access and Automation permissions
|
|
145
|
+
- **Rate Limits**: Apple may throttle excessive automation
|
|
146
|
+
|
|
147
|
+
## Development
|
|
148
|
+
|
|
149
|
+
### Building
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
cd typescript && npm run build
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Testing
|
|
156
|
+
|
|
157
|
+
Testing requires a macOS environment with Messages app configured:
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
npm test
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Troubleshooting
|
|
164
|
+
|
|
165
|
+
### "Cannot access Messages app"
|
|
166
|
+
|
|
167
|
+
1. Ensure Full Disk Access is granted
|
|
168
|
+
2. Ensure Automation permissions are granted
|
|
169
|
+
3. Try opening Messages app manually first
|
|
170
|
+
|
|
171
|
+
### "Service not available"
|
|
172
|
+
|
|
173
|
+
1. Check that you're running on macOS
|
|
174
|
+
2. Verify the Messages app is installed and configured
|
|
175
|
+
|
|
176
|
+
### Messages not sending
|
|
177
|
+
|
|
178
|
+
1. Check that iMessage is signed in and working
|
|
179
|
+
2. Verify the recipient has iMessage enabled
|
|
180
|
+
3. Check for rate limiting (try again later)
|
|
181
|
+
|
|
182
|
+
## License
|
|
183
|
+
|
|
184
|
+
MIT
|
package/auto-enable.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Auto-enable check for @elizaos/plugin-imessage.
|
|
2
|
+
//
|
|
3
|
+
// Plugin manifest entry-point — referenced by package.json's
|
|
4
|
+
// `elizaos.plugin.autoEnableModule`. Keep this module light: env reads only,
|
|
5
|
+
// no service init, no transitive imports of the full plugin runtime. The
|
|
6
|
+
// auto-enable engine loads dozens of these per boot.
|
|
7
|
+
import type { PluginAutoEnableContext } from "@elizaos/core";
|
|
8
|
+
|
|
9
|
+
/** Enable when an `imessage` connector block is present and not explicitly disabled. */
|
|
10
|
+
export function shouldEnable(ctx: PluginAutoEnableContext): boolean {
|
|
11
|
+
const c = (ctx.config?.connectors as Record<string, unknown> | undefined)
|
|
12
|
+
?.imessage;
|
|
13
|
+
if (!c || typeof c !== "object") return false;
|
|
14
|
+
const config = c as Record<string, unknown>;
|
|
15
|
+
if (config.enabled === false) return false;
|
|
16
|
+
// The full per-connector field check (chat.db / Messages.app integration)
|
|
17
|
+
// lives in the central engine's isConnectorConfigured. We delegate to a
|
|
18
|
+
// simple "block present + not explicitly disabled" check here; the central
|
|
19
|
+
// engine's stricter check remains as a fallback during migration.
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type http from "node:http";
|
|
2
|
+
import type { RouteHelpers } from "./imessage-routes.js";
|
|
3
|
+
export interface BlueBubblesRouteState {
|
|
4
|
+
runtime?: {
|
|
5
|
+
getService(type: string): unknown;
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
export declare function resolveBlueBubblesWebhookPath(state: BlueBubblesRouteState): string;
|
|
9
|
+
export declare function handleBlueBubblesRoute(req: http.IncomingMessage, res: http.ServerResponse, pathname: string, method: string, state: BlueBubblesRouteState, helpers: RouteHelpers): Promise<boolean>;
|
|
10
|
+
//# sourceMappingURL=bluebubbles-routes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bluebubbles-routes.d.ts","sourceRoot":"","sources":["../../src/api/bluebubbles-routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AA0BzD,MAAM,WAAW,qBAAqB;IACpC,OAAO,CAAC,EAAE;QACR,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;KACnC,CAAC;CACH;AAUD,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,qBAAqB,GAAG,MAAM,CAOlF;AAED,wBAAsB,sBAAsB,CAC1C,GAAG,EAAE,IAAI,CAAC,eAAe,EACzB,GAAG,EAAE,IAAI,CAAC,cAAc,EACxB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,qBAAqB,EAC5B,OAAO,EAAE,YAAY,GACpB,OAAO,CAAC,OAAO,CAAC,CAkJlB"}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
const BLUEBUBBLES_SERVICE_NAME = "bluebubbles";
|
|
2
|
+
const DEFAULT_WEBHOOK_PATH = "/webhooks/bluebubbles";
|
|
3
|
+
const MAX_BODY_BYTES = 1_048_576;
|
|
4
|
+
function resolveService(state) {
|
|
5
|
+
if (!state.runtime) {
|
|
6
|
+
return null;
|
|
7
|
+
}
|
|
8
|
+
const raw = state.runtime.getService(BLUEBUBBLES_SERVICE_NAME);
|
|
9
|
+
return raw ?? null;
|
|
10
|
+
}
|
|
11
|
+
export function resolveBlueBubblesWebhookPath(state) {
|
|
12
|
+
const service = resolveService(state);
|
|
13
|
+
const configuredPath = service?.getWebhookPath();
|
|
14
|
+
if (typeof configuredPath === "string" && configuredPath.trim().length > 0) {
|
|
15
|
+
return configuredPath.trim();
|
|
16
|
+
}
|
|
17
|
+
return DEFAULT_WEBHOOK_PATH;
|
|
18
|
+
}
|
|
19
|
+
export async function handleBlueBubblesRoute(req, res, pathname, method, state, helpers) {
|
|
20
|
+
const webhookPath = resolveBlueBubblesWebhookPath(state);
|
|
21
|
+
const isWebhookPath = pathname === webhookPath;
|
|
22
|
+
const isApiPath = pathname.startsWith("/api/bluebubbles");
|
|
23
|
+
if (!isWebhookPath && !isApiPath) {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
if (method === "GET" && pathname === "/api/bluebubbles/status") {
|
|
27
|
+
const service = resolveService(state);
|
|
28
|
+
if (!service) {
|
|
29
|
+
helpers.json(res, {
|
|
30
|
+
available: false,
|
|
31
|
+
connected: false,
|
|
32
|
+
webhookPath,
|
|
33
|
+
reason: "bluebubbles service not registered",
|
|
34
|
+
});
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
helpers.json(res, {
|
|
38
|
+
available: true,
|
|
39
|
+
connected: service.isConnected(),
|
|
40
|
+
webhookPath,
|
|
41
|
+
});
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
if (method === "GET" && pathname === "/api/bluebubbles/chats") {
|
|
45
|
+
const service = resolveService(state);
|
|
46
|
+
if (!service) {
|
|
47
|
+
helpers.error(res, "bluebubbles service not registered", 503);
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
const client = service.getClient();
|
|
51
|
+
if (!client) {
|
|
52
|
+
helpers.error(res, "bluebubbles client not available", 503);
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
const url = new URL(req.url ?? pathname, "http://localhost");
|
|
56
|
+
const limit = Math.min(Math.max(1, Number.parseInt(url.searchParams.get("limit") ?? "100", 10) || 100), 500);
|
|
57
|
+
const offset = Math.max(0, Number.parseInt(url.searchParams.get("offset") ?? "0", 10) || 0);
|
|
58
|
+
try {
|
|
59
|
+
const chats = await client.listChats(limit, offset);
|
|
60
|
+
helpers.json(res, { chats, count: chats.length, limit, offset });
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
helpers.error(res, `failed to read bluebubbles chats: ${error instanceof Error ? error.message : String(error)}`, 500);
|
|
64
|
+
}
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
if (method === "GET" && pathname === "/api/bluebubbles/messages") {
|
|
68
|
+
const service = resolveService(state);
|
|
69
|
+
if (!service) {
|
|
70
|
+
helpers.error(res, "bluebubbles service not registered", 503);
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
const client = service.getClient();
|
|
74
|
+
if (!client) {
|
|
75
|
+
helpers.error(res, "bluebubbles client not available", 503);
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
const url = new URL(req.url ?? pathname, "http://localhost");
|
|
79
|
+
const chatGuid = (url.searchParams.get("chatGuid") ?? "").trim();
|
|
80
|
+
if (!chatGuid) {
|
|
81
|
+
helpers.error(res, "chatGuid query parameter is required", 400);
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
const limit = Math.min(Math.max(1, Number.parseInt(url.searchParams.get("limit") ?? "50", 10) || 50), 500);
|
|
85
|
+
const offset = Math.max(0, Number.parseInt(url.searchParams.get("offset") ?? "0", 10) || 0);
|
|
86
|
+
try {
|
|
87
|
+
const messages = await client.getMessages(chatGuid, limit, offset);
|
|
88
|
+
helpers.json(res, {
|
|
89
|
+
chatGuid,
|
|
90
|
+
messages,
|
|
91
|
+
count: messages.length,
|
|
92
|
+
limit,
|
|
93
|
+
offset,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
helpers.error(res, `failed to read bluebubbles messages: ${error instanceof Error ? error.message : String(error)}`, 500);
|
|
98
|
+
}
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
if (method === "POST" && isWebhookPath) {
|
|
102
|
+
const service = resolveService(state);
|
|
103
|
+
if (!service) {
|
|
104
|
+
helpers.error(res, "bluebubbles service not registered", 503);
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
const payload = await helpers.readJsonBody(req, res, {
|
|
108
|
+
maxBytes: MAX_BODY_BYTES,
|
|
109
|
+
});
|
|
110
|
+
if (!payload) {
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
if (typeof payload.type !== "string" ||
|
|
114
|
+
!payload.type.trim() ||
|
|
115
|
+
typeof payload.data !== "object" ||
|
|
116
|
+
payload.data === null ||
|
|
117
|
+
Array.isArray(payload.data)) {
|
|
118
|
+
helpers.error(res, "invalid BlueBubbles webhook payload", 400);
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
try {
|
|
122
|
+
await service.handleWebhook(payload);
|
|
123
|
+
helpers.json(res, { ok: true });
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
helpers.error(res, `failed to handle bluebubbles webhook: ${error instanceof Error ? error.message : String(error)}`, 500);
|
|
127
|
+
}
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
//# sourceMappingURL=bluebubbles-routes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bluebubbles-routes.js","sourceRoot":"","sources":["../../src/api/bluebubbles-routes.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* iMessage connector HTTP routes.
|
|
3
|
+
*
|
|
4
|
+
* Exposes the @elizaos/plugin-imessage service state through Eliza's
|
|
5
|
+
* HTTP API so downstream UI layers (the dashboard, a future CLI, third-
|
|
6
|
+
* party integrations) can read and write against the macOS Messages.app
|
|
7
|
+
* world without each client having to go straight to chat.db or to
|
|
8
|
+
* AppleScript.
|
|
9
|
+
*
|
|
10
|
+
* Routes served (all under `/api/imessage`):
|
|
11
|
+
*
|
|
12
|
+
* GET /api/imessage/status service health + cursor + counts
|
|
13
|
+
* GET /api/imessage/messages recent messages from chat.db
|
|
14
|
+
* GET /api/imessage/chats list of chats (DMs + groups)
|
|
15
|
+
* GET /api/imessage/contacts every contact with full detail
|
|
16
|
+
* POST /api/imessage/contacts create a new contact
|
|
17
|
+
* PATCH /api/imessage/contacts/:id update an existing contact
|
|
18
|
+
* DELETE /api/imessage/contacts/:id delete a contact
|
|
19
|
+
*
|
|
20
|
+
* Each handler pulls the IMessageService instance off the runtime via
|
|
21
|
+
* `runtime.getService("imessage")` and calls the public methods added
|
|
22
|
+
* in the plugin's patched branch. If the service isn't registered (the
|
|
23
|
+
* plugin isn't enabled, Eliza booted before it was loaded, etc.) we
|
|
24
|
+
* return 503 with a structured reason so the UI can render an
|
|
25
|
+
* informative empty state.
|
|
26
|
+
*
|
|
27
|
+
* Write endpoints (POST/PATCH/DELETE on contacts) touch the real macOS
|
|
28
|
+
* Contacts.app and will trigger a one-time TCC permission prompt the
|
|
29
|
+
* first time they fire. That prompt targets whichever process ran the
|
|
30
|
+
* osascript child; in Eliza's case that's `bun`/`node`. Once granted
|
|
31
|
+
* the permission is persistent across restarts.
|
|
32
|
+
*/
|
|
33
|
+
import type http from "node:http";
|
|
34
|
+
/**
|
|
35
|
+
* Minimal structural copy of the agent's RouteHelpers / RouteRequestMeta
|
|
36
|
+
* surface, inlined here so this file can live inside @elizaos/plugin-imessage
|
|
37
|
+
* without taking a build-time dependency on @elizaos/agent.
|
|
38
|
+
*/
|
|
39
|
+
export interface ReadJsonBodyOptions {
|
|
40
|
+
maxBytes?: number;
|
|
41
|
+
requireObject?: boolean;
|
|
42
|
+
readErrorStatus?: number;
|
|
43
|
+
nonObjectStatus?: number;
|
|
44
|
+
parseErrorStatus?: number;
|
|
45
|
+
readErrorMessage?: string;
|
|
46
|
+
nonObjectMessage?: string;
|
|
47
|
+
parseErrorMessage?: string;
|
|
48
|
+
}
|
|
49
|
+
export interface RouteRequestMeta {
|
|
50
|
+
req: http.IncomingMessage;
|
|
51
|
+
res: http.ServerResponse;
|
|
52
|
+
method: string;
|
|
53
|
+
pathname: string;
|
|
54
|
+
}
|
|
55
|
+
export interface RouteHelpers {
|
|
56
|
+
json: (res: http.ServerResponse, data: unknown, status?: number) => void;
|
|
57
|
+
error: (res: http.ServerResponse, message: string, status?: number) => void;
|
|
58
|
+
readJsonBody: <T extends object>(req: http.IncomingMessage, res: http.ServerResponse, options?: ReadJsonBodyOptions) => Promise<T | null>;
|
|
59
|
+
}
|
|
60
|
+
export interface IMessageRouteState {
|
|
61
|
+
/**
|
|
62
|
+
* The running AgentRuntime (or a test stub). Typed loosely as
|
|
63
|
+
* `unknown` so this route file doesn't re-declare core's stricter
|
|
64
|
+
* generic getService signature — we narrow the result inside
|
|
65
|
+
* resolveService via an unknown cast. Optional so route files
|
|
66
|
+
* tolerate the boot window where the runtime hasn't finished
|
|
67
|
+
* registering services yet.
|
|
68
|
+
*/
|
|
69
|
+
runtime?: {
|
|
70
|
+
getService(type: string): unknown;
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Route handler entry point. Returns `true` when a route matched and
|
|
75
|
+
* the response has been written; returns `false` so the caller can
|
|
76
|
+
* continue to other route handlers (mirrors the handleWhatsAppRoute /
|
|
77
|
+
* handleWalletRoute pattern used elsewhere in this codebase).
|
|
78
|
+
*/
|
|
79
|
+
export declare function handleIMessageRoute(req: http.IncomingMessage, res: http.ServerResponse, pathname: string, method: string, state: IMessageRouteState, helpers: RouteHelpers): Promise<boolean>;
|
|
80
|
+
//# sourceMappingURL=imessage-routes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"imessage-routes.d.ts","sourceRoot":"","sources":["../../src/api/imessage-routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAElC;;;;GAIG;AACH,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,IAAI,CAAC,eAAe,CAAC;IAC1B,GAAG,EAAE,IAAI,CAAC,cAAc,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,CAAC,GAAG,EAAE,IAAI,CAAC,cAAc,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IACzE,KAAK,EAAE,CAAC,GAAG,EAAE,IAAI,CAAC,cAAc,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5E,YAAY,EAAE,CAAC,CAAC,SAAS,MAAM,EAC7B,GAAG,EAAE,IAAI,CAAC,eAAe,EACzB,GAAG,EAAE,IAAI,CAAC,cAAc,EACxB,OAAO,CAAC,EAAE,mBAAmB,KAC1B,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;CACxB;AA2ED,MAAM,WAAW,kBAAkB;IACjC;;;;;;;OAOG;IACH,OAAO,CAAC,EAAE;QACR,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;KACnC,CAAC;CACH;AAyBD;;;;;GAKG;AACH,wBAAsB,mBAAmB,CACvC,GAAG,EAAE,IAAI,CAAC,eAAe,EACzB,GAAG,EAAE,IAAI,CAAC,cAAc,EACxB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,kBAAkB,EACzB,OAAO,EAAE,YAAY,GACpB,OAAO,CAAC,OAAO,CAAC,CA0NlB"}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* iMessage connector HTTP routes.
|
|
3
|
+
*
|
|
4
|
+
* Exposes the @elizaos/plugin-imessage service state through Eliza's
|
|
5
|
+
* HTTP API so downstream UI layers (the dashboard, a future CLI, third-
|
|
6
|
+
* party integrations) can read and write against the macOS Messages.app
|
|
7
|
+
* world without each client having to go straight to chat.db or to
|
|
8
|
+
* AppleScript.
|
|
9
|
+
*
|
|
10
|
+
* Routes served (all under `/api/imessage`):
|
|
11
|
+
*
|
|
12
|
+
* GET /api/imessage/status service health + cursor + counts
|
|
13
|
+
* GET /api/imessage/messages recent messages from chat.db
|
|
14
|
+
* GET /api/imessage/chats list of chats (DMs + groups)
|
|
15
|
+
* GET /api/imessage/contacts every contact with full detail
|
|
16
|
+
* POST /api/imessage/contacts create a new contact
|
|
17
|
+
* PATCH /api/imessage/contacts/:id update an existing contact
|
|
18
|
+
* DELETE /api/imessage/contacts/:id delete a contact
|
|
19
|
+
*
|
|
20
|
+
* Each handler pulls the IMessageService instance off the runtime via
|
|
21
|
+
* `runtime.getService("imessage")` and calls the public methods added
|
|
22
|
+
* in the plugin's patched branch. If the service isn't registered (the
|
|
23
|
+
* plugin isn't enabled, Eliza booted before it was loaded, etc.) we
|
|
24
|
+
* return 503 with a structured reason so the UI can render an
|
|
25
|
+
* informative empty state.
|
|
26
|
+
*
|
|
27
|
+
* Write endpoints (POST/PATCH/DELETE on contacts) touch the real macOS
|
|
28
|
+
* Contacts.app and will trigger a one-time TCC permission prompt the
|
|
29
|
+
* first time they fire. That prompt targets whichever process ran the
|
|
30
|
+
* osascript child; in Eliza's case that's `bun`/`node`. Once granted
|
|
31
|
+
* the permission is persistent across restarts.
|
|
32
|
+
*/
|
|
33
|
+
const IMESSAGE_SERVICE_NAME = "imessage";
|
|
34
|
+
const MAX_BODY_BYTES = 256 * 1024; // Contacts payloads are tiny; cap aggressively.
|
|
35
|
+
function resolveService(state) {
|
|
36
|
+
if (!state.runtime)
|
|
37
|
+
return null;
|
|
38
|
+
const raw = state.runtime.getService(IMESSAGE_SERVICE_NAME);
|
|
39
|
+
return raw ?? null;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Extract the `:id` segment from a contact path like
|
|
43
|
+
* `/api/imessage/contacts/ABCD-EFGH-...`. Returns null if the path
|
|
44
|
+
* doesn't match. The id is URL-decoded since Contacts.app ids are
|
|
45
|
+
* GUID-style and safe, but callers could URL-encode for paranoia.
|
|
46
|
+
*/
|
|
47
|
+
function parseContactId(pathname) {
|
|
48
|
+
const prefix = "/api/imessage/contacts/";
|
|
49
|
+
if (!pathname.startsWith(prefix))
|
|
50
|
+
return null;
|
|
51
|
+
const rest = pathname.slice(prefix.length);
|
|
52
|
+
if (!rest)
|
|
53
|
+
return null;
|
|
54
|
+
return decodeURIComponent(rest);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Route handler entry point. Returns `true` when a route matched and
|
|
58
|
+
* the response has been written; returns `false` so the caller can
|
|
59
|
+
* continue to other route handlers (mirrors the handleWhatsAppRoute /
|
|
60
|
+
* handleWalletRoute pattern used elsewhere in this codebase).
|
|
61
|
+
*/
|
|
62
|
+
export async function handleIMessageRoute(req, res, pathname, method, state, helpers) {
|
|
63
|
+
if (!pathname.startsWith("/api/imessage"))
|
|
64
|
+
return false;
|
|
65
|
+
const meta = { req, res, method, pathname };
|
|
66
|
+
// ── GET /api/imessage/status ──────────────────────────────────────
|
|
67
|
+
if (method === "GET" && pathname === "/api/imessage/status") {
|
|
68
|
+
const service = resolveService(state);
|
|
69
|
+
if (!service) {
|
|
70
|
+
helpers.json(res, {
|
|
71
|
+
available: false,
|
|
72
|
+
reason: "imessage service not registered",
|
|
73
|
+
});
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
helpers.json(res, {
|
|
77
|
+
available: true,
|
|
78
|
+
connected: service.isConnected(),
|
|
79
|
+
...(service.getStatus?.() ?? {}),
|
|
80
|
+
});
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
// ── GET /api/imessage/messages?limit=N ────────────────────────────
|
|
84
|
+
if (method === "GET" && pathname === "/api/imessage/messages") {
|
|
85
|
+
const service = resolveService(state);
|
|
86
|
+
if (!service) {
|
|
87
|
+
helpers.error(res, "imessage service not registered", 503);
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
const url = new URL(req.url ?? pathname, "http://localhost");
|
|
91
|
+
const limitParam = url.searchParams.get("limit");
|
|
92
|
+
const limit = Math.min(Math.max(1, Number.parseInt(limitParam ?? "50", 10) || 50), 500);
|
|
93
|
+
try {
|
|
94
|
+
const messages = await service.getRecentMessages(limit);
|
|
95
|
+
helpers.json(res, { messages, count: messages.length });
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
helpers.error(res, `failed to read messages: ${error instanceof Error ? error.message : String(error)}`, 500);
|
|
99
|
+
}
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
// ── GET /api/imessage/chats ───────────────────────────────────────
|
|
103
|
+
if (method === "GET" && pathname === "/api/imessage/chats") {
|
|
104
|
+
const service = resolveService(state);
|
|
105
|
+
if (!service) {
|
|
106
|
+
helpers.error(res, "imessage service not registered", 503);
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
try {
|
|
110
|
+
const chats = await service.getChats();
|
|
111
|
+
helpers.json(res, { chats, count: chats.length });
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
helpers.error(res, `failed to read chats: ${error instanceof Error ? error.message : String(error)}`, 500);
|
|
115
|
+
}
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
// ── GET /api/imessage/contacts ────────────────────────────────────
|
|
119
|
+
if (method === "GET" && pathname === "/api/imessage/contacts") {
|
|
120
|
+
const service = resolveService(state);
|
|
121
|
+
if (!service) {
|
|
122
|
+
helpers.error(res, "imessage service not registered", 503);
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
try {
|
|
126
|
+
const contacts = await service.listAllContacts();
|
|
127
|
+
helpers.json(res, { contacts, count: contacts.length });
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
helpers.error(res, `failed to read contacts: ${error instanceof Error ? error.message : String(error)}`, 500);
|
|
131
|
+
}
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
// ── POST /api/imessage/contacts ───────────────────────────────────
|
|
135
|
+
if (method === "POST" && pathname === "/api/imessage/contacts") {
|
|
136
|
+
const service = resolveService(state);
|
|
137
|
+
if (!service) {
|
|
138
|
+
helpers.error(res, "imessage service not registered", 503);
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
const body = await helpers.readJsonBody(req, res, { maxBytes: MAX_BODY_BYTES });
|
|
142
|
+
if (!body)
|
|
143
|
+
return true; // helpers.readJsonBody has already sent the error.
|
|
144
|
+
if (!body.firstName && !body.lastName && !body.phones?.length && !body.emails?.length) {
|
|
145
|
+
helpers.error(res, "at least one of firstName, lastName, phones, or emails is required", 400);
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
try {
|
|
149
|
+
const id = await service.addContact({
|
|
150
|
+
firstName: body.firstName,
|
|
151
|
+
lastName: body.lastName,
|
|
152
|
+
phones: body.phones,
|
|
153
|
+
emails: body.emails,
|
|
154
|
+
});
|
|
155
|
+
if (!id) {
|
|
156
|
+
helpers.error(res, "contact creation failed — see server logs. Common cause: Contacts write permission not granted yet.", 500);
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
helpers.json(res, { id, created: true }, 201);
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
helpers.error(res, `addContact threw: ${error instanceof Error ? error.message : String(error)}`, 500);
|
|
163
|
+
}
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
// ── PATCH /api/imessage/contacts/:id ──────────────────────────────
|
|
167
|
+
if (method === "PATCH" && pathname.startsWith("/api/imessage/contacts/")) {
|
|
168
|
+
const id = parseContactId(pathname);
|
|
169
|
+
if (!id) {
|
|
170
|
+
helpers.error(res, "contact id is required in the path", 400);
|
|
171
|
+
return true;
|
|
172
|
+
}
|
|
173
|
+
const service = resolveService(state);
|
|
174
|
+
if (!service) {
|
|
175
|
+
helpers.error(res, "imessage service not registered", 503);
|
|
176
|
+
return true;
|
|
177
|
+
}
|
|
178
|
+
const body = await helpers.readJsonBody(req, res, { maxBytes: MAX_BODY_BYTES });
|
|
179
|
+
if (!body)
|
|
180
|
+
return true;
|
|
181
|
+
try {
|
|
182
|
+
const ok = await service.updateContact(id, {
|
|
183
|
+
firstName: body.firstName,
|
|
184
|
+
lastName: body.lastName,
|
|
185
|
+
addPhones: body.addPhones,
|
|
186
|
+
removePhones: body.removePhones,
|
|
187
|
+
addEmails: body.addEmails,
|
|
188
|
+
removeEmails: body.removeEmails,
|
|
189
|
+
});
|
|
190
|
+
if (!ok) {
|
|
191
|
+
helpers.error(res, "contact update failed — see server logs. Contact may not exist, or write permission may be denied.", 500);
|
|
192
|
+
return true;
|
|
193
|
+
}
|
|
194
|
+
helpers.json(res, { id, updated: true });
|
|
195
|
+
}
|
|
196
|
+
catch (error) {
|
|
197
|
+
helpers.error(res, `updateContact threw: ${error instanceof Error ? error.message : String(error)}`, 500);
|
|
198
|
+
}
|
|
199
|
+
return true;
|
|
200
|
+
}
|
|
201
|
+
// ── DELETE /api/imessage/contacts/:id ─────────────────────────────
|
|
202
|
+
if (method === "DELETE" && pathname.startsWith("/api/imessage/contacts/")) {
|
|
203
|
+
const id = parseContactId(pathname);
|
|
204
|
+
if (!id) {
|
|
205
|
+
helpers.error(res, "contact id is required in the path", 400);
|
|
206
|
+
return true;
|
|
207
|
+
}
|
|
208
|
+
const service = resolveService(state);
|
|
209
|
+
if (!service) {
|
|
210
|
+
helpers.error(res, "imessage service not registered", 503);
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
try {
|
|
214
|
+
const ok = await service.deleteContact(id);
|
|
215
|
+
if (!ok) {
|
|
216
|
+
helpers.error(res, "contact delete failed — see server logs. Contact may not exist, or write permission may be denied.", 500);
|
|
217
|
+
return true;
|
|
218
|
+
}
|
|
219
|
+
helpers.json(res, { id, deleted: true });
|
|
220
|
+
}
|
|
221
|
+
catch (error) {
|
|
222
|
+
helpers.error(res, `deleteContact threw: ${error instanceof Error ? error.message : String(error)}`, 500);
|
|
223
|
+
}
|
|
224
|
+
return true;
|
|
225
|
+
}
|
|
226
|
+
// Path starts with /api/imessage but none of the above matched.
|
|
227
|
+
void meta; // reserved for future telemetry spans
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
//# sourceMappingURL=imessage-routes.js.map
|