@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
|
@@ -0,0 +1,481 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* macOS Contacts reader for @elizaos/plugin-imessage.
|
|
3
|
+
*
|
|
4
|
+
* Incoming iMessages arrive tagged with a raw handle — a phone number in
|
|
5
|
+
* E.164 form (`+15551234567`) or an email address. Raw handles are ugly
|
|
6
|
+
* to read and make the agent's replies feel impersonal. This module
|
|
7
|
+
* resolves each handle to the real display name from the user's Apple
|
|
8
|
+
* Contacts so the agent sees "Mom" or "Alex Chen" instead of a string
|
|
9
|
+
* of digits.
|
|
10
|
+
*
|
|
11
|
+
* ---
|
|
12
|
+
*
|
|
13
|
+
* Backend: **AppleScript against Contacts.app**. Unlike Messages.app
|
|
14
|
+
* (whose scripting dictionary does not expose the message table), the
|
|
15
|
+
* Contacts app ships with a complete and officially-supported AppleScript
|
|
16
|
+
* vocabulary covering `person`, `phone`, and `email` classes. Reading
|
|
17
|
+
* contacts this way is the Apple-blessed path and does not require Full
|
|
18
|
+
* Disk Access — only the macOS "Contacts" TCC permission, which the OS
|
|
19
|
+
* prompts for on first use.
|
|
20
|
+
*
|
|
21
|
+
* The reader runs once on service start and caches the result. Contacts
|
|
22
|
+
* rarely change mid-session, so a periodic refresh is overkill for v1.
|
|
23
|
+
* Callers can force a reload by constructing a fresh reader instance.
|
|
24
|
+
*
|
|
25
|
+
* Graceful degradation: if Contacts is not authorized, or returns no
|
|
26
|
+
* rows, or AppleScript fails for any other reason, the reader returns
|
|
27
|
+
* an empty map. The service treats that as "handles remain anonymous"
|
|
28
|
+
* and proceeds normally — no crash, no hard failure.
|
|
29
|
+
*/
|
|
30
|
+
import { execFile } from "node:child_process";
|
|
31
|
+
import { promisify } from "node:util";
|
|
32
|
+
import { logger } from "@elizaos/core";
|
|
33
|
+
const execFileAsync = promisify(execFile);
|
|
34
|
+
const DEFAULT_CONTACTS_SCRIPT_TIMEOUT_MS = 5_000;
|
|
35
|
+
/**
|
|
36
|
+
* Normalize a handle to the canonical form used as a key in the
|
|
37
|
+
* ContactsMap. Strips whitespace, parentheses, hyphens, and dots from
|
|
38
|
+
* phone numbers and lowercases emails. Leaves a leading `+` in place.
|
|
39
|
+
*/
|
|
40
|
+
export function normalizeContactHandle(raw) {
|
|
41
|
+
const trimmed = raw.trim();
|
|
42
|
+
if (!trimmed)
|
|
43
|
+
return "";
|
|
44
|
+
// Email: lowercase
|
|
45
|
+
if (trimmed.includes("@")) {
|
|
46
|
+
return trimmed.toLowerCase();
|
|
47
|
+
}
|
|
48
|
+
// Phone: strip formatting characters, preserve leading +
|
|
49
|
+
const hasPlus = trimmed.startsWith("+");
|
|
50
|
+
const digitsOnly = trimmed.replace(/[^\d]/g, "");
|
|
51
|
+
return hasPlus ? `+${digitsOnly}` : digitsOnly;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* AppleScript that asks Contacts.app to dump every person's name and
|
|
55
|
+
* every phone/email handle they own, one row per handle, tab-delimited.
|
|
56
|
+
*
|
|
57
|
+
* Format per line: `<kind>\t<handle>\t<name>` where `<kind>` is
|
|
58
|
+
* `phone` or `email`. Lines with empty values are skipped by the parser.
|
|
59
|
+
*/
|
|
60
|
+
const CONTACTS_DUMP_SCRIPT = `
|
|
61
|
+
tell application "Contacts"
|
|
62
|
+
launch
|
|
63
|
+
set AppleScript's text item delimiters to tab
|
|
64
|
+
set outputLines to {}
|
|
65
|
+
repeat with p in people
|
|
66
|
+
set personName to name of p
|
|
67
|
+
if personName is missing value then set personName to ""
|
|
68
|
+
repeat with ph in phones of p
|
|
69
|
+
set phoneValue to value of ph
|
|
70
|
+
if phoneValue is not missing value then
|
|
71
|
+
set end of outputLines to "phone" & tab & phoneValue & tab & personName
|
|
72
|
+
end if
|
|
73
|
+
end repeat
|
|
74
|
+
repeat with em in emails of p
|
|
75
|
+
set emailValue to value of em
|
|
76
|
+
if emailValue is not missing value then
|
|
77
|
+
set end of outputLines to "email" & tab & emailValue & tab & personName
|
|
78
|
+
end if
|
|
79
|
+
end repeat
|
|
80
|
+
end repeat
|
|
81
|
+
set AppleScript's text item delimiters to linefeed
|
|
82
|
+
set outputText to outputLines as string
|
|
83
|
+
set AppleScript's text item delimiters to ""
|
|
84
|
+
return outputText
|
|
85
|
+
end tell
|
|
86
|
+
`.trim();
|
|
87
|
+
/**
|
|
88
|
+
* Parse the tab-delimited output of `CONTACTS_DUMP_SCRIPT` into a
|
|
89
|
+
* ContactsMap. Exported so tests can exercise it with fixture strings
|
|
90
|
+
* without needing a live Contacts.app.
|
|
91
|
+
*
|
|
92
|
+
* Input format per line: `kind\thandle\tname`.
|
|
93
|
+
* Empty lines are skipped. Lines with fewer than 3 fields are skipped.
|
|
94
|
+
* Empty handles are skipped. Duplicate handles keep the first entry
|
|
95
|
+
* (AppleScript's iteration order is generally stable).
|
|
96
|
+
*/
|
|
97
|
+
export function parseContactsOutput(raw) {
|
|
98
|
+
const map = new Map();
|
|
99
|
+
if (!raw?.trim())
|
|
100
|
+
return map;
|
|
101
|
+
for (const line of raw.split("\n")) {
|
|
102
|
+
const trimmed = line.trim();
|
|
103
|
+
if (!trimmed)
|
|
104
|
+
continue;
|
|
105
|
+
const fields = trimmed.split("\t");
|
|
106
|
+
if (fields.length < 3)
|
|
107
|
+
continue;
|
|
108
|
+
const [_kind, handle, name] = fields;
|
|
109
|
+
if (!handle || !name)
|
|
110
|
+
continue;
|
|
111
|
+
const normalized = normalizeContactHandle(handle);
|
|
112
|
+
if (!normalized)
|
|
113
|
+
continue;
|
|
114
|
+
if (map.has(normalized))
|
|
115
|
+
continue;
|
|
116
|
+
map.set(normalized, { name: name.trim() });
|
|
117
|
+
}
|
|
118
|
+
return map;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Run the contacts dump AppleScript and return a ContactsMap. Returns
|
|
122
|
+
* an empty map (with a warning log) on any failure — most commonly, the
|
|
123
|
+
* user hasn't authorized Contacts access yet, in which case macOS
|
|
124
|
+
* surfaces a one-time system prompt and this call returns empty until
|
|
125
|
+
* the user accepts on a subsequent run.
|
|
126
|
+
*/
|
|
127
|
+
export async function loadContacts() {
|
|
128
|
+
try {
|
|
129
|
+
const stdout = await runContactsScript(CONTACTS_DUMP_SCRIPT);
|
|
130
|
+
const map = parseContactsOutput(stdout);
|
|
131
|
+
logger.info(`[imessage] Contacts loaded: ${map.size} handle(s) resolved from Contacts.app`);
|
|
132
|
+
return map;
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
136
|
+
if (/not authorized|1743/.test(reason)) {
|
|
137
|
+
logger.warn("[imessage] Contacts access not yet authorized. macOS will prompt " +
|
|
138
|
+
"the user on the next run. Inbound messages will use raw handles " +
|
|
139
|
+
"(phone numbers / emails) until Contacts access is granted.");
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
logger.warn(`[imessage] Failed to load Contacts.app data: ${reason}. ` +
|
|
143
|
+
"Inbound messages will use raw handles instead of names.");
|
|
144
|
+
}
|
|
145
|
+
return new Map();
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Escape a JavaScript string so it can be embedded inside an
|
|
150
|
+
* AppleScript double-quoted string literal. Handles backslashes and
|
|
151
|
+
* quotes — AppleScript doesn't treat newlines specially inside quoted
|
|
152
|
+
* strings, so those pass through as-is.
|
|
153
|
+
*/
|
|
154
|
+
function escapeAppleScriptString(value) {
|
|
155
|
+
return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Whether we've already confirmed Contacts.app is running in this
|
|
159
|
+
* process lifetime. Avoids paying the `open -a` cost on every call.
|
|
160
|
+
*/
|
|
161
|
+
let contactsLaunched = false;
|
|
162
|
+
function getContactsScriptTimeoutMs() {
|
|
163
|
+
const raw = Number(process.env.IMESSAGE_CONTACTS_TIMEOUT_MS);
|
|
164
|
+
return Number.isFinite(raw) && raw > 0 ? raw : DEFAULT_CONTACTS_SCRIPT_TIMEOUT_MS;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Ensure Contacts.app is actually in the running process list before
|
|
168
|
+
* handing an AppleScript to it. Annoyingly, the `launch` verb inside
|
|
169
|
+
* `tell application "Contacts"` is a no-op on modern macOS when the
|
|
170
|
+
* app isn't already running — it doesn't actually start the process,
|
|
171
|
+
* and the subsequent scripting call returns `Application isn't running
|
|
172
|
+
* (-600)`. The reliable fix is to use the shell-level `open` which
|
|
173
|
+
* goes through LaunchServices and spawns the app.
|
|
174
|
+
*
|
|
175
|
+
* `-g` launches in the background so Contacts doesn't steal focus from
|
|
176
|
+
* whatever window the user was just looking at (a plain `open -a`
|
|
177
|
+
* raises the app to the foreground, which interrupts the UI mid-boot).
|
|
178
|
+
* `-j` hides the app entirely on first launch so the user never sees a
|
|
179
|
+
* Contacts window flash across the screen — the AppleScript bridge
|
|
180
|
+
* still works against a hidden instance.
|
|
181
|
+
*
|
|
182
|
+
* Idempotent: after the first successful launch we set a flag and
|
|
183
|
+
* subsequent calls are no-ops. If Contacts.app is force-quit mid-run
|
|
184
|
+
* the next osascript will still throw -600 and the caller's error
|
|
185
|
+
* path will log it; at that point restarting the plugin recovers.
|
|
186
|
+
*/
|
|
187
|
+
async function ensureContactsLaunched() {
|
|
188
|
+
if (contactsLaunched)
|
|
189
|
+
return;
|
|
190
|
+
try {
|
|
191
|
+
await execFileAsync("open", ["-g", "-j", "-a", "Contacts"]);
|
|
192
|
+
// Give LaunchServices a beat to register the process so the next
|
|
193
|
+
// scripting-bridge call finds it. 400ms is empirical: 100ms was
|
|
194
|
+
// flaky, 250ms worked most of the time, 400ms has been reliable.
|
|
195
|
+
await new Promise((r) => setTimeout(r, 400));
|
|
196
|
+
contactsLaunched = true;
|
|
197
|
+
}
|
|
198
|
+
catch {
|
|
199
|
+
// If `open` fails (unlikely — it's a LaunchServices primitive
|
|
200
|
+
// that almost never fails on a healthy Mac), we still try the
|
|
201
|
+
// osascript below. The error from there will be more informative.
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Run an AppleScript against Contacts.app and return its stdout. Shared
|
|
206
|
+
* helper used by every read/write in this file. On failure — which
|
|
207
|
+
* includes both TCC denials and actual script errors — throws an Error
|
|
208
|
+
* with the stderr/message for the caller to classify.
|
|
209
|
+
*/
|
|
210
|
+
async function runContactsScript(script) {
|
|
211
|
+
await ensureContactsLaunched();
|
|
212
|
+
const { stdout } = await execFileAsync("osascript", ["-e", script], {
|
|
213
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
214
|
+
timeout: getContactsScriptTimeoutMs(),
|
|
215
|
+
killSignal: "SIGTERM",
|
|
216
|
+
});
|
|
217
|
+
return stdout;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Parse the tab-delimited output of the full-contacts dump. One row per
|
|
221
|
+
* contact with pipe-separated phone and email lists so the script can
|
|
222
|
+
* return everything in a single round-trip.
|
|
223
|
+
*
|
|
224
|
+
* Row format (tab-delimited):
|
|
225
|
+
* `<id>\t<name>\t<firstName>\t<lastName>\t<phones>\t<emails>`
|
|
226
|
+
* where <phones> is `label1=value1|label2=value2` and same for emails.
|
|
227
|
+
* Empty fields are empty strings, not missing.
|
|
228
|
+
*/
|
|
229
|
+
function parseFullContactsOutput(raw) {
|
|
230
|
+
const out = [];
|
|
231
|
+
if (!raw?.trim())
|
|
232
|
+
return out;
|
|
233
|
+
for (const line of raw.split("\n")) {
|
|
234
|
+
const trimmed = line.trim();
|
|
235
|
+
if (!trimmed)
|
|
236
|
+
continue;
|
|
237
|
+
const fields = trimmed.split("\t");
|
|
238
|
+
if (fields.length < 6)
|
|
239
|
+
continue;
|
|
240
|
+
const [id, name, firstName, lastName, phonesField, emailsField] = fields;
|
|
241
|
+
if (!id)
|
|
242
|
+
continue;
|
|
243
|
+
const parsePairs = (input) => {
|
|
244
|
+
if (!input)
|
|
245
|
+
return [];
|
|
246
|
+
return input
|
|
247
|
+
.split("|")
|
|
248
|
+
.filter(Boolean)
|
|
249
|
+
.map((pair) => {
|
|
250
|
+
const eqIdx = pair.indexOf("=");
|
|
251
|
+
if (eqIdx === -1)
|
|
252
|
+
return { label: null, value: pair };
|
|
253
|
+
return {
|
|
254
|
+
label: pair.slice(0, eqIdx) || null,
|
|
255
|
+
value: pair.slice(eqIdx + 1),
|
|
256
|
+
};
|
|
257
|
+
});
|
|
258
|
+
};
|
|
259
|
+
out.push({
|
|
260
|
+
id,
|
|
261
|
+
name: name || "",
|
|
262
|
+
firstName: firstName || null,
|
|
263
|
+
lastName: lastName || null,
|
|
264
|
+
phones: parsePairs(phonesField),
|
|
265
|
+
emails: parsePairs(emailsField),
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
return out;
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* AppleScript that dumps every contact as a tab-delimited row with
|
|
272
|
+
* pipe-separated phone and email lists. Matches `parseFullContactsOutput`
|
|
273
|
+
* above. Runs in a single round-trip against Contacts.app.
|
|
274
|
+
*/
|
|
275
|
+
const FULL_CONTACTS_DUMP_SCRIPT = `
|
|
276
|
+
tell application "Contacts"
|
|
277
|
+
launch
|
|
278
|
+
set AppleScript's text item delimiters to tab
|
|
279
|
+
set outputLines to {}
|
|
280
|
+
repeat with p in people
|
|
281
|
+
set personId to id of p
|
|
282
|
+
set personName to name of p
|
|
283
|
+
if personName is missing value then set personName to ""
|
|
284
|
+
set personFirst to first name of p
|
|
285
|
+
if personFirst is missing value then set personFirst to ""
|
|
286
|
+
set personLast to last name of p
|
|
287
|
+
if personLast is missing value then set personLast to ""
|
|
288
|
+
|
|
289
|
+
set phoneList to ""
|
|
290
|
+
repeat with ph in phones of p
|
|
291
|
+
set phoneValue to value of ph
|
|
292
|
+
if phoneValue is not missing value then
|
|
293
|
+
set phoneLabel to label of ph
|
|
294
|
+
if phoneLabel is missing value then set phoneLabel to ""
|
|
295
|
+
if phoneList is not "" then set phoneList to phoneList & "|"
|
|
296
|
+
set phoneList to phoneList & phoneLabel & "=" & phoneValue
|
|
297
|
+
end if
|
|
298
|
+
end repeat
|
|
299
|
+
|
|
300
|
+
set emailList to ""
|
|
301
|
+
repeat with em in emails of p
|
|
302
|
+
set emailValue to value of em
|
|
303
|
+
if emailValue is not missing value then
|
|
304
|
+
set emailLabel to label of em
|
|
305
|
+
if emailLabel is missing value then set emailLabel to ""
|
|
306
|
+
if emailList is not "" then set emailList to emailList & "|"
|
|
307
|
+
set emailList to emailList & emailLabel & "=" & emailValue
|
|
308
|
+
end if
|
|
309
|
+
end repeat
|
|
310
|
+
|
|
311
|
+
set end of outputLines to personId & tab & personName & tab & personFirst & tab & personLast & tab & phoneList & tab & emailList
|
|
312
|
+
end repeat
|
|
313
|
+
set AppleScript's text item delimiters to linefeed
|
|
314
|
+
set outputText to outputLines as string
|
|
315
|
+
set AppleScript's text item delimiters to ""
|
|
316
|
+
return outputText
|
|
317
|
+
end tell
|
|
318
|
+
`.trim();
|
|
319
|
+
/**
|
|
320
|
+
* List every contact in the user's address book as a full `FullContact`
|
|
321
|
+
* record. Returns an empty array on any failure (permission denied,
|
|
322
|
+
* script error, etc.) with a warning log.
|
|
323
|
+
*/
|
|
324
|
+
export async function listAllContacts() {
|
|
325
|
+
try {
|
|
326
|
+
const stdout = await runContactsScript(FULL_CONTACTS_DUMP_SCRIPT);
|
|
327
|
+
return parseFullContactsOutput(stdout);
|
|
328
|
+
}
|
|
329
|
+
catch (error) {
|
|
330
|
+
logger.warn(`[imessage] listAllContacts failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
331
|
+
return [];
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Create a new contact in Contacts.app. Returns the new person's id on
|
|
336
|
+
* success, or null on failure (permission denied, validation, etc.).
|
|
337
|
+
*
|
|
338
|
+
* Requires Contacts WRITE permission, which macOS prompts for on the
|
|
339
|
+
* first write call. Read-only sessions never trigger this prompt.
|
|
340
|
+
*/
|
|
341
|
+
export async function addContact(input) {
|
|
342
|
+
const first = escapeAppleScriptString(input.firstName ?? "");
|
|
343
|
+
const last = escapeAppleScriptString(input.lastName ?? "");
|
|
344
|
+
// Build phone + email blocks as separate `make new phone/email` verbs
|
|
345
|
+
// nested inside a `tell newPerson` block. This lets us set the label
|
|
346
|
+
// on each one, which the simpler `{phones: {...}}` property syntax
|
|
347
|
+
// doesn't support.
|
|
348
|
+
const phoneLines = (input.phones ?? [])
|
|
349
|
+
.filter((p) => p.value)
|
|
350
|
+
.map((p) => {
|
|
351
|
+
const value = escapeAppleScriptString(p.value);
|
|
352
|
+
const label = escapeAppleScriptString(p.label ?? "mobile");
|
|
353
|
+
return `make new phone at end of phones with properties {value:"${value}", label:"${label}"}`;
|
|
354
|
+
})
|
|
355
|
+
.join("\n ");
|
|
356
|
+
const emailLines = (input.emails ?? [])
|
|
357
|
+
.filter((e) => e.value)
|
|
358
|
+
.map((e) => {
|
|
359
|
+
const value = escapeAppleScriptString(e.value);
|
|
360
|
+
const label = escapeAppleScriptString(e.label ?? "home");
|
|
361
|
+
return `make new email at end of emails with properties {value:"${value}", label:"${label}"}`;
|
|
362
|
+
})
|
|
363
|
+
.join("\n ");
|
|
364
|
+
const script = `
|
|
365
|
+
tell application "Contacts"
|
|
366
|
+
launch
|
|
367
|
+
set newPerson to make new person with properties {first name:"${first}", last name:"${last}"}
|
|
368
|
+
tell newPerson
|
|
369
|
+
${phoneLines}
|
|
370
|
+
${emailLines}
|
|
371
|
+
end tell
|
|
372
|
+
save
|
|
373
|
+
return id of newPerson
|
|
374
|
+
end tell
|
|
375
|
+
`.trim();
|
|
376
|
+
try {
|
|
377
|
+
const stdout = await runContactsScript(script);
|
|
378
|
+
const id = stdout.trim();
|
|
379
|
+
if (!id)
|
|
380
|
+
return null;
|
|
381
|
+
logger.info(`[imessage] Contact created: ${id}`);
|
|
382
|
+
return id;
|
|
383
|
+
}
|
|
384
|
+
catch (error) {
|
|
385
|
+
logger.warn(`[imessage] addContact failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
386
|
+
return null;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
export async function updateContact(personId, patch) {
|
|
390
|
+
const id = escapeAppleScriptString(personId);
|
|
391
|
+
const fragments = [];
|
|
392
|
+
if (patch.firstName !== undefined) {
|
|
393
|
+
fragments.push(`set first name of thePerson to "${escapeAppleScriptString(patch.firstName)}"`);
|
|
394
|
+
}
|
|
395
|
+
if (patch.lastName !== undefined) {
|
|
396
|
+
fragments.push(`set last name of thePerson to "${escapeAppleScriptString(patch.lastName)}"`);
|
|
397
|
+
}
|
|
398
|
+
for (const phone of patch.addPhones ?? []) {
|
|
399
|
+
if (!phone.value)
|
|
400
|
+
continue;
|
|
401
|
+
fragments.push(`tell thePerson to make new phone at end of phones with properties {value:"${escapeAppleScriptString(phone.value)}", label:"${escapeAppleScriptString(phone.label ?? "mobile")}"}`);
|
|
402
|
+
}
|
|
403
|
+
for (const phoneValue of patch.removePhones ?? []) {
|
|
404
|
+
if (!phoneValue)
|
|
405
|
+
continue;
|
|
406
|
+
fragments.push(`
|
|
407
|
+
tell thePerson
|
|
408
|
+
repeat with ph in phones
|
|
409
|
+
if (value of ph) is "${escapeAppleScriptString(phoneValue)}" then
|
|
410
|
+
delete ph
|
|
411
|
+
exit repeat
|
|
412
|
+
end if
|
|
413
|
+
end repeat
|
|
414
|
+
end tell`);
|
|
415
|
+
}
|
|
416
|
+
for (const email of patch.addEmails ?? []) {
|
|
417
|
+
if (!email.value)
|
|
418
|
+
continue;
|
|
419
|
+
fragments.push(`tell thePerson to make new email at end of emails with properties {value:"${escapeAppleScriptString(email.value)}", label:"${escapeAppleScriptString(email.label ?? "home")}"}`);
|
|
420
|
+
}
|
|
421
|
+
for (const emailValue of patch.removeEmails ?? []) {
|
|
422
|
+
if (!emailValue)
|
|
423
|
+
continue;
|
|
424
|
+
fragments.push(`
|
|
425
|
+
tell thePerson
|
|
426
|
+
repeat with em in emails
|
|
427
|
+
if (value of em) is "${escapeAppleScriptString(emailValue)}" then
|
|
428
|
+
delete em
|
|
429
|
+
exit repeat
|
|
430
|
+
end if
|
|
431
|
+
end repeat
|
|
432
|
+
end tell`);
|
|
433
|
+
}
|
|
434
|
+
if (fragments.length === 0) {
|
|
435
|
+
// Nothing to do — treat as a successful no-op.
|
|
436
|
+
return true;
|
|
437
|
+
}
|
|
438
|
+
const script = `
|
|
439
|
+
tell application "Contacts"
|
|
440
|
+
launch
|
|
441
|
+
set thePerson to person id "${id}"
|
|
442
|
+
${fragments.join("\n ")}
|
|
443
|
+
save
|
|
444
|
+
return "ok"
|
|
445
|
+
end tell
|
|
446
|
+
`.trim();
|
|
447
|
+
try {
|
|
448
|
+
await runContactsScript(script);
|
|
449
|
+
logger.info(`[imessage] Contact updated: ${personId}`);
|
|
450
|
+
return true;
|
|
451
|
+
}
|
|
452
|
+
catch (error) {
|
|
453
|
+
logger.warn(`[imessage] updateContact failed for ${personId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
454
|
+
return false;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
458
|
+
* Delete a contact by Contacts.app id. Requires write permission.
|
|
459
|
+
* Returns false on any failure (not found, permission denied, etc.).
|
|
460
|
+
*/
|
|
461
|
+
export async function deleteContact(personId) {
|
|
462
|
+
const id = escapeAppleScriptString(personId);
|
|
463
|
+
const script = `
|
|
464
|
+
tell application "Contacts"
|
|
465
|
+
launch
|
|
466
|
+
delete (person id "${id}")
|
|
467
|
+
save
|
|
468
|
+
return "ok"
|
|
469
|
+
end tell
|
|
470
|
+
`.trim();
|
|
471
|
+
try {
|
|
472
|
+
await runContactsScript(script);
|
|
473
|
+
logger.info(`[imessage] Contact deleted: ${personId}`);
|
|
474
|
+
return true;
|
|
475
|
+
}
|
|
476
|
+
catch (error) {
|
|
477
|
+
logger.warn(`[imessage] deleteContact failed for ${personId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
478
|
+
return false;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
//# sourceMappingURL=contacts-reader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contacts-reader.js","sourceRoot":"","sources":["../src/contacts-reader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAEvC,MAAM,aAAa,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;AAC1C,MAAM,kCAAkC,GAAG,KAAK,CAAC;AAoBjD;;;;GAIG;AACH,MAAM,UAAU,sBAAsB,CAAC,GAAW;IAChD,MAAM,OAAO,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;IAC3B,IAAI,CAAC,OAAO;QAAE,OAAO,EAAE,CAAC;IAExB,mBAAmB;IACnB,IAAI,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QAC1B,OAAO,OAAO,CAAC,WAAW,EAAE,CAAC;IAC/B,CAAC;IAED,yDAAyD;IACzD,MAAM,OAAO,GAAG,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;IACxC,MAAM,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;IACjD,OAAO,OAAO,CAAC,CAAC,CAAC,IAAI,UAAU,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC;AACjD,CAAC;AAED;;;;;;GAMG;AACH,MAAM,oBAAoB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;CA0B5B,CAAC,IAAI,EAAE,CAAC;AAET;;;;;;;;;GASG;AACH,MAAM,UAAU,mBAAmB,CAAC,GAAW;IAC7C,MAAM,GAAG,GAAgB,IAAI,GAAG,EAAE,CAAC;IACnC,IAAI,CAAC,GAAG,EAAE,IAAI,EAAE;QAAE,OAAO,GAAG,CAAC;IAE7B,KAAK,MAAM,IAAI,IAAI,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACnC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC5B,IAAI,CAAC,OAAO;YAAE,SAAS;QAEvB,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACnC,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC;YAAE,SAAS;QAEhC,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,GAAG,MAAM,CAAC;QACrC,IAAI,CAAC,MAAM,IAAI,CAAC,IAAI;YAAE,SAAS;QAE/B,MAAM,UAAU,GAAG,sBAAsB,CAAC,MAAM,CAAC,CAAC;QAClD,IAAI,CAAC,UAAU;YAAE,SAAS;QAC1B,IAAI,GAAG,CAAC,GAAG,CAAC,UAAU,CAAC;YAAE,SAAS;QAElC,GAAG,CAAC,GAAG,CAAC,UAAU,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IAC7C,CAAC;IAED,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY;IAChC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,iBAAiB,CAAC,oBAAoB,CAAC,CAAC;QAC7D,MAAM,GAAG,GAAG,mBAAmB,CAAC,MAAM,CAAC,CAAC;QACxC,MAAM,CAAC,IAAI,CAAC,+BAA+B,GAAG,CAAC,IAAI,uCAAuC,CAAC,CAAC;QAC5F,OAAO,GAAG,CAAC;IACb,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,MAAM,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACtE,IAAI,qBAAqB,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;YACvC,MAAM,CAAC,IAAI,CACT,mEAAmE;gBACjE,kEAAkE;gBAClE,4DAA4D,CAC/D,CAAC;QACJ,CAAC;aAAM,CAAC;YACN,MAAM,CAAC,IAAI,CACT,gDAAgD,MAAM,IAAI;gBACxD,yDAAyD,CAC5D,CAAC;QACJ,CAAC;QACD,OAAO,IAAI,GAAG,EAAE,CAAC;IACnB,CAAC;AACH,CAAC;AA2CD;;;;;GAKG;AACH,SAAS,uBAAuB,CAAC,KAAa;IAC5C,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;AAC3D,CAAC;AAED;;;GAGG;AACH,IAAI,gBAAgB,GAAG,KAAK,CAAC;AAE7B,SAAS,0BAA0B;IACjC,MAAM,GAAG,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,4BAA4B,CAAC,CAAC;IAC7D,OAAO,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,kCAAkC,CAAC;AACpF,CAAC;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,KAAK,UAAU,sBAAsB;IACnC,IAAI,gBAAgB;QAAE,OAAO;IAC7B,IAAI,CAAC;QACH,MAAM,aAAa,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC;QAC5D,iEAAiE;QACjE,gEAAgE;QAChE,iEAAiE;QACjE,MAAM,IAAI,OAAO,CAAO,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;QACnD,gBAAgB,GAAG,IAAI,CAAC;IAC1B,CAAC;IAAC,MAAM,CAAC;QACP,8DAA8D;QAC9D,8DAA8D;QAC9D,kEAAkE;IACpE,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,KAAK,UAAU,iBAAiB,CAAC,MAAc;IAC7C,MAAM,sBAAsB,EAAE,CAAC;IAC/B,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,aAAa,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE;QAClE,SAAS,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI;QAC3B,OAAO,EAAE,0BAA0B,EAAE;QACrC,UAAU,EAAE,SAAS;KACtB,CAAC,CAAC;IACH,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;;;;;GASG;AACH,SAAS,uBAAuB,CAAC,GAAW;IAC1C,MAAM,GAAG,GAAkB,EAAE,CAAC;IAC9B,IAAI,CAAC,GAAG,EAAE,IAAI,EAAE;QAAE,OAAO,GAAG,CAAC;IAC7B,KAAK,MAAM,IAAI,IAAI,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACnC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC5B,IAAI,CAAC,OAAO;YAAE,SAAS;QACvB,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACnC,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC;YAAE,SAAS;QAChC,MAAM,CAAC,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,WAAW,EAAE,WAAW,CAAC,GAAG,MAAM,CAAC;QACzE,IAAI,CAAC,EAAE;YAAE,SAAS;QAClB,MAAM,UAAU,GAAG,CAAC,KAAa,EAAkD,EAAE;YACnF,IAAI,CAAC,KAAK;gBAAE,OAAO,EAAE,CAAC;YACtB,OAAO,KAAK;iBACT,KAAK,CAAC,GAAG,CAAC;iBACV,MAAM,CAAC,OAAO,CAAC;iBACf,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;gBACZ,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;gBAChC,IAAI,KAAK,KAAK,CAAC,CAAC;oBAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;gBACtD,OAAO;oBACL,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,IAAI,IAAI;oBACnC,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC;iBAC7B,CAAC;YACJ,CAAC,CAAC,CAAC;QACP,CAAC,CAAC;QACF,GAAG,CAAC,IAAI,CAAC;YACP,EAAE;YACF,IAAI,EAAE,IAAI,IAAI,EAAE;YAChB,SAAS,EAAE,SAAS,IAAI,IAAI;YAC5B,QAAQ,EAAE,QAAQ,IAAI,IAAI;YAC1B,MAAM,EAAE,UAAU,CAAC,WAAW,CAAC;YAC/B,MAAM,EAAE,UAAU,CAAC,WAAW,CAAC;SAChC,CAAC,CAAC;IACL,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;GAIG;AACH,MAAM,yBAAyB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2CjC,CAAC,IAAI,EAAE,CAAC;AAET;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe;IACnC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,iBAAiB,CAAC,yBAAyB,CAAC,CAAC;QAClE,OAAO,uBAAuB,CAAC,MAAM,CAAC,CAAC;IACzC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,CAAC,IAAI,CACT,sCAAsC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAC/F,CAAC;QACF,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,KAAsB;IACrD,MAAM,KAAK,GAAG,uBAAuB,CAAC,KAAK,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC;IAC7D,MAAM,IAAI,GAAG,uBAAuB,CAAC,KAAK,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC;IAE3D,sEAAsE;IACtE,qEAAqE;IACrE,mEAAmE;IACnE,mBAAmB;IACnB,MAAM,UAAU,GAAG,CAAC,KAAK,CAAC,MAAM,IAAI,EAAE,CAAC;SACpC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC;SACtB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QACT,MAAM,KAAK,GAAG,uBAAuB,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;QAC/C,MAAM,KAAK,GAAG,uBAAuB,CAAC,CAAC,CAAC,KAAK,IAAI,QAAQ,CAAC,CAAC;QAC3D,OAAO,2DAA2D,KAAK,aAAa,KAAK,IAAI,CAAC;IAChG,CAAC,CAAC;SACD,IAAI,CAAC,QAAQ,CAAC,CAAC;IAElB,MAAM,UAAU,GAAG,CAAC,KAAK,CAAC,MAAM,IAAI,EAAE,CAAC;SACpC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC;SACtB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QACT,MAAM,KAAK,GAAG,uBAAuB,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;QAC/C,MAAM,KAAK,GAAG,uBAAuB,CAAC,CAAC,CAAC,KAAK,IAAI,MAAM,CAAC,CAAC;QACzD,OAAO,2DAA2D,KAAK,aAAa,KAAK,IAAI,CAAC;IAChG,CAAC,CAAC;SACD,IAAI,CAAC,QAAQ,CAAC,CAAC;IAElB,MAAM,MAAM,GAAG;;;kEAGiD,KAAK,iBAAiB,IAAI;;MAEtF,UAAU;MACV,UAAU;;;;;GAKb,CAAC,IAAI,EAAE,CAAC;IAET,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,iBAAiB,CAAC,MAAM,CAAC,CAAC;QAC/C,MAAM,EAAE,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC;QACzB,IAAI,CAAC,EAAE;YAAE,OAAO,IAAI,CAAC;QACrB,MAAM,CAAC,IAAI,CAAC,+BAA+B,EAAE,EAAE,CAAC,CAAC;QACjD,OAAO,EAAE,CAAC;IACZ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,CAAC,IAAI,CACT,iCAAiC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAC1F,CAAC;QACF,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAkBD,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,QAAgB,EAAE,KAAmB;IACvE,MAAM,EAAE,GAAG,uBAAuB,CAAC,QAAQ,CAAC,CAAC;IAC7C,MAAM,SAAS,GAAa,EAAE,CAAC;IAE/B,IAAI,KAAK,CAAC,SAAS,KAAK,SAAS,EAAE,CAAC;QAClC,SAAS,CAAC,IAAI,CAAC,mCAAmC,uBAAuB,CAAC,KAAK,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;IACjG,CAAC;IACD,IAAI,KAAK,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;QACjC,SAAS,CAAC,IAAI,CAAC,kCAAkC,uBAAuB,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;IAC/F,CAAC;IAED,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,SAAS,IAAI,EAAE,EAAE,CAAC;QAC1C,IAAI,CAAC,KAAK,CAAC,KAAK;YAAE,SAAS;QAC3B,SAAS,CAAC,IAAI,CACZ,6EAA6E,uBAAuB,CAAC,KAAK,CAAC,KAAK,CAAC,aAAa,uBAAuB,CAAC,KAAK,CAAC,KAAK,IAAI,QAAQ,CAAC,IAAI,CACnL,CAAC;IACJ,CAAC;IACD,KAAK,MAAM,UAAU,IAAI,KAAK,CAAC,YAAY,IAAI,EAAE,EAAE,CAAC;QAClD,IAAI,CAAC,UAAU;YAAE,SAAS;QAC1B,SAAS,CAAC,IAAI,CAAC;;;+BAGY,uBAAuB,CAAC,UAAU,CAAC;;;;;aAKrD,CAAC,CAAC;IACb,CAAC;IAED,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,SAAS,IAAI,EAAE,EAAE,CAAC;QAC1C,IAAI,CAAC,KAAK,CAAC,KAAK;YAAE,SAAS;QAC3B,SAAS,CAAC,IAAI,CACZ,6EAA6E,uBAAuB,CAAC,KAAK,CAAC,KAAK,CAAC,aAAa,uBAAuB,CAAC,KAAK,CAAC,KAAK,IAAI,MAAM,CAAC,IAAI,CACjL,CAAC;IACJ,CAAC;IACD,KAAK,MAAM,UAAU,IAAI,KAAK,CAAC,YAAY,IAAI,EAAE,EAAE,CAAC;QAClD,IAAI,CAAC,UAAU;YAAE,SAAS;QAC1B,SAAS,CAAC,IAAI,CAAC;;;+BAGY,uBAAuB,CAAC,UAAU,CAAC;;;;;aAKrD,CAAC,CAAC;IACb,CAAC;IAED,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC3B,+CAA+C;QAC/C,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,MAAM,GAAG;;;gCAGe,EAAE;IAC9B,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC;;;;GAIvB,CAAC,IAAI,EAAE,CAAC;IAET,IAAI,CAAC;QACH,MAAM,iBAAiB,CAAC,MAAM,CAAC,CAAC;QAChC,MAAM,CAAC,IAAI,CAAC,+BAA+B,QAAQ,EAAE,CAAC,CAAC;QACvD,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,CAAC,IAAI,CACT,uCAAuC,QAAQ,KAAK,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAC7G,CAAC;QACF,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,QAAgB;IAClD,MAAM,EAAE,GAAG,uBAAuB,CAAC,QAAQ,CAAC,CAAC;IAC7C,MAAM,MAAM,GAAG;;;uBAGM,EAAE;;;;GAItB,CAAC,IAAI,EAAE,CAAC;IAET,IAAI,CAAC;QACH,MAAM,iBAAiB,CAAC,MAAM,CAAC,CAAC;QAChC,MAAM,CAAC,IAAI,CAAC,+BAA+B,QAAQ,EAAE,CAAC,CAAC;QACvD,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,CAAC,IAAI,CACT,uCAAuC,QAAQ,KAAK,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAC7G,CAAC;QACF,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* iMessage Plugin for
|
|
2
|
+
* iMessage Plugin for elizaOS
|
|
3
3
|
*
|
|
4
|
-
* Provides iMessage integration for
|
|
4
|
+
* Provides iMessage integration for Eliza agents on macOS.
|
|
5
5
|
* Uses AppleScript and/or CLI tools to send and receive messages.
|
|
6
6
|
*/
|
|
7
|
-
import type
|
|
8
|
-
import {
|
|
9
|
-
import { chatContextProvider } from "./providers/index.js";
|
|
10
|
-
import { IMessageService, parseChatsFromAppleScript, parseMessagesFromAppleScript } from "./service.js";
|
|
11
|
-
export * from "./types.js";
|
|
12
|
-
export { IMessageService, parseMessagesFromAppleScript, parseChatsFromAppleScript };
|
|
13
|
-
export { sendMessage };
|
|
14
|
-
export { chatContextProvider };
|
|
7
|
+
import { type Plugin } from "@elizaos/core";
|
|
8
|
+
import { chatDbMessageToPublicShape, IMessageService, parseChatsFromAppleScript, parseMessagesFromAppleScript } from "./service.js";
|
|
15
9
|
export { DEFAULT_ACCOUNT_ID, type IMessageAccountConfig, type IMessageGroupConfig, type IMessageMultiAccountConfig, isIMessageMentionRequired, isIMessageUserAllowed, isMultiAccountEnabled, listEnabledIMessageAccounts, listIMessageAccountIds, normalizeAccountId, type ResolvedIMessageAccount, resolveDefaultIMessageAccountId, resolveIMessageAccount, resolveIMessageGroupConfig, } from "./accounts.js";
|
|
10
|
+
export { appleDateToJsMs, type ChatDbAccessIssue, type ChatDbMessage, type ChatDbReader, createFullDiskAccessAction, DEFAULT_CHAT_DB_PATH, getLastChatDbAccessIssue, MACOS_FULL_DISK_ACCESS_SETTINGS_URL, openChatDb, } from "./chatdb-reader.js";
|
|
11
|
+
export { addContact, type ContactPatch, type ContactsMap, deleteContact, type FullContact, listAllContacts, loadContacts, type NewContactInput, normalizeContactHandle, parseContactsOutput, type ResolvedContact, updateContact, } from "./contacts-reader.js";
|
|
16
12
|
export { createIMessageRpcClient, DEFAULT_PROBE_TIMEOUT_MS, DEFAULT_REQUEST_TIMEOUT_MS, getChatInfo, getContactInfo, getMessages, type IMessageAttachment, type IMessageChat, type IMessageContact, type IMessageMessage, IMessageRpcClient, type IMessageRpcClientOptions, type IMessageRpcError, type IMessageRpcNotification, type IMessageRpcResponse, listChats, listContacts, probeIMessageRpc, sendIMessageRpc, } from "./rpc.js";
|
|
13
|
+
export * from "./types.js";
|
|
14
|
+
export { chatDbMessageToPublicShape, IMessageService, parseChatsFromAppleScript, parseMessagesFromAppleScript, };
|
|
17
15
|
/**
|
|
18
|
-
* iMessage plugin for
|
|
16
|
+
* iMessage plugin for Eliza agents.
|
|
19
17
|
*/
|
|
20
18
|
declare const imessagePlugin: Plugin;
|
|
21
19
|
export default imessagePlugin;
|
|
20
|
+
export { type BlueBubblesRouteState, handleBlueBubblesRoute, resolveBlueBubblesWebhookPath, } from "./api/bluebubbles-routes.js";
|
|
21
|
+
export { handleIMessageRoute, type IMessageRouteState, type ReadJsonBodyOptions as IMessageRouteReadJsonBodyOptions, type RouteHelpers as IMessageRouteHelpers, type RouteRequestMeta as IMessageRouteRequestMeta, } from "./api/imessage-routes.js";
|
|
22
22
|
export type { IMessageConfig, IMessageReactionNotificationMode, } from "./config.js";
|
|
23
23
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAA0D,KAAK,MAAM,EAAE,MAAM,eAAe,CAAC;AAMpG,OAAO,EACL,0BAA0B,EAC1B,eAAe,EACf,yBAAyB,EACzB,4BAA4B,EAC7B,MAAM,cAAc,CAAC;AAItB,OAAO,EACL,kBAAkB,EAClB,KAAK,qBAAqB,EAC1B,KAAK,mBAAmB,EACxB,KAAK,0BAA0B,EAC/B,yBAAyB,EACzB,qBAAqB,EACrB,qBAAqB,EACrB,2BAA2B,EAC3B,sBAAsB,EACtB,kBAAkB,EAClB,KAAK,uBAAuB,EAC5B,+BAA+B,EAC/B,sBAAsB,EACtB,0BAA0B,GAC3B,MAAM,eAAe,CAAC;AAEvB,OAAO,EACL,eAAe,EACf,KAAK,iBAAiB,EACtB,KAAK,aAAa,EAClB,KAAK,YAAY,EACjB,0BAA0B,EAC1B,oBAAoB,EACpB,wBAAwB,EACxB,mCAAmC,EACnC,UAAU,GACX,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EACL,UAAU,EACV,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,aAAa,EACb,KAAK,WAAW,EAChB,eAAe,EACf,YAAY,EACZ,KAAK,eAAe,EACpB,sBAAsB,EACtB,mBAAmB,EACnB,KAAK,eAAe,EACpB,aAAa,GACd,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EACL,uBAAuB,EACvB,wBAAwB,EACxB,0BAA0B,EAC1B,WAAW,EACX,cAAc,EACd,WAAW,EACX,KAAK,kBAAkB,EACvB,KAAK,YAAY,EACjB,KAAK,eAAe,EACpB,KAAK,eAAe,EACpB,iBAAiB,EACjB,KAAK,wBAAwB,EAC7B,KAAK,gBAAgB,EACrB,KAAK,uBAAuB,EAC5B,KAAK,mBAAmB,EACxB,SAAS,EACT,YAAY,EACZ,gBAAgB,EAChB,eAAe,GAChB,MAAM,UAAU,CAAC;AAElB,cAAc,YAAY,CAAC;AAC3B,OAAO,EACL,0BAA0B,EAC1B,eAAe,EACf,yBAAyB,EACzB,4BAA4B,GAC7B,CAAC;AAEF;;GAEG;AACH,QAAA,MAAM,cAAc,EAAE,MAwDrB,CAAC;AAEF,eAAe,cAAc,CAAC;AAE9B,OAAO,EACL,KAAK,qBAAqB,EAC1B,sBAAsB,EACtB,6BAA6B,GAC9B,MAAM,6BAA6B,CAAC;AAMrC,OAAO,EACL,mBAAmB,EACnB,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,IAAI,gCAAgC,EAC5D,KAAK,YAAY,IAAI,oBAAoB,EACzC,KAAK,gBAAgB,IAAI,wBAAwB,GAClD,MAAM,0BAA0B,CAAC;AAElC,YAAY,EACV,cAAc,EACd,gCAAgC,GACjC,MAAM,aAAa,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,35 +1,61 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* iMessage Plugin for
|
|
2
|
+
* iMessage Plugin for elizaOS
|
|
3
3
|
*
|
|
4
|
-
* Provides iMessage integration for
|
|
4
|
+
* Provides iMessage integration for Eliza agents on macOS.
|
|
5
5
|
* Uses AppleScript and/or CLI tools to send and receive messages.
|
|
6
6
|
*/
|
|
7
7
|
import { platform } from "node:os";
|
|
8
|
-
import { logger } from "@elizaos/core";
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
//
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
export { chatContextProvider };
|
|
8
|
+
import { getConnectorAccountManager, logger } from "@elizaos/core";
|
|
9
|
+
import { createIMessageConnectorAccountProvider } from "./connector-account-provider.js";
|
|
10
|
+
// The former iMessage-specific send action duplicated the MessageConnector
|
|
11
|
+
// path. The connector registered by IMessageService.registerSendHandlers is
|
|
12
|
+
// now the canonical delivery path through MESSAGE operation=send. This plugin
|
|
13
|
+
// no longer registers its own send action.
|
|
14
|
+
import { chatDbMessageToPublicShape, IMessageService, parseChatsFromAppleScript, parseMessagesFromAppleScript, } from "./service.js";
|
|
15
|
+
import { imessageSetupRoutes } from "./setup-routes.js";
|
|
17
16
|
// Account management exports
|
|
18
17
|
export { DEFAULT_ACCOUNT_ID, isIMessageMentionRequired, isIMessageUserAllowed, isMultiAccountEnabled, listEnabledIMessageAccounts, listIMessageAccountIds, normalizeAccountId, resolveDefaultIMessageAccountId, resolveIMessageAccount, resolveIMessageGroupConfig, } from "./accounts.js";
|
|
18
|
+
// chat.db reader (bun:sqlite-backed inbound polling)
|
|
19
|
+
export { appleDateToJsMs, createFullDiskAccessAction, DEFAULT_CHAT_DB_PATH, getLastChatDbAccessIssue, MACOS_FULL_DISK_ACCESS_SETTINGS_URL, openChatDb, } from "./chatdb-reader.js";
|
|
20
|
+
// Apple Contacts reader (display-name resolution for inbound handles)
|
|
21
|
+
export { addContact, deleteContact, listAllContacts, loadContacts, normalizeContactHandle, parseContactsOutput, updateContact, } from "./contacts-reader.js";
|
|
19
22
|
// RPC client exports
|
|
20
23
|
export { createIMessageRpcClient, DEFAULT_PROBE_TIMEOUT_MS, DEFAULT_REQUEST_TIMEOUT_MS, getChatInfo, getContactInfo, getMessages, IMessageRpcClient, listChats, listContacts, probeIMessageRpc, sendIMessageRpc, } from "./rpc.js";
|
|
24
|
+
// Re-export types and service
|
|
25
|
+
export * from "./types.js";
|
|
26
|
+
export { chatDbMessageToPublicShape, IMessageService, parseChatsFromAppleScript, parseMessagesFromAppleScript, };
|
|
21
27
|
/**
|
|
22
|
-
* iMessage plugin for
|
|
28
|
+
* iMessage plugin for Eliza agents.
|
|
23
29
|
*/
|
|
24
30
|
const imessagePlugin = {
|
|
25
31
|
name: "imessage",
|
|
26
|
-
description: "iMessage plugin for
|
|
32
|
+
description: "iMessage plugin for Eliza agents (macOS only)",
|
|
27
33
|
services: [IMessageService],
|
|
28
|
-
actions: [
|
|
29
|
-
providers: [
|
|
34
|
+
actions: [],
|
|
35
|
+
providers: [],
|
|
36
|
+
routes: imessageSetupRoutes,
|
|
30
37
|
tests: [],
|
|
31
|
-
|
|
38
|
+
// Self-declared auto-enable: activate when the "imessage" connector is
|
|
39
|
+
// configured under config.connectors. The hardcoded CONNECTOR_PLUGINS map
|
|
40
|
+
// in plugin-auto-enable-engine.ts still serves as a fallback.
|
|
41
|
+
autoEnable: {
|
|
42
|
+
connectorKeys: ["imessage"],
|
|
43
|
+
},
|
|
44
|
+
init: async (config, runtime) => {
|
|
32
45
|
logger.info("Initializing iMessage plugin...");
|
|
46
|
+
// Register the iMessage provider with the ConnectorAccountManager so the
|
|
47
|
+
// HTTP CRUD surface (packages/agent/src/api/connector-account-routes.ts)
|
|
48
|
+
// can list, create, patch, and delete iMessage accounts.
|
|
49
|
+
try {
|
|
50
|
+
const manager = getConnectorAccountManager(runtime);
|
|
51
|
+
manager.registerProvider(createIMessageConnectorAccountProvider(runtime));
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
logger.warn({
|
|
55
|
+
src: "plugin:imessage",
|
|
56
|
+
err: err instanceof Error ? err.message : String(err),
|
|
57
|
+
}, "Failed to register iMessage provider with ConnectorAccountManager");
|
|
58
|
+
}
|
|
33
59
|
const isMacOS = platform() === "darwin";
|
|
34
60
|
logger.info("iMessage plugin configuration:");
|
|
35
61
|
logger.info(` - Platform: ${platform()}`);
|
|
@@ -43,4 +69,10 @@ const imessagePlugin = {
|
|
|
43
69
|
},
|
|
44
70
|
};
|
|
45
71
|
export default imessagePlugin;
|
|
72
|
+
export { handleBlueBubblesRoute, resolveBlueBubblesWebhookPath, } from "./api/bluebubbles-routes.js";
|
|
73
|
+
// Legacy HTTP route handlers (mounted by the agent's raw HTTP router).
|
|
74
|
+
// These are the moved counterparts of the agent's old api/imessage-routes.ts
|
|
75
|
+
// and api/bluebubbles-routes.ts files. Per the audit, BlueBubbles is treated
|
|
76
|
+
// as part of iMessage, so both live here.
|
|
77
|
+
export { handleIMessageRoute, } from "./api/imessage-routes.js";
|
|
46
78
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,EAAE,0BAA0B,EAAsB,MAAM,EAAe,MAAM,eAAe,CAAC;AACpG,OAAO,EAAE,sCAAsC,EAAE,MAAM,iCAAiC,CAAC;AACzF,2EAA2E;AAC3E,4EAA4E;AAC5E,8EAA8E;AAC9E,2CAA2C;AAC3C,OAAO,EACL,0BAA0B,EAC1B,eAAe,EACf,yBAAyB,EACzB,4BAA4B,GAC7B,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAExD,6BAA6B;AAC7B,OAAO,EACL,kBAAkB,EAIlB,yBAAyB,EACzB,qBAAqB,EACrB,qBAAqB,EACrB,2BAA2B,EAC3B,sBAAsB,EACtB,kBAAkB,EAElB,+BAA+B,EAC/B,sBAAsB,EACtB,0BAA0B,GAC3B,MAAM,eAAe,CAAC;AACvB,qDAAqD;AACrD,OAAO,EACL,eAAe,EAIf,0BAA0B,EAC1B,oBAAoB,EACpB,wBAAwB,EACxB,mCAAmC,EACnC,UAAU,GACX,MAAM,oBAAoB,CAAC;AAC5B,sEAAsE;AACtE,OAAO,EACL,UAAU,EAGV,aAAa,EAEb,eAAe,EACf,YAAY,EAEZ,sBAAsB,EACtB,mBAAmB,EAEnB,aAAa,GACd,MAAM,sBAAsB,CAAC;AAC9B,qBAAqB;AACrB,OAAO,EACL,uBAAuB,EACvB,wBAAwB,EACxB,0BAA0B,EAC1B,WAAW,EACX,cAAc,EACd,WAAW,EAKX,iBAAiB,EAKjB,SAAS,EACT,YAAY,EACZ,gBAAgB,EAChB,eAAe,GAChB,MAAM,UAAU,CAAC;AAClB,8BAA8B;AAC9B,cAAc,YAAY,CAAC;AAC3B,OAAO,EACL,0BAA0B,EAC1B,eAAe,EACf,yBAAyB,EACzB,4BAA4B,GAC7B,CAAC;AAEF;;GAEG;AACH,MAAM,cAAc,GAAW;IAC7B,IAAI,EAAE,UAAU;IAChB,WAAW,EAAE,+CAA+C;IAE5D,QAAQ,EAAE,CAAC,eAAe,CAAC;IAC3B,OAAO,EAAE,EAAE;IACX,SAAS,EAAE,EAAE;IACb,MAAM,EAAE,mBAAmB;IAC3B,KAAK,EAAE,EAAE;IAET,uEAAuE;IACvE,0EAA0E;IAC1E,8DAA8D;IAC9D,UAAU,EAAE;QACV,aAAa,EAAE,CAAC,UAAU,CAAC;KAC5B;IAED,IAAI,EAAE,KAAK,EAAE,MAA8B,EAAE,OAAsB,EAAiB,EAAE;QACpF,MAAM,CAAC,IAAI,CAAC,iCAAiC,CAAC,CAAC;QAE/C,yEAAyE;QACzE,yEAAyE;QACzE,yDAAyD;QACzD,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,0BAA0B,CAAC,OAAO,CAAC,CAAC;YACpD,OAAO,CAAC,gBAAgB,CAAC,sCAAsC,CAAC,OAAO,CAAC,CAAC,CAAC;QAC5E,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,CAAC,IAAI,CACT;gBACE,GAAG,EAAE,iBAAiB;gBACtB,GAAG,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;aACtD,EACD,mEAAmE,CACpE,CAAC;QACJ,CAAC;QAED,MAAM,OAAO,GAAG,QAAQ,EAAE,KAAK,QAAQ,CAAC;QAExC,MAAM,CAAC,IAAI,CAAC,gCAAgC,CAAC,CAAC;QAC9C,MAAM,CAAC,IAAI,CAAC,iBAAiB,QAAQ,EAAE,EAAE,CAAC,CAAC;QAC3C,MAAM,CAAC,IAAI,CAAC,cAAc,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;QACpD,MAAM,CAAC,IAAI,CACT,iBAAiB,MAAM,CAAC,iBAAiB,IAAI,OAAO,CAAC,GAAG,CAAC,iBAAiB,IAAI,gBAAgB,EAAE,CACjG,CAAC;QACF,MAAM,CAAC,IAAI,CACT,kBAAkB,MAAM,CAAC,kBAAkB,IAAI,OAAO,CAAC,GAAG,CAAC,kBAAkB,IAAI,SAAS,EAAE,CAC7F,CAAC;QAEF,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,CAAC,IAAI,CACT,2FAA2F,CAC5F,CAAC;QACJ,CAAC;QAED,MAAM,CAAC,IAAI,CAAC,6BAA6B,CAAC,CAAC;IAC7C,CAAC;CACF,CAAC;AAEF,eAAe,cAAc,CAAC;AAE9B,OAAO,EAEL,sBAAsB,EACtB,6BAA6B,GAC9B,MAAM,6BAA6B,CAAC;AAErC,uEAAuE;AACvE,6EAA6E;AAC7E,6EAA6E;AAC7E,0CAA0C;AAC1C,OAAO,EACL,mBAAmB,GAKpB,MAAM,0BAA0B,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/providers/index.ts"],"names":[],"mappings":"AAAA;;GAEG
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/providers/index.ts"],"names":[],"mappings":"AAAA;;GAEG"}
|