@elizaos/plugin-imessage 2.0.0-alpha.3

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/src/types.ts ADDED
@@ -0,0 +1,291 @@
1
+ /**
2
+ * Type definitions for the iMessage plugin.
3
+ */
4
+
5
+ import type { Service } from "@elizaos/core";
6
+
7
+ /** Maximum message length for iMessage */
8
+ export const MAX_IMESSAGE_MESSAGE_LENGTH = 4000;
9
+
10
+ /** Default poll interval in ms */
11
+ export const DEFAULT_POLL_INTERVAL_MS = 5000;
12
+
13
+ /** iMessage service name constant */
14
+ export const IMESSAGE_SERVICE_NAME = "imessage";
15
+
16
+ /**
17
+ * Event types emitted by the iMessage plugin
18
+ */
19
+ export const IMessageEventTypes = {
20
+ MESSAGE_RECEIVED: "IMESSAGE_MESSAGE_RECEIVED",
21
+ MESSAGE_SENT: "IMESSAGE_MESSAGE_SENT",
22
+ CONNECTION_READY: "IMESSAGE_CONNECTION_READY",
23
+ ERROR: "IMESSAGE_ERROR",
24
+ } as const;
25
+
26
+ export type IMessageEventType =
27
+ (typeof IMessageEventTypes)[keyof typeof IMessageEventTypes];
28
+
29
+ /**
30
+ * iMessage chat types
31
+ */
32
+ export type IMessageChatType = "direct" | "group";
33
+
34
+ /**
35
+ * Configuration settings for the iMessage plugin
36
+ */
37
+ export interface IMessageSettings {
38
+ /** Path to iMessage CLI tool */
39
+ cliPath: string;
40
+ /** Path to iMessage database */
41
+ dbPath?: string;
42
+ /** Polling interval in ms */
43
+ pollIntervalMs: number;
44
+ /** DM policy */
45
+ dmPolicy: "open" | "pairing" | "allowlist" | "disabled";
46
+ /** Group policy */
47
+ groupPolicy: "open" | "allowlist" | "disabled";
48
+ /** Handles/phone numbers for allowlist */
49
+ allowFrom: string[];
50
+ /** Enable/disable the plugin */
51
+ enabled: boolean;
52
+ }
53
+
54
+ /**
55
+ * iMessage contact
56
+ */
57
+ export interface IMessageContact {
58
+ /** Handle (phone number or email) */
59
+ handle: string;
60
+ /** Display name */
61
+ displayName?: string;
62
+ /** Is this a phone number? */
63
+ isPhoneNumber: boolean;
64
+ }
65
+
66
+ /**
67
+ * iMessage chat
68
+ */
69
+ export interface IMessageChat {
70
+ /** Chat ID */
71
+ chatId: string;
72
+ /** Chat type */
73
+ chatType: IMessageChatType;
74
+ /** Display name */
75
+ displayName?: string;
76
+ /** Participants */
77
+ participants: IMessageContact[];
78
+ }
79
+
80
+ /**
81
+ * iMessage message
82
+ */
83
+ export interface IMessageMessage {
84
+ /** Message ID (ROWID) */
85
+ id: string;
86
+ /** Message text */
87
+ text: string;
88
+ /** Sender handle */
89
+ handle: string;
90
+ /** Chat ID */
91
+ chatId: string;
92
+ /** Timestamp */
93
+ timestamp: number;
94
+ /** Is from me */
95
+ isFromMe: boolean;
96
+ /** Has attachments */
97
+ hasAttachments: boolean;
98
+ /** Attachment paths */
99
+ attachmentPaths?: string[];
100
+ }
101
+
102
+ /**
103
+ * Options for sending a message
104
+ */
105
+ export interface IMessageSendOptions {
106
+ /** Media URL or path to attach */
107
+ mediaUrl?: string;
108
+ /** Max bytes for media */
109
+ maxBytes?: number;
110
+ }
111
+
112
+ /**
113
+ * Result from sending a message
114
+ */
115
+ export interface IMessageSendResult {
116
+ success: boolean;
117
+ messageId?: string;
118
+ chatId?: string;
119
+ error?: string;
120
+ }
121
+
122
+ /**
123
+ * Service interface for iMessage
124
+ */
125
+ export interface IIMessageService extends Service {
126
+ /** Check if the service is connected */
127
+ isConnected(): boolean;
128
+
129
+ /** Check if running on macOS */
130
+ isMacOS(): boolean;
131
+
132
+ /** Send a message */
133
+ sendMessage(
134
+ to: string,
135
+ text: string,
136
+ options?: IMessageSendOptions,
137
+ ): Promise<IMessageSendResult>;
138
+
139
+ /** Get recent messages */
140
+ getRecentMessages(limit?: number): Promise<IMessageMessage[]>;
141
+
142
+ /** Get chats */
143
+ getChats(): Promise<IMessageChat[]>;
144
+ }
145
+
146
+ /**
147
+ * iMessage plugin errors
148
+ */
149
+ export class IMessagePluginError extends Error {
150
+ constructor(
151
+ message: string,
152
+ public readonly code: string,
153
+ public readonly details?: Record<string, unknown>,
154
+ ) {
155
+ super(message);
156
+ this.name = "IMessagePluginError";
157
+ }
158
+ }
159
+
160
+ export class IMessageConfigurationError extends IMessagePluginError {
161
+ constructor(message: string, setting?: string) {
162
+ super(message, "CONFIGURATION_ERROR", setting ? { setting } : undefined);
163
+ this.name = "IMessageConfigurationError";
164
+ }
165
+ }
166
+
167
+ export class IMessageNotSupportedError extends IMessagePluginError {
168
+ constructor(message: string = "iMessage is only supported on macOS") {
169
+ super(message, "NOT_SUPPORTED");
170
+ this.name = "IMessageNotSupportedError";
171
+ }
172
+ }
173
+
174
+ export class IMessageCliError extends IMessagePluginError {
175
+ constructor(message: string, exitCode?: number) {
176
+ super(
177
+ message,
178
+ "CLI_ERROR",
179
+ exitCode !== undefined ? { exitCode } : undefined,
180
+ );
181
+ this.name = "IMessageCliError";
182
+ }
183
+ }
184
+
185
+ // Utility functions
186
+
187
+ /**
188
+ * Check if a string looks like a phone number
189
+ */
190
+ export function isPhoneNumber(input: string): boolean {
191
+ // Remove common formatting
192
+ const cleaned = input.replace(/[\s\-().]/g, "");
193
+ // Check if it's a phone number pattern
194
+ return /^\+?\d{10,15}$/.test(cleaned);
195
+ }
196
+
197
+ /**
198
+ * Check if a string looks like an email
199
+ */
200
+ export function isEmail(input: string): boolean {
201
+ return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(input);
202
+ }
203
+
204
+ /**
205
+ * Check if a string is a valid iMessage target (phone or email)
206
+ */
207
+ export function isValidIMessageTarget(target: string): boolean {
208
+ const trimmed = target.trim();
209
+ return (
210
+ isPhoneNumber(trimmed) || isEmail(trimmed) || trimmed.startsWith("chat_id:")
211
+ );
212
+ }
213
+
214
+ /**
215
+ * Normalize an iMessage target
216
+ */
217
+ export function normalizeIMessageTarget(target: string): string | null {
218
+ const trimmed = target.trim();
219
+ if (!trimmed) {
220
+ return null;
221
+ }
222
+
223
+ // Handle chat_id: prefix
224
+ if (trimmed.startsWith("chat_id:")) {
225
+ return trimmed;
226
+ }
227
+
228
+ // Handle imessage: prefix
229
+ if (trimmed.toLowerCase().startsWith("imessage:")) {
230
+ return trimmed.slice(9).trim();
231
+ }
232
+
233
+ // Return as-is for phone numbers and emails
234
+ return trimmed;
235
+ }
236
+
237
+ /**
238
+ * Format a phone number for iMessage
239
+ */
240
+ export function formatPhoneNumber(phone: string): string {
241
+ // Remove formatting
242
+ let cleaned = phone.replace(/[\s\-().]/g, "");
243
+
244
+ // Add + prefix if missing for international
245
+ if (cleaned.length > 10 && !cleaned.startsWith("+")) {
246
+ cleaned = `+${cleaned}`;
247
+ }
248
+
249
+ return cleaned;
250
+ }
251
+
252
+ /**
253
+ * Split text for iMessage
254
+ */
255
+ export function splitMessageForIMessage(
256
+ text: string,
257
+ maxLength: number = MAX_IMESSAGE_MESSAGE_LENGTH,
258
+ ): string[] {
259
+ if (text.length <= maxLength) {
260
+ return [text];
261
+ }
262
+
263
+ const chunks: string[] = [];
264
+ let remaining = text;
265
+
266
+ while (remaining.length > 0) {
267
+ if (remaining.length <= maxLength) {
268
+ chunks.push(remaining);
269
+ break;
270
+ }
271
+
272
+ let breakPoint = maxLength;
273
+
274
+ // Try newline first
275
+ const newlineIdx = remaining.lastIndexOf("\n", maxLength);
276
+ if (newlineIdx > maxLength / 2) {
277
+ breakPoint = newlineIdx + 1;
278
+ } else {
279
+ // Try space
280
+ const spaceIdx = remaining.lastIndexOf(" ", maxLength);
281
+ if (spaceIdx > maxLength / 2) {
282
+ breakPoint = spaceIdx + 1;
283
+ }
284
+ }
285
+
286
+ chunks.push(remaining.slice(0, breakPoint).trimEnd());
287
+ remaining = remaining.slice(breakPoint).trimStart();
288
+ }
289
+
290
+ return chunks;
291
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "Bundler",
6
+ "lib": ["ES2022"],
7
+ "outDir": "./dist",
8
+ "rootDir": "./src",
9
+ "strict": true,
10
+ "esModuleInterop": true,
11
+ "skipLibCheck": true,
12
+ "forceConsistentCasingInFileNames": true,
13
+ "declaration": true,
14
+ "declarationMap": true,
15
+ "sourceMap": true,
16
+ "resolveJsonModule": true
17
+ },
18
+ "include": ["src/**/*"],
19
+ "exclude": ["node_modules", "dist", "**/*.test.ts"]
20
+ }