imsg-mcp 1.1.1 → 1.2.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 CHANGED
@@ -3,6 +3,18 @@
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.2.0](https://github.com/george43g/imsg-mcp/compare/v1.1.1...v1.2.0) (2026-06-07)
7
+
8
+
9
+ ### Bug Fixes
10
+
11
+ * **tui:** module-pane Tab cycles type; defer compute so Loading paints ([a69539a](https://github.com/george43g/imsg-mcp/commit/a69539ae6271dc6623dcfe9663728a760cc4fade))
12
+
13
+
14
+ ### Features
15
+
16
+ * **tui:** command palette + feature module system ([59d5628](https://github.com/george43g/imsg-mcp/commit/59d5628238fefc65d6806ad9969716fa508ea2c7))
17
+
6
18
  ## [1.1.1](https://github.com/george43g/imsg-mcp/compare/v1.1.0...v1.1.1) (2026-06-07)
7
19
 
8
20
 
package/dist/index.js CHANGED
@@ -7,6 +7,7 @@ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
7
7
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
8
8
  import { ListToolsRequestSchema, CallToolRequestSchema, ListResourcesRequestSchema, ListResourceTemplatesRequestSchema, ReadResourceRequestSchema } from "@modelcontextprotocol/sdk/types.js";
9
9
  import { c as checkLocalAccess, f as formatAccessReport, s as shutdown, g as getImsgDbPath, a as getContactsDbPaths, b as getSlugsDbPath, d as appendLog, e as getFileLogLines, h as getLogDirectory, p as perf, i as checkMessagesAvailable, j as sendToChat, k as sendToChatId, l as sendMessageReliable, m as sendMessageAlt, n as sendAttachment, o as checkImessageAvailability, q as installShutdownHandlers, r as registerCleanup, t as enableStdinEofDetection, u as enableOrphanWatchdog, v as logStartup, w as info, x as startHeapMonitor, y as getLogs, z as setLastSendError, A as logShutdown, B as stopHeapMonitor, C as getLogFilePath, D as getLastSendError } from "./shutdown-BJifUGdo.js";
10
+ import { r as resolveRecipient, d as defaultCountryFromEnv, a as rankFuzzy, b as dispatchAnalytic, i as installWatchdog, n as noteActivity, c as readWatchdogState } from "./watchdog-0RGOHpj4.js";
10
11
  import { homedir } from "node:os";
11
12
  import Database from "better-sqlite3";
12
13
  import { streamExport } from "./exportStream-Bu5recJ4.js";
@@ -14,262 +15,8 @@ import { I as IMessageDB, h as hasNativeModule } from "./imessage-db-BYU5mxM-.js
14
15
  import { z } from "zod";
15
16
  import { A as APP_VERSION, a as APP_NAME } from "./meta-D3NoTAjA.js";
16
17
  import { randomUUID } from "node:crypto";
17
- import { r as resolveRecipient, d as defaultCountryFromEnv, i as installWatchdog, n as noteActivity, a as readWatchdogState } from "./watchdog-pMKk8IGO.js";
18
18
  import { parseUserDate } from "./dateParse-DJXMfq3a.js";
19
19
  import { m as minMessageId } from "./types-Bbi1xtl-.js";
20
- const MS_PER_DAY = 864e5;
21
- function localDateKey(d) {
22
- const y = d.getFullYear();
23
- const m = String(d.getMonth() + 1).padStart(2, "0");
24
- const day = String(d.getDate()).padStart(2, "0");
25
- return `${y}-${m}-${day}`;
26
- }
27
- function contactKey(m) {
28
- return m.chatId;
29
- }
30
- function byDate(a, b) {
31
- return a.date.getTime() - b.date.getTime();
32
- }
33
- function computeStreaks(messages) {
34
- const byContact = /* @__PURE__ */ new Map();
35
- for (const m of messages) {
36
- if (m.isReaction) continue;
37
- const c = contactKey(m);
38
- if (!byContact.has(c)) byContact.set(c, /* @__PURE__ */ new Set());
39
- byContact.get(c).add(localDateKey(m.date));
40
- }
41
- localDateKey(/* @__PURE__ */ new Date());
42
- const results = [];
43
- for (const [contact, dayset] of byContact) {
44
- const days = Array.from(dayset).sort();
45
- let longest = 0;
46
- let longestStart = null;
47
- let longestEnd = null;
48
- let runLen = 0;
49
- let runStart = null;
50
- for (let i = 0; i < days.length; i++) {
51
- if (i === 0 || new Date(days[i]).getTime() - new Date(days[i - 1]).getTime() !== MS_PER_DAY) {
52
- runLen = 1;
53
- runStart = days[i];
54
- } else {
55
- runLen++;
56
- }
57
- if (runLen > longest) {
58
- longest = runLen;
59
- longestStart = runStart;
60
- longestEnd = days[i];
61
- }
62
- }
63
- let curLen = 0;
64
- const cursor = /* @__PURE__ */ new Date();
65
- while (dayset.has(localDateKey(cursor))) {
66
- curLen++;
67
- cursor.setDate(cursor.getDate() - 1);
68
- }
69
- results.push({
70
- contact,
71
- longestStreakDays: longest,
72
- longestStreakStart: longestStart,
73
- longestStreakEnd: longestEnd,
74
- currentStreakDays: curLen
75
- });
76
- }
77
- results.sort((a, b) => b.longestStreakDays - a.longestStreakDays);
78
- return results;
79
- }
80
- function computeDoubleTexts(messages) {
81
- const byContact = /* @__PURE__ */ new Map();
82
- for (const m of messages) {
83
- if (m.isReaction) continue;
84
- const c = contactKey(m);
85
- if (!byContact.has(c)) byContact.set(c, []);
86
- byContact.get(c).push(m);
87
- }
88
- const results = [];
89
- for (const [contact, msgs] of byContact) {
90
- msgs.sort(byDate);
91
- let mineRun = 0;
92
- let theirsRun = 0;
93
- let mineDoubles = 0;
94
- let theirsDoubles = 0;
95
- for (const m of msgs) {
96
- if (m.isFromMe) {
97
- mineRun++;
98
- theirsRun = 0;
99
- if (mineRun >= 2) mineDoubles++;
100
- } else {
101
- theirsRun++;
102
- mineRun = 0;
103
- if (theirsRun >= 2) theirsDoubles++;
104
- }
105
- }
106
- results.push({
107
- contact,
108
- doubleTextsFromMe: mineDoubles,
109
- doubleTextsFromThem: theirsDoubles
110
- });
111
- }
112
- results.sort(
113
- (a, b) => b.doubleTextsFromMe + b.doubleTextsFromThem - (a.doubleTextsFromMe + a.doubleTextsFromThem)
114
- );
115
- return results;
116
- }
117
- function percentile(sorted, p) {
118
- if (sorted.length === 0) return 0;
119
- const idx = Math.min(sorted.length - 1, Math.floor(p / 100 * sorted.length));
120
- return sorted[idx] ?? 0;
121
- }
122
- function computeResponseTimes(messages) {
123
- const byContact = /* @__PURE__ */ new Map();
124
- for (const m of messages) {
125
- if (m.isReaction) continue;
126
- const c = contactKey(m);
127
- if (!byContact.has(c)) byContact.set(c, []);
128
- byContact.get(c).push(m);
129
- }
130
- const results = [];
131
- for (const [contact, msgs] of byContact) {
132
- msgs.sort(byDate);
133
- const deltas = [];
134
- for (let i = 1; i < msgs.length; i++) {
135
- const prev = msgs[i - 1];
136
- const curr = msgs[i];
137
- if (!prev.isFromMe && curr.isFromMe) {
138
- deltas.push(curr.date.getTime() - prev.date.getTime());
139
- }
140
- }
141
- if (deltas.length === 0) continue;
142
- deltas.sort((a, b) => a - b);
143
- const mean = deltas.reduce((s, n) => s + n, 0) / deltas.length;
144
- results.push({
145
- contact,
146
- count: deltas.length,
147
- medianMs: percentile(deltas, 50),
148
- p95Ms: percentile(deltas, 95),
149
- meanMs: Math.round(mean)
150
- });
151
- }
152
- results.sort((a, b) => a.medianMs - b.medianMs);
153
- return results;
154
- }
155
- function computeHeatmap(messages) {
156
- const grid = Array.from({ length: 7 }, () => Array.from({ length: 24 }, () => 0));
157
- let total = 0;
158
- for (const m of messages) {
159
- if (m.isReaction) continue;
160
- const dow = m.date.getDay();
161
- const hr = m.date.getHours();
162
- const dowRow = grid[dow];
163
- if (dowRow && hr >= 0 && hr < 24) {
164
- dowRow[hr] = (dowRow[hr] ?? 0) + 1;
165
- total++;
166
- }
167
- }
168
- return { grid, total };
169
- }
170
- function computeTapbacks(messages) {
171
- const byContact = /* @__PURE__ */ new Map();
172
- for (const m of messages) {
173
- if (!m.isReaction || !m.reaction) continue;
174
- const c = contactKey(m);
175
- if (!byContact.has(c)) {
176
- byContact.set(c, {
177
- contact: c,
178
- heart: 0,
179
- thumbsUp: 0,
180
- thumbsDown: 0,
181
- haha: 0,
182
- exclaim: 0,
183
- question: 0,
184
- emoji: 0,
185
- total: 0
186
- });
187
- }
188
- const r = byContact.get(c);
189
- const t = (m.reaction.type || "").toLowerCase();
190
- if (t.includes("heart") || t === "love") r.heart++;
191
- else if (t.includes("thumb") && t.includes("up") || t === "like") r.thumbsUp++;
192
- else if (t.includes("thumb") && t.includes("down") || t === "dislike") r.thumbsDown++;
193
- else if (t.includes("haha") || t === "laugh") r.haha++;
194
- else if (t.includes("exclaim") || t === "emphasis") r.exclaim++;
195
- else if (t.includes("question")) r.question++;
196
- else r.emoji++;
197
- r.total++;
198
- }
199
- const out = Array.from(byContact.values());
200
- out.sort((a, b) => b.total - a.total);
201
- return out;
202
- }
203
- function computeWrapped(messages) {
204
- if (messages.length === 0) {
205
- return {
206
- windowStart: "",
207
- windowEnd: "",
208
- totalSent: 0,
209
- totalReceived: 0,
210
- totalReactions: 0,
211
- topContacts: [],
212
- peakDay: null,
213
- longestStreakDays: 0,
214
- longestStreakContact: null
215
- };
216
- }
217
- let sent = 0;
218
- let received = 0;
219
- let reactions = 0;
220
- const perContact = /* @__PURE__ */ new Map();
221
- const perDay = /* @__PURE__ */ new Map();
222
- for (const m of messages) {
223
- if (m.isReaction) {
224
- reactions++;
225
- continue;
226
- }
227
- if (m.isFromMe) sent++;
228
- else received++;
229
- const c = contactKey(m);
230
- if (!perContact.has(c)) perContact.set(c, { sent: 0, received: 0, total: 0 });
231
- const pc = perContact.get(c);
232
- if (m.isFromMe) pc.sent++;
233
- else pc.received++;
234
- pc.total++;
235
- const dk = localDateKey(m.date);
236
- perDay.set(dk, (perDay.get(dk) ?? 0) + 1);
237
- }
238
- const topContacts = Array.from(perContact.entries()).map(([contact, v]) => ({ contact, ...v })).sort((a, b) => b.total - a.total).slice(0, 5);
239
- let peakDay = null;
240
- for (const [d, count] of perDay) {
241
- if (!peakDay || count > peakDay.count) peakDay = { date: d, count };
242
- }
243
- const streaks = computeStreaks(messages);
244
- const top = streaks[0];
245
- return {
246
- windowStart: localDateKey(messages[0].date),
247
- windowEnd: localDateKey(messages[messages.length - 1].date),
248
- totalSent: sent,
249
- totalReceived: received,
250
- totalReactions: reactions,
251
- topContacts,
252
- peakDay,
253
- longestStreakDays: top?.longestStreakDays ?? 0,
254
- longestStreakContact: top?.contact ?? null
255
- };
256
- }
257
- function dispatchAnalytic(type, messages) {
258
- switch (type) {
259
- case "messaging_streaks":
260
- return { type, data: computeStreaks(messages) };
261
- case "double_texts":
262
- return { type, data: computeDoubleTexts(messages) };
263
- case "response_time_stats":
264
- return { type, data: computeResponseTimes(messages) };
265
- case "daily_heatmap":
266
- return { type, data: computeHeatmap(messages) };
267
- case "tapback_summary":
268
- return { type, data: computeTapbacks(messages) };
269
- case "year_in_review_wrapped":
270
- return { type, data: computeWrapped(messages) };
271
- }
272
- }
273
20
  const DEFAULT_TTL_MS = 24 * 60 * 60 * 1e3;
274
21
  let dbPath = join(homedir(), ".imsg-mcp", "analytics-cache.db");
275
22
  let db = null;
@@ -342,81 +89,6 @@ function resolveContactSelector(selector) {
342
89
  }
343
90
  return null;
344
91
  }
345
- const EMOJI_REGEX = /[\u{1F300}-\u{1FAFF}\u{2600}-\u{27BF}\u{1F1E6}-\u{1F1FF}]/gu;
346
- const WHITESPACE_REGEX = /\s+/g;
347
- function cleanText(s) {
348
- return s.toLowerCase().replace(EMOJI_REGEX, " ").replace(WHITESPACE_REGEX, " ").trim();
349
- }
350
- function tokenSet(s) {
351
- if (!s) return /* @__PURE__ */ new Set();
352
- return new Set(s.split(" ").filter(Boolean));
353
- }
354
- function levenshtein(a, b, maxLen = 200) {
355
- if (a === b) return 0;
356
- const aLen = Math.min(a.length, maxLen);
357
- const bLen = Math.min(b.length, maxLen);
358
- if (aLen === 0) return bLen;
359
- if (bLen === 0) return aLen;
360
- let prev = new Array(bLen + 1);
361
- let curr = new Array(bLen + 1);
362
- for (let j = 0; j <= bLen; j++) prev[j] = j;
363
- for (let i = 1; i <= aLen; i++) {
364
- curr[0] = i;
365
- for (let j = 1; j <= bLen; j++) {
366
- const cost = a[i - 1] === b[j - 1] ? 0 : 1;
367
- const del = (prev[j] ?? 0) + 1;
368
- const ins = (curr[j - 1] ?? 0) + 1;
369
- const sub = (prev[j - 1] ?? 0) + cost;
370
- curr[j] = Math.min(del, ins, sub);
371
- }
372
- [prev, curr] = [curr, prev];
373
- }
374
- return prev[bLen] ?? Math.max(aLen, bLen);
375
- }
376
- function levenshteinRatio(a, b) {
377
- if (a === b) return 1;
378
- const aLen = Math.min(a.length, 200);
379
- const bLen = Math.min(b.length, 200);
380
- const maxLen = Math.max(aLen, bLen);
381
- if (maxLen === 0) return 1;
382
- return 1 - levenshtein(a, b) / maxLen;
383
- }
384
- function fuzzyScore(query, candidate) {
385
- if (query && candidate && candidate.toLowerCase().includes(query.toLowerCase())) {
386
- return 0.95;
387
- }
388
- const q = cleanText(query);
389
- const c = cleanText(candidate);
390
- if (!q || !c) return 0;
391
- if (c.includes(q)) return 0.95;
392
- if (q.includes(c)) return 0.9;
393
- const qTokens = tokenSet(q);
394
- const cTokens = tokenSet(c);
395
- let intersection = 0;
396
- for (const tok of qTokens) {
397
- if (cTokens.has(tok)) intersection++;
398
- }
399
- const dice = 2 * intersection / (qTokens.size + cTokens.size || 1);
400
- let bestTokenLev = 0;
401
- if (qTokens.size === 1) {
402
- for (const tok of cTokens) {
403
- const r = levenshteinRatio(q, tok);
404
- if (r > bestTokenLev) bestTokenLev = r;
405
- }
406
- }
407
- const levRatio = Math.max(levenshteinRatio(q, c), bestTokenLev);
408
- const blend = qTokens.size <= 1 ? 0.1 * dice + 0.9 * levRatio : 0.7 * dice + 0.3 * levRatio;
409
- return Math.max(0, Math.min(1, blend));
410
- }
411
- function rankFuzzy(query, candidates, getText, minScore = 0.6) {
412
- const out = [];
413
- for (const c of candidates) {
414
- const score = fuzzyScore(query, getText(c));
415
- if (score >= minScore) out.push({ item: c, score });
416
- }
417
- out.sort((a, b) => b.score - a.score);
418
- return out;
419
- }
420
92
  const ignoreOverride = /* @__PURE__ */ Symbol("Let zodToJsonSchema decide on which parser to use");
421
93
  const defaultOptions = {
422
94
  name: void 0,