imsg-mcp 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,775 @@
1
+ import { existsSync, readdirSync, appendFileSync, mkdirSync, writeFileSync, unlinkSync } from "node:fs";
2
+ import os, { homedir, freemem, tmpdir } from "node:os";
3
+ import Database from "better-sqlite3";
4
+ import { execFile } from "node:child_process";
5
+ import { randomUUID, randomBytes } from "node:crypto";
6
+ import { join, sep, dirname, resolve } from "node:path";
7
+ import { promisify } from "node:util";
8
+ function resolveEnvPath(value, fallback) {
9
+ if (!value) return fallback;
10
+ if (value.startsWith("~/") || value === "~") {
11
+ return join(homedir(), value.slice(2));
12
+ }
13
+ return resolve(process.cwd(), value);
14
+ }
15
+ function getEnv() {
16
+ return process.env.VITE_ENV ?? "development";
17
+ }
18
+ function isAiEnv() {
19
+ return getEnv() === "ai";
20
+ }
21
+ function getImsgDbPath() {
22
+ return resolveEnvPath(
23
+ process.env.VITE_IMSG_DB_PATH,
24
+ join(homedir(), "Library", "Messages", "chat.db")
25
+ );
26
+ }
27
+ function getContactsDbPaths() {
28
+ const main = process.env.VITE_CONTACTS_DB_PATH ?? join(homedir(), "Library", "Application Support", "AddressBook", "AddressBook-v22.abcddb");
29
+ const mainResolved = resolveEnvPath(main, "");
30
+ const paths = /* @__PURE__ */ new Set([mainResolved]);
31
+ const sourcesSegment = `${sep}Sources${sep}`;
32
+ const sourcesIndex = mainResolved.indexOf(sourcesSegment);
33
+ const addressBookDir = sourcesIndex >= 0 ? mainResolved.slice(0, sourcesIndex) : dirname(mainResolved);
34
+ const addSourceDb = (path) => {
35
+ if (existsSync(path)) {
36
+ paths.add(path);
37
+ }
38
+ };
39
+ const uuid = process.env.VITE_ADDRESS_BOOK_UUID;
40
+ if (uuid) {
41
+ addSourceDb(join(addressBookDir, "Sources", uuid, "AddressBook-v22.abcddb"));
42
+ }
43
+ const sourcesDir = join(addressBookDir, "Sources");
44
+ if (existsSync(sourcesDir)) {
45
+ try {
46
+ const entries = readdirSync(sourcesDir, { withFileTypes: true });
47
+ for (const entry of entries) {
48
+ if (!entry.isDirectory()) continue;
49
+ addSourceDb(join(sourcesDir, entry.name, "AddressBook-v22.abcddb"));
50
+ }
51
+ } catch {
52
+ }
53
+ }
54
+ return [...paths];
55
+ }
56
+ function getSlugsDbPath() {
57
+ return resolveEnvPath(process.env.VITE_SLUGS_DB_PATH, join(homedir(), ".imsg-mcp", "slugs.db"));
58
+ }
59
+ const config = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
60
+ __proto__: null,
61
+ getContactsDbPaths,
62
+ getEnv,
63
+ getImsgDbPath,
64
+ getSlugsDbPath,
65
+ isAiEnv
66
+ }, Symbol.toStringTag, { value: "Module" }));
67
+ const MAX_LOG_LINES = 500;
68
+ const MAX_FILE_BYTES = 10 * 1024 * 1024;
69
+ function isFileLoggingEnabled() {
70
+ return process.env.IMSG_DEV === "1";
71
+ }
72
+ function isVerboseLogging() {
73
+ return process.env.IMSG_LOG_VERBOSE === "1";
74
+ }
75
+ const memoryLines = [];
76
+ let logFilePath = null;
77
+ let logFileBytes = 0;
78
+ let lastSendError = null;
79
+ function getLogDir() {
80
+ return join(tmpdir(), "imsg-mcp");
81
+ }
82
+ function ensureLogFile() {
83
+ if (logFilePath && logFileBytes < MAX_FILE_BYTES) return logFilePath;
84
+ try {
85
+ const dir = getLogDir();
86
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
87
+ const date = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
88
+ logFilePath = join(dir, `imsg-mcp-${process.pid}-${date}.ndjson`);
89
+ logFileBytes = 0;
90
+ return logFilePath;
91
+ } catch {
92
+ return null;
93
+ }
94
+ }
95
+ function writeToFile(json) {
96
+ if (!isFileLoggingEnabled()) return;
97
+ const path = ensureLogFile();
98
+ if (!path) return;
99
+ try {
100
+ const line = `${json}
101
+ `;
102
+ appendFileSync(path, line);
103
+ logFileBytes += line.length;
104
+ } catch {
105
+ }
106
+ }
107
+ function heapMB() {
108
+ return Math.round(process.memoryUsage().heapUsed / 1024 / 1024 * 10) / 10;
109
+ }
110
+ function formatMemoryLine(entry) {
111
+ let line = `${entry.ts} [${entry.level}] ${entry.msg}`;
112
+ if (entry.dur_ms != null) line += ` (${entry.dur_ms.toFixed(1)}ms)`;
113
+ if (entry.data != null) line += ` ${JSON.stringify(entry.data)}`;
114
+ return line;
115
+ }
116
+ function emit(entry) {
117
+ const line = formatMemoryLine(entry);
118
+ memoryLines.push(line);
119
+ if (memoryLines.length > MAX_LOG_LINES) {
120
+ memoryLines.splice(0, memoryLines.length - MAX_LOG_LINES);
121
+ }
122
+ writeToFile(JSON.stringify(entry));
123
+ }
124
+ function info(msg, data) {
125
+ emit({ ts: (/* @__PURE__ */ new Date()).toISOString(), level: "info", msg, mem_mb: heapMB(), data });
126
+ }
127
+ function warn(msg, data) {
128
+ emit({ ts: (/* @__PURE__ */ new Date()).toISOString(), level: "warn", msg, mem_mb: heapMB(), data });
129
+ }
130
+ function error(msg, data) {
131
+ emit({ ts: (/* @__PURE__ */ new Date()).toISOString(), level: "error", msg, mem_mb: heapMB(), data });
132
+ }
133
+ function perf(msg) {
134
+ const startTime = performance.now();
135
+ const startHeap = heapMB();
136
+ return {
137
+ end(data) {
138
+ const dur_ms = performance.now() - startTime;
139
+ const endHeap = heapMB();
140
+ emit({
141
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
142
+ level: "perf",
143
+ msg,
144
+ dur_ms,
145
+ mem_mb: endHeap,
146
+ mem_delta_mb: Math.round((endHeap - startHeap) * 10) / 10,
147
+ data
148
+ });
149
+ return dur_ms;
150
+ }
151
+ };
152
+ }
153
+ function appendLog(level, message, data) {
154
+ const entry = {
155
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
156
+ level,
157
+ msg: message,
158
+ mem_mb: heapMB(),
159
+ data: data != null ? typeof data === "object" ? data : { value: data } : void 0
160
+ };
161
+ emit(entry);
162
+ }
163
+ function getLogs(tail) {
164
+ if (tail != null && tail > 0) return memoryLines.slice(-tail);
165
+ return [...memoryLines];
166
+ }
167
+ function setLastSendError(details) {
168
+ lastSendError = { ...details, timestamp: (/* @__PURE__ */ new Date()).toISOString() };
169
+ error("send_message failed", details);
170
+ }
171
+ function getLastSendError() {
172
+ return lastSendError ? { ...lastSendError } : null;
173
+ }
174
+ function getLogFilePath() {
175
+ return logFilePath;
176
+ }
177
+ function getLogDirectory() {
178
+ return getLogDir();
179
+ }
180
+ function logStartup(entrypoint) {
181
+ info("startup", { pid: process.pid, ppid: process.ppid, entrypoint, node: process.version });
182
+ }
183
+ function logShutdown(reason) {
184
+ info("shutdown", { pid: process.pid, reason, uptime_s: Math.round(process.uptime()) });
185
+ }
186
+ function getFileLogLines(tail = 50) {
187
+ try {
188
+ const { readdirSync: readdirSync2, readFileSync } = require("node:fs");
189
+ const dir = getLogDir();
190
+ const files = readdirSync2(dir).filter((f) => f.endsWith(".ndjson")).sort().reverse();
191
+ if (files.length === 0) return [];
192
+ const content = readFileSync(join(dir, files[0]), "utf8");
193
+ const lines = content.trim().split("\n").filter(Boolean);
194
+ return tail > 0 ? lines.slice(-tail) : lines;
195
+ } catch {
196
+ return [];
197
+ }
198
+ }
199
+ const HEAP_WARN_MB = 150;
200
+ let heapMonitorTimer = null;
201
+ function startHeapMonitor() {
202
+ if (!isFileLoggingEnabled()) return;
203
+ if (heapMonitorTimer) return;
204
+ const intervalMs = isVerboseLogging() ? 1e4 : 6e4;
205
+ heapMonitorTimer = setInterval(() => {
206
+ const heap = heapMB();
207
+ const { rss } = process.memoryUsage();
208
+ const rssMb = Math.round(rss / 1024 / 1024 * 10) / 10;
209
+ if (heap > HEAP_WARN_MB) {
210
+ warn("heap exceeds threshold", { heap_mb: heap, rss_mb: rssMb, threshold_mb: HEAP_WARN_MB });
211
+ }
212
+ const freeMb = Math.round(freemem() / 1024 / 1024);
213
+ emit({
214
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
215
+ level: "info",
216
+ msg: "heartbeat",
217
+ mem_mb: heap,
218
+ data: {
219
+ rss_mb: rssMb,
220
+ uptime_s: Math.round(process.uptime()),
221
+ system_free_mb: freeMb || void 0
222
+ }
223
+ });
224
+ }, intervalMs);
225
+ heapMonitorTimer.unref();
226
+ }
227
+ function stopHeapMonitor() {
228
+ if (heapMonitorTimer) {
229
+ clearInterval(heapMonitorTimer);
230
+ heapMonitorTimer = null;
231
+ }
232
+ }
233
+ const MAC_EPOCH_OFFSET = 978307200;
234
+ const NANOS_PER_SECOND = 1e9;
235
+ const Tables = {
236
+ MESSAGE: "message",
237
+ HANDLE: "handle",
238
+ CHAT: "chat",
239
+ CHAT_MESSAGE_JOIN: "chat_message_join",
240
+ CHAT_HANDLE_JOIN: "chat_handle_join",
241
+ ATTACHMENT: "attachment",
242
+ MESSAGE_ATTACHMENT_JOIN: "message_attachment_join"
243
+ };
244
+ const AssociatedMessageType = {
245
+ NORMAL: 0
246
+ };
247
+ const ASSOCIATED_MESSAGE_GUID_REGEX = /^p:(\d+)\/(.+)$/;
248
+ const OBJECT_REPLACEMENT_CHAR = "";
249
+ function macTimestampToDate(timestamp) {
250
+ if (timestamp == null || timestamp === 0) return null;
251
+ const unixSeconds = timestamp / NANOS_PER_SECOND + MAC_EPOCH_OFFSET;
252
+ return new Date(unixSeconds * 1e3);
253
+ }
254
+ function parseAssociatedMessageGuid(guid) {
255
+ if (!guid) return null;
256
+ const match = guid.match(ASSOCIATED_MESSAGE_GUID_REGEX);
257
+ if (match) {
258
+ return { partIndex: parseInt(match[1], 10), targetGuid: match[2] };
259
+ }
260
+ return null;
261
+ }
262
+ function isReactionType(associatedMessageType) {
263
+ return associatedMessageType >= 1e3 && associatedMessageType !== 0;
264
+ }
265
+ function dateToMacTimestamp(date) {
266
+ return Math.floor((date.getTime() / 1e3 - MAC_EPOCH_OFFSET) * NANOS_PER_SECOND);
267
+ }
268
+ function resolveChat(db, opts) {
269
+ let chatRow;
270
+ if (opts.chatGuid) {
271
+ chatRow = db.prepare("SELECT ROWID, chat_identifier FROM chat WHERE guid = ?").get(opts.chatGuid);
272
+ }
273
+ if (!chatRow && opts.chatIdentifier) {
274
+ chatRow = db.prepare("SELECT ROWID, chat_identifier FROM chat WHERE chat_identifier = ?").get(opts.chatIdentifier);
275
+ }
276
+ if (!chatRow) return null;
277
+ const handle = db.prepare("SELECT ROWID FROM handle WHERE id = ?").get(chatRow.chat_identifier);
278
+ const handleId = handle?.ROWID ?? 0;
279
+ return { chatRowId: chatRow.ROWID, handleId };
280
+ }
281
+ function insertSentMessage(dbPath, target, text) {
282
+ const db = new Database(dbPath);
283
+ db.pragma("journal_mode = WAL");
284
+ try {
285
+ const resolved = resolveChat(db, target);
286
+ if (!resolved) {
287
+ console.warn("[mock-send] Could not resolve chat for", target);
288
+ return null;
289
+ }
290
+ const now = /* @__PURE__ */ new Date();
291
+ const macDate = dateToMacTimestamp(now);
292
+ const guid = `mock:${randomUUID()}`;
293
+ const insert = db.prepare(`
294
+ INSERT INTO message (guid, text, handle_id, date, is_from_me, associated_message_type, is_read, is_delivered, service)
295
+ VALUES (?, ?, ?, ?, 1, 0, 1, 1, 'iMessage')
296
+ `);
297
+ const result = insert.run(guid, text, resolved.handleId, macDate);
298
+ const messageRowId = Number(result.lastInsertRowid);
299
+ db.prepare(
300
+ "INSERT INTO chat_message_join (chat_id, message_id, message_date) VALUES (?, ?, ?)"
301
+ ).run(resolved.chatRowId, messageRowId, macDate);
302
+ return messageRowId;
303
+ } finally {
304
+ db.close();
305
+ }
306
+ }
307
+ const execFileAsync = promisify(execFile);
308
+ function isPhoneLike(handle) {
309
+ return /^\+?[\d\s\-()]+$/.test(handle.trim());
310
+ }
311
+ const MOCK = isAiEnv() || process.env.VITEST === "true";
312
+ function mockSend(text, target) {
313
+ if (process.env.VITEST !== "true") {
314
+ try {
315
+ insertSentMessage(getImsgDbPath(), target, text);
316
+ } catch (err) {
317
+ console.warn("[mock-send] DB insert failed (non-fatal):", err);
318
+ }
319
+ }
320
+ return { success: true, timestamp: /* @__PURE__ */ new Date() };
321
+ }
322
+ async function runAppleScript(script, captureErrorForSend = false) {
323
+ try {
324
+ const { stdout, stderr } = await execFileAsync("osascript", ["-e", script], {
325
+ timeout: 3e4
326
+ });
327
+ if (stderr?.trim()) {
328
+ console.error("[osascript] stderr:", stderr);
329
+ }
330
+ return stdout.trim();
331
+ } catch (error2) {
332
+ if (captureErrorForSend) {
333
+ setLastSendError({
334
+ message: error2.message || String(error2),
335
+ stderr: error2.stderr ?? void 0,
336
+ stdout: error2.stdout ?? void 0,
337
+ code: error2.code ?? void 0
338
+ });
339
+ }
340
+ if (error2.code === "ENOENT") {
341
+ throw new Error("osascript not found. This tool requires macOS.");
342
+ }
343
+ if (error2.stderr) {
344
+ throw new Error(`AppleScript error: ${error2.stderr}`);
345
+ }
346
+ throw error2;
347
+ }
348
+ }
349
+ function appleScriptEscape(str) {
350
+ return str.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t");
351
+ }
352
+ async function sendMessageAlt(recipient, message) {
353
+ if (MOCK) return mockSend(message, { chatIdentifier: recipient });
354
+ const escapedRecipient = appleScriptEscape(recipient);
355
+ const escapedMessage = appleScriptEscape(message);
356
+ const script = `
357
+ tell application "Messages"
358
+ send "${escapedMessage}" to buddy "${escapedRecipient}" of (service 1 whose service type is iMessage)
359
+ end tell
360
+ `;
361
+ try {
362
+ await runAppleScript(script, true);
363
+ return { success: true, timestamp: /* @__PURE__ */ new Date() };
364
+ } catch (_error) {
365
+ return sendSMS(recipient, message);
366
+ }
367
+ }
368
+ async function sendSMS(phoneNumber, message) {
369
+ if (MOCK) return mockSend(message, { chatIdentifier: phoneNumber });
370
+ const escapedPhone = appleScriptEscape(phoneNumber);
371
+ const escapedMessage = appleScriptEscape(message);
372
+ const script = `
373
+ tell application "Messages"
374
+ send "${escapedMessage}" to buddy "${escapedPhone}" of (service 1 whose service type is SMS)
375
+ end tell
376
+ `;
377
+ try {
378
+ await runAppleScript(script, true);
379
+ return { success: true, timestamp: /* @__PURE__ */ new Date() };
380
+ } catch (error2) {
381
+ return { success: false, error: error2.message || String(error2) };
382
+ }
383
+ }
384
+ async function sendToChat(chatName, message) {
385
+ if (MOCK) return mockSend(message, { chatIdentifier: chatName });
386
+ const escapedName = appleScriptEscape(chatName);
387
+ const escapedMessage = appleScriptEscape(message);
388
+ const script = `
389
+ tell application "Messages"
390
+ send "${escapedMessage}" to chat "${escapedName}"
391
+ end tell
392
+ `;
393
+ try {
394
+ await runAppleScript(script, true);
395
+ return { success: true, timestamp: /* @__PURE__ */ new Date() };
396
+ } catch (error2) {
397
+ return { success: false, error: error2.message || String(error2) };
398
+ }
399
+ }
400
+ async function sendToChatId(chatId, message) {
401
+ if (MOCK) return mockSend(message, { chatGuid: chatId });
402
+ const escapedId = appleScriptEscape(chatId);
403
+ const escapedMessage = appleScriptEscape(message);
404
+ const script = `
405
+ tell application "Messages"
406
+ send "${escapedMessage}" to text chat id "${escapedId}"
407
+ end tell
408
+ `;
409
+ try {
410
+ await runAppleScript(script, true);
411
+ return { success: true, timestamp: /* @__PURE__ */ new Date() };
412
+ } catch (error2) {
413
+ return { success: false, error: error2.message || String(error2) };
414
+ }
415
+ }
416
+ async function checkMessagesAvailable() {
417
+ if (MOCK) return true;
418
+ const script = `
419
+ tell application "System Events"
420
+ return exists application process "Messages"
421
+ end tell
422
+ `;
423
+ try {
424
+ const result = await runAppleScript(script);
425
+ return result === "true";
426
+ } catch {
427
+ return false;
428
+ }
429
+ }
430
+ async function sendMessageReliable(recipient, message) {
431
+ if (MOCK) return mockSend(message, { chatIdentifier: recipient });
432
+ const tmpFile = join(tmpdir(), `imsg-send-${randomBytes(8).toString("hex")}.txt`);
433
+ try {
434
+ writeFileSync(tmpFile, message, { encoding: "utf8" });
435
+ } catch (error2) {
436
+ return { success: false, error: `Failed to stage send payload: ${error2.message ?? error2}` };
437
+ }
438
+ const escapedRecipient = appleScriptEscape(recipient);
439
+ const escapedPath = appleScriptEscape(tmpFile);
440
+ const phoneFallback = isPhoneLike(recipient);
441
+ const script = phoneFallback ? `
442
+ tell application "Messages"
443
+ set msgBody to read (POSIX file "${escapedPath}") as «class utf8»
444
+ try
445
+ set iSvc to 1st account whose service type = iMessage
446
+ send msgBody to participant "${escapedRecipient}" of iSvc
447
+ return "iMessage"
448
+ on error
449
+ set sSvc to 1st account whose service type = SMS
450
+ send msgBody to participant "${escapedRecipient}" of sSvc
451
+ return "SMS"
452
+ end try
453
+ end tell
454
+ ` : `
455
+ tell application "Messages"
456
+ set msgBody to read (POSIX file "${escapedPath}") as «class utf8»
457
+ set iSvc to 1st account whose service type = iMessage
458
+ send msgBody to participant "${escapedRecipient}" of iSvc
459
+ return "iMessage"
460
+ end tell
461
+ `;
462
+ try {
463
+ const service = await runAppleScript(script, true);
464
+ return {
465
+ success: true,
466
+ timestamp: /* @__PURE__ */ new Date(),
467
+ service: service === "SMS" || service === "iMessage" ? service : void 0
468
+ };
469
+ } catch (error2) {
470
+ return { success: false, error: error2.message || String(error2) };
471
+ } finally {
472
+ try {
473
+ unlinkSync(tmpFile);
474
+ } catch {
475
+ }
476
+ }
477
+ }
478
+ async function sendAttachment(recipient, filepath) {
479
+ if (MOCK) return mockSend(`[attachment:${filepath}]`, { chatIdentifier: recipient });
480
+ const escapedRecipient = appleScriptEscape(recipient);
481
+ const escapedPath = appleScriptEscape(filepath);
482
+ const phoneFallback = isPhoneLike(recipient);
483
+ const script = phoneFallback ? `
484
+ tell application "Messages"
485
+ try
486
+ set iSvc to 1st account whose service type = iMessage
487
+ send (POSIX file "${escapedPath}") to participant "${escapedRecipient}" of iSvc
488
+ return "iMessage"
489
+ on error
490
+ set sSvc to 1st account whose service type = SMS
491
+ send (POSIX file "${escapedPath}") to participant "${escapedRecipient}" of sSvc
492
+ return "SMS"
493
+ end try
494
+ end tell
495
+ ` : `
496
+ tell application "Messages"
497
+ set iSvc to 1st account whose service type = iMessage
498
+ send (POSIX file "${escapedPath}") to participant "${escapedRecipient}" of iSvc
499
+ return "iMessage"
500
+ end tell
501
+ `;
502
+ try {
503
+ const service = await runAppleScript(script, true);
504
+ return {
505
+ success: true,
506
+ timestamp: /* @__PURE__ */ new Date(),
507
+ service: service === "SMS" || service === "iMessage" ? service : void 0
508
+ };
509
+ } catch (error2) {
510
+ return { success: false, error: error2.message || String(error2) };
511
+ }
512
+ }
513
+ async function checkImessageAvailability(handle) {
514
+ if (MOCK) {
515
+ return { service: "iMessage", reachable: true };
516
+ }
517
+ const escaped = appleScriptEscape(handle);
518
+ const script = `
519
+ tell application "Messages"
520
+ try
521
+ set b to buddy "${escaped}" of (1st account whose service type is iMessage)
522
+ return "iMessage"
523
+ on error
524
+ try
525
+ set b to buddy "${escaped}" of (1st account whose service type is SMS)
526
+ return "SMS"
527
+ on error
528
+ return "unknown"
529
+ end try
530
+ end try
531
+ end tell
532
+ `;
533
+ try {
534
+ const result = await runAppleScript(script);
535
+ if (result === "iMessage" || result === "SMS") {
536
+ return { service: result, reachable: true };
537
+ }
538
+ return {
539
+ service: "unknown",
540
+ reachable: false,
541
+ hint: isPhoneLike(handle) ? "Handle not found in iMessage or SMS buddies. Verify the number format (try '+1...' for US) and that the recipient has at least one of iMessage or SMS reachable." : "Handle not found in iMessage buddies. For email addresses, the recipient must have iMessage active on that address."
542
+ };
543
+ } catch (error2) {
544
+ return {
545
+ service: "unknown",
546
+ reachable: false,
547
+ hint: `Availability check failed: ${error2.message ?? error2}. Most common cause: Messages.app Automation permission is not granted to this terminal/IDE.`
548
+ };
549
+ }
550
+ }
551
+ function inspectSqlite(path) {
552
+ const db = new Database(path, { readonly: true, fileMustExist: true });
553
+ try {
554
+ db.pragma("schema_version");
555
+ return null;
556
+ } finally {
557
+ db.close();
558
+ }
559
+ }
560
+ function classifyDbError(error2) {
561
+ const message = error2 instanceof Error ? error2.message : String(error2);
562
+ if (/operation not permitted/i.test(message) || /authorization denied/i.test(message)) {
563
+ return [
564
+ "Full Disk Access is missing for the app running this command.",
565
+ "Grant Full Disk Access to Terminal, iTerm2, Warp, VS Code, Cursor, or whichever app is launching imsg-mcp, then fully restart that app.",
566
+ "Open System Settings -> Privacy & Security -> Full Disk Access."
567
+ ].join(" ");
568
+ }
569
+ if (/unable to open database file/i.test(message) || /no such file/i.test(message)) {
570
+ return "Database file was not found at the configured path.";
571
+ }
572
+ return message;
573
+ }
574
+ function iconFor(status) {
575
+ if (status === "ok") return "OK";
576
+ if (status === "warn") return "WARN";
577
+ return "ERR";
578
+ }
579
+ async function checkLocalAccess() {
580
+ const items = [];
581
+ items.push({
582
+ key: "platform",
583
+ label: "Platform",
584
+ status: process.platform === "darwin" ? "ok" : "error",
585
+ detail: process.platform === "darwin" ? `Running on macOS ${os.release()}` : `Running on ${process.platform}; live iMessage access only works on macOS.`
586
+ });
587
+ const imsgDbPath = getImsgDbPath();
588
+ if (!existsSync(imsgDbPath)) {
589
+ items.push({
590
+ key: "messages-db",
591
+ label: "Messages DB",
592
+ status: "error",
593
+ detail: `Missing ${imsgDbPath}`
594
+ });
595
+ } else {
596
+ try {
597
+ const error2 = inspectSqlite(imsgDbPath);
598
+ items.push({
599
+ key: "messages-db",
600
+ label: "Messages DB",
601
+ status: error2 ? "error" : "ok",
602
+ detail: error2 ? classifyDbError(error2) : `Readable at ${imsgDbPath}`
603
+ });
604
+ } catch (error2) {
605
+ items.push({
606
+ key: "messages-db",
607
+ label: "Messages DB",
608
+ status: "error",
609
+ detail: classifyDbError(error2)
610
+ });
611
+ }
612
+ }
613
+ const contactPaths = getContactsDbPaths() ?? [];
614
+ const existingContactPaths = contactPaths.filter((path) => existsSync(path));
615
+ if (existingContactPaths.length === 0) {
616
+ items.push({
617
+ key: "contacts-db",
618
+ label: "Contacts DB",
619
+ status: "warn",
620
+ detail: "No readable Address Book database was found. Message reads still work, but names may stay as raw phone numbers or emails."
621
+ });
622
+ } else {
623
+ const failures = [];
624
+ for (const path of existingContactPaths) {
625
+ try {
626
+ inspectSqlite(path);
627
+ } catch (error2) {
628
+ failures.push(`${path}: ${classifyDbError(error2)}`);
629
+ }
630
+ }
631
+ items.push({
632
+ key: "contacts-db",
633
+ label: "Contacts DB",
634
+ status: failures.length === 0 ? "ok" : "warn",
635
+ detail: failures.length === 0 ? `Readable at ${existingContactPaths.join(", ")}` : `Some contact databases are unreadable. ${failures.join(" ")}`
636
+ });
637
+ }
638
+ const messagesRunning = await checkMessagesAvailable().catch(() => false);
639
+ items.push({
640
+ key: "messages-app",
641
+ label: "Messages.app",
642
+ status: messagesRunning ? "ok" : "warn",
643
+ detail: messagesRunning ? "Messages.app is running." : "Messages.app is not running. Reading still works, but sending requires Messages.app to be open."
644
+ });
645
+ return {
646
+ ok: items.every((item) => item.status !== "error"),
647
+ items
648
+ };
649
+ }
650
+ function formatAccessReport(report) {
651
+ const lines = ["imsg-mcp doctor", ""];
652
+ for (const item of report.items) {
653
+ lines.push(`${iconFor(item.status)} ${item.label}`);
654
+ lines.push(` ${item.detail}`);
655
+ }
656
+ lines.push("");
657
+ lines.push(
658
+ report.ok ? "Environment looks ready for local message reads." : "Fix the ERR items above, then rerun `imsg doctor`."
659
+ );
660
+ return lines.join("\n");
661
+ }
662
+ const registry = /* @__PURE__ */ new Set();
663
+ let shuttingDown = false;
664
+ let watchdogTimer = null;
665
+ function registerCleanup(fn) {
666
+ registry.add(fn);
667
+ }
668
+ async function shutdown(exitCode = 0) {
669
+ if (shuttingDown) {
670
+ setTimeout(() => process.exit(exitCode), 3e3).unref();
671
+ return new Promise(() => {
672
+ });
673
+ }
674
+ shuttingDown = true;
675
+ if (watchdogTimer) {
676
+ clearInterval(watchdogTimer);
677
+ watchdogTimer = null;
678
+ }
679
+ for (const fn of registry) {
680
+ try {
681
+ await fn();
682
+ } catch {
683
+ }
684
+ }
685
+ registry.clear();
686
+ process.exit(exitCode);
687
+ }
688
+ function syncCleanup() {
689
+ for (const fn of registry) {
690
+ try {
691
+ const result = fn();
692
+ if (result && typeof result.catch === "function") {
693
+ result.catch(() => {
694
+ });
695
+ }
696
+ } catch {
697
+ }
698
+ }
699
+ }
700
+ function installShutdownHandlers() {
701
+ const onSignal = (signal) => {
702
+ try {
703
+ appendLog("info", "signal_received", { signal });
704
+ } catch {
705
+ }
706
+ shutdown(signal === "SIGINT" ? 130 : 0);
707
+ };
708
+ for (const sig of ["SIGINT", "SIGTERM", "SIGHUP", "SIGQUIT"]) {
709
+ process.on(sig, () => onSignal(sig));
710
+ }
711
+ process.on("exit", syncCleanup);
712
+ }
713
+ function enableStdinEofDetection() {
714
+ process.stdin.on("end", () => {
715
+ if (!shuttingDown) shutdown(0);
716
+ });
717
+ process.stdin.resume();
718
+ }
719
+ function enableOrphanWatchdog(intervalMs = 5e3) {
720
+ if (watchdogTimer) return;
721
+ const parentPid = process.ppid;
722
+ watchdogTimer = setInterval(() => {
723
+ if (process.ppid === 1 || process.ppid !== parentPid) {
724
+ shutdown(0);
725
+ }
726
+ }, intervalMs);
727
+ watchdogTimer.unref();
728
+ }
729
+ function isShuttingDown() {
730
+ return shuttingDown;
731
+ }
732
+ export {
733
+ stopHeapMonitor as A,
734
+ getLogFilePath as B,
735
+ getLastSendError as C,
736
+ isShuttingDown as D,
737
+ warn as E,
738
+ error as F,
739
+ AssociatedMessageType as G,
740
+ macTimestampToDate as H,
741
+ isReactionType as I,
742
+ parseAssociatedMessageGuid as J,
743
+ config as K,
744
+ MAC_EPOCH_OFFSET as M,
745
+ NANOS_PER_SECOND as N,
746
+ OBJECT_REPLACEMENT_CHAR as O,
747
+ Tables as T,
748
+ getContactsDbPaths as a,
749
+ getSlugsDbPath as b,
750
+ checkLocalAccess as c,
751
+ appendLog as d,
752
+ getFileLogLines as e,
753
+ formatAccessReport as f,
754
+ getImsgDbPath as g,
755
+ getLogDirectory as h,
756
+ checkMessagesAvailable as i,
757
+ sendToChat as j,
758
+ sendToChatId as k,
759
+ sendMessageReliable as l,
760
+ sendMessageAlt as m,
761
+ sendAttachment as n,
762
+ checkImessageAvailability as o,
763
+ perf as p,
764
+ installShutdownHandlers as q,
765
+ registerCleanup as r,
766
+ shutdown as s,
767
+ enableStdinEofDetection as t,
768
+ enableOrphanWatchdog as u,
769
+ logStartup as v,
770
+ info as w,
771
+ startHeapMonitor as x,
772
+ getLogs as y,
773
+ logShutdown as z
774
+ };
775
+ //# sourceMappingURL=shutdown-B9ClCyco.js.map