@elizaos/plugin-imessage 2.0.0-alpha.8 → 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.
Files changed (59) hide show
  1. package/README.md +184 -0
  2. package/auto-enable.ts +21 -0
  3. package/dist/api/bluebubbles-routes.d.ts +10 -0
  4. package/dist/api/bluebubbles-routes.d.ts.map +1 -0
  5. package/dist/api/bluebubbles-routes.js +132 -0
  6. package/dist/api/bluebubbles-routes.js.map +1 -0
  7. package/dist/api/imessage-routes.d.ts +80 -0
  8. package/dist/api/imessage-routes.d.ts.map +1 -0
  9. package/dist/api/imessage-routes.js +230 -0
  10. package/dist/api/imessage-routes.js.map +1 -0
  11. package/dist/chatdb-reader.d.ts +240 -0
  12. package/dist/chatdb-reader.d.ts.map +1 -0
  13. package/dist/chatdb-reader.js +647 -0
  14. package/dist/chatdb-reader.js.map +1 -0
  15. package/dist/connector-account-provider.d.ts +18 -0
  16. package/dist/connector-account-provider.d.ts.map +1 -0
  17. package/dist/connector-account-provider.js +83 -0
  18. package/dist/connector-account-provider.js.map +1 -0
  19. package/dist/contacts-reader.d.ts +147 -0
  20. package/dist/contacts-reader.d.ts.map +1 -0
  21. package/dist/contacts-reader.js +481 -0
  22. package/dist/contacts-reader.js.map +1 -0
  23. package/dist/index.d.ts +11 -11
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +48 -16
  26. package/dist/index.js.map +1 -1
  27. package/dist/providers/index.d.ts +1 -2
  28. package/dist/providers/index.d.ts.map +1 -1
  29. package/dist/providers/index.js +2 -2
  30. package/dist/providers/index.js.map +1 -1
  31. package/dist/rpc.d.ts +16 -4
  32. package/dist/rpc.d.ts.map +1 -1
  33. package/dist/rpc.js +103 -11
  34. package/dist/rpc.js.map +1 -1
  35. package/dist/service.d.ts +203 -8
  36. package/dist/service.d.ts.map +1 -1
  37. package/dist/service.js +1368 -107
  38. package/dist/service.js.map +1 -1
  39. package/dist/setup-routes.d.ts +38 -0
  40. package/dist/setup-routes.d.ts.map +1 -0
  41. package/dist/setup-routes.js +322 -0
  42. package/dist/setup-routes.js.map +1 -0
  43. package/dist/types.d.ts +29 -0
  44. package/dist/types.d.ts.map +1 -1
  45. package/dist/types.js +2 -0
  46. package/dist/types.js.map +1 -1
  47. package/package.json +19 -10
  48. package/dist/actions/index.d.ts +0 -5
  49. package/dist/actions/index.d.ts.map +0 -1
  50. package/dist/actions/index.js +0 -5
  51. package/dist/actions/index.js.map +0 -1
  52. package/dist/actions/sendMessage.d.ts +0 -6
  53. package/dist/actions/sendMessage.d.ts.map +0 -1
  54. package/dist/actions/sendMessage.js +0 -161
  55. package/dist/actions/sendMessage.js.map +0 -1
  56. package/dist/providers/chatContext.d.ts +0 -6
  57. package/dist/providers/chatContext.d.ts.map +0 -1
  58. package/dist/providers/chatContext.js +0 -60
  59. 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