imsg-mcp 1.15.1 → 1.16.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 +7 -0
- package/dist/cli.js +3 -3
- package/dist/{imessage-db-7ldOiURZ.js → imessage-db-DXFIm7is.js} +151 -76
- package/dist/imessage-db-DXFIm7is.js.map +1 -0
- package/dist/index.js +21 -11
- package/dist/index.js.map +1 -1
- package/dist/{meta-BzRJT7r0.js → meta-DcLrk46N.js} +2 -2
- package/dist/{meta-BzRJT7r0.js.map → meta-DcLrk46N.js.map} +1 -1
- package/dist/tui.js +1 -1
- package/package.json +1 -1
- package/dist/imessage-db-7ldOiURZ.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,13 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and follows [Semantic Versioning](https://semver.org/).
|
|
5
5
|
|
|
6
|
+
# [1.16.0](https://github.com/george43g/imsg-mcp/compare/v1.15.1...v1.16.0) (2026-07-25)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* **contacts:** rank search_contacts by relevance with matchedField ([352ccd2](https://github.com/george43g/imsg-mcp/commit/352ccd2da1263f26baa31ef38737a9d52bdf3aa0))
|
|
12
|
+
|
|
6
13
|
## [1.15.1](https://github.com/george43g/imsg-mcp/compare/v1.15.0...v1.15.1) (2026-07-25)
|
|
7
14
|
|
|
8
15
|
|
package/dist/cli.js
CHANGED
|
@@ -4,7 +4,7 @@ import { createInterface } from "node:readline";
|
|
|
4
4
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
5
5
|
import { Command } from "commander";
|
|
6
6
|
import { d as checkLocalAccess, f as formatAccessReport, I as IMPLEMENTED_TYPES, A as ANALYTIC_INFO, i as installShutdownHandlers, r as registerCleanup, l as looksLikeThreadSlug } from "./shutdown-0TCv4XJB.js";
|
|
7
|
-
import { A as APP_VERSION, t as toYaml } from "./meta-
|
|
7
|
+
import { A as APP_VERSION, t as toYaml } from "./meta-DcLrk46N.js";
|
|
8
8
|
import { spawn } from "node:child_process";
|
|
9
9
|
import { join, dirname } from "node:path";
|
|
10
10
|
function distRoot() {
|
|
@@ -447,7 +447,7 @@ async function runExportCommand(target, opts) {
|
|
|
447
447
|
const { homedir } = await import("node:os");
|
|
448
448
|
const { dirname: dirname2, join: join2, isAbsolute, resolve } = await import("node:path");
|
|
449
449
|
const { getContactsDbPaths, getImsgDbPath, getSlugsDbPath } = await import("./shutdown-0TCv4XJB.js").then((n) => n.a2);
|
|
450
|
-
const { IMessageDB } = await import("./imessage-db-
|
|
450
|
+
const { IMessageDB } = await import("./imessage-db-DXFIm7is.js").then((n) => n.i);
|
|
451
451
|
const { streamExport } = await import("./exportStream-BFxilSlL.js");
|
|
452
452
|
const { parseUserDate } = await import("./date-parse-DJXMfq3a.js");
|
|
453
453
|
const format = normalizeFormat(opts.format ?? "md");
|
|
@@ -719,7 +719,7 @@ program.command("interpret <rowId>").description("Interpret one attachment (tran
|
|
|
719
719
|
return;
|
|
720
720
|
}
|
|
721
721
|
const { getContactsDbPaths, getImsgDbPath, getSlugsDbPath } = await import("./shutdown-0TCv4XJB.js").then((n) => n.a2);
|
|
722
|
-
const { IMessageDB } = await import("./imessage-db-
|
|
722
|
+
const { IMessageDB } = await import("./imessage-db-DXFIm7is.js").then((n) => n.i);
|
|
723
723
|
const { getInterpretRuntime, refForAttachment } = await import("./media-intel-runtime-BkxG4bmW.js").then((n) => n.c);
|
|
724
724
|
const { deleteMediaIntel } = await import("./media-intel-cache-mCgH4z9P.js");
|
|
725
725
|
const db = new IMessageDB(getImsgDbPath(), getContactsDbPaths(), getSlugsDbPath());
|
|
@@ -6,6 +6,144 @@ import { fileURLToPath } from "node:url";
|
|
|
6
6
|
import { existsSync, readdirSync, mkdirSync } from "node:fs";
|
|
7
7
|
import { O as OBJECT_REPLACEMENT_CHAR, p as perf, m as isGroupGuid, n as isGroupChatIdentifier, q as generateThreadSlug, T as Tables, t as AssociatedMessageType, u as macAutoTimestampToDate, v as isReactionType, M as MAC_EPOCH_OFFSET, N as NANOS_PER_SECOND, x as macTimestampToDate$1, y as parseAssociatedMessageGuid$1 } from "./shutdown-0TCv4XJB.js";
|
|
8
8
|
import bplist, { parseBuffer } from "bplist-parser";
|
|
9
|
+
const EMOJI_REGEX = /[\u{1F300}-\u{1FAFF}\u{2600}-\u{27BF}\u{1F1E6}-\u{1F1FF}]/gu;
|
|
10
|
+
const WHITESPACE_REGEX = /\s+/g;
|
|
11
|
+
function cleanText(s) {
|
|
12
|
+
return s.toLowerCase().replace(EMOJI_REGEX, " ").replace(WHITESPACE_REGEX, " ").trim();
|
|
13
|
+
}
|
|
14
|
+
function tokenSet(s) {
|
|
15
|
+
if (!s) return /* @__PURE__ */ new Set();
|
|
16
|
+
return new Set(s.split(" ").filter(Boolean));
|
|
17
|
+
}
|
|
18
|
+
function levenshtein(a, b, maxLen = 200) {
|
|
19
|
+
if (a === b) return 0;
|
|
20
|
+
const aLen = Math.min(a.length, maxLen);
|
|
21
|
+
const bLen = Math.min(b.length, maxLen);
|
|
22
|
+
if (aLen === 0) return bLen;
|
|
23
|
+
if (bLen === 0) return aLen;
|
|
24
|
+
let prev = new Array(bLen + 1);
|
|
25
|
+
let curr = new Array(bLen + 1);
|
|
26
|
+
for (let j = 0; j <= bLen; j++) prev[j] = j;
|
|
27
|
+
for (let i = 1; i <= aLen; i++) {
|
|
28
|
+
curr[0] = i;
|
|
29
|
+
for (let j = 1; j <= bLen; j++) {
|
|
30
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
31
|
+
const del = (prev[j] ?? 0) + 1;
|
|
32
|
+
const ins = (curr[j - 1] ?? 0) + 1;
|
|
33
|
+
const sub = (prev[j - 1] ?? 0) + cost;
|
|
34
|
+
curr[j] = Math.min(del, ins, sub);
|
|
35
|
+
}
|
|
36
|
+
[prev, curr] = [curr, prev];
|
|
37
|
+
}
|
|
38
|
+
return prev[bLen] ?? Math.max(aLen, bLen);
|
|
39
|
+
}
|
|
40
|
+
function levenshteinRatio(a, b) {
|
|
41
|
+
if (a === b) return 1;
|
|
42
|
+
const aLen = Math.min(a.length, 200);
|
|
43
|
+
const bLen = Math.min(b.length, 200);
|
|
44
|
+
const maxLen = Math.max(aLen, bLen);
|
|
45
|
+
if (maxLen === 0) return 1;
|
|
46
|
+
return 1 - levenshtein(a, b) / maxLen;
|
|
47
|
+
}
|
|
48
|
+
function fuzzyScore(query, candidate) {
|
|
49
|
+
if (query && candidate && candidate.toLowerCase().includes(query.toLowerCase())) {
|
|
50
|
+
return 0.95;
|
|
51
|
+
}
|
|
52
|
+
const q = cleanText(query);
|
|
53
|
+
const c = cleanText(candidate);
|
|
54
|
+
if (!q || !c) return 0;
|
|
55
|
+
if (c.includes(q)) return 0.95;
|
|
56
|
+
if (q.includes(c)) return 0.9;
|
|
57
|
+
const qTokens = tokenSet(q);
|
|
58
|
+
const cTokens = tokenSet(c);
|
|
59
|
+
let intersection = 0;
|
|
60
|
+
for (const tok of qTokens) {
|
|
61
|
+
if (cTokens.has(tok)) intersection++;
|
|
62
|
+
}
|
|
63
|
+
const dice = 2 * intersection / (qTokens.size + cTokens.size || 1);
|
|
64
|
+
let bestTokenLev = 0;
|
|
65
|
+
if (qTokens.size === 1) {
|
|
66
|
+
for (const tok of cTokens) {
|
|
67
|
+
const r = levenshteinRatio(q, tok);
|
|
68
|
+
if (r > bestTokenLev) bestTokenLev = r;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
const levRatio = Math.max(levenshteinRatio(q, c), bestTokenLev);
|
|
72
|
+
const blend = qTokens.size <= 1 ? 0.1 * dice + 0.9 * levRatio : 0.7 * dice + 0.3 * levRatio;
|
|
73
|
+
return Math.max(0, Math.min(1, blend));
|
|
74
|
+
}
|
|
75
|
+
function rankFuzzy(query, candidates, getText, minScore = 0.6) {
|
|
76
|
+
const out = [];
|
|
77
|
+
for (const c of candidates) {
|
|
78
|
+
const score = fuzzyScore(query, getText(c));
|
|
79
|
+
if (score >= minScore) out.push({ item: c, score });
|
|
80
|
+
}
|
|
81
|
+
out.sort((a, b) => b.score - a.score);
|
|
82
|
+
return out;
|
|
83
|
+
}
|
|
84
|
+
function scoreTextField(query, value) {
|
|
85
|
+
if (!value) return 0;
|
|
86
|
+
const v = value.toLowerCase();
|
|
87
|
+
const q = query.toLowerCase().trim();
|
|
88
|
+
if (!q) return 0;
|
|
89
|
+
if (v === q) return 1;
|
|
90
|
+
if (v.startsWith(q)) return 0.9;
|
|
91
|
+
if (v.includes(q)) return 0.7;
|
|
92
|
+
return fuzzyScore(q, v);
|
|
93
|
+
}
|
|
94
|
+
function scorePhoneField(query, phone) {
|
|
95
|
+
const qd = query.replace(/\D/g, "");
|
|
96
|
+
const pd = phone.replace(/\D/g, "");
|
|
97
|
+
if (!qd || !pd) return 0;
|
|
98
|
+
if (pd === qd) return 1;
|
|
99
|
+
if (pd.includes(qd)) return 0.8;
|
|
100
|
+
return 0;
|
|
101
|
+
}
|
|
102
|
+
const NAME_FIELDS = [
|
|
103
|
+
["displayName", (c) => c.displayName],
|
|
104
|
+
["firstName", (c) => c.firstName],
|
|
105
|
+
["lastName", (c) => c.lastName],
|
|
106
|
+
["nickname", (c) => c.nickname],
|
|
107
|
+
["organization", (c) => c.organization]
|
|
108
|
+
];
|
|
109
|
+
function scoreContactMatch(query, contact, floor = 0.6) {
|
|
110
|
+
let best = 0;
|
|
111
|
+
let field = "displayName";
|
|
112
|
+
for (const [name, get] of NAME_FIELDS) {
|
|
113
|
+
const s = scoreTextField(query, get(contact));
|
|
114
|
+
if (s > best) {
|
|
115
|
+
best = s;
|
|
116
|
+
field = name;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
for (const phone of contact.phoneNumbers) {
|
|
120
|
+
const s = scorePhoneField(query, phone);
|
|
121
|
+
if (s > best) {
|
|
122
|
+
best = s;
|
|
123
|
+
field = "phone";
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
for (const email of contact.emails) {
|
|
127
|
+
const s = scoreTextField(query, email);
|
|
128
|
+
if (s > best) {
|
|
129
|
+
best = s;
|
|
130
|
+
field = "email";
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
if (best < floor) return null;
|
|
134
|
+
return { contact, score: Math.round(best * 1e3) / 1e3, matchedField: field };
|
|
135
|
+
}
|
|
136
|
+
function rankContacts(query, contacts, floor = 0.6) {
|
|
137
|
+
const out = [];
|
|
138
|
+
for (const c of contacts) {
|
|
139
|
+
const m = scoreContactMatch(query, c, floor);
|
|
140
|
+
if (m) out.push(m);
|
|
141
|
+
}
|
|
142
|
+
out.sort(
|
|
143
|
+
(a, b) => b.score - a.score || a.contact.displayName.localeCompare(b.contact.displayName)
|
|
144
|
+
);
|
|
145
|
+
return out;
|
|
146
|
+
}
|
|
9
147
|
function normalizePhoneNumber(phone) {
|
|
10
148
|
const digits = phone.replace(/\D/g, "");
|
|
11
149
|
if (digits.length === 11 && digits.startsWith("1")) {
|
|
@@ -319,6 +457,18 @@ class ContactsDB {
|
|
|
319
457
|
(c) => c.displayName.toLowerCase().includes(lowerQuery) || c.phoneNumbers.some((p) => p.includes(query)) || c.emails.some((e) => e.toLowerCase().includes(lowerQuery))
|
|
320
458
|
);
|
|
321
459
|
}
|
|
460
|
+
/**
|
|
461
|
+
* Search contacts, ranked by relevance with the matched field annotated.
|
|
462
|
+
* Preserves the substring recall of searchContacts (a substring match scores
|
|
463
|
+
* 0.7, above the inclusion floor) while ordering best-first so callers can
|
|
464
|
+
* disambiguate close matches instead of eyeballing an unordered list.
|
|
465
|
+
*/
|
|
466
|
+
searchContactsRanked(query) {
|
|
467
|
+
if (!this.initialized) {
|
|
468
|
+
this.initialize();
|
|
469
|
+
}
|
|
470
|
+
return rankContacts(query, Array.from(this.contactCache.values()));
|
|
471
|
+
}
|
|
322
472
|
/**
|
|
323
473
|
* Build display name from contact fields
|
|
324
474
|
*/
|
|
@@ -373,81 +523,6 @@ class ContactsDB {
|
|
|
373
523
|
};
|
|
374
524
|
}
|
|
375
525
|
}
|
|
376
|
-
const EMOJI_REGEX = /[\u{1F300}-\u{1FAFF}\u{2600}-\u{27BF}\u{1F1E6}-\u{1F1FF}]/gu;
|
|
377
|
-
const WHITESPACE_REGEX = /\s+/g;
|
|
378
|
-
function cleanText(s) {
|
|
379
|
-
return s.toLowerCase().replace(EMOJI_REGEX, " ").replace(WHITESPACE_REGEX, " ").trim();
|
|
380
|
-
}
|
|
381
|
-
function tokenSet(s) {
|
|
382
|
-
if (!s) return /* @__PURE__ */ new Set();
|
|
383
|
-
return new Set(s.split(" ").filter(Boolean));
|
|
384
|
-
}
|
|
385
|
-
function levenshtein(a, b, maxLen = 200) {
|
|
386
|
-
if (a === b) return 0;
|
|
387
|
-
const aLen = Math.min(a.length, maxLen);
|
|
388
|
-
const bLen = Math.min(b.length, maxLen);
|
|
389
|
-
if (aLen === 0) return bLen;
|
|
390
|
-
if (bLen === 0) return aLen;
|
|
391
|
-
let prev = new Array(bLen + 1);
|
|
392
|
-
let curr = new Array(bLen + 1);
|
|
393
|
-
for (let j = 0; j <= bLen; j++) prev[j] = j;
|
|
394
|
-
for (let i = 1; i <= aLen; i++) {
|
|
395
|
-
curr[0] = i;
|
|
396
|
-
for (let j = 1; j <= bLen; j++) {
|
|
397
|
-
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
398
|
-
const del = (prev[j] ?? 0) + 1;
|
|
399
|
-
const ins = (curr[j - 1] ?? 0) + 1;
|
|
400
|
-
const sub = (prev[j - 1] ?? 0) + cost;
|
|
401
|
-
curr[j] = Math.min(del, ins, sub);
|
|
402
|
-
}
|
|
403
|
-
[prev, curr] = [curr, prev];
|
|
404
|
-
}
|
|
405
|
-
return prev[bLen] ?? Math.max(aLen, bLen);
|
|
406
|
-
}
|
|
407
|
-
function levenshteinRatio(a, b) {
|
|
408
|
-
if (a === b) return 1;
|
|
409
|
-
const aLen = Math.min(a.length, 200);
|
|
410
|
-
const bLen = Math.min(b.length, 200);
|
|
411
|
-
const maxLen = Math.max(aLen, bLen);
|
|
412
|
-
if (maxLen === 0) return 1;
|
|
413
|
-
return 1 - levenshtein(a, b) / maxLen;
|
|
414
|
-
}
|
|
415
|
-
function fuzzyScore(query, candidate) {
|
|
416
|
-
if (query && candidate && candidate.toLowerCase().includes(query.toLowerCase())) {
|
|
417
|
-
return 0.95;
|
|
418
|
-
}
|
|
419
|
-
const q = cleanText(query);
|
|
420
|
-
const c = cleanText(candidate);
|
|
421
|
-
if (!q || !c) return 0;
|
|
422
|
-
if (c.includes(q)) return 0.95;
|
|
423
|
-
if (q.includes(c)) return 0.9;
|
|
424
|
-
const qTokens = tokenSet(q);
|
|
425
|
-
const cTokens = tokenSet(c);
|
|
426
|
-
let intersection = 0;
|
|
427
|
-
for (const tok of qTokens) {
|
|
428
|
-
if (cTokens.has(tok)) intersection++;
|
|
429
|
-
}
|
|
430
|
-
const dice = 2 * intersection / (qTokens.size + cTokens.size || 1);
|
|
431
|
-
let bestTokenLev = 0;
|
|
432
|
-
if (qTokens.size === 1) {
|
|
433
|
-
for (const tok of cTokens) {
|
|
434
|
-
const r = levenshteinRatio(q, tok);
|
|
435
|
-
if (r > bestTokenLev) bestTokenLev = r;
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
const levRatio = Math.max(levenshteinRatio(q, c), bestTokenLev);
|
|
439
|
-
const blend = qTokens.size <= 1 ? 0.1 * dice + 0.9 * levRatio : 0.7 * dice + 0.3 * levRatio;
|
|
440
|
-
return Math.max(0, Math.min(1, blend));
|
|
441
|
-
}
|
|
442
|
-
function rankFuzzy(query, candidates, getText, minScore = 0.6) {
|
|
443
|
-
const out = [];
|
|
444
|
-
for (const c of candidates) {
|
|
445
|
-
const score = fuzzyScore(query, getText(c));
|
|
446
|
-
if (score >= minScore) out.push({ item: c, score });
|
|
447
|
-
}
|
|
448
|
-
out.sort((a, b) => b.score - a.score);
|
|
449
|
-
return out;
|
|
450
|
-
}
|
|
451
526
|
const __dirname$1 = dirname(fileURLToPath(import.meta.url));
|
|
452
527
|
let _native;
|
|
453
528
|
function tryLoadNative() {
|
|
@@ -3221,4 +3296,4 @@ export {
|
|
|
3221
3296
|
normalizedPhoneVariants as n,
|
|
3222
3297
|
rankFuzzy as r
|
|
3223
3298
|
};
|
|
3224
|
-
//# sourceMappingURL=imessage-db-
|
|
3299
|
+
//# sourceMappingURL=imessage-db-DXFIm7is.js.map
|