imsg-mcp 1.15.0 → 1.15.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/CHANGELOG.md CHANGED
@@ -3,6 +3,14 @@
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.15.1](https://github.com/george43g/imsg-mcp/compare/v1.15.0...v1.15.1) (2026-07-25)
7
+
8
+
9
+ ### Bug Fixes
10
+
11
+ * **tui:** refresh sidebar after in-thread send + navigable filter results ([f6552be](https://github.com/george43g/imsg-mcp/commit/f6552be71a6c11abb240d38c853d0dadefcb25a9))
12
+ * **tui:** show identity-canonical service on conversation badges ([0b05827](https://github.com/george43g/imsg-mcp/commit/0b05827eb48b2d217b0d56c6af6b0dbe66e7829b))
13
+
6
14
  # [1.15.0](https://github.com/george43g/imsg-mcp/compare/v1.14.0...v1.15.0) (2026-07-23)
7
15
 
8
16
 
package/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
  [![Release](https://github.com/george43g/imsg-mcp/actions/workflows/release.yml/badge.svg?branch=main)](https://github.com/george43g/imsg-mcp/actions/workflows/release.yml)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
7
7
 
8
- **MCP server, CLI, and terminal UI for iMessage on macOS.** Let AI agents read your iMessage / SMS, search history, send messages, and export conversations — without ever leaving your machine. Includes a self-healing watchdog so a wedged query can't take your agent down.
8
+ **MCP server, CLI, and terminal UI for iMessage on macOS.** Let AI agents read your iMessage / SMS, search history, send messages, export conversations, and *understand* media — transcribe voice notes, caption images & video — without ever leaving your machine by default. Includes a self-healing watchdog so a wedged query can't take your agent down.
9
9
 
10
10
  ![TUI overview](docs/screenshots/tui-overview.png)
11
11
 
@@ -41,6 +41,7 @@ Prefer the canonical `npx -y` config above. If you'd rather install the MCPB ext
41
41
  ```bash
42
42
  npx -y imsg-mcp setup --write claude # or --write cursor
43
43
  npx -y imsg-mcp doctor # verify Full Disk Access + DB
44
+ npx -y imsg-mcp setup --interactive # (optional) configure media interpretation
44
45
  ```
45
46
 
46
47
  ---
@@ -85,7 +86,7 @@ Full walkthrough with screenshots: [**docs/WORKFLOWS.md**](docs/WORKFLOWS.md).
85
86
  | `wait_for_reply` | Poll for the next reply — includes the user's own interjections from other devices. |
86
87
  | `export_messages` | Stream a chat to file (md/csv/json/ndjson). |
87
88
  | `search_attachments` | Find attachments by mime/date/chat. |
88
- | `get_attachment` | Fetch attachment bytes (inline or path). |
89
+ | `get_attachment` | Fetch an attachment (image block / poster / audio) + optional AI interpretation (`interpret`). |
89
90
  | `check_imessage_availability` | Pre-flight iMessage vs SMS reachability. |
90
91
  | `chat_analytics` | Pre-aggregated stats (heatmaps, leaderboards, etc). |
91
92
  | `list_contacts` / `search_contacts` | Browse/search contacts (name, phone, email). |
@@ -108,18 +109,47 @@ Full reference (CLI subcommands + MCP tools + every flag): [**docs/TOOLS.md**](d
108
109
  imsg tui
109
110
  ```
110
111
 
111
- Vim-style: `j/k` move, `gg/G` jump, `Enter` drawer, `i` per-thread info + attachment browser, `o` Quick Look an attachment, `:` jump to date, `V` visual select, `e` export, `S` send via other app, `y` copy slug, `c` compose in current thread, `N` compose to new recipient (phone / email / contact name), `q` quit.
112
+ Vim-style: `j/k` move, `gg/G` jump, `Enter` drawer, `i` per-thread info + attachment browser, `o` Quick Look an attachment, `f` reveal in Finder, `R` run/retry media interpretation, `:` jump to date, `V` visual select, `e` export, `S` send via other app, `y` copy slug, `c` compose in current thread, `N` compose to new recipient (phone / email / contact name), `,` settings panel, `q` quit.
112
113
 
113
114
  Themes (`safe` / `powerline`) and a single accent color drive the whole palette. See [docs/TOOLS.md#tui-configuration](docs/TOOLS.md#tui-configuration).
114
115
 
115
116
  ---
116
117
 
118
+ ## Media interpretation
119
+
120
+ imsg-mcp reads what Apple already computed **for free, on-device**: iPhone-synced voice-note
121
+ transcripts and Genmoji descriptions surface automatically with zero setup and zero network. For
122
+ everything else it runs a configurable **chain** per media type:
123
+
124
+ ```
125
+ apple → local (hear / yap / whisper-cli) → provider:<cloud>
126
+ ```
127
+
128
+ - **Free-first & opt-in.** `interpret.auto` defaults to `free`, so only Apple + local legs run
129
+ unless you deliberately add a cloud provider. **Audio and images leave your device only when a
130
+ cloud leg you configured actually fires** — never by default.
131
+ - **Local tools** (optional, free): `brew install sveinbjornt/hear/hear` · `brew install yap` ·
132
+ `brew install whisper-cpp`. Optional `brew install ffmpeg mpv` improves video framing/playback.
133
+ - **Cloud providers** (optional): presets for `openai`, `groq`, `openrouter`, `cloudflare`,
134
+ `huggingface`, `ollama` (local, no key), or a custom OpenAI-compatible base URL. Configure with
135
+ `imsg setup --interactive`; keys are stored in `~/.imsg-mcp/credentials.json` (chmod 600), never
136
+ in the shared config.
137
+ - **Cached forever.** Every result is cached in `~/.imsg-mcp/media-intel.db` — the same attachment
138
+ is never interpreted (or paid for) twice. Bulk exports confirm before making more than
139
+ `exportConfirmThreshold` (default 25) uncached cloud calls.
140
+
141
+ Trigger it via `get_attachment` (`interpret`), `export_messages` (`interpret`), `imsg interpret
142
+ <rowId>`, or the TUI (`R`). Full config reference: [docs/TOOLS.md](docs/TOOLS.md).
143
+
144
+ ---
145
+
117
146
  ## Permissions
118
147
 
119
148
  - **Full Disk Access** — required to read `chat.db` and Address Book. System Settings → Privacy & Security → Full Disk Access → add the terminal/IDE you actually run from.
120
- - **Automation** — required only for sending. macOS prompts on first send.
149
+ - **Automation** — required only for sending, and for the attachment **sync nudge** (opening a conversation so Messages downloads its media). macOS prompts on first use.
150
+ - **Accessibility** *(opt-in, off by default)* — only if you enable the Tier-2 "Sync Now" nudge (`interpret.nudge.tier2SyncNow`), which UI-scripts the Messages ▸ Settings ▸ Sync Now button. Leave it off unless you need it. System Settings → Privacy & Security → Accessibility.
121
151
 
122
- Privacy: only ever reads your **local** `~/Library/Messages/chat.db`. Nothing is uploaded. The connected MCP host (Claude / Cursor / Warp / …) sees whatever messages the agent fetches — treat those hosts the same way you treat any app with Full Disk Access.
152
+ Privacy: reads only your **local** `~/Library/Messages/chat.db` — nothing is uploaded by default. Cloud media interpretation is strictly opt-in (see above) and only sends the specific attachment you interpret to the provider you configured. The connected MCP host (Claude / Cursor / Warp / …) sees whatever messages the agent fetches — treat those hosts the same way you treat any app with Full Disk Access.
123
153
 
124
154
  ---
125
155
 
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-ByD-lGq7.js";
7
+ import { A as APP_VERSION, t as toYaml } from "./meta-BzRJT7r0.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-C1owvGNS.js").then((n) => n.i);
450
+ const { IMessageDB } = await import("./imessage-db-7ldOiURZ.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-C1owvGNS.js").then((n) => n.i);
722
+ const { IMessageDB } = await import("./imessage-db-7ldOiURZ.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());
@@ -1532,6 +1532,24 @@ class IMessageDB {
1532
1532
  }
1533
1533
  return "iMessage";
1534
1534
  }
1535
+ /**
1536
+ * Identity-canonical service for a conversation's badge (prefer iMessage).
1537
+ * Unlike detectServiceForChat (one leg's service), this reflects the whole
1538
+ * identity: if ANY leg is iMessage the thread is iMessage. It matches the
1539
+ * ~imsg~/~sms~ slug segment (computeSlugForChat) and the slug-store send
1540
+ * route, so the badge can't disagree with them. Without this, a single newer
1541
+ * SMS leg of a fundamentally-iMessage thread wins the merge cascade and flips
1542
+ * the badge to "SMS" while the slug stays ~imsg~. Groups have no cross-service
1543
+ * identity, so fall back to the chat's own service.
1544
+ */
1545
+ canonicalServiceForChat(chat) {
1546
+ if (isGroupGuid(chat.guid) || isGroupChatIdentifier(chat.chat_identifier)) {
1547
+ return this.detectServiceForChat(chat);
1548
+ }
1549
+ this.ensureIdentityServiceMap();
1550
+ const mergeKey = this.getConversationMergeKey(chat.chat_identifier, chat.guid, false);
1551
+ return this.identityServiceMap?.get(mergeKey) ?? this.detectServiceForChat(chat);
1552
+ }
1535
1553
  /**
1536
1554
  * Get the N most recent messages across all conversations
1537
1555
  * By default excludes reactions (tapbacks) for cleaner output
@@ -1900,8 +1918,7 @@ class IMessageDB {
1900
1918
  const participants = isGroup ? this.fetchChatParticipants(chat.ROWID) : [rawIdentifier];
1901
1919
  const mergeKey = this.getConversationMergeKey(rawIdentifier, chat.guid, isGroup);
1902
1920
  const slug = this.getSlugForChatGuid(chat.guid) ?? this.syncSlugForChat(chat);
1903
- const chatData = this.slugMap.get(slug);
1904
- const serviceType = chatData ? this.detectServiceForChat(chatData) : this.detectServiceForChat(chat);
1921
+ const serviceType = this.canonicalServiceForChat(chat);
1905
1922
  return {
1906
1923
  last,
1907
1924
  mergeKey,
@@ -1965,8 +1982,7 @@ class IMessageDB {
1965
1982
  displayName = resolved !== rawIdentifier ? resolved : null;
1966
1983
  }
1967
1984
  const slug = this.getSlugForChatGuid(found.guid) ?? this.syncSlugForChat(found);
1968
- const chatData = this.slugMap.get(slug);
1969
- const serviceType = chatData ? this.detectServiceForChat(chatData) : this.detectServiceForChat(found);
1985
+ const serviceType = this.canonicalServiceForChat(found);
1970
1986
  return {
1971
1987
  chatId: found.guid,
1972
1988
  chatIdentifier: rawIdentifier,
@@ -3205,4 +3221,4 @@ export {
3205
3221
  normalizedPhoneVariants as n,
3206
3222
  rankFuzzy as r
3207
3223
  };
3208
- //# sourceMappingURL=imessage-db-C1owvGNS.js.map
3224
+ //# sourceMappingURL=imessage-db-7ldOiURZ.js.map