imsg-mcp 1.3.2 → 1.4.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.
- package/CHANGELOG.md +25 -0
- package/README.md +3 -2
- package/dist/cli.js +47 -5
- package/dist/cli.js.map +1 -1
- package/dist/{imessage-db-Cz8MIIpG.js → imessage-db-C8WKHFQA.js} +102 -15
- package/dist/imessage-db-C8WKHFQA.js.map +1 -0
- package/dist/index.js +848 -73
- package/dist/index.js.map +1 -1
- package/dist/{meta-DzE7CSrZ.js → meta-CGz9n7A2.js} +2 -2
- package/dist/{meta-DzE7CSrZ.js.map → meta-CGz9n7A2.js.map} +1 -1
- package/dist/{setup-BuXuKAHA.js → setup-BDnz3OuH.js} +2 -2
- package/dist/{setup-BuXuKAHA.js.map → setup-BDnz3OuH.js.map} +1 -1
- package/dist/{shutdown-pPAPSnk3.js → shutdown-CqGQKFmr.js} +73 -47
- package/dist/shutdown-CqGQKFmr.js.map +1 -0
- package/dist/tui.js +216 -60
- package/dist/tui.js.map +1 -1
- package/dist/{watchdog-Cbgm050b.js → watchdog-CizaS0Jv.js} +80 -4
- package/dist/watchdog-CizaS0Jv.js.map +1 -0
- package/native/imsg-native.darwin-arm64.node +0 -0
- package/package.json +1 -1
- package/skills/humans/SKILL.md +136 -0
- package/skills/imsg-mcp/SKILL.md +8 -0
- package/dist/imessage-db-Cz8MIIpG.js.map +0 -1
- package/dist/shutdown-pPAPSnk3.js.map +0 -1
- package/dist/watchdog-Cbgm050b.js.map +0 -1
|
@@ -4,7 +4,7 @@ import Database from "better-sqlite3";
|
|
|
4
4
|
import { createRequire } from "node:module";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import { existsSync, readdirSync, mkdirSync } from "node:fs";
|
|
7
|
-
import { O as OBJECT_REPLACEMENT_CHAR, p as perf,
|
|
7
|
+
import { O as OBJECT_REPLACEMENT_CHAR, p as perf, M as isGroupGuid, I as isGroupChatIdentifier, N as generateThreadSlug, T as Tables, P as AssociatedMessageType, Q as macAutoTimestampToDate, R as isReactionType, S as MAC_EPOCH_OFFSET, U as NANOS_PER_SECOND, V as macTimestampToDate$1, W as parseAssociatedMessageGuid$1 } from "./shutdown-CqGQKFmr.js";
|
|
8
8
|
import { parseBuffer } from "bplist-parser";
|
|
9
9
|
function normalizePhoneNumber(phone) {
|
|
10
10
|
const digits = phone.replace(/\D/g, "");
|
|
@@ -633,6 +633,8 @@ function getNativeParser() {
|
|
|
633
633
|
}
|
|
634
634
|
function nativeResultLooksTrustworthy(text) {
|
|
635
635
|
if (/at_\d+_[0-9A-F-]{8,}/i.test(text)) return false;
|
|
636
|
+
if (/^.?[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}$/i.test(text)) return false;
|
|
637
|
+
if (/^.?at_[0-9A-F][0-9A-F_-]{19,}$/i.test(text)) return false;
|
|
636
638
|
if (/__kIM[A-Z]/.test(text)) return false;
|
|
637
639
|
if (/\$class|streamtyped|NSKeyedArchiver/.test(text)) return false;
|
|
638
640
|
if (/^NS[A-Z][a-z]+/.test(text)) return false;
|
|
@@ -656,6 +658,13 @@ const METADATA_PATTERNS = [
|
|
|
656
658
|
/^NSNumber$/,
|
|
657
659
|
/^NSValue$/,
|
|
658
660
|
/^at_\d+_[0-9A-F-]+$/i,
|
|
661
|
+
// Bare-UUID attribute values (sticker/attachment GUIDs), optionally with the
|
|
662
|
+
// leaked typedstream length byte in front ("$FE7B0D17-…", '$' = 0x24 = 36 =
|
|
663
|
+
// a UUID's length). No human message is exactly a bare UUID.
|
|
664
|
+
/^[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}$/i,
|
|
665
|
+
/^.[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}$/i,
|
|
666
|
+
// Transfer-GUID with leaked length byte ("Mat_BDA9FB97-…", 'M' = 77).
|
|
667
|
+
/^.at_[0-9A-F][0-9A-F_-]{19,}$/i,
|
|
659
668
|
/FileTransferGUIDAttributeName/i,
|
|
660
669
|
/BaseWritingDirectionAttributeName/i,
|
|
661
670
|
/^\d{2}.*��/
|
|
@@ -707,23 +716,30 @@ function extractAttributedBodyText(blob) {
|
|
|
707
716
|
if (score > prev) candidates.set(normalized, score);
|
|
708
717
|
}
|
|
709
718
|
};
|
|
719
|
+
let structuredTrusted = false;
|
|
710
720
|
try {
|
|
711
721
|
if (Date.now() < deadline) {
|
|
722
|
+
const strings = parser.parseAllNSStrings();
|
|
723
|
+
structuredTrusted = strings.some(
|
|
724
|
+
(entry) => normalizeCandidate(entry.content) !== null || /^[\uFFFC\s]+$/.test(entry.content)
|
|
725
|
+
);
|
|
712
726
|
remember(
|
|
713
|
-
|
|
727
|
+
strings.map((entry) => entry.content),
|
|
714
728
|
STRUCTURED_BOOST
|
|
715
729
|
);
|
|
716
730
|
}
|
|
717
731
|
} catch {
|
|
718
732
|
}
|
|
719
|
-
|
|
733
|
+
if (!structuredTrusted) {
|
|
734
|
+
try {
|
|
735
|
+
if (Date.now() < deadline) {
|
|
736
|
+
remember(parser.extractReadableText());
|
|
737
|
+
}
|
|
738
|
+
} catch {
|
|
739
|
+
}
|
|
720
740
|
if (Date.now() < deadline) {
|
|
721
|
-
remember(
|
|
741
|
+
remember(blob.toString("utf8").split(CONTROL_SPLIT_RE));
|
|
722
742
|
}
|
|
723
|
-
} catch {
|
|
724
|
-
}
|
|
725
|
-
if (Date.now() < deadline) {
|
|
726
|
-
remember(blob.toString("utf8").split(CONTROL_SPLIT_RE));
|
|
727
743
|
}
|
|
728
744
|
const best = [...candidates.entries()].sort((a, b) => b[1] - a[1])[0];
|
|
729
745
|
return best?.[0];
|
|
@@ -760,6 +776,10 @@ function normalizeText(value) {
|
|
|
760
776
|
const normalized = value?.replace(/\s+/g, " ").trim();
|
|
761
777
|
return normalized && normalized.length >= 4 ? normalized : void 0;
|
|
762
778
|
}
|
|
779
|
+
function isPlausibleHumanText(s) {
|
|
780
|
+
const wordish = (s.match(/[\p{L}\p{N}\s]/gu) ?? []).length;
|
|
781
|
+
return wordish / s.length >= 0.7 && new RegExp("\\p{L}{2}", "u").test(s);
|
|
782
|
+
}
|
|
763
783
|
function extractNullPaddedAsciiText(blob) {
|
|
764
784
|
if (!blob) return void 0;
|
|
765
785
|
let best = "";
|
|
@@ -781,7 +801,8 @@ function extractNullPaddedAsciiText(blob) {
|
|
|
781
801
|
flush();
|
|
782
802
|
}
|
|
783
803
|
flush();
|
|
784
|
-
|
|
804
|
+
const normalized = normalizeText(best);
|
|
805
|
+
return normalized && isPlausibleHumanText(normalized) ? normalized : void 0;
|
|
785
806
|
}
|
|
786
807
|
function extractArchivedAttributedStringText(blob) {
|
|
787
808
|
if (!blob) return void 0;
|
|
@@ -1420,6 +1441,7 @@ class IMessageDB {
|
|
|
1420
1441
|
m.is_read,
|
|
1421
1442
|
m.date_read,
|
|
1422
1443
|
m.is_delivered,
|
|
1444
|
+
m.error,
|
|
1423
1445
|
m.date_delivered,
|
|
1424
1446
|
m.associated_message_type,
|
|
1425
1447
|
m.associated_message_guid,
|
|
@@ -1529,13 +1551,69 @@ class IMessageDB {
|
|
|
1529
1551
|
const messages = await this.getMessagesForChat(chatIdentifier, 1);
|
|
1530
1552
|
return messages.length > 0 ? messages[messages.length - 1] : null;
|
|
1531
1553
|
}
|
|
1554
|
+
/**
|
|
1555
|
+
* The service the conversation ACTUALLY works on, judged by delivery
|
|
1556
|
+
* evidence: the most recent message that either came FROM the other party
|
|
1557
|
+
* (any service they use reaches us) or was sent by us WITHOUT error.
|
|
1558
|
+
* Failed sends are excluded — otherwise one wrong-service attempt (e.g. an
|
|
1559
|
+
* iMessage 'sent' to an SMS-only number, error 22) mints a phantom leg,
|
|
1560
|
+
* flips the thread's apparent service, and every later send repeats the
|
|
1561
|
+
* mistake. Returns null when the conversation has no usable evidence.
|
|
1562
|
+
*/
|
|
1563
|
+
getPreferredSendService(chatIdentifier) {
|
|
1564
|
+
const chats = this.resolveChatsForConversation(chatIdentifier);
|
|
1565
|
+
if (chats.length === 0) return null;
|
|
1566
|
+
const placeholders = chats.map(() => "?").join(",");
|
|
1567
|
+
const row = this.raw.prepare(
|
|
1568
|
+
`SELECT m.service
|
|
1569
|
+
FROM ${Tables.MESSAGE} m
|
|
1570
|
+
JOIN ${Tables.CHAT_MESSAGE_JOIN} j ON j.message_id = m.ROWID
|
|
1571
|
+
WHERE j.chat_id IN (${placeholders})
|
|
1572
|
+
AND (m.associated_message_type = 0 OR m.associated_message_type IS NULL)
|
|
1573
|
+
AND (m.is_from_me = 0 OR m.error = 0 OR m.error IS NULL)
|
|
1574
|
+
AND m.service IN ('iMessage', 'SMS')
|
|
1575
|
+
ORDER BY m.date DESC, m.ROWID DESC
|
|
1576
|
+
LIMIT 1`
|
|
1577
|
+
).get(...chats.map((c) => c.ROWID));
|
|
1578
|
+
if (row?.service === "SMS") return "SMS";
|
|
1579
|
+
if (row?.service === "iMessage") return "iMessage";
|
|
1580
|
+
return null;
|
|
1581
|
+
}
|
|
1582
|
+
/**
|
|
1583
|
+
* Aggregate stats over ALL merged legs of a conversation — one SQL
|
|
1584
|
+
* COUNT/MIN/MAX instead of materializing messages. Reactions and other
|
|
1585
|
+
* associated messages are excluded (normal items only). Used by
|
|
1586
|
+
* init_human to prefill first/last contact + volume.
|
|
1587
|
+
*/
|
|
1588
|
+
getChatStats(chatIdentifier) {
|
|
1589
|
+
const chats = this.resolveChatsForConversation(chatIdentifier);
|
|
1590
|
+
if (chats.length === 0) return { count: 0, first: null, last: null };
|
|
1591
|
+
const placeholders = chats.map(() => "?").join(",");
|
|
1592
|
+
const row = this.raw.prepare(
|
|
1593
|
+
`SELECT COUNT(*) as count, MIN(m.date) as first, MAX(m.date) as last
|
|
1594
|
+
FROM ${Tables.MESSAGE} m
|
|
1595
|
+
JOIN ${Tables.CHAT_MESSAGE_JOIN} j ON j.message_id = m.ROWID
|
|
1596
|
+
WHERE j.chat_id IN (${placeholders})
|
|
1597
|
+
AND (m.associated_message_type = 0 OR m.associated_message_type IS NULL)`
|
|
1598
|
+
).get(...chats.map((c) => c.ROWID));
|
|
1599
|
+
return {
|
|
1600
|
+
count: row.count ?? 0,
|
|
1601
|
+
first: row.first != null ? macTimestampToDate(row.first) : null,
|
|
1602
|
+
last: row.last != null ? macTimestampToDate(row.last) : null
|
|
1603
|
+
};
|
|
1604
|
+
}
|
|
1532
1605
|
/**
|
|
1533
1606
|
* Get messages after a specific message ID (for polling new messages).
|
|
1534
|
-
*
|
|
1607
|
+
* Sorted by date ascending (chronological). By default returns incoming
|
|
1608
|
+
* messages only; `includeSelf` also returns from-me rows (the user's own
|
|
1609
|
+
* interjections from other devices) — callers using it must suppress the
|
|
1610
|
+
* agent's own send echo themselves (see SentEchoRegistry).
|
|
1535
1611
|
*/
|
|
1536
|
-
async getMessagesAfter(chatIdentifier, afterMessageId) {
|
|
1612
|
+
async getMessagesAfter(chatIdentifier, afterMessageId, options = {}) {
|
|
1537
1613
|
const messages = await this.getMessagesForChat(chatIdentifier, 1e3, { afterMessageId });
|
|
1538
|
-
const filtered = messages.filter(
|
|
1614
|
+
const filtered = messages.filter(
|
|
1615
|
+
(m) => m.id !== afterMessageId && (options.includeSelf || !m.isFromMe)
|
|
1616
|
+
);
|
|
1539
1617
|
filtered.sort((a, b) => a.date.getTime() - b.date.getTime());
|
|
1540
1618
|
return filtered;
|
|
1541
1619
|
}
|
|
@@ -2230,6 +2308,10 @@ class IMessageDB {
|
|
|
2230
2308
|
dateDelivered: macTimestampToDate(ext.date_delivered ?? null),
|
|
2231
2309
|
isRead: ext.is_read != null ? Boolean(ext.is_read) : true,
|
|
2232
2310
|
isDelivered: ext.is_delivered != null ? Boolean(ext.is_delivered) : true,
|
|
2311
|
+
// Send failure surface: chat.db sets error != 0 (and is_sent = 0) on
|
|
2312
|
+
// from-me messages that never went out — e.g. an iMessage attempt to an
|
|
2313
|
+
// SMS-only number. Messages.app shows "Not Delivered"; so must we.
|
|
2314
|
+
sendError: raw.is_from_me && ext.error ? ext.error : void 0,
|
|
2233
2315
|
chatId,
|
|
2234
2316
|
service: this.detectServiceForMessage(ext),
|
|
2235
2317
|
isReaction,
|
|
@@ -2312,6 +2394,7 @@ class IMessageDB {
|
|
|
2312
2394
|
m.is_read,
|
|
2313
2395
|
m.date_read,
|
|
2314
2396
|
m.is_delivered,
|
|
2397
|
+
m.error,
|
|
2315
2398
|
m.date_delivered,
|
|
2316
2399
|
h.id as handle_id,
|
|
2317
2400
|
h.service as handle_service,
|
|
@@ -2348,7 +2431,8 @@ class IMessageDB {
|
|
|
2348
2431
|
const stmt = this.raw.prepare(`
|
|
2349
2432
|
SELECT
|
|
2350
2433
|
m.ROWID as _rowid,
|
|
2351
|
-
m.is_read, m.date_read, m.is_delivered,
|
|
2434
|
+
m.is_read, m.date_read, m.is_delivered,
|
|
2435
|
+
m.error, m.date_delivered,
|
|
2352
2436
|
h.id as handle_id, h.service as handle_service,
|
|
2353
2437
|
m.associated_message_type, m.associated_message_guid,
|
|
2354
2438
|
m.associated_message_emoji, m.thread_originator_guid,
|
|
@@ -2381,7 +2465,8 @@ class IMessageDB {
|
|
|
2381
2465
|
const cutoffNanos = Math.floor((cutoffMs / 1e3 - MAC_EPOCH_OFFSET) * NANOS_PER_SECOND);
|
|
2382
2466
|
const sql = `
|
|
2383
2467
|
SELECT m.ROWID, m.guid, m.text, m.attributedBody, m.date, m.date_read, m.date_delivered,
|
|
2384
|
-
m.is_read, m.is_delivered,
|
|
2468
|
+
m.is_read, m.is_delivered,
|
|
2469
|
+
m.error, m.is_from_me, h.id as handle_id,
|
|
2385
2470
|
h.service as handle_service,
|
|
2386
2471
|
m.cache_has_attachments, m.associated_message_type,
|
|
2387
2472
|
m.associated_message_guid, m.associated_message_emoji,
|
|
@@ -2517,6 +2602,7 @@ class IMessageDB {
|
|
|
2517
2602
|
fetchAttachments(messageRowId) {
|
|
2518
2603
|
const stmt = this.raw.prepare(`
|
|
2519
2604
|
SELECT
|
|
2605
|
+
a.ROWID as row_id,
|
|
2520
2606
|
a.filename,
|
|
2521
2607
|
a.mime_type,
|
|
2522
2608
|
a.transfer_name,
|
|
@@ -2527,6 +2613,7 @@ class IMessageDB {
|
|
|
2527
2613
|
`);
|
|
2528
2614
|
const rows = stmt.all(messageRowId);
|
|
2529
2615
|
return rows.map((r) => ({
|
|
2616
|
+
rowId: r.row_id,
|
|
2530
2617
|
filename: r.filename || "",
|
|
2531
2618
|
mimeType: r.mime_type,
|
|
2532
2619
|
transferName: r.transfer_name,
|
|
@@ -2662,4 +2749,4 @@ export {
|
|
|
2662
2749
|
imessageDb as i,
|
|
2663
2750
|
normalizedPhoneVariants as n
|
|
2664
2751
|
};
|
|
2665
|
-
//# sourceMappingURL=imessage-db-
|
|
2752
|
+
//# sourceMappingURL=imessage-db-C8WKHFQA.js.map
|