@mgcrea/mcp-apple-messages 1.3.0 → 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/dist/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { A as MESSAGES_SURFACE, V as BUILD_INFO, a as loadConfig, r as createServer } from "./server-
|
|
2
|
+
import { A as MESSAGES_SURFACE, V as BUILD_INFO, a as loadConfig, r as createServer } from "./server-BSHulHkz.js";
|
|
3
3
|
import { runStdioServer } from "@mgcrea/mcp-apple-core";
|
|
4
4
|
//#region src/cli.ts
|
|
5
5
|
const LOG_PREFIX = "apple-messages-mcp";
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { A as MESSAGES_SURFACE, B as toAppleSeconds, C as locateStore, D as ChatNotFoundError, E as AppleMessagesError, F as SendTargetNotFoundError, I as CORE_DATA_EPOCH_OFFSET, L as appleSecondsSql, M as MessagesUnavailableError, N as SchemaDriftError, O as IndexUnavailableError, P as SendFailedError, R as fromAppleSeconds, S as defaultStorePath, T as PRELUDE, V as BUILD_INFO, _ as decodeMessageRef, a as loadConfig, b as ATTACHMENTS_RELATIVE, c as introspect, d as decodeAttributedBody, f as outline, g as decodeChatRef, h as MESSAGE_REF_VERSION, i as registerTools, j as MessageNotFoundError, k as MESSAGES_BUNDLE_ID, l as openStore, m as InvalidMessageRefError, n as SERVER_VERSION, o as AppleMessagesClient, p as CHAT_REF_VERSION, r as createServer, s as MessagesStore, t as SERVER_NAME, u as reactionLabel, v as encodeChatRef, w as SEND_MESSAGE, x as STORE_RELATIVE, y as encodeMessageRef, z as renderInstant } from "./server-
|
|
1
|
+
import { A as MESSAGES_SURFACE, B as toAppleSeconds, C as locateStore, D as ChatNotFoundError, E as AppleMessagesError, F as SendTargetNotFoundError, I as CORE_DATA_EPOCH_OFFSET, L as appleSecondsSql, M as MessagesUnavailableError, N as SchemaDriftError, O as IndexUnavailableError, P as SendFailedError, R as fromAppleSeconds, S as defaultStorePath, T as PRELUDE, V as BUILD_INFO, _ as decodeMessageRef, a as loadConfig, b as ATTACHMENTS_RELATIVE, c as introspect, d as decodeAttributedBody, f as outline, g as decodeChatRef, h as MESSAGE_REF_VERSION, i as registerTools, j as MessageNotFoundError, k as MESSAGES_BUNDLE_ID, l as openStore, m as InvalidMessageRefError, n as SERVER_VERSION, o as AppleMessagesClient, p as CHAT_REF_VERSION, r as createServer, s as MessagesStore, t as SERVER_NAME, u as reactionLabel, v as encodeChatRef, w as SEND_MESSAGE, x as STORE_RELATIVE, y as encodeMessageRef, z as renderInstant } from "./server-BSHulHkz.js";
|
|
2
2
|
export { ATTACHMENTS_RELATIVE, AppleMessagesClient, AppleMessagesError, BUILD_INFO, CHAT_REF_VERSION, CORE_DATA_EPOCH_OFFSET, ChatNotFoundError, IndexUnavailableError, InvalidMessageRefError, MESSAGES_BUNDLE_ID, MESSAGES_SURFACE, MESSAGE_REF_VERSION, MessageNotFoundError, MessagesStore, MessagesUnavailableError, PRELUDE, SEND_MESSAGE, SERVER_NAME, SERVER_VERSION, STORE_RELATIVE, SchemaDriftError, SendFailedError, SendTargetNotFoundError, appleSecondsSql, createServer, decodeAttributedBody, decodeChatRef, decodeMessageRef, defaultStorePath, encodeChatRef, encodeMessageRef, fromAppleSeconds, introspect, loadConfig, locateStore, openStore, outline, reactionLabel, registerTools, renderInstant, toAppleSeconds };
|
|
@@ -13,8 +13,8 @@ const pkg = readPackageIdentity(new URL("../package.json", import.meta.url), {
|
|
|
13
13
|
const BUILD_INFO = {
|
|
14
14
|
name: pkg.name,
|
|
15
15
|
version: pkg.version,
|
|
16
|
-
gitCommit: "
|
|
17
|
-
gitCommitDate: "2026-08-
|
|
16
|
+
gitCommit: "1779320",
|
|
17
|
+
gitCommitDate: "2026-08-30T12:29:09+02:00"
|
|
18
18
|
};
|
|
19
19
|
//#endregion
|
|
20
20
|
//#region src/client/dates.ts
|
|
@@ -2134,4 +2134,4 @@ const createServer = (opts) => {
|
|
|
2134
2134
|
//#endregion
|
|
2135
2135
|
export { MESSAGES_SURFACE as A, toAppleSeconds as B, locateStore as C, ChatNotFoundError as D, AppleMessagesError as E, SendTargetNotFoundError as F, CORE_DATA_EPOCH_OFFSET as I, appleSecondsSql as L, MessagesUnavailableError as M, SchemaDriftError$1 as N, IndexUnavailableError$1 as O, SendFailedError as P, fromAppleSeconds as R, defaultStorePath as S, PRELUDE as T, BUILD_INFO as V, decodeMessageRef as _, loadConfig$1 as a, ATTACHMENTS_RELATIVE as b, introspect as c, decodeAttributedBody as d, outline as f, decodeChatRef as g, MESSAGE_REF_VERSION as h, registerTools as i, MessageNotFoundError as j, MESSAGES_BUNDLE_ID as k, openStore as l, InvalidMessageRefError as m, SERVER_VERSION as n, AppleMessagesClient as o, CHAT_REF_VERSION as p, createServer as r, MessagesStore as s, SERVER_NAME as t, reactionLabel as u, encodeChatRef as v, SEND_MESSAGE as w, STORE_RELATIVE as x, encodeMessageRef as y, renderInstant as z };
|
|
2136
2136
|
|
|
2137
|
-
//# sourceMappingURL=server-
|
|
2137
|
+
//# sourceMappingURL=server-BSHulHkz.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server-DH4U3LBX.js","names":["#col","#date","#body","#messageColumns","#joins","#toRow","#chats","#participants","#config","#logger","#home","#runner","#contacts","#contactsTried","#located","#storeTried","#store","loadContactsConfig","#resolved","#contactsClient","#resolve","#correspondent","#render","#require","resolvePath","#reconcile","#invalidate","#chatFor","loadConfig"],"sources":["../src/build-info.ts","../src/client/dates.ts","../src/client/errors.ts","../src/client/jxa/core.ts","../src/client/jxa/write.ts","../src/client/locate.ts","../src/client/ref.ts","../src/client/typedstream.ts","../src/client/store.ts","../src/client/messages.ts","../src/config.ts","../src/guide.ts","../src/prompts.ts","../src/tools/util.ts","../src/tools/diagnostics.ts","../src/tools/actions.ts","../src/tools/attachments.ts","../src/tools/chats.ts","../src/tools/messages.ts","../src/tools/index.ts","../src/server.ts"],"sourcesContent":["// Build-time / runtime identity for the running server. `name`/`version` are\n// read from package.json at startup (always accurate); `gitCommit` /\n// `gitCommitDate` are injected by tsdown's `define` substitution at build time\n// and fall back to \"unknown\" when running from source (e.g. vitest).\n\nimport { readPackageIdentity, type BuildInfo } from \"@mgcrea/mcp-apple-core\";\n\n// oxlint-disable no-underscore-dangle -- bundler-injected build-time constants.\ndeclare const __GIT_COMMIT__: string;\ndeclare const __GIT_COMMIT_DATE__: string;\n\nconst pkg = readPackageIdentity(new URL(\"../package.json\", import.meta.url), {\n name: \"@mgcrea/mcp-apple-messages\",\n version: \"0.0.0\",\n});\n\nexport type { BuildInfo };\n\nexport const BUILD_INFO: BuildInfo = {\n name: pkg.name,\n version: pkg.version,\n gitCommit: typeof __GIT_COMMIT__ === \"string\" ? __GIT_COMMIT__ : \"unknown\",\n gitCommitDate: typeof __GIT_COMMIT_DATE__ === \"string\" ? __GIT_COMMIT_DATE__ : \"unknown\",\n};\n","/**\n * Messages' dates, which do not fit in a JavaScript number.\n *\n * `docs/messages.md` measured every populated date column as **nanoseconds since\n * 2001-01-01** — eighteen digits, about 7.9e17, two orders of magnitude past\n * `Number.MAX_SAFE_INTEGER`. `node:sqlite` throws on those rather than\n * truncating, which is correct of it and fatal to a naive `SELECT date`.\n *\n * The failure mode is what makes this worth its own module. Wrapped in the usual\n * try/catch the throw is swallowed and the column reports as EMPTY — the probe\n * did exactly that on its first granted run and announced \"no dates present\" for\n * all seven columns across 97,414 messages. A section written to catch a silent\n * 31-year error was itself silently wrong.\n *\n * ## The fix: divide in SQL, not in JavaScript\n *\n * `CAST(date AS REAL) / 1e9` never materialises the integer on the JS side, so\n * there is nothing to throw. Precision is fine: 7.9e8 seconds with a fractional\n * part is comfortably inside a double, and this surface has no use for\n * nanosecond resolution anyway.\n *\n * Every query in `store.ts` uses `APPLE_SECONDS_SQL`. Reading one of these\n * columns any other way is the bug.\n */\n\n/** Seconds between 1970-01-01 and 2001-01-01. Same constant as `packages/core`. */\nimport { CORE_DATA_EPOCH_OFFSET } from \"@mgcrea/mcp-apple-core\";\n\n/**\n * Below this, a value is seconds rather than nanoseconds.\n *\n * Messages switched around macOS 10.13 and old rows were not rewritten, so a\n * store with history from both eras carries both. 1e12 apple-seconds is the year\n * 33,658 and 1e12 apple-nanoseconds is 2001 — no real timestamp is near it in\n * either reading, which is what makes the split safe.\n */\nconst NANOSECOND_FLOOR = 1e12;\n\n/**\n * SQL that yields apple-SECONDS as a REAL, whichever unit the row holds.\n *\n * Done in SQL rather than in JS because the point is to never let the raw\n * integer reach `node:sqlite`'s value conversion.\n */\nexport const appleSecondsSql = (column: string): string =>\n `CASE WHEN ${column} IS NULL THEN NULL` +\n ` WHEN ABS(CAST(${column} AS REAL)) > ${NANOSECOND_FLOOR} THEN CAST(${column} AS REAL) / 1000000000.0` +\n ` ELSE CAST(${column} AS REAL) END`;\n\n/** Apple-seconds to a JS Date. Null in, null out. */\nexport const fromAppleSeconds = (value: number | null): Date | null => {\n if (value === null || !Number.isFinite(value) || value === 0) return null;\n return new Date((value + CORE_DATA_EPOCH_OFFSET) * 1000);\n};\n\n/** A JS Date to apple-seconds, for range bounds. */\nexport const toAppleSeconds = (date: Date): number =>\n date.getTime() / 1000 - CORE_DATA_EPOCH_OFFSET;\n\n/** ISO-8601, or null. What every date field on a result carries. */\nexport const renderInstant = (value: number | null): string | null =>\n fromAppleSeconds(value)?.toISOString() ?? null;\n\nexport { CORE_DATA_EPOCH_OFFSET };\n","/**\n * Messages' error surface. The taxonomy lives in `@mgcrea/mcp-apple-core`; what\n * belongs here is the identity those messages are written against.\n */\n\nimport { AppleAutomationError, type SurfaceContext } from \"@mgcrea/mcp-apple-core\";\n\nexport const MESSAGES_SURFACE: SurfaceContext = {\n appName: \"Messages\",\n envPrefix: \"APPLE_MESSAGES\",\n};\n\n/**\n * Used by exactly one code path, and unused by every read.\n *\n * Messages is the one surface with NO Apple Events read lane at all — measured,\n * not assumed. `docs/messages.md` records every attempt failing, and the reason\n * is peculiar enough to write down: Messages answers \"Application isn't running\"\n * while `NSRunningApplication` reports it running, because it lives as a\n * windowless background process that declines to wake for a script. The liveness\n * check and the app's own answer disagree and neither is lying.\n *\n * The id is the liveness check for `send`, which is the only thing Apple Events\n * can do on this surface — see `client/jxa/core.ts`.\n */\nexport const MESSAGES_BUNDLE_ID = \"com.apple.MobileSMS\";\n\nexport {\n AppleAutomationError as AppleMessagesError,\n IndexUnavailableError,\n PlatformError,\n PreconditionError,\n ProtocolError,\n SchemaDriftError,\n TccDeniedError,\n} from \"@mgcrea/mcp-apple-core\";\n\n/** A message ref no longer resolves — deleted, or the chat was cleared. */\nexport class MessageNotFoundError extends AppleAutomationError {\n override readonly name = \"MessageNotFoundError\";\n\n constructor(ref: string) {\n super(\n `No message for ref \"${ref}\". It was probably deleted since the search ran. Re-run the ` +\n `search to get a current ref.`,\n { ref },\n );\n }\n}\n\n/** A chat ref no longer resolves. */\nexport class ChatNotFoundError extends AppleAutomationError {\n override readonly name = \"ChatNotFoundError\";\n\n constructor(ref: string) {\n super(`No chat for ref \"${ref}\". Use apple_messages_list_chats to get a current ref.`, { ref });\n }\n}\n\n/**\n * The store could not be read.\n *\n * Its own error because this surface fails harder than any other: there is no\n * Apple Events fallback, so without Full Disk Access there is no server at all.\n * `docs/distribution.md`'s \"try before you grant\" was retired partly because of\n * this surface, and the message says so rather than implying a degraded mode\n * that does not exist.\n */\nexport class MessagesUnavailableError extends AppleAutomationError {\n override readonly name = \"MessagesUnavailableError\";\n\n constructor(reason: string) {\n super(reason, {});\n }\n}\n\n/**\n * Messages would not accept any form of recipient for a send.\n *\n * Its own error because the cause is almost never the recipient. Every rung of\n * the ladder in `client/jxa/core.ts` except the first one enumerates something,\n * and enumeration is exactly what this app refuses — so the usual cause of this\n * error is that the chat is new (no guid in the store to address it by) rather\n * than that the person does not exist. The message says so, because \"not found\"\n * would send a caller looking for a typo that is not there.\n */\nexport class SendTargetNotFoundError extends AppleAutomationError {\n override readonly name = \"SendTargetNotFoundError\";\n\n constructor(recipient: string, attempts: readonly string[]) {\n super(\n `Messages would not resolve \"${recipient}\" to a chat or participant, so nothing was sent. ` +\n `This usually means there is no existing conversation with them on this Mac: Messages ` +\n `refuses to enumerate participants for a script, so an existing chat is the only handle ` +\n `this server can address reliably. Open the conversation once in Messages.app and retry.`,\n { recipient, attempts: [...attempts] },\n );\n }\n}\n\n/** Messages accepted the target and then refused the send itself. */\nexport class SendFailedError extends AppleAutomationError {\n override readonly name = \"SendFailedError\";\n\n constructor(message: string, attempts: readonly string[]) {\n super(`Messages refused the send: ${message}`, { attempts: [...attempts] });\n }\n}\n","/**\n * The Apple Events lane for Messages — which exists for exactly one verb.\n *\n * Every script here is a static constant. None may contain a template\n * interpolation: `assertStaticScript` rejects any script containing a dollar\n * sign followed by a brace, including one written inside the JXA source. Use\n * string concatenation in JXA code.\n *\n * Contract, shared with every other surface:\n * - parameters arrive as `JSON.parse(argv[0])`\n * - success returns `JSON.stringify({ok: true, data})`\n * - an application-level failure returns `{ok: false, error: {code, message}}`\n * and still exits 0, so a non-zero exit always means infrastructure.\n *\n * ## There is no read.ts here, and there never can be\n *\n * Adding a send did not add a read lane, and on this surface that is not a\n * policy choice — it is the measurement in `docs/messages.md`:\n *\n * | attempt | result |\n * | ---------------- | ----------------------------------------- |\n * | `chats()` | `Error: Application isn't running.` |\n * | `chats.id()` | `TypeError: M.chats.id is not a function` |\n * | `participants()` | `Error: Application isn't running.` |\n * | `buddies()` | `Error: Application isn't running.` |\n * | messages of chat | `Error: Application isn't running.` |\n *\n * Messages answers \"Application isn't running\" while `NSRunningApplication`\n * reports it running, because it lives as a windowless background process that\n * declines to wake for a script. `test/jxa.test.ts` asserts `read.ts` does not\n * exist, so this cannot be re-added out of helpfulness.\n *\n * The consequence for the code below is concrete: **the target resolution steps\n * that enumerate anything are expected to fail**, which is why they are a ladder\n * and why every rung reports itself. The one rung that does not enumerate —\n * `chats.byId(guid)`, with the guid handed over by the file lane — is the one\n * this design is built around.\n *\n * ## What the dictionary actually offers\n *\n * MEASURED from `sdef /System/Applications/Messages.app` on macOS 26.6. Three\n * commands, and only one of them is a write:\n *\n * send text (or a file) to a participant or a chat\n * login log in to all accounts\n * logout log out of all accounts\n *\n * `login`/`logout` are not exposed as tools: logging a user out of iMessage on\n * every device is not something to do behind a tool call, and there is no read\n * to justify logging in.\n *\n * `send`'s direct parameter is typed `file` OR `text`. **This ships text only.**\n * The file form is one branch away and deliberately not taken: a tool that\n * transfers an arbitrary local path to a remote person is an exfiltration\n * primitive, and unlike the text form its blast radius is not bounded by what\n * the model can say. Recorded here so the omission reads as a decision.\n *\n * ## The `to` parameter, and why the file lane picks the target\n *\n * `send` takes a `participant` or a `chat`. Getting one of those normally means\n * enumerating, which is the thing that does not work here. But the `chat` class\n * carries `id` — \"A guid identifier for this chat\" — and the file lane already\n * holds `chat.guid` for all 1,027 chats on the measured store. So the read lane\n * chooses the target and the write lane addresses it by id, which is the only\n * arrangement where neither lane has to do the thing it cannot.\n *\n * That the two guids are the same string is NOT assumed. It is the exact class\n * of thing this project has been wrong about before — `docs/messages.md` calls\n * the id bridge \"unanswerable by construction\" because Messages returned no\n * identifier to bridge FROM. So `chats.byId` is rung one of a ladder, every rung\n * records why it failed, and the strategy that worked is reported back to the\n * caller in the tool result.\n */\n\n/** The bundle id, which does not match the display name: Messages.app is still MobileSMS. */\nexport const PRELUDE = `\nObjC.import(\"AppKit\");\n\nfunction ok(data) { return JSON.stringify({ ok: true, data: data }); }\nfunction err(code, message, extra) {\n var e = { code: code, message: String(message) };\n if (extra) e.detail = extra;\n return JSON.stringify({ ok: false, error: e });\n}\n\n/** Read one property defensively — every read on this surface is allowed to fail. */\nfunction prop(fn, fallback) {\n try {\n var v = fn();\n return v === undefined ? fallback : v;\n } catch (e) {\n return fallback;\n }\n}\n\nfunction isMessagesRunning() {\n var apps = $.NSRunningApplication.runningApplicationsWithBundleIdentifier(\"com.apple.MobileSMS\");\n return apps.count > 0;\n}\n\n/** The dictionary's service type enumeration: SMS, iMessage, RCS. */\nfunction serviceEnum(name) {\n var s = String(name || \"\").toLowerCase();\n if (s === \"sms\") return \"SMS\";\n if (s === \"rcs\") return \"RCS\";\n return \"iMessage\";\n}\n\n/**\n * Find something \\`send\\` will accept as its \\`to\\`.\n *\n * Ordered cheapest and most-likely-to-work first. Every rung is wrapped, every\n * failure is recorded, and the caller is told which one answered — on a surface\n * whose whole read half is known broken, \"it worked\" without \"how\" is not a\n * result anyone can act on later.\n */\nfunction resolveTarget(M, p, tried) {\n var i;\n\n // 1. The chat guid the file lane read out of chat.db. No enumeration.\n if (p.chatGuid) {\n try {\n var chat = M.chats.byId(p.chatGuid);\n chat.id();\n return { target: chat, strategy: \"chat-guid\", kind: \"chat\" };\n } catch (e) {\n tried.push(\"chat-guid: \" + (e.message || e));\n }\n }\n\n if (!p.handle) return null;\n\n // 2. The guid Messages composes for a one-to-one chat, spelled the way the\n // store spells it: \"iMessage;-;+15551234567\". Constructed rather than read,\n // so it is below the real one and above everything that enumerates.\n var services = p.service ? [serviceEnum(p.service)] : [\"iMessage\", \"SMS\", \"RCS\"];\n for (i = 0; i < services.length; i++) {\n var guess = services[i] + \";-;\" + p.handle;\n try {\n var guessed = M.chats.byId(guess);\n guessed.id();\n return { target: guessed, strategy: \"chat-guid-guess\", kind: \"chat\", guid: guess };\n } catch (e2) {\n tried.push(\"chat-guid-guess(\" + guess + \"): \" + (e2.message || e2));\n }\n }\n\n // 3. A participant reached through its account. This enumerates, so it is\n // expected to fail with \"Application isn't running\" — kept because it is\n // the form every AppleScript example on the internet uses, and because if\n // launching the app does wake the scripting interface, this is what works.\n for (i = 0; i < services.length; i++) {\n try {\n var accounts = M.accounts.whose({ serviceType: services[i] })();\n for (var j = 0; j < accounts.length; j++) {\n try {\n var buddy = accounts[j].participants.whose({ handle: p.handle })()[0];\n if (buddy) {\n buddy.id();\n return { target: buddy, strategy: \"account-participant\", kind: \"participant\" };\n }\n } catch (e4) {\n tried.push(\"account-participant(\" + services[i] + \"): \" + (e4.message || e4));\n }\n }\n } catch (e3) {\n tried.push(\"accounts(\" + services[i] + \"): \" + (e3.message || e3));\n }\n }\n\n // 4. The flat participant list, last because it is the widest enumeration.\n try {\n var flat = M.participants.whose({ handle: p.handle })()[0];\n if (flat) {\n flat.id();\n return { target: flat, strategy: \"participant\", kind: \"participant\" };\n }\n tried.push(\"participant: no participant with that handle\");\n } catch (e5) {\n tried.push(\"participant: \" + (e5.message || e5));\n }\n\n return null;\n}\n`;\n","import { PRELUDE } from \"./core.js\";\n\n/**\n * One script, one verb.\n *\n * `send` is the only mutating command in the Messages dictionary that this\n * server exposes — see `core.ts` for the full list and for why `login`/`logout`\n * and the file form of `send` are left out.\n *\n * ## What this script deliberately does NOT do\n *\n * It does not report success from its own read-back, because there is nothing to\n * read back: `send` returns no value, and every read this app offers fails. A\n * script that answered `{ok: true}` and stopped would be claiming delivery on\n * the strength of a command that did not throw — the exact shape of \"plausible,\n * wrong and silent\" this repo keeps designing against.\n *\n * So the script's answer is deliberately narrow: **the send command was accepted\n * by Messages, and here is how the target was addressed.** Whether a row landed\n * is a question for the file lane, and `client/messages.ts` asks it immediately\n * afterwards by polling chat.db for the outgoing row. That split is the answer\n * to the open question `docs/messages.md` left — \"whether a send should\n * re-resolve by scanning the store for a recent row on the target chat\" — and it\n * is what makes a send reportable at all on a surface with no id bridge.\n */\nexport const SEND_MESSAGE = `${PRELUDE}\nfunction run(argv) {\n var p = JSON.parse(argv[0]);\n var M = Application(\"Messages\");\n\n var wasRunning = isMessagesRunning();\n if (!wasRunning && !p.allowLaunch) {\n return err(\"APP_NOT_RUNNING\", \"Messages is not running.\");\n }\n\n var tried = [];\n var resolved = resolveTarget(M, p, tried);\n if (!resolved) {\n return err(\n \"SEND_TARGET_NOT_FOUND\",\n \"Messages would not resolve a chat or participant for that recipient.\",\n tried\n );\n }\n\n try {\n M.send(p.text, { to: resolved.target });\n } catch (e) {\n return err(\"SEND_FAILED\", e.message || e, tried);\n }\n\n return ok({\n strategy: resolved.strategy,\n targetKind: resolved.kind,\n // Best effort, and allowed to be null: reading the id back is itself a read.\n targetId: prop(function () { return String(resolved.target.id()); }, null),\n launched: !wasRunning,\n attempts: tried\n });\n}\n`;\n","import { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\nimport { describeStore, type StoreFacts } from \"@mgcrea/mcp-apple-core\";\n\n/**\n * Find Messages' store.\n *\n * The easiest locator in the repo: `~/Library/Messages/chat.db` is a constant,\n * with no generated directory name to list (Reminders) and no per-account fan-out\n * (Contacts). `statSync` succeeds on a TCC-protected file while `open` is denied,\n * so this distinguishes \"not there\" from \"not allowed\" with no permission at all\n * — which is most of what diagnostics is for on a surface where the grant is\n * mandatory.\n */\n\nexport const STORE_RELATIVE = join(\"Library\", \"Messages\", \"chat.db\");\n\n/** Attachment bytes live here, referenced by `attachment.filename`. */\nexport const ATTACHMENTS_RELATIVE = join(\"Library\", \"Messages\", \"Attachments\");\n\n/**\n * The directory an attachment path is required to sit inside.\n *\n * Deliberately the Messages root rather than `Attachments` itself. Measured\n * shapes of `attachment.filename` include stickers, which Messages keeps in a\n * sibling directory, so confining to `Attachments` would refuse real files —\n * and confining to nothing at all would let a row in a database this server\n * does not write choose which file gets copied out.\n */\nexport const MESSAGES_ROOT_RELATIVE = join(\"Library\", \"Messages\");\n\nexport type LocateResult = StoreFacts & {\n storePath: string;\n attachmentsPath: string;\n /** Nothing outside this is copied out, whatever the store says. */\n messagesRoot: string;\n reason: string | null;\n};\n\nexport const defaultStorePath = (home: string = homedir()): string => join(home, STORE_RELATIVE);\n\nconst FDA_HINT =\n \"Grant Full Disk Access to the app running this server (System Settings > Privacy & Security > \" +\n \"Full Disk Access) and restart it. Granting it to Messages.app does nothing — the reader needs \" +\n \"the permission. Unlike every other surface here, there is no Apple Events fallback: Messages \" +\n \"has no read path in its scripting dictionary at all, so without this grant there is no server.\";\n\nexport const locateStore = (\n opts: { storePath?: string | undefined; home?: string } = {},\n): LocateResult => {\n const home = opts.home ?? homedir();\n const storePath = opts.storePath ?? defaultStorePath(home);\n const facts = describeStore(storePath);\n\n return {\n ...facts,\n storePath,\n attachmentsPath: join(home, ATTACHMENTS_RELATIVE),\n messagesRoot: join(home, MESSAGES_ROOT_RELATIVE),\n reason: facts.readable\n ? null\n : facts.exists\n ? `Found the Messages store at ${storePath} but cannot read it. ${FDA_HINT}`\n : opts.storePath\n ? `No file at ${storePath}. APPLE_MESSAGES_STORE points at nothing.`\n : `No Messages store at ${storePath}. Has Messages ever been set up on this account?`,\n };\n};\n","import { AppleAutomationError } from \"@mgcrea/mcp-apple-core\";\n\n/**\n * Refs for messages and chats.\n *\n * ## Why a GUID rather than a rowid\n *\n * Both exist, and the rowid is faster to look up. The GUID wins anyway because\n * **rowids are reused.** SQLite hands a deleted row's id to the next insert\n * unless the table is `AUTOINCREMENT`, and Messages deletes constantly — every\n * \"delete this conversation\" frees a block of them. A ref handed to a model in\n * one turn and used two turns later would then resolve to a DIFFERENT message,\n * with no error anywhere. That is the failure this project keeps designing\n * against: plausible, wrong, and silent.\n *\n * The GUID is also what Apple itself joins on — `associated_message_guid` for\n * reactions, `thread_originator_guid` for replies — so it is the identifier the\n * schema already treats as stable.\n *\n * ## Why `m1:` and `mc1:`\n *\n * `c1:` is Calendar's, `r1:` is Reminders', `k1:` is Contacts'. A ref that\n * decodes under two surfaces would be worse than one that decodes under none,\n * so each prefix is claimed once and the version digit keeps a future scheme\n * change additive.\n */\n\nexport const MESSAGE_REF_VERSION = \"m1\";\nexport const CHAT_REF_VERSION = \"mc1\";\n\n/**\n * A GUID is opaque and not always a UUID — measured shapes include\n * `iMessage;-;+15551234567` on chats, which carries semicolons and a phone\n * number. So the tail is greedy and nothing inside it is parsed.\n */\nconst MESSAGE_PATTERN = /^m1:(.+)$/;\nconst CHAT_PATTERN = /^mc1:(.+)$/;\n\nconst otherSurface = (raw: string): string => {\n if (raw.startsWith(\"c1:\")) return \" That one is a Calendar event ref.\";\n if (raw.startsWith(\"r1:\")) return \" That one is a Reminders ref.\";\n if (raw.startsWith(\"k1:\")) return \" That one is a Contacts ref.\";\n if (raw.startsWith(\"mc1:\")) return \" That one is a CHAT ref — this wants a message ref.\";\n if (raw.startsWith(\"m1:\")) return \" That one is a MESSAGE ref — this wants a chat ref.\";\n return \"\";\n};\n\nexport class InvalidMessageRefError extends AppleAutomationError {\n override readonly name = \"InvalidMessageRefError\";\n\n constructor(raw: string, want: \"message\" | \"chat\") {\n const shape = want === \"message\" ? '\"m1:<guid>\"' : '\"mc1:<guid>\"';\n super(\n `\"${raw}\" is not a ${want} ref. Refs come from apple_messages_* results and look like ` +\n `${shape} — they are opaque and must not be constructed by hand.${otherSurface(raw)}`,\n { ref: raw },\n );\n }\n}\n\nexport const encodeMessageRef = (guid: string): string => `${MESSAGE_REF_VERSION}:${guid}`;\nexport const encodeChatRef = (guid: string): string => `${CHAT_REF_VERSION}:${guid}`;\n\nexport const decodeMessageRef = (raw: string): string => {\n const m = MESSAGE_PATTERN.exec(raw.trim());\n if (!m?.[1]) throw new InvalidMessageRefError(raw, \"message\");\n return m[1];\n};\n\nexport const decodeChatRef = (raw: string): string => {\n const m = CHAT_PATTERN.exec(raw.trim());\n if (!m?.[1]) throw new InvalidMessageRefError(raw, \"chat\");\n return m[1];\n};\n","/**\n * NSArchiver `typedstream` reader — enough of it to pull the text out of a\n * Messages `attributedBody` blob.\n *\n * ## Why this has to exist\n *\n * `docs/messages.md` measured it: 97,092 of 97,414 messages carry a blob and\n * only 94,049 carry `text`. **The blob is the norm and `text` is the redundant\n * copy**, not the other way round. 3,043 messages — one in thirty-two — have an\n * empty `text` and content only in here, so a server that reads the column\n * returns nothing for them, silently and with no error to notice.\n *\n * The archive header is `04 0B streamtyped 81 E8 03`. Not `bplist00`, not gzip,\n * and not the protobuf that Notes turned out to hold, so none of the existing\n * decoders apply.\n *\n * ## The format, as far as this reader needs it\n *\n * A stream of tagged values. Integers are a single signed byte unless prefixed:\n *\n * 0x81 int16 follows (little-endian)\n * 0x82 int32 follows\n * 0x83 float or double follows\n * 0x84 START — a new class or object definition\n * 0x85 nil / empty\n * 0x86 END of the current object\n * >=0x92 a back-reference; index = byte - 0x92\n *\n * Strings arrive length-prefixed. Class names and object contents both use that\n * shape, which is why this walks structurally instead of pattern-matching: the\n * same bytes mean different things depending on where you are.\n *\n * ## Honest failure, never a guess\n *\n * The Notes decoder was documented backwards because a `LIMIT 1` sample landed\n * on an outlier, and this project has now been bitten three times by a heuristic\n * that produced a plausible wrong answer. So there is no \"longest printable run\"\n * fallback here. If the structure does not parse, `ok` is false and the caller\n * counts it — and `text` remains available as the answer for the 96.5% of rows\n * that have one.\n *\n * Pure and I/O-free.\n *\n * ## Two copies, deliberately\n *\n * `scripts/lib/typedstream.mjs` is the same reader, and the probe uses it to\n * measure this one against a real store. They are kept in step by hand, as\n * `packages/notes/src/client/protobuf.ts` and `scripts/lib/note-protobuf.mjs`\n * already are: a probe that imported from a package would stop being runnable\n * before that package exists, which is the wrong way round.\n */\n\nconst SIGNATURE = \"streamtyped\";\n\nconst TAG_I16 = 0x81;\nconst TAG_I32 = 0x82;\nconst TAG_DECIMAL = 0x83;\nconst TAG_START = 0x84;\nconst TAG_EMPTY = 0x85;\nconst TAG_END = 0x86;\n/** Anything at or above this is an index into the table of things already seen. */\nconst TAG_REFERENCE = 0x92;\n\n/** A blob larger than this is not a message; refuse rather than chew through it. */\nconst MAX_BLOB_BYTES = 4 * 1024 * 1024;\n/**\n * Structural guard for the walk BEFORE the text is reached — class chains and\n * type encodings, which are a few dozen tokens on every blob measured. Reaching\n * this means the stream is not shaped like an archived attributed string.\n */\nconst MAX_TOKENS = 20_000;\n\nconst utf8 = new TextDecoder(\"utf-8\", { fatal: true });\nconst latin1 = new TextDecoder(\"latin1\");\n\nclass Cursor {\n readonly buf: Uint8Array;\n pos: number;\n\n constructor(buf: Uint8Array) {\n this.buf = buf;\n this.pos = 0;\n }\n get done(): boolean {\n return this.pos >= this.buf.length;\n }\n get remaining(): number {\n return this.buf.length - this.pos;\n }\n byte(): number {\n return this.buf[this.pos++] as number;\n }\n peek(): number {\n return this.buf[this.pos] as number;\n }\n take(n: number): Uint8Array {\n const out = this.buf.subarray(this.pos, this.pos + n);\n this.pos += n;\n return out;\n }\n\n /**\n * A byte at an offset from the cursor. Every caller checks `remaining` first,\n * so the index is in range — this exists to say that once rather than to\n * scatter non-null assertions through the integer readers.\n */\n at(offset: number): number {\n return this.buf[this.pos + offset] as number;\n }\n}\n\n/**\n * A signed integer, which is also how every length in the stream is written.\n *\n * Returns null rather than throwing on a truncated read — a truncated blob is a\n * finding, not a crash.\n */\nconst readInt = (c: Cursor): number | null => {\n if (c.done) return null;\n const b = c.byte();\n if (b === TAG_I16) {\n if (c.remaining < 2) return null;\n const v = c.at(0) | (c.at(1) << 8);\n c.pos += 2;\n // Sign-extend: lengths are positive, but the same encoding carries indices.\n return v > 0x7fff ? v - 0x10000 : v;\n }\n if (b === TAG_I32) {\n if (c.remaining < 4) return null;\n const v = (c.at(0) | (c.at(1) << 8) | (c.at(2) << 16) | (c.at(3) << 24)) >>> 0;\n c.pos += 4;\n return v > 0x7fffffff ? v - 0x100000000 : v;\n }\n if (b === TAG_DECIMAL) {\n // Present in attribute values (font sizes). Skipped deliberately: nothing\n // this reader reports needs the number, and consuming it keeps the walk\n // aligned. Width is not knowable from the tag alone, so this is where an\n // unfamiliar blob is most likely to derail — and it will say so.\n return null;\n }\n return b > 0x7f ? b - 0x100 : b;\n};\n\n/** A length-prefixed string. `null` when the bytes are not valid UTF-8. */\nconst readString = (c: Cursor): string | null => {\n const len = readInt(c);\n if (len === null || len < 0 || len > c.remaining) return null;\n const bytes = c.take(len);\n try {\n return utf8.decode(bytes);\n } catch {\n return null;\n }\n};\n\n/**\n * A length-prefixed UTF-8 payload — the `+` type encoding's value.\n *\n * The length is in BYTES, which is the trap: a 22-character string of accents\n * and kana declares 36. Slicing by character count would desynchronise the walk\n * and corrupt everything after it.\n */\nconst readByteArray = (c: Cursor): string | null => {\n const len = readInt(c);\n if (len === null || len < 0 || len > c.remaining) return null;\n const bytes = c.take(len);\n try {\n return utf8.decode(bytes);\n } catch {\n return null;\n }\n};\n\n/**\n * Validate the archive header and position the cursor after it.\n *\n * The system-version integer that follows the signature is read and discarded —\n * it is 1000 on every blob measured, and pinning it would reject a future macOS\n * for no reason.\n */\nconst readHeader = (c: Cursor): string | null => {\n if (c.remaining < 16) return \"too short to be a typedstream\";\n const version = c.byte();\n const sigLen = c.byte();\n if (sigLen !== SIGNATURE.length) return `bad signature length ${sigLen}`;\n const sig = latin1.decode(c.take(sigLen));\n if (sig !== SIGNATURE) return `not a typedstream (signature ${JSON.stringify(sig)})`;\n readInt(c);\n return version === 4 ? null : `unexpected streamer version ${version}`;\n};\n\n/**\n * Walk the stream, dispatching on the type encoding.\n *\n * The layout, read off real bytes rather than inferred — `NSArchiver` still\n * ships on macOS 26, so `NSAttributedString(string: \"Hello, world\")` archived\n * through it produces exactly what Messages stores, with a plaintext nobody has\n * to guess at:\n *\n * 04 0b \"streamtyped\" 81 e8 03 header\n * 84 01 40 START, type \"@\" — an object\n * 84 84 84 12 \"NSAttributedString\" 00\n * 84 84 08 \"NSObject\" 00 85 92\n * 84 84 84 08 \"NSString\" 01 94\n * 84 01 2b 0c \"Hello, world\" START, type \"+\", then the bytes\n * 86 END\n *\n * So the content is whatever follows the type encoding `+`, and the length is in\n * BYTES of UTF-8 — 0x24 for a 22-character string of accents and kana. Past 127\n * it takes the 0x81 int16 form, measured at 0x81 90 01 for a 400-byte payload.\n *\n * Walking structurally rather than scanning for `84 01 2b` matters: 0x84 is a\n * valid UTF-8 continuation byte, so that sequence can occur INSIDE a message.\n * Consuming each payload by its declared length is what keeps the walk in sync\n * and makes a false positive impossible.\n */\ntype WalkResult = {\n strings: string[];\n classes: string[];\n firstPayloadEnd: number | null;\n error: string | null;\n};\n\nconst walk = (c: Cursor): WalkResult => {\n const strings: string[] = [];\n const classes: string[] = [];\n let firstPayloadEnd: number | null = null;\n let tokens = 0;\n\n while (!c.done) {\n if (++tokens > MAX_TOKENS) {\n return { strings, classes, firstPayloadEnd, error: \"token limit exceeded\" };\n }\n const b = c.peek();\n\n if (b !== TAG_START) {\n // nil, END, a back-reference, a version byte or a scalar. None of them\n // carries text, and all are one byte wide.\n c.pos += 1;\n continue;\n }\n\n c.pos += 1;\n // A class chain: START START START <name>. Nothing to read at this level.\n if (c.peek() === TAG_START) continue;\n\n const before = c.pos;\n const encoding = readString(c);\n if (encoding === null) {\n c.pos = before;\n continue;\n }\n\n if (encoding === \"+\") {\n const value = readByteArray(c);\n if (value === null) {\n return { strings, classes, firstPayloadEnd, error: \"truncated byte array\" };\n }\n strings.push(value);\n firstPayloadEnd = c.pos;\n // STOP HERE. The first `+` payload is the backing store, and nothing past\n // it is decoded by design — so walking on can only fail.\n //\n // It did. Measured over 97,094 real blobs, this reader failed exactly\n // twice, both \"token limit exceeded\", and both had ALREADY read their text\n // (936 and 2,095 bytes) before exhausting the walk on attribute runs. Two\n // messages were being thrown away for work whose result was discarded.\n // Whether anything follows is a position comparison, not a walk.\n return { strings, classes, firstPayloadEnd, error: null };\n }\n\n // A class name rather than a type encoding. Recorded because which classes\n // appear is how the open question in docs/messages.md gets answered.\n if (/^[A-Za-z_][A-Za-z0-9_.]*$/.test(encoding) && encoding.length > 2) classes.push(encoding);\n }\n return { strings, classes, firstPayloadEnd, error: null };\n};\n\n/**\n * The message text.\n *\n * ## Text only, and that is now a measured decision rather than a shortcut\n *\n * `docs/messages.md` left this open: *\"whether `attributedBody` carries\n * formatting that matters, or is only ever a redundant copy of `text` plus\n * attachment placeholders. This decides whether the decoder must preserve\n * structure or merely extract a string.\"*\n *\n * It is the second. Two things settle it:\n *\n * 1. **Attribute values are back-REFERENCED, not inline.** Archiving an\n * attachment-shaped string through the real `NSArchiver` produces\n * `92 84 98 98 22 \"__kIMFileTransferGUIDAttributeName\" 86` — where `98` is an\n * index into the object table rather than a type encoding. Resolving those\n * means reconstructing the whole table, which is materially more code and\n * more ways to be silently wrong.\n * 2. **Nothing needs it.** The one attribute worth having is the file-transfer\n * GUID, and attachments are already reachable relationally — 17,529 rows in\n * their own table, joined through `message_attachment_join`. The blob's copy\n * is redundant, and the placeholder character itself (U+FFFC) survives in the\n * text, so the position is not lost either.\n *\n * So this returns the backing string and says plainly that it stopped there.\n * `hasAttributes` reports whether anything followed it, which is what a future\n * decision to go further would be based on — a count, not a guess.\n */\nexport type DecodedBody =\n | {\n ok: true;\n text: string;\n classes: string[];\n /** Bytes remained after the backing store — attribute runs this does not decode. */\n hasAttributes: boolean;\n error: null;\n }\n | { ok: false; text?: undefined; classes?: string[]; hasAttributes?: undefined; error: string };\n\nexport const decodeAttributedBody = (buffer: Uint8Array | null | undefined): DecodedBody => {\n if (!buffer || buffer.length === 0) return { ok: false, error: \"empty blob\" };\n if (buffer.length > MAX_BLOB_BYTES) return { ok: false, error: \"blob too large\" };\n\n const c = new Cursor(buffer);\n const headerError = readHeader(c);\n if (headerError) return { ok: false, error: headerError };\n\n const { strings, classes, error, firstPayloadEnd } = walk(c);\n if (error) return { ok: false, error, classes };\n if (strings.length === 0) return { ok: false, error: \"no content string found\", classes };\n\n return {\n ok: true,\n text: strings[0] as string,\n classes,\n /** Bytes remained after the backing store — attribute runs this does not decode. */\n hasAttributes: firstPayloadEnd !== null && firstPayloadEnd < buffer.length - 2,\n error: null,\n };\n};\n\n/**\n * A redacted structural outline of one blob.\n *\n * For measuring the format on a machine that has the data, without the report\n * ever carrying a message. Every string is reduced to its length and its\n * character class; only Apple's own constants survive as themselves.\n */\nexport const outline = (buffer: Uint8Array, maxTokens = 60): string[] => {\n const c = new Cursor(buffer);\n const headerError = readHeader(c);\n if (headerError) return [`ERROR ${headerError}`];\n\n const out: string[] = [];\n let tokens = 0;\n while (!c.done && tokens < maxTokens) {\n tokens += 1;\n const b = c.peek();\n if (b === TAG_START) {\n c.pos += 1;\n out.push(\"START\");\n continue;\n }\n if (b === TAG_EMPTY) {\n c.pos += 1;\n out.push(\"nil\");\n continue;\n }\n if (b === TAG_END) {\n c.pos += 1;\n out.push(\"END\");\n continue;\n }\n if (b >= TAG_REFERENCE) {\n c.pos += 1;\n out.push(`ref#${b - TAG_REFERENCE}`);\n continue;\n }\n const before = c.pos;\n const s = readString(c);\n if (s === null) {\n c.pos = before + 1;\n out.push(`byte 0x${b.toString(16).padStart(2, \"0\")}`);\n continue;\n }\n // Apple constants and type encodings are safe to print. Anything else is\n // somebody's message, and only its shape leaves the process.\n if (/^(NS|IM|__kIM|CF)[A-Za-z0-9_]*$/.test(s) || s.length <= 2) out.push(`\"${s}\"`);\n else out.push(`<str len=${s.length} ${/^[\\x20-\\x7e]*$/.test(s) ? \"ascii\" : \"unicode\"}>`);\n }\n if (!c.done) out.push(\"…\");\n return out;\n};\n\nexport const TYPEDSTREAM_SIGNATURE = SIGNATURE;\n","import type { DatabaseSync } from \"node:sqlite\";\n\nimport {\n columnsOf,\n escapeLike,\n fingerprintSchema,\n openReadOnly,\n SchemaDriftError,\n type Logger,\n type ReadOnlyMode,\n} from \"@mgcrea/mcp-apple-core\";\n\nimport { appleSecondsSql } from \"./dates.js\";\nimport { decodeAttributedBody } from \"./typedstream.js\";\n\n/**\n * Messages' file lane — the only lane there is.\n *\n * `docs/messages.md` measured every Apple Events read attempt failing, so unlike\n * every other surface here there is nothing to fall back to. That shapes two\n * things: `SchemaDriftError` is the only fatal condition, and a missing store is\n * reported as a reason rather than as an empty list.\n *\n * ## Three measurements this file is built on\n *\n * **The blob is the norm, and increasingly the only thing there.** 97,094 rows\n * carry `attributedBody` and 94,043 carry `text`; 3,051 have only the blob. That\n * 3.1% is an average over a decade, and it hides the real shape: measured\n * through this server, 2016-2025 are ~99% plain `text` and everything from\n * MARCH 2026 ONWARD is blob-only. A reader that selects `text` would today\n * report that the conversation stopped in February. Every read here goes through\n * `#body()`, which prefers the column and falls back to the decoder — validated\n * at **100.000% agreement across all 94,043 rows** where both exist, with zero failures\n * across all 97,094.\n *\n * **Dates do not fit in a JavaScript number.** See `dates.ts`. Every date column\n * is projected through `appleSecondsSql`, never selected raw.\n *\n * **Reactions are messages.** 2,788 rows carry `associated_message_type != 0`,\n * and a reader that does not filter them renders `Liked \"see you at 8\"` as if\n * somebody had typed it. Filtering is table stakes, not an enhancement.\n */\n\n/** Tables the lane cannot work without. */\nconst REQUIRED = [\"message\", \"chat\", \"handle\"] as const;\n\nconst PROBED_FINGERPRINT = \"87b01c58a631\";\nconst PROBED_MACOS = \"26.6\";\n\n/**\n * A tapback rather than something somebody typed.\n *\n * 2000–2005 add a reaction, 3000–3005 remove one; 0 is an ordinary message.\n * The ranges are Apple's and are not documented anywhere public, so the code\n * treats \"not zero\" as the test and only uses the ranges to LABEL — an\n * unrecognised value is still excluded from the conversation, which is the safe\n * direction.\n */\nconst REACTION_LABELS: Record<number, string> = {\n 2000: \"loved\",\n 2001: \"liked\",\n 2002: \"disliked\",\n 2003: \"laughed\",\n 2004: \"emphasized\",\n 2005: \"questioned\",\n};\n\nexport const reactionLabel = (type: number): string => {\n if (REACTION_LABELS[type]) return REACTION_LABELS[type] as string;\n if (type >= 3000 && type <= 3005) return `removed ${REACTION_LABELS[type - 1000] ?? \"reaction\"}`;\n return `reaction ${type}`;\n};\n\nexport type StoreCapabilities = {\n fingerprint: string;\n messageColumns: Set<string>;\n chatColumns: Set<string>;\n handleColumns: Set<string>;\n attachmentColumns: Set<string>;\n hasAttachments: boolean;\n hasReactions: boolean;\n hasThreads: boolean;\n hasEdits: boolean;\n};\n\nexport type MessageRow = {\n guid: string;\n chatGuid: string | null;\n chatName: string | null;\n handle: string | null;\n service: string | null;\n isFromMe: boolean;\n /** Apple-seconds. Render with `renderInstant`. */\n sentAt: number | null;\n readAt: number | null;\n deliveredAt: number | null;\n editedAt: number | null;\n text: string | null;\n /** Which lane answered — the column, or the decoder. */\n textSource: \"column\" | \"decoded\" | \"none\";\n subject: string | null;\n isRead: boolean;\n isSent: boolean;\n isDelivered: boolean;\n hasAttachments: boolean;\n /** Set when this row is a tapback on another message rather than a message. */\n reactionType: number | null;\n reactionTarget: string | null;\n /** Set when this message is a reply in a thread. */\n threadOriginator: string | null;\n /** Non-zero for a group event — someone joined, left, or renamed the chat. */\n itemType: number | null;\n};\n\nexport type ChatRow = {\n guid: string;\n identifier: string | null;\n displayName: string | null;\n /** 43 is a group chat, 45 is one-to-one. Reported raw beside the boolean. */\n style: number | null;\n isGroup: boolean;\n service: string | null;\n participants: string[];\n messages: number;\n lastMessageAt: number | null;\n};\n\nexport type RangeQuery = {\n chatGuid?: string | undefined;\n fromApple?: number | undefined;\n toApple?: number | undefined;\n includeReactions?: boolean | undefined;\n limit: number;\n};\n\nconst num = (v: unknown): number | null => (typeof v === \"number\" ? v : null);\nconst text = (v: unknown): string | null => (typeof v === \"string\" && v.length > 0 ? v : null);\nconst bool = (v: unknown): boolean => v === 1 || v === true;\n\nexport class MessagesStore {\n readonly db: DatabaseSync;\n readonly mode: string;\n readonly caps: StoreCapabilities;\n\n constructor(db: DatabaseSync, mode: string, caps: StoreCapabilities) {\n this.db = db;\n this.mode = mode;\n this.caps = caps;\n }\n\n /** Project a column, or a typed NULL when this store does not have it. */\n #col(present: Set<string>, table: string, name: string, alias = name): string {\n return present.has(name) ? `${table}.\"${name}\" AS ${alias}` : `NULL AS ${alias}`;\n }\n\n #date(present: Set<string>, table: string, name: string, alias: string): string {\n return present.has(name)\n ? `${appleSecondsSql(`${table}.\"${name}\"`)} AS ${alias}`\n : `NULL AS ${alias}`;\n }\n\n /**\n * The text of a message, from whichever source has it.\n *\n * The column first because it is free, then the decoder. `textSource` is on\n * every result so a caller can see which answered — and so the 3.1% that only\n * the decoder can reach are visible rather than indistinguishable from empty.\n */\n #body(row: Record<string, unknown>): { text: string | null; source: MessageRow[\"textSource\"] } {\n const column = text(row.text);\n if (column !== null) return { text: column, source: \"column\" };\n const blob = row.attributedBody;\n if (blob instanceof Uint8Array && blob.length > 0) {\n const decoded = decodeAttributedBody(blob);\n if (decoded.ok && decoded.text.length > 0) return { text: decoded.text, source: \"decoded\" };\n }\n return { text: null, source: \"none\" };\n }\n\n #messageColumns(): string {\n const m = this.caps.messageColumns;\n return [\n `m.\"guid\" AS guid`,\n `m.\"text\" AS text`,\n m.has(\"attributedBody\") ? `m.\"attributedBody\" AS attributedBody` : `NULL AS attributedBody`,\n this.#col(m, \"m\", \"subject\"),\n this.#col(m, \"m\", \"service\"),\n this.#col(m, \"m\", \"is_from_me\", \"isFromMe\"),\n this.#col(m, \"m\", \"is_read\", \"isRead\"),\n this.#col(m, \"m\", \"is_sent\", \"isSent\"),\n this.#col(m, \"m\", \"is_delivered\", \"isDelivered\"),\n this.#col(m, \"m\", \"cache_has_attachments\", \"hasAttachments\"),\n this.#col(m, \"m\", \"associated_message_type\", \"reactionType\"),\n this.#col(m, \"m\", \"associated_message_guid\", \"reactionTarget\"),\n this.#col(m, \"m\", \"thread_originator_guid\", \"threadOriginator\"),\n this.#col(m, \"m\", \"item_type\", \"itemType\"),\n this.#date(m, \"m\", \"date\", \"sentAt\"),\n this.#date(m, \"m\", \"date_read\", \"readAt\"),\n this.#date(m, \"m\", \"date_delivered\", \"deliveredAt\"),\n this.#date(m, \"m\", \"date_edited\", \"editedAt\"),\n `h.\"id\" AS handle`,\n `c.\"guid\" AS chatGuid`,\n this.#col(this.caps.chatColumns, \"c\", \"display_name\", \"chatName\"),\n ].join(\",\\n \");\n }\n\n #joins(): string {\n return `LEFT JOIN \"handle\" h ON h.\"ROWID\" = m.\"handle_id\"\n LEFT JOIN \"chat_message_join\" cmj ON cmj.\"message_id\" = m.\"ROWID\"\n LEFT JOIN \"chat\" c ON c.\"ROWID\" = cmj.\"chat_id\"`;\n }\n\n #toRow(r: Record<string, unknown>): MessageRow {\n const body = this.#body(r);\n return {\n guid: String(r.guid),\n chatGuid: text(r.chatGuid),\n chatName: text(r.chatName),\n handle: text(r.handle),\n service: text(r.service),\n isFromMe: bool(r.isFromMe),\n sentAt: num(r.sentAt),\n readAt: num(r.readAt),\n deliveredAt: num(r.deliveredAt),\n editedAt: num(r.editedAt),\n text: body.text,\n textSource: body.source,\n subject: text(r.subject),\n isRead: bool(r.isRead),\n isSent: bool(r.isSent),\n isDelivered: bool(r.isDelivered),\n hasAttachments: bool(r.hasAttachments),\n reactionType: num(r.reactionType) || null,\n reactionTarget: text(r.reactionTarget),\n threadOriginator: text(r.threadOriginator),\n itemType: num(r.itemType),\n };\n }\n\n /**\n * A window of messages, newest first.\n *\n * Reactions are excluded by default. They are rows in this table like any\n * other, and 2,788 of them would otherwise appear as messages reading\n * `Liked \"see you at 8\"` — which is not something anybody typed.\n */\n range(q: RangeQuery): MessageRow[] {\n const m = this.caps.messageColumns;\n const where: string[] = [];\n const params: unknown[] = [];\n\n if (q.chatGuid) {\n where.push(`c.\"guid\" = ?`);\n params.push(q.chatGuid);\n }\n if (q.fromApple !== undefined && m.has(\"date\")) {\n where.push(`${appleSecondsSql('m.\"date\"')} >= ?`);\n params.push(q.fromApple);\n }\n if (q.toApple !== undefined && m.has(\"date\")) {\n where.push(`${appleSecondsSql('m.\"date\"')} < ?`);\n params.push(q.toApple);\n }\n if (!q.includeReactions && m.has(\"associated_message_type\")) {\n where.push(`(m.\"associated_message_type\" IS NULL OR m.\"associated_message_type\" = 0)`);\n }\n\n const sql = `\n SELECT ${this.#messageColumns()}\n FROM \"message\" m\n ${this.#joins()}\n ${where.length ? `WHERE ${where.join(\"\\n AND \")}` : \"\"}\n ORDER BY m.\"date\" DESC\n LIMIT ${Math.max(1, Math.trunc(q.limit))}`;\n const rows = this.db.prepare(sql).all(...(params as never[])) as Record<string, unknown>[];\n return rows.map((r) => this.#toRow(r));\n }\n\n /**\n * Text search, in two passes — and the second one is the point.\n *\n * Pass 1 is `LIKE` on the column: measured at **16 ms over 97,416 rows** with\n * 27 existing indexes, so there is no index-vs-scan tradeoff to litigate and\n * no FTS table to build.\n *\n * Pass 2 covers what pass 1 structurally cannot. 3,051 messages have an empty\n * `text` and content only in `attributedBody`, and no amount of SQL reaches\n * inside a blob. Decoding them costs about **6 ms** — the decoder runs at\n * 2 ms per thousand — so completeness here is nearly free, and a search that\n * silently omitted one message in thirty-two would be the worst kind of wrong.\n */\n search(query: string, limit: number, includeReactions = false): MessageRow[] {\n const m = this.caps.messageColumns;\n const needle = `%${escapeLike(query)}%`;\n const reactionFilter =\n !includeReactions && m.has(\"associated_message_type\")\n ? `AND (m.\"associated_message_type\" IS NULL OR m.\"associated_message_type\" = 0)`\n : \"\";\n const cap = Math.max(1, Math.trunc(limit));\n\n const fromColumn = this.db\n .prepare(\n `SELECT ${this.#messageColumns()}\n FROM \"message\" m\n ${this.#joins()}\n WHERE m.\"text\" LIKE ? ESCAPE '\\\\' ${reactionFilter}\n ORDER BY m.\"date\" DESC\n LIMIT ${cap}`,\n )\n .all(needle) as Record<string, unknown>[];\n\n const results = fromColumn.map((r) => this.#toRow(r));\n if (results.length >= cap || !m.has(\"attributedBody\")) return results;\n\n // Only the blob-only rows: 3,051 of 97,416 on the probed store.\n const blobOnly = this.db\n .prepare(\n `SELECT ${this.#messageColumns()}\n FROM \"message\" m\n ${this.#joins()}\n WHERE (m.\"text\" IS NULL OR m.\"text\" = '')\n AND m.\"attributedBody\" IS NOT NULL ${reactionFilter}\n ORDER BY m.\"date\" DESC`,\n )\n .all() as Record<string, unknown>[];\n\n const lowered = query.toLowerCase();\n for (const raw of blobOnly) {\n if (results.length >= cap) break;\n const row = this.#toRow(raw);\n if (row.text && row.text.toLowerCase().includes(lowered)) results.push(row);\n }\n return results.slice(0, cap);\n }\n\n byGuid(guid: string): MessageRow | null {\n const rows = this.db\n .prepare(\n `SELECT ${this.#messageColumns()}\n FROM \"message\" m\n ${this.#joins()}\n WHERE m.\"guid\" = ?\n LIMIT 1`,\n )\n .all(guid) as Record<string, unknown>[];\n return rows[0] ? this.#toRow(rows[0]) : null;\n }\n\n /** Tapbacks aimed at one message. */\n reactionsFor(guid: string): { type: number; label: string; handle: string | null }[] {\n if (!this.caps.hasReactions) return [];\n // Apple prefixes the target with `p:0/` on some rows, so match the tail.\n const rows = this.db\n .prepare(\n `SELECT m.\"associated_message_type\" AS type, h.\"id\" AS handle\n FROM \"message\" m\n LEFT JOIN \"handle\" h ON h.\"ROWID\" = m.\"handle_id\"\n WHERE m.\"associated_message_guid\" LIKE ? ESCAPE '\\\\'\n AND m.\"associated_message_type\" > 0\n ORDER BY m.\"date\" ASC`,\n )\n .all(`%${escapeLike(guid)}`) as Record<string, unknown>[];\n return rows.flatMap((r) => {\n const type = num(r.type);\n if (type === null) return [];\n return [{ type, label: reactionLabel(type), handle: text(r.handle) }];\n });\n }\n\n /**\n * The attachments on one message.\n *\n * `id` is `attachment.guid`, for the reason `ref.ts` gives at length about\n * messages: the ROWID is faster and gets REUSED. Every \"delete this\n * conversation\" frees a block of attachment ids for the next insert, so a\n * caller that listed attachments in one turn and saved one two turns later\n * would write out a different file, silently. The guid is `UNIQUE NOT NULL`\n * in the shipped schema and is what Apple's own sync joins on.\n *\n * `path` is the raw `filename` column, reported so a caller can see WHERE the\n * bytes are before asking for them — it is frequently `~`-prefixed, and it is\n * empty for an attachment iCloud has offloaded.\n */\n attachmentsFor(guid: string): {\n id: string | null;\n path: string | null;\n mimeType: string | null;\n transferName: string | null;\n bytes: number | null;\n isSticker: boolean;\n }[] {\n if (!this.caps.hasAttachments) return [];\n const a = this.caps.attachmentColumns;\n const rows = this.db\n .prepare(\n `SELECT ${this.#col(a, \"a\", \"guid\", \"id\")},\n ${this.#col(a, \"a\", \"filename\", \"path\")},\n ${this.#col(a, \"a\", \"mime_type\", \"mimeType\")},\n ${this.#col(a, \"a\", \"transfer_name\", \"transferName\")},\n ${this.#col(a, \"a\", \"total_bytes\", \"bytes\")},\n ${this.#col(a, \"a\", \"is_sticker\", \"isSticker\")}\n FROM \"attachment\" a\n JOIN \"message_attachment_join\" maj ON maj.\"attachment_id\" = a.\"ROWID\"\n JOIN \"message\" m ON m.\"ROWID\" = maj.\"message_id\"\n WHERE m.\"guid\" = ?`,\n )\n .all(guid) as Record<string, unknown>[];\n return rows.map((r) => ({\n id: text(r.id),\n path: text(r.path),\n mimeType: text(r.mimeType),\n transferName: text(r.transferName),\n bytes: num(r.bytes),\n isSticker: num(r.isSticker) === 1,\n }));\n }\n\n /** One attachment by its guid, wherever it hangs. Null when it is gone. */\n attachmentById(id: string): {\n id: string | null;\n path: string | null;\n mimeType: string | null;\n transferName: string | null;\n bytes: number | null;\n } | null {\n if (!this.caps.hasAttachments) return null;\n const a = this.caps.attachmentColumns;\n if (!a.has(\"guid\")) return null;\n const row = this.db\n .prepare(\n `SELECT ${this.#col(a, \"a\", \"guid\", \"id\")},\n ${this.#col(a, \"a\", \"filename\", \"path\")},\n ${this.#col(a, \"a\", \"mime_type\", \"mimeType\")},\n ${this.#col(a, \"a\", \"transfer_name\", \"transferName\")},\n ${this.#col(a, \"a\", \"total_bytes\", \"bytes\")}\n FROM \"attachment\" a WHERE a.\"guid\" = ? LIMIT 1`,\n )\n .get(id) as Record<string, unknown> | undefined;\n if (!row) return null;\n return {\n id: text(row.id),\n path: text(row.path),\n mimeType: text(row.mimeType),\n transferName: text(row.transferName),\n bytes: num(row.bytes),\n };\n }\n\n chats(limit: number): ChatRow[] {\n return this.#chats(\"\", [], limit);\n }\n\n /** One chat, by the guid a ref carries. Null when it has been deleted. */\n chatByGuid(guid: string): ChatRow | null {\n return this.#chats(`WHERE c.\"guid\" = ?`, [guid], 1)[0] ?? null;\n }\n\n /**\n * The chats that already exist with a set of handles, newest first.\n *\n * This is what makes a send addressable at all. Messages will not enumerate\n * participants for a script, so the write lane cannot look a person up — but\n * it can address a chat by guid, and the guid lives here. The read lane\n * choosing the target for the write lane is the whole arrangement; see\n * `client/jxa/core.ts`.\n *\n * Handles are matched as given. Suffix matching happens a layer up in\n * `client/messages.ts`, where `packages/contacts`' measured `suffixKey` is\n * available and the candidate list is the store's own 1,075 handles.\n */\n chatsForHandles(handles: readonly string[], limit = 10): ChatRow[] {\n if (!handles.length) return [];\n const marks = handles.map(() => \"?\").join(\", \");\n return this.#chats(\n `WHERE c.\"ROWID\" IN (\n SELECT chj.\"chat_id\"\n FROM \"chat_handle_join\" chj\n JOIN \"handle\" h2 ON h2.\"ROWID\" = chj.\"handle_id\"\n WHERE h2.\"id\" IN (${marks}))`,\n [...handles],\n limit,\n );\n }\n\n /**\n * Outgoing messages in a set of chats since an instant — the send's receipt.\n *\n * `docs/messages.md` recorded that Apple Events returns no chat identifier, so\n * a send \"cannot report what it wrote by id\". That is true of the write lane\n * alone and false of the pair: the row Messages writes for an outgoing message\n * is an ordinary row in this table, and a narrow window plus the target chat\n * identifies it. Matching on text as well would be wrong — two identical\n * messages a minute apart are a normal thing to send — so the caller passes a\n * `sinceApple` taken immediately BEFORE the send and takes the oldest match.\n */\n sentSince(chatGuids: readonly string[], sinceApple: number, limit = 10): MessageRow[] {\n const m = this.caps.messageColumns;\n if (!chatGuids.length || !m.has(\"date\")) return [];\n const marks = chatGuids.map(() => \"?\").join(\", \");\n const fromMe = m.has(\"is_from_me\") ? `AND m.\"is_from_me\" = 1` : \"\";\n const rows = this.db\n .prepare(\n `SELECT ${this.#messageColumns()}\n FROM \"message\" m\n ${this.#joins()}\n WHERE c.\"guid\" IN (${marks})\n AND ${appleSecondsSql('m.\"date\"')} >= ?\n ${fromMe}\n ORDER BY m.\"date\" ASC\n LIMIT ${Math.max(1, Math.trunc(limit))}`,\n )\n .all(...([...chatGuids, sinceApple] as never[])) as Record<string, unknown>[];\n return rows.map((r) => this.#toRow(r));\n }\n\n #chats(where: string, params: readonly unknown[], limit: number): ChatRow[] {\n const c = this.caps.chatColumns;\n const rows = this.db\n .prepare(\n `SELECT c.\"ROWID\" AS rowid,\n c.\"guid\" AS guid,\n ${this.#col(c, \"c\", \"chat_identifier\", \"identifier\")},\n ${this.#col(c, \"c\", \"display_name\", \"displayName\")},\n ${this.#col(c, \"c\", \"style\")},\n ${this.#col(c, \"c\", \"service_name\", \"service\")},\n COUNT(cmj.\"message_id\") AS messages,\n ${appleSecondsSql('MAX(m.\"date\")')} AS lastMessageAt\n FROM \"chat\" c\n LEFT JOIN \"chat_message_join\" cmj ON cmj.\"chat_id\" = c.\"ROWID\"\n LEFT JOIN \"message\" m ON m.\"ROWID\" = cmj.\"message_id\"\n ${where}\n GROUP BY c.\"ROWID\"\n ORDER BY MAX(m.\"date\") DESC\n LIMIT ${Math.max(1, Math.trunc(limit))}`,\n )\n .all(...(params as never[])) as Record<string, unknown>[];\n\n const participants = this.#participants(rows.map((r) => Number(r.rowid)));\n return rows.map((r) => {\n const style = num(r.style);\n return {\n guid: String(r.guid),\n identifier: text(r.identifier),\n displayName: text(r.displayName),\n style,\n // 43 is a group chat and 45 is one-to-one, measured. The participant\n // count is the check: a \"one-to-one\" with four people is a drift signal.\n isGroup: style === 43 || (participants.get(Number(r.rowid))?.length ?? 0) > 1,\n service: text(r.service),\n participants: participants.get(Number(r.rowid)) ?? [],\n messages: Number(r.messages ?? 0),\n lastMessageAt: num(r.lastMessageAt),\n };\n });\n }\n\n #participants(chatRowIds: readonly number[]): Map<number, string[]> {\n const out = new Map<number, string[]>();\n if (!chatRowIds.length) return out;\n const marks = chatRowIds.map(() => \"?\").join(\", \");\n const rows = this.db\n .prepare(\n `SELECT chj.\"chat_id\" AS chatId, h.\"id\" AS handle\n FROM \"chat_handle_join\" chj\n JOIN \"handle\" h ON h.\"ROWID\" = chj.\"handle_id\"\n WHERE chj.\"chat_id\" IN (${marks})`,\n )\n .all(...(chatRowIds as never[])) as Record<string, unknown>[];\n for (const r of rows) {\n const id = Number(r.chatId);\n const handle = text(r.handle);\n if (!handle) continue;\n const bucket = out.get(id);\n if (bucket) bucket.push(handle);\n else out.set(id, [handle]);\n }\n return out;\n }\n\n /** Every distinct handle in the store, for a bulk resolve. */\n handles(): string[] {\n const rows = this.db\n .prepare(`SELECT DISTINCT \"id\" AS id FROM \"handle\" WHERE \"id\" IS NOT NULL AND \"id\" <> ''`)\n .all() as Record<string, unknown>[];\n return rows.flatMap((r) => {\n const h = text(r.id);\n return h ? [h] : [];\n });\n }\n\n counts(): { messages: number; chats: number; handles: number; attachments: number } {\n const one = (sql: string): number => {\n try {\n return Number((this.db.prepare(sql).get() as { c: number }).c ?? 0);\n } catch {\n return 0;\n }\n };\n return {\n messages: one(`SELECT COUNT(*) AS c FROM \"message\"`),\n chats: one(`SELECT COUNT(*) AS c FROM \"chat\"`),\n handles: one(`SELECT COUNT(*) AS c FROM \"handle\"`),\n attachments: this.caps.hasAttachments ? one(`SELECT COUNT(*) AS c FROM \"attachment\"`) : 0,\n };\n }\n\n close(): void {\n try {\n this.db.close();\n } catch {\n // Closing a database that already failed is not worth reporting.\n }\n }\n}\n\nexport const introspect = (db: DatabaseSync): StoreCapabilities => {\n const messageColumns = new Set(columnsOf(db, \"message\"));\n const chatColumns = new Set(columnsOf(db, \"chat\"));\n const handleColumns = new Set(columnsOf(db, \"handle\"));\n\n for (const t of REQUIRED) {\n const cols = t === \"message\" ? messageColumns : t === \"chat\" ? chatColumns : handleColumns;\n if (cols.size === 0) {\n throw new SchemaDriftError(\n `This Messages store has no ${t} table. It was probed on macOS ${PROBED_MACOS} with ` +\n `schema fingerprint ${PROBED_FINGERPRINT} (a PROBE fingerprint — compare it against ` +\n `another probe run, not against the one diagnostics reports); re-run ` +\n `\\`pnpm probe:messages\\` to see what changed.`,\n );\n }\n }\n\n const attachmentColumns = new Set(columnsOf(db, \"attachment\"));\n return {\n fingerprint: fingerprintSchema(db),\n messageColumns,\n chatColumns,\n handleColumns,\n attachmentColumns,\n hasAttachments:\n attachmentColumns.size > 0 && columnsOf(db, \"message_attachment_join\").length > 0,\n hasReactions: messageColumns.has(\"associated_message_type\"),\n hasThreads: messageColumns.has(\"thread_originator_guid\"),\n hasEdits: messageColumns.has(\"date_edited\"),\n };\n};\n\nexport const openStore = (\n path: string | null,\n mode: ReadOnlyMode,\n logger?: Logger,\n): MessagesStore | null => {\n if (!path) return null;\n const {\n db,\n mode: used,\n validated,\n } = openReadOnly<StoreCapabilities>(path, mode, {\n label: \"Messages store\",\n envVar: \"APPLE_MESSAGES_INDEX_MODE\",\n validate: introspect,\n fatal: (err) => err instanceof SchemaDriftError,\n onFallback: () =>\n logger?.debug?.(\n \"opened the Messages store with immutable=1, which skips the write-ahead log — recent \" +\n \"messages may be missing until Messages checkpoints. The measured WAL was ~0.5 MB.\",\n ),\n });\n return new MessagesStore(db, used, validated);\n};\n","import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { basename, join, resolve as resolvePath, sep } from \"node:path\";\n\nimport {\n AppleContactsClient,\n emailKey,\n handleKind,\n loadConfig as loadContactsConfig,\n suffixKey,\n type ResolvedHandle,\n} from \"@mgcrea/mcp-apple-contacts\";\nimport {\n createOsascriptRunner,\n IndexUnavailableError,\n withBusyRetry,\n type Logger,\n type OsascriptRunner,\n} from \"@mgcrea/mcp-apple-core\";\n\nimport type { Config } from \"../config.js\";\nimport { renderInstant, toAppleSeconds } from \"./dates.js\";\nimport {\n ChatNotFoundError,\n MESSAGES_SURFACE,\n MessagesUnavailableError,\n PreconditionError,\n SendFailedError,\n SendTargetNotFoundError,\n} from \"./errors.js\";\nimport { SEND_MESSAGE } from \"./jxa/write.js\";\nimport { locateStore, type LocateResult } from \"./locate.js\";\nimport { decodeChatRef, encodeChatRef, encodeMessageRef } from \"./ref.js\";\nimport { openStore, type ChatRow, type MessageRow, type MessagesStore } from \"./store.js\";\n\n/**\n * Messages' one lane, plus the resolver.\n *\n * ## Why this depends on another surface\n *\n * `chat.db` records a correspondent as `+15551234567` and nothing else, so a\n * server built on it alone answers *\"+15551234567 said …\"* — complete, and\n * useless. `packages/contacts` exists because of that, and this is the consumer\n * it was built for. It is a workspace import rather than an MCP call: reaching a\n * function in the same repo through a protocol would be absurd.\n *\n * `docs/contacts.md` measured what to expect, and the numbers set the contract:\n *\n * | denominator | resolved |\n * | ----------------------------- | -------- |\n * | every handle ever seen | 27.6% |\n * | messages in the last year | 97.6% |\n * | the 25 busiest correspondents | 84% |\n *\n * So **`unknown` is a normal outcome, not an error.** About one in six of even\n * the busiest correspondents does not resolve, and a caller that treats that as\n * a failure will be wrong several times on any real inbox. Every rendered\n * correspondent therefore carries both the resolved name and the raw handle.\n *\n * ## Degrading\n *\n * If Contacts cannot be read — its own TCC grant, separate from Full Disk\n * Access, and one the user may simply not have answered — resolution is skipped\n * and handles are returned raw. That is a capability downgrade, reported through\n * `diagnostics`, never a throw: a Messages server with no names is still a\n * Messages server.\n */\n\nexport type CreateClientOptions = {\n config: Config;\n logger?: Logger;\n /** Injected by tests. */\n home?: string;\n /** Injected by tests, so no test reaches the developer's real address book. */\n contacts?: AppleContactsClient | null;\n /** Injected by tests, so no test sends a real message to a real person. */\n osascript?: OsascriptRunner;\n};\n\n/**\n * What a send actually knows, which is less than a caller might assume.\n *\n * `sent` means Messages accepted the command without throwing. `delivered` is\n * not a field here at all, because nothing in either lane reports it at send\n * time. What closes the gap is `message`: the row the file lane found afterwards\n * — a real ref, usable with `apple_messages_get_message` like any other.\n */\nexport type SendResult = {\n sent: true;\n /** Which rung of the ladder in `client/jxa/core.ts` answered. */\n strategy: string;\n targetKind: string;\n /** The chat the file lane chose, when it could choose one. */\n chatRef: string | null;\n chat: string | null;\n to: Correspondent | null;\n /** Whether Messages had to be launched. A launch is visible to the user. */\n launched: boolean;\n /** `matched` | `pending` | `unavailable` — see `#reconcile`. */\n reconciliation: string;\n message: RenderedMessage | null;\n note?: string;\n};\n\nexport type Correspondent = {\n /** Always present. The raw `handle.id` from the store. */\n handle: string | null;\n /** Null when unresolved, which is normal — see above. */\n name: string | null;\n /** `resolved` | `unknown` | `ambiguous` | `shortcode` | `unavailable`. */\n resolution: string;\n};\n\nexport type RenderedMessage = {\n ref: string;\n chatRef: string | null;\n chat: string | null;\n from: Correspondent;\n fromMe: boolean;\n sentAt: string | null;\n editedAt: string | null;\n text: string | null;\n /** `column` | `decoded` | `none` — which lane produced the text. */\n textSource: string;\n subject: string | null;\n service: string | null;\n isRead: boolean;\n hasAttachments: boolean;\n /** Present only when this row is a reply. */\n replyToRef?: string;\n /** Present only when this row is a group event rather than a message. */\n itemType?: number;\n};\n\nexport type RenderedChat = {\n ref: string;\n name: string | null;\n isGroup: boolean;\n service: string | null;\n participants: Correspondent[];\n messages: number;\n lastMessageAt: string | null;\n};\n\nexport class AppleMessagesClient {\n readonly #config: Config;\n readonly #logger: Logger | undefined;\n readonly #home: string | undefined;\n\n #located: LocateResult | null = null;\n #store: MessagesStore | null = null;\n #storeTried = false;\n\n #contacts: AppleContactsClient | null;\n #contactsTried: boolean;\n #resolved = new Map<string, ResolvedHandle>();\n\n /**\n * Built even when writes are off, and that costs nothing.\n *\n * `createOsascriptRunner` spawns no process until something calls it, and with\n * `allowWrites` off nothing does — the send tool is never registered. So this\n * server still sends no Apple Event and still asks for no Automation grant in\n * its default configuration, which is the claim `surfaces.json` makes.\n */\n readonly #runner: OsascriptRunner;\n\n constructor(opts: CreateClientOptions) {\n this.#config = opts.config;\n this.#logger = opts.logger;\n this.#home = opts.home;\n // `null` means \"explicitly none\" (tests); `undefined` means \"make one\".\n this.#contacts = opts.contacts ?? null;\n this.#contactsTried = opts.contacts !== undefined;\n this.#runner =\n opts.osascript ??\n createOsascriptRunner({\n surface: MESSAGES_SURFACE,\n osascriptPath: opts.config.osascriptPath,\n timeoutMs: opts.config.osascriptTimeoutMs,\n ...(opts.logger ? { logger: opts.logger } : {}),\n });\n }\n\n get config(): Config {\n return this.#config;\n }\n\n located(): LocateResult {\n this.#located ??= locateStore({\n storePath: this.#config.storePath,\n ...(this.#home ? { home: this.#home } : {}),\n });\n return this.#located;\n }\n\n store(): MessagesStore | null {\n if (this.#storeTried) return this.#store;\n this.#storeTried = true;\n if (this.#config.indexMode === \"off\") return null;\n const located = this.located();\n if (!located.readable) return null;\n const mode = this.#config.indexMode === \"auto\" ? \"ro\" : this.#config.indexMode;\n this.#store = openStore(located.storePath, mode, this.#logger);\n return this.#store;\n }\n\n /**\n * Never `[]` when the store is missing.\n *\n * An empty list is a valid answer to \"no messages match\", and this surface has\n * no second lane to produce one from. Callers get a reason instead — and on\n * this surface the reason is load-bearing, because the fix is a permission.\n */\n #require(): MessagesStore {\n const store = this.store();\n if (store) return store;\n if (this.#config.indexMode === \"off\") {\n throw new IndexUnavailableError(\n \"The Messages index is disabled (APPLE_MESSAGES_INDEX_MODE=off). This surface has no \" +\n \"Apple Events read lane at all, so nothing can be read until it is re-enabled.\",\n );\n }\n throw new MessagesUnavailableError(\n this.located().reason ?? \"No readable Messages store was found.\",\n );\n }\n\n // ─── contacts ──────────────────────────────────────────────────────────────\n\n #contactsClient(): AppleContactsClient | null {\n if (this.#contactsTried) return this.#contacts;\n this.#contactsTried = true;\n if (!this.#config.resolveContacts) return null;\n try {\n this.#contacts = new AppleContactsClient({\n config: loadContactsConfig({}),\n ...(this.#logger ? { logger: this.#logger } : {}),\n ...(this.#home ? { home: this.#home } : {}),\n });\n } catch (err) {\n this.#logger?.debug?.(`contacts resolver unavailable: ${String(err)}`);\n this.#contacts = null;\n }\n return this.#contacts;\n }\n\n /**\n * Resolve a batch, memoised for the life of the process.\n *\n * Batched because building the lookup walks every contact once (3 ms on the\n * measured store) and every subsequent handle is a map hit. Memoised because a\n * conversation renders the same correspondent hundreds of times.\n */\n #resolve(handles: readonly (string | null)[]): void {\n const wanted = [...new Set(handles.filter((h): h is string => Boolean(h)))].filter(\n (h) => !this.#resolved.has(h),\n );\n if (!wanted.length) return;\n const contacts = this.#contactsClient();\n if (!contacts) return;\n try {\n for (const r of contacts.resolve(wanted).results) this.#resolved.set(r.handle, r);\n } catch (err) {\n // A Contacts failure must not take Messages down with it.\n this.#logger?.debug?.(`resolve failed: ${String(err)}`);\n this.#contacts = null;\n }\n }\n\n #correspondent(handle: string | null): Correspondent {\n if (!handle) return { handle: null, name: null, resolution: \"unknown\" };\n const hit = this.#resolved.get(handle);\n if (!hit) {\n return {\n handle,\n name: null,\n // Distinct from \"unknown\": nobody looked, rather than nobody matched.\n resolution: this.#contacts ? \"unknown\" : \"unavailable\",\n };\n }\n return { handle, name: hit.name, resolution: hit.status };\n }\n\n // ─── reads ─────────────────────────────────────────────────────────────────\n\n #render(rows: readonly MessageRow[]): RenderedMessage[] {\n this.#resolve(rows.map((r) => r.handle));\n return rows.map((r) => ({\n ref: encodeMessageRef(r.guid),\n chatRef: r.chatGuid ? encodeChatRef(r.chatGuid) : null,\n chat: r.chatName,\n from: r.isFromMe\n ? { handle: null, name: \"me\", resolution: \"self\" }\n : this.#correspondent(r.handle),\n fromMe: r.isFromMe,\n sentAt: renderInstant(r.sentAt),\n editedAt: renderInstant(r.editedAt),\n text: r.text,\n textSource: r.textSource,\n subject: r.subject,\n service: r.service,\n isRead: r.isRead,\n hasAttachments: r.hasAttachments,\n ...(r.threadOriginator ? { replyToRef: encodeMessageRef(r.threadOriginator) } : {}),\n ...(r.itemType ? { itemType: r.itemType } : {}),\n }));\n }\n\n listMessages(opts: {\n chatRef?: string | undefined;\n fromApple?: number | undefined;\n toApple?: number | undefined;\n includeReactions?: boolean | undefined;\n limit?: number | undefined;\n }): RenderedMessage[] {\n return this.#render(\n this.#require().range({\n ...(opts.chatRef ? { chatGuid: opts.chatRef } : {}),\n ...(opts.fromApple === undefined ? {} : { fromApple: opts.fromApple }),\n ...(opts.toApple === undefined ? {} : { toApple: opts.toApple }),\n ...(opts.includeReactions === undefined ? {} : { includeReactions: opts.includeReactions }),\n limit: opts.limit ?? this.#config.maxResults,\n }),\n );\n }\n\n searchMessages(query: string, limit?: number): RenderedMessage[] {\n return this.#render(this.#require().search(query, limit ?? this.#config.maxResults));\n }\n\n getMessage(guid: string):\n | (RenderedMessage & {\n reactions: { label: string; from: Correspondent }[];\n attachments: ReturnType<MessagesStore[\"attachmentsFor\"]>;\n })\n | null {\n const store = this.#require();\n const row = store.byGuid(guid);\n if (!row) return null;\n const reactions = store.reactionsFor(guid);\n this.#resolve(reactions.map((r) => r.handle));\n const [rendered] = this.#render([row]);\n return {\n ...(rendered as RenderedMessage),\n reactions: reactions.map((r) => ({ label: r.label, from: this.#correspondent(r.handle) })),\n attachments: store.attachmentsFor(guid),\n };\n }\n\n /**\n * Copy one attachment out of the Messages store onto disk.\n *\n * ## Why this is a copy and not an extraction\n *\n * Mail's equivalent has to parse MIME out of an `.emlx` because the bytes are\n * inside the message file. Messages does not work that way: `attachment` rows\n * point at real files under `~/Library/Messages`, so the work here is finding\n * the row, deciding the path is one we are willing to read, and copying.\n *\n * ## Two boundaries, not one\n *\n * The DESTINATION boundary is the same one Mail and Notes enforce:\n * `attachmentDir` is a confinement, `directory` may only select inside it, and\n * the leaf name is `basename`d because it comes from whoever sent the message.\n *\n * The SOURCE boundary is this surface's own. `filename` comes out of a\n * database this server never writes, and it is a fully-qualified path: taken\n * at face value it names any file the process can read. So it is required to\n * resolve inside the Messages root before a single byte is read. That check\n * has never fired on a real store and is not expected to — it is here so that\n * the day the schema surprises us, the surprise is a refusal.\n */\n async saveAttachment(\n attachmentId: string,\n opts: { directory?: string | undefined; overwrite?: boolean } = {},\n ): Promise<{ path: string; bytes: number; source: string; mimeType: string | null }> {\n const store = this.#require();\n const meta = store.attachmentById(attachmentId);\n if (!meta) {\n throw new PreconditionError(\n `No attachment with id \"${attachmentId}\". Ids come from apple_messages_get_message and ` +\n \"are the attachment's guid; the message may have been deleted since it was listed.\",\n );\n }\n if (!meta.path) {\n throw new PreconditionError(\n \"That attachment has no file path in the store, which normally means iCloud has \" +\n \"offloaded it. Open the conversation in Messages to download it, then try again.\",\n );\n }\n\n const home = this.#home ?? homedir();\n // `~/Library/Messages/…` is the shape the column actually holds — not a\n // shell convention this can leave to something else to expand.\n const expanded = meta.path.startsWith(\"~/\") ? join(home, meta.path.slice(2)) : meta.path;\n const source = resolvePath(expanded);\n const root = resolvePath(this.located().messagesRoot);\n if (source !== root && !source.startsWith(root + sep)) {\n throw new PreconditionError(\n `Refusing to read ${source}: it is outside ${root}, and this tool only copies files ` +\n \"Messages itself stores.\",\n );\n }\n if (!existsSync(source) || !statSync(source).isFile()) {\n throw new PreconditionError(\n `The store names ${source} but there is no file there. Messages prunes attachment bytes ` +\n \"while keeping the row, so this is a normal state for an old conversation.\",\n );\n }\n\n const dest = resolvePath(this.#config.attachmentDir);\n const dir = opts.directory ? resolvePath(dest, opts.directory) : dest;\n if (dir !== dest && !dir.startsWith(dest + sep)) {\n throw new PreconditionError(\n `Refusing to write outside ${dest}. Set APPLE_MESSAGES_ATTACHMENT_DIR to change the ` +\n \"destination.\",\n );\n }\n // The sender chose `transfer_name`, so it is basename'd before it is\n // trusted — path traversal wants exactly that field.\n const name = basename(meta.transferName ?? basename(source));\n const target = resolvePath(join(dir, name));\n if (target !== join(dir, name) || !target.startsWith(dir + sep)) {\n throw new PreconditionError(`Refusing to write outside ${dir}.`);\n }\n if (existsSync(target) && !opts.overwrite) {\n throw new PreconditionError(`${target} already exists; refusing to overwrite it.`);\n }\n\n const bytes = readFileSync(source);\n mkdirSync(dir, { recursive: true });\n writeFileSync(target, bytes, { mode: 0o600 });\n return { path: target, bytes: bytes.length, source, mimeType: meta.mimeType };\n }\n\n listChats(limit?: number): RenderedChat[] {\n const rows: ChatRow[] = this.#require().chats(limit ?? this.#config.maxResults);\n this.#resolve(rows.flatMap((c) => c.participants));\n return rows.map((c) => ({\n ref: encodeChatRef(c.guid),\n // A group chat usually has a name; a one-to-one never does, so fall back\n // to the resolved participant rather than showing a bare guid.\n name:\n c.displayName ??\n (c.participants.length === 1 && c.participants[0]\n ? (this.#correspondent(c.participants[0]).name ?? c.participants[0])\n : null),\n isGroup: c.isGroup,\n service: c.service,\n participants: c.participants.map((h) => this.#correspondent(h)),\n messages: c.messages,\n lastMessageAt: renderInstant(c.lastMessageAt),\n }));\n }\n\n // ─── writes ────────────────────────────────────────────────────────────────\n // One verb, and it is the only thing Apple Events can do on this surface.\n //\n // The shape is unlike every other surface here. Elsewhere a write is a\n // round-trip: change something, re-read it, report what the app stored.\n // Messages cannot re-read — every read through its scripting dictionary fails\n // — so the write lane fires and the FILE lane reports. See `#reconcile`.\n\n /**\n * Pick the chat to address, using the store.\n *\n * Handles are matched the way `packages/contacts` measured them rather than by\n * string equality: a caller who types `06 12 34 56 78` and a store that holds\n * `+33612345678` are the same person, and `suffixKey`'s last-9 rule is what\n * says so. The candidate set is the store's own handles — 1,075 on the\n * measured machine — so this is a scan over a list that is already in memory.\n */\n #chatFor(input: { chatRef?: string | undefined; to?: string | undefined }): {\n guid: string | null;\n chat: ChatRow | null;\n handle: string | null;\n } {\n // `store()`, not `#require()`. A send is the one operation on this surface\n // that can work without the file lane — the JXA ladder's lower rungs guess a\n // chat guid from the handle — so an unreadable store degrades the RESULT\n // (no ref to report) rather than blocking the send. Reads still refuse.\n const store = this.store();\n if (input.chatRef) {\n const guid = decodeChatRef(input.chatRef);\n const chat = store?.chatByGuid(guid) ?? null;\n if (store && !chat) throw new ChatNotFoundError(input.chatRef);\n return { guid, chat, handle: chat?.participants[0] ?? null };\n }\n const to = input.to?.trim();\n if (!to) return { guid: null, chat: null, handle: null };\n if (!store) return { guid: null, chat: null, handle: to };\n\n const kind = handleKind(to);\n const wantedSuffix = kind === \"phone\" ? suffixKey(to) : null;\n const wantedEmail = kind === \"email\" ? emailKey(to) : null;\n const candidates = store.handles().filter((h) => {\n if (h === to) return true;\n if (wantedEmail) return emailKey(h) === wantedEmail;\n if (wantedSuffix) return suffixKey(h) === wantedSuffix;\n return false;\n });\n if (!candidates.length) return { guid: null, chat: null, handle: to };\n\n // A one-to-one chat wins over a group with the same person in it: \"message\n // Alice\" must not land in a six-person thread Alice happens to be in.\n const chats = store.chatsForHandles(candidates, 25);\n const direct = chats.find((c) => !c.isGroup) ?? null;\n return { guid: direct?.guid ?? null, chat: direct, handle: candidates[0] ?? to };\n }\n\n /**\n * Find the row Messages wrote, or say plainly that it was not found.\n *\n * `docs/messages.md` left this as an open question — \"whether a send should\n * re-resolve by scanning the store for a recent row on the target chat\" — and\n * this is the answer, because the alternative is a send that can report\n * nothing at all. Apple Events hands back no identifier of any kind.\n *\n * Three things make the match safe rather than merely plausible:\n *\n * 1. `since` is taken BEFORE the send, so nothing earlier can match.\n * 2. The window is scoped to the target chat.\n * 3. Text is compared when it is available, which separates our row from one\n * the user sent from their phone in the same second. Only when it is not\n * available does the oldest row in the window win.\n *\n * A miss is `pending`, never an error: the send already happened, and iMessage\n * writes its row asynchronously. Reporting a failure there would be the worst\n * possible lie — it would invite a retry that sends the message twice.\n */\n async #reconcile(guid: string, since: number, text: string): Promise<RenderedMessage | null> {\n const deadline = Date.now() + this.#config.sendReconcileMs;\n for (;;) {\n // Reopen: this handle was opened before the send, and the point is to see\n // what another process has written since.\n this.#invalidate();\n const store = this.store();\n if (!store) return null;\n const rows = store.sentSince([guid], since, 10);\n const hit = rows.find((r) => r.text !== null && r.text === text) ?? rows[0];\n if (hit) return this.#render([hit])[0] ?? null;\n if (Date.now() >= deadline) return null;\n await new Promise((resolve) => setTimeout(resolve, 250));\n }\n }\n\n #invalidate(): void {\n this.#store?.close();\n this.#store = null;\n this.#storeTried = false;\n }\n\n /**\n * Send one message. A real one, to a real person, immediately.\n *\n * Everything difficult about this is in `client/jxa/core.ts`; what is left\n * here is choosing the target from the file lane and reconciling afterwards.\n */\n async sendMessage(input: {\n chatRef?: string | undefined;\n to?: string | undefined;\n text: string;\n service?: string | undefined;\n }): Promise<SendResult> {\n const { guid, chat, handle } = this.#chatFor(input);\n const since = toAppleSeconds(new Date(Date.now() - 2_000));\n\n let data: {\n strategy?: unknown;\n targetKind?: unknown;\n launched?: unknown;\n attempts?: unknown;\n };\n try {\n data = await withBusyRetry(() =>\n this.#runner.run<Record<string, unknown>>(SEND_MESSAGE, {\n ...(guid ? { chatGuid: guid } : {}),\n ...(handle ? { handle } : {}),\n ...(input.service ? { service: input.service } : {}),\n text: input.text,\n allowLaunch: true,\n }),\n );\n } catch (err) {\n const details = (err as { details?: { code?: string; detail?: unknown } })?.details;\n const attempts = Array.isArray(details?.detail) ? (details.detail as string[]) : [];\n const message = err instanceof Error ? err.message : String(err);\n if (details?.code === \"SEND_TARGET_NOT_FOUND\") {\n throw new SendTargetNotFoundError(input.chatRef ?? input.to ?? \"\", attempts);\n }\n if (details?.code === \"SEND_FAILED\") throw new SendFailedError(message, attempts);\n throw err;\n }\n\n const message = guid ? await this.#reconcile(guid, since, input.text) : null;\n if (handle) this.#resolve([handle]);\n return {\n sent: true,\n strategy: typeof data.strategy === \"string\" ? data.strategy : \"unknown\",\n targetKind: typeof data.targetKind === \"string\" ? data.targetKind : \"unknown\",\n chatRef: guid ? encodeChatRef(guid) : null,\n chat: chat?.displayName ?? null,\n to: handle ? this.#correspondent(handle) : null,\n launched: data.launched === true,\n reconciliation: message ? \"matched\" : guid ? \"pending\" : \"unavailable\",\n message,\n ...(message\n ? {}\n : {\n note: guid\n ? \"Messages accepted the send, but no matching row had appeared in the store yet. \" +\n \"That is normal for a slow network — re-run apple_messages_list_messages on this \" +\n \"chat rather than sending again.\"\n : \"Messages accepted the send, but there was no existing chat to reconcile it \" +\n \"against, so no ref can be reported for it. Read the chat back once it exists.\",\n }),\n };\n }\n\n /** Bounds for a range query, as apple-seconds. */\n window(from?: Date, to?: Date): { fromApple?: number; toApple?: number } {\n return {\n ...(from ? { fromApple: toAppleSeconds(from) } : {}),\n ...(to ? { toApple: toAppleSeconds(to) } : {}),\n };\n }\n\n status(): {\n located: LocateResult;\n store: { opened: boolean; mode: string | null; fingerprint: string | null };\n counts: ReturnType<MessagesStore[\"counts\"]> | null;\n contacts: { enabled: boolean; available: boolean; resolved: number };\n } {\n const store = this.store();\n return {\n located: this.located(),\n store: {\n opened: Boolean(store),\n mode: store?.mode ?? null,\n fingerprint: store?.caps.fingerprint ?? null,\n },\n counts: store?.counts() ?? null,\n contacts: {\n enabled: this.#config.resolveContacts,\n available: Boolean(this.#contactsClient()),\n resolved: this.#resolved.size,\n },\n };\n }\n\n close(): void {\n this.#store?.close();\n this.#store = null;\n this.#storeTried = false;\n this.#contacts?.close();\n this.#resolved.clear();\n }\n}\n","import { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\nimport {\n BaseConfigSchema,\n parseBool,\n parseConfig,\n parseIntOpt,\n trimmed,\n} from \"@mgcrea/mcp-apple-core\";\nimport { z } from \"zod\";\n\n/**\n * Configuration is environment-only — this server holds no secret at all, its\n * access is the macOS permission the user granted.\n *\n * `allowWrites` is inherited from `BaseConfigSchema` and, since 1.2.0, means\n * something here: it gates the one mutating tool, `send_message`. With it off\n * this server registers no write tool and therefore sends no Apple Event at all,\n * which is what keeps its \"no Automation grant\" claim true by default.\n *\n * `docs/messages.md` recorded why sending went unprobed for so long — \"probing\n * it would mean sending a real message to a real person\" — and left the id\n * bridge open, because Apple Events returns no chat identifier to reconcile\n * against. `sendReconcileMs` is that decision made: the file lane finds the row\n * instead.\n */\nconst ConfigSchema = BaseConfigSchema.extend({\n /** Explicit store path. Bypasses discovery — for tests and forensic copies. */\n storePath: z.string().optional(),\n indexMode: z.enum([\"auto\", \"ro\", \"immutable\", \"off\"]).default(\"auto\"),\n /**\n * Look names up in Contacts.\n *\n * On by default: without it this server answers \"+15551234567 said …\", which\n * is why `packages/contacts` was built. Off is for a machine where the\n * Contacts permission has not been granted and the prompt is unwelcome — the\n * resolver degrades to raw handles either way, so this only decides whether it\n * is attempted.\n */\n resolveContacts: z.boolean().default(true),\n /**\n * The only directory `save_attachment` may write into.\n *\n * A boundary rather than a default: the tool's `directory` argument selects a\n * subdirectory of this and cannot escape it. Saving is write-gated for the\n * same reason it is in Mail and Notes — it puts a file on the user's disk,\n * even though it changes nothing in Messages.\n */\n attachmentDir: z.string().default(join(homedir(), \"Downloads\")),\n /** Window for a range query that names only a start. */\n defaultRangeDays: z.number().int().min(1).max(3_660).default(30),\n /**\n * How long to wait for a sent message to appear in the store.\n *\n * Messages writes the outgoing row asynchronously, so a send that returns\n * instantly has usually not been written yet. Five seconds is generous for a\n * local write and short enough that a tool call does not hang on a bad\n * network; a miss is reported as `pending`, never as a failure. Zero means one\n * immediate check and no polling, which is what the test suite uses.\n */\n sendReconcileMs: z.number().int().min(0).max(60_000).default(5_000),\n}).strict();\n\nexport type Config = z.infer<typeof ConfigSchema>;\n\nexport const loadConfig = (env: NodeJS.ProcessEnv = process.env): Config =>\n parseConfig(ConfigSchema, {\n allowWrites: parseBool(env.APPLE_MESSAGES_ALLOW_WRITES),\n exposePrompts: parseBool(env.APPLE_MESSAGES_EXPOSE_PROMPTS),\n debug: parseBool(env.APPLE_MESSAGES_DEBUG),\n storePath: trimmed(env.APPLE_MESSAGES_STORE),\n indexMode: trimmed(env.APPLE_MESSAGES_INDEX_MODE),\n resolveContacts: parseBool(env.APPLE_MESSAGES_RESOLVE_CONTACTS),\n attachmentDir: trimmed(env.APPLE_MESSAGES_ATTACHMENT_DIR),\n defaultRangeDays: parseIntOpt(env.APPLE_MESSAGES_DEFAULT_RANGE_DAYS),\n sendReconcileMs: parseIntOpt(env.APPLE_MESSAGES_SEND_RECONCILE_MS),\n osascriptPath: trimmed(env.APPLE_MESSAGES_OSASCRIPT_PATH),\n osascriptTimeoutMs: parseIntOpt(env.APPLE_MESSAGES_OSASCRIPT_TIMEOUT_MS),\n maxResults: parseIntOpt(env.APPLE_MESSAGES_MAX_RESULTS),\n });\n","/**\n * The Messages operating manual, served as `cupertino://messages/guide` and\n * embedded ahead of every Messages prompt. Static by design — see the note in\n * the Mail guide.\n */\nexport const MESSAGES_GUIDE = `# Apple Messages — how to drive this server\n\n## Refs are opaque\n\nChats come back as \\`mc1:<guid>\\`, messages as \\`m1:<guid>\\`. Pass them back\nverbatim; never construct one.\n\n## Full Disk Access is mandatory here, unlike every other surface\n\nThere is no Apple Events read lane to fall back on. Measured: Messages answers\n\"Application isn't running\" even while it is running, because it is a windowless\nbackground process that declines to wake for a script. Without the grant this\nserver can do **nothing** on the read side — that is a permission problem, not\nan empty message history.\n\n## Which tool, under which constraint\n\n- **\\`apple_messages_list_chats\\`** first, when you need to know which\n conversation you are looking at.\n- **\\`apple_messages_list_messages\\`** reads one conversation in order.\n- **\\`apple_messages_search_messages\\`** searches across them.\n- **\\`apple_messages_get_message\\`** returns one message with its tapbacks and\n attachments.\n\n## Handles are not names\n\nA chat identifies people by phone number or email address. Names come from\nContacts, which has its own separate permission, so \\`unknown\\` is a normal\noutcome — about one in six of even the busiest correspondents has no card. When\ndiagnostics reports \\`contacts.available: false\\`, **nobody looked at all** and\nevery handle is raw. Do not present a raw handle as though the person is\nunidentified when the truth is that lookup was never possible.\n\n## Two things that look like missing messages and are not\n\n**Recent messages are stored as blobs.** Messages stopped writing the plain\n\\`text\\` column between late February and late March 2026; everything since lives\nonly as an archived blob, which this server decodes. \\`textSource\\` on each result\nsays which lane answered. A reader without the decoder would report that the\nconversation stopped in February — if you ever see history that appears to end\nthere, that is this, not silence.\n\n**Tapbacks are rows in the message table.** They are filtered out of\nconversations and reported on the message they target. A \"liked\" is not a reply\nand should not be summarised as one.\n\n## Sending\n\n\\`apple_messages_send_message\\` exists only when writes are enabled, and it is the\n**only** thing this server can change — the dictionary has no edit, delete,\nmark-as-read or reaction command.\n\nSending is real and immediate. There is no draft state, no undo, and no\nconfirmation step after the call. Confirm the recipient handle with the user\nbefore sending, exactly as it will be used.\n\nMessages hands back no identifier for what it sent, so the sent row is found by\nre-reading the store. \\`reconciliation: \"pending\"\\` means it has not appeared yet.\nThat is **not a failure and must not be retried** — retrying sends the message\ntwice.\n`;\n","import {\n promptArg,\n registerWorkflowPrompt,\n requiredPromptArg,\n type PromptContext,\n} from \"@mgcrea/mcp-apple-core\";\nimport type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\n\nimport { MESSAGES_GUIDE } from \"./guide.js\";\n\nconst CTX: PromptContext = { surface: \"messages\", guide: MESSAGES_GUIDE };\n\n/**\n * Messages' workflow prompts.\n *\n * The send prompt carries the only genuinely irreversible action in this\n * bundle. Everywhere else a mistake leaves a draft, an extra reminder or a\n * wrong-looking event; here it puts words in someone's pocket under the user's\n * name, with no undo and no draft state to catch it.\n */\nexport const registerPrompts = (server: McpServer, allowWrites: boolean): void => {\n registerWorkflowPrompt(server, CTX, {\n name: \"apple_messages_catch_up\",\n title: \"Catch up on a conversation\",\n description:\n \"Read back what has been said in a chat and what is waiting on a reply, with handles \" +\n \"resolved to names where that is actually possible. Read-only.\",\n argsSchema: {\n chat: promptArg(\n \"Who or which chat — a name, a handle, or a chat ref. Omit for recent chats.\",\n ),\n since: promptArg('How far back, e.g. \"yesterday\", \"last week\". Defaults to recent history.'),\n },\n build: ({\n chat,\n since,\n }) => `Catch me up on ${chat ? `the conversation with ${chat}` : \"my recent messages\"}${\n since ? `, covering ${since}` : \"\"\n }.\n\n1. \\`apple_messages_list_chats\\` to find ${\n chat\n ? \"the right conversation. If several match, ask which rather than picking.\"\n : \"what has been active.\"\n }\n2. \\`apple_messages_list_messages\\` over that chat${since ? \" with a date bound\" : \"\"}.\n3. Summarise what was actually said and, most importantly, **what is waiting on\n me** — a question asked and not answered is the thing worth surfacing.\n4. Where a handle has no name, say so and show the handle raw. Do not guess who\n it is from context. If \\`contacts.available\\` is false in diagnostics, say that\n names could not be looked up at all — that is different from these people\n being unknown.\n\nIgnore tapbacks as messages; they are reactions on the message they target and\na \"liked\" is not a reply. If the history appears to stop in early 2026, that is\nthe blob-storage change described in the guide, not the end of the conversation\n— check \\`textSource\\` before reporting a gap.`,\n });\n\n if (!allowWrites) return;\n\n registerWorkflowPrompt(server, CTX, {\n name: \"apple_messages_send\",\n title: \"Send a message\",\n description:\n \"Compose and send an iMessage or SMS. Requires writes. Sending is immediate and cannot be \" +\n \"undone — this prompt confirms the recipient before anything goes out.\",\n argsSchema: {\n to: requiredPromptArg(\"Who to send to — a name, phone number or email address.\"),\n message: promptArg(\"What to say, or the gist of it. Omit to be asked.\"),\n },\n build: ({ to, message }) => `Send a message to ${to}.${\n message ? `\\n\\nWhat it should say: ${message}` : \"\"\n }\n\n1. Work out the exact handle. \\`apple_messages_list_chats\\` to find an existing\n conversation with this person — an established chat is better evidence of the\n right handle than an address book match, because it is the one they actually\n reply on.\n2. Read the last few messages in that chat. It tells you which language they\n write in, how formal they are, and whether something is already pending.\n3. **Show the user the exact handle and the exact text, and wait for them to\n confirm.** There is no draft state here and no undo: the call sends. A\n message to the wrong handle cannot be recalled, and it is sent under the\n user's name.\n4. On confirmation, \\`apple_messages_send_message\\`.\n5. If the result says \\`reconciliation: \"pending\"\\`, **the message was sent.**\n Messages returns no identifier, so the sent row is found by re-reading the\n store and has not appeared yet. Do not retry — retrying sends it twice. Say\n it went out and that confirmation is lagging.`,\n });\n};\n","/**\n * Messages' tool helpers. The generic half lives in `@mgcrea/mcp-apple-core`;\n * re-exported here so tools import from one place.\n */\nexport {\n compact,\n confirmArg,\n fail,\n limitArg,\n ok,\n okText,\n toFailure,\n wrap,\n wrapResult,\n type ToolResult,\n} from \"@mgcrea/mcp-apple-core\";\n\nimport { z } from \"zod\";\n\nexport const chatRefArg = z\n .string()\n .optional()\n .describe(\n 'An opaque chat ref from apple_messages_list_chats (looks like \"mc1:<guid>\"). Do not ' +\n \"construct one by hand.\",\n );\n\nexport const messageRefArg = z\n .string()\n .min(1)\n .describe(\n 'An opaque message ref from a list or search result (looks like \"m1:<guid>\"). Do not ' +\n \"construct one by hand.\",\n );\n\nexport const fromArg = z\n .string()\n .optional()\n .describe('Start of the window, ISO-8601 — \"2026-08-01\" or \"2026-08-01T09:00\".');\n\nexport const toArg = z\n .string()\n .optional()\n .describe(\"End of the window, ISO-8601. Defaults to now.\");\n\nexport const includeReactionsArg = z\n .boolean()\n .optional()\n .describe(\n \"Include tapbacks as if they were messages. Off by default, and you almost never want it \" +\n 'on: a tapback renders as `Liked \"see you at 8\"`, which nobody typed. Use ' +\n \"apple_messages_get_message to see the reactions on a specific message instead.\",\n );\n","import type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\n\nimport { BUILD_INFO } from \"../build-info.js\";\nimport type { AppleMessagesClient } from \"../client/messages.js\";\nimport { wrap } from \"./util.js\";\n\n/**\n * Build the report.\n *\n * Split out of the tool registration so the `cupertino://messages/diagnostics`\n * resource can serve the same bytes. Two renderings of one probe: duplicated,\n * the resource and the tool would drift, and the disagreement would surface as\n * \"the diagnostics lied\" — the one thing this file must never do.\n */\nexport const buildDiagnostics = async (\n client: AppleMessagesClient,\n): Promise<Record<string, unknown>> => {\n const status = client.status();\n const writes = client.config.allowWrites;\n return {\n server: { name: BUILD_INFO.name, version: BUILD_INFO.version },\n // Off means the prompts and the cupertino:// resources are not registered at\n // all. Reported here because this tool still is, so it stays the one place\n // that explains a capability the client cannot see.\n settings: { exposePrompts: client.config.exposePrompts },\n lane: {\n reads: \"file lane (read-only SQLite)\",\n writes: writes\n ? \"apple_messages_send_message, over Apple Events. The only mutating command \" +\n \"the Messages dictionary offers.\"\n : \"off — APPLE_MESSAGES_ALLOW_WRITES is not set, so the send tool is not \" +\n \"registered and this server sends no Apple Event at all.\",\n appleEvents:\n \"no read path exists, and never will. Measured: every read attempt fails, and \" +\n 'Messages answers \"Application isn\\'t running\" even while it is running, because ' +\n \"it is a windowless background process that declines to wake for a script. \" +\n \"Sending is the one thing that works.\",\n },\n store: {\n path: status.located.storePath,\n exists: status.located.exists,\n readable: status.located.readable,\n opened: status.store.opened,\n mode: status.store.mode,\n fingerprint: status.store.fingerprint,\n counts: status.counts,\n reason: status.located.reason,\n },\n contacts: status.contacts,\n caveats: [\n \"Full Disk Access is MANDATORY here, unlike every other surface in this bundle. \" +\n \"There is no Apple Events read lane to fall back to, so without the grant this \" +\n \"server can do nothing at all.\",\n \"Messages stopped writing the plain `text` column between late February and late \" +\n \"March 2026: every message since is stored only as an archived blob, which is \" +\n \"decoded here. `textSource` on each result says which lane answered. Across all \" +\n \"history the blob-only share is about 3%, but for anything recent it is ~100%, \" +\n \"so a reader without the decoder would report that the conversation stopped in \" +\n \"February.\",\n \"Names come from Contacts, which has its own separate permission. `unknown` is a \" +\n \"normal outcome and not an error — about one in six of even the busiest \" +\n \"correspondents has no contact card. When `contacts.available` is false, nobody \" +\n \"looked at all and every handle is raw.\",\n \"Tapbacks are rows in the message table. They are filtered out of conversations by \" +\n \"default and reported on the message they target instead.\",\n writes\n ? \"Sending is real and immediate, and it is the ONLY thing this server can \" +\n \"change — the dictionary has no edit, delete, mark-as-read or reaction \" +\n \"command. Messages hands back no identifier for what it sent, so the sent row \" +\n 'is found by re-reading the store; `reconciliation: \"pending\"` means it has ' +\n \"not appeared yet, which is not a failure and must not be retried.\"\n : \"This server cannot send: APPLE_MESSAGES_ALLOW_WRITES is off. With it on, one \" +\n \"tool appears — apple_messages_send_message — and it is the only mutating \" +\n \"command the Messages dictionary offers.\",\n ],\n };\n};\n\nexport const registerDiagnosticsTools = (server: McpServer, client: AppleMessagesClient): void => {\n server.registerTool(\n \"apple_messages_diagnostics\",\n {\n description:\n \"Report whether the Messages store could be opened, how much is in it, whether names \" +\n \"are being resolved, and what this server cannot do. Start here when a read returns \" +\n \"nothing.\",\n inputSchema: {},\n annotations: { readOnlyHint: true },\n },\n async () => wrap(() => buildDiagnostics(client)),\n );\n};\n","import type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { z } from \"zod\";\n\nimport { PreconditionError } from \"../client/errors.js\";\nimport type { AppleMessagesClient } from \"../client/messages.js\";\nimport { confirmArg, wrap } from \"./util.js\";\n\n/**\n * The one mutating tool, registered only when `allowWrites` is on.\n *\n * Never merely refused at call time: an MCP client caches the tool list, so a\n * tool that exists and says no is a tool the model will keep trying. With writes\n * off this file is not reached and the send tool does not exist.\n *\n * ## One verb, because the dictionary has one\n *\n * `sdef /System/Applications/Messages.app` lists three commands — `send`,\n * `login`, `logout`. There is no edit, no delete, no mark-as-read, no typing\n * indicator, and no reaction: everything else this server can show you, it\n * cannot change. `login`/`logout` are not exposed because logging a user out of\n * iMessage across every device is not something to do behind a tool call.\n *\n * `send`'s direct parameter is typed `file` OR `text` in the dictionary, and\n * only the text form ships. See `client/jxa/core.ts` for why.\n */\n\nconst SIDE_EFFECT =\n \"This sends a REAL message from the user's own iMessage/SMS account, immediately and \" +\n \"irreversibly. There is no unsend, no draft and no preview: the recipient's phone buzzes as \" +\n \"soon as this returns. Messages may be launched to do it, which the user will see. Confirm the \" +\n \"exact wording and the exact recipient with the user before calling this.\";\n\nexport const registerActionTools = (server: McpServer, client: AppleMessagesClient): void => {\n server.registerTool(\n \"apple_messages_send_message\",\n {\n description:\n \"Send a message to an existing conversation or to a phone number / email address. \" +\n SIDE_EFFECT +\n \" Prefer chatRef from apple_messages_list_chats over a raw handle: Messages refuses to \" +\n \"enumerate participants for a script, so an existing conversation is the only target \" +\n \"this server can address reliably — a handle with no conversation on this Mac will \" +\n \"usually fail rather than start a new thread. The result reports which targeting \" +\n \"strategy worked, and reconciles against the message store to hand back a real message \" +\n \"ref: reconciliation `matched` means the sent row was found and `message` is it, \" +\n \"`pending` means Messages accepted the send but has not written the row yet (normal on a \" +\n \"slow network — read the chat back, do NOT send again), and `unavailable` means there \" +\n \"was no existing chat to look in.\",\n inputSchema: {\n chatRef: z\n .string()\n .optional()\n .describe(\n 'An opaque chat ref from apple_messages_list_chats (\"mc1:<guid>\"). The reliable ' +\n \"way to target a send. Do not construct one by hand.\",\n ),\n to: z\n .string()\n .optional()\n .describe(\n \"A phone number or email address, when there is no chat ref. Matched against the \" +\n \"store's own handles by last-9-digits, so local and international spellings of \" +\n \"the same number both work.\",\n ),\n text: z.string().min(1).describe(\"Exactly what to send. Sent verbatim.\"),\n service: z\n .enum([\"imessage\", \"sms\", \"rcs\"])\n .optional()\n .describe(\n \"Which service to prefer when addressing a handle with no existing chat. Ignored \" +\n \"when chatRef is given, since the chat already knows its service.\",\n ),\n confirm: confirmArg,\n },\n annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false },\n },\n async ({ chatRef, to, text, service }) =>\n wrap(async () => {\n // Both, or neither, is a caller error rather than something to guess at:\n // guessing here sends a real message to the wrong person.\n if (Boolean(chatRef) === Boolean(to)) {\n throw new PreconditionError(\n \"Give exactly one of chatRef or to. chatRef targets an existing conversation and is \" +\n \"the reliable form; to addresses a phone number or email directly.\",\n );\n }\n return client.sendMessage({\n ...(chatRef ? { chatRef } : {}),\n ...(to ? { to } : {}),\n ...(service ? { service } : {}),\n text,\n });\n }),\n );\n};\n","import type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { z } from \"zod\";\n\nimport type { AppleMessagesClient } from \"../client/messages.js\";\nimport { wrap } from \"./util.js\";\n\n/**\n * Saving an attachment out of a conversation.\n *\n * ## Why this is not in `actions.ts`\n *\n * That file's argument is that Messages' scripting dictionary has exactly one\n * usable verb, so this server has exactly one mutating tool — and that with\n * writes off it sends no Apple Event at all. Both remain true with this tool\n * registered: it opens a file and writes a file, and never speaks to\n * Messages.app. Filing it next to `send` would quietly weaken a claim\n * `diagnostics` makes about what the write gate buys.\n *\n * ## Why it is behind the write gate anyway\n *\n * It puts a file on the user's disk. Mail and Notes made the same call for the\n * same reason, and the three should not disagree about it: a user who has not\n * turned writes on has not agreed to this server creating files.\n */\nexport const registerAttachmentTools = (server: McpServer, client: AppleMessagesClient): void => {\n server.registerTool(\n \"apple_messages_save_attachment\",\n {\n description:\n \"Save one attachment from a conversation to disk — a photo, a video, a PDF, a voice \" +\n \"memo. Take the `id` from an attachment on apple_messages_get_message. Needs Full Disk \" +\n \"Access, like every read here. It can only write into APPLE_MESSAGES_ATTACHMENT_DIR \" +\n \"(default ~/Downloads) and will not overwrite an existing file unless you ask it to. \" +\n \"Write-gated because it puts a file on the user's disk, even though it changes nothing \" +\n \"in Messages. An attachment iCloud has offloaded has no bytes on this Mac and is \" +\n \"refused with that reason — open the conversation in Messages to pull it down first.\",\n inputSchema: {\n attachmentId: z\n .string()\n .min(1)\n .describe(\n \"The `id` of an attachment from apple_messages_get_message. Opaque; do not \" +\n \"construct one, and do not pass the file path shown next to it.\",\n ),\n directory: z\n .string()\n .optional()\n .describe(\"Override the target directory. Must still resolve inside the configured one.\"),\n overwrite: z.boolean().optional().describe(\"Replace an existing file. Default false.\"),\n },\n annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false },\n },\n async ({ attachmentId, directory, overwrite }) =>\n wrap(async () =>\n client.saveAttachment(attachmentId, {\n ...(directory ? { directory } : {}),\n ...(overwrite ? { overwrite } : {}),\n }),\n ),\n );\n};\n","import type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\n\nimport type { AppleMessagesClient } from \"../client/messages.js\";\nimport { limitArg, wrap } from \"./util.js\";\n\n/**\n * NOTE ON `async` BELOW: core's `wrap` is typed `() => Promise<T>` because most\n * surfaces reach Apple Events. Messages reads synchronous SQLite and never\n * leaves the process, so the thunks are marked async here rather than widening a\n * shared signature for every surface to accommodate one.\n */\nexport const registerChatTools = (server: McpServer, client: AppleMessagesClient): void => {\n server.registerTool(\n \"apple_messages_list_chats\",\n {\n description:\n \"List conversations, most recently active first, with their participants and message \" +\n \"counts. Names come from Contacts where they resolve; where they do not, the raw phone \" +\n \"number or email is shown and `resolution` says why. Use the returned ref to read one \" +\n \"conversation with apple_messages_list_messages.\",\n inputSchema: { limit: limitArg },\n annotations: { readOnlyHint: true },\n },\n async ({ limit }) => wrap(async () => client.listChats(limit)),\n );\n};\n","import type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { z } from \"zod\";\n\nimport type { AppleMessagesClient } from \"../client/messages.js\";\nimport { decodeChatRef, decodeMessageRef } from \"../client/ref.js\";\nimport {\n chatRefArg,\n fail,\n fromArg,\n includeReactionsArg,\n limitArg,\n messageRefArg,\n ok,\n toArg,\n wrap,\n wrapResult,\n} from \"./util.js\";\n\n/** ISO-8601 in, or a clear refusal. No relative grammar on this surface yet. */\nconst parseBound = (raw: string | undefined, field: string): Date | undefined => {\n if (raw === undefined) return undefined;\n const d = new Date(raw);\n if (Number.isNaN(d.getTime())) {\n throw new Error(\n `Could not read ${field} from ${JSON.stringify(raw)}. Use ISO-8601: \"2026-08-01\" or ` +\n `\"2026-08-01T09:00\".`,\n );\n }\n return d;\n};\n\nexport const registerMessageTools = (server: McpServer, client: AppleMessagesClient): void => {\n server.registerTool(\n \"apple_messages_list_messages\",\n {\n description:\n \"Read messages, newest first — the whole store, or one conversation, or a date window. \" +\n \"Tapbacks are excluded by default because they are rows in the same table and would \" +\n \"otherwise read as messages nobody typed.\\n\\n\" +\n \"Every message carries `from.name` where Contacts resolves it and `from.handle` always. \" +\n \"`from.resolution` is worth reading: `unknown` is COMMON AND NOT AN ERROR — measured on a \" +\n \"real store, about one in six of even the busiest correspondents has no contact card.\",\n inputSchema: {\n chatRef: chatRefArg,\n from: fromArg,\n to: toArg,\n includeReactions: includeReactionsArg,\n limit: limitArg,\n },\n annotations: { readOnlyHint: true },\n },\n async ({ chatRef, from, to, includeReactions, limit }) =>\n wrap(async () => {\n const window = client.window(parseBound(from, \"from\"), parseBound(to, \"to\"));\n return client.listMessages({\n ...(chatRef ? { chatRef: decodeChatRef(chatRef) } : {}),\n ...window,\n ...(includeReactions === undefined ? {} : { includeReactions }),\n ...(limit === undefined ? {} : { limit }),\n });\n }),\n );\n\n server.registerTool(\n \"apple_messages_search_messages\",\n {\n description:\n \"Search message text across every conversation. Matches are substring and \" +\n \"case-insensitive.\\n\\n\" +\n \"This searches ALL messages, including the roughly 3% whose text lives only in an \" +\n \"archived blob rather than in a plain column — those are invisible to any SQL query and \" +\n \"are decoded here. `textSource` on each result says which produced it.\",\n inputSchema: {\n query: z.string().min(1).describe(\"Text to look for.\"),\n limit: limitArg,\n },\n annotations: { readOnlyHint: true },\n },\n async ({ query, limit }) => wrap(async () => client.searchMessages(query, limit)),\n );\n\n server.registerTool(\n \"apple_messages_get_message\",\n {\n description:\n \"One message in full, with its tapbacks and attachments. Reactions are reported here \" +\n \"rather than mixed into the conversation, which is the whole reason list_messages \" +\n \"filters them out.\",\n inputSchema: { ref: messageRefArg },\n annotations: { readOnlyHint: true },\n },\n async ({ ref }) =>\n wrapResult(async () => {\n const message = client.getMessage(decodeMessageRef(ref));\n if (!message) {\n return fail(\n `No message for ref \"${ref}\". It was probably deleted since the search ran. ` +\n `Re-run the search to get a current ref.`,\n );\n }\n return ok(message);\n }),\n );\n};\n","import type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\n\nimport type { AppleMessagesClient } from \"../client/messages.js\";\nimport { registerActionTools } from \"./actions.js\";\nimport { registerAttachmentTools } from \"./attachments.js\";\nimport { registerChatTools } from \"./chats.js\";\nimport { registerDiagnosticsTools } from \"./diagnostics.js\";\nimport { registerMessageTools } from \"./messages.js\";\n\nexport type ToolContext = {\n /**\n * Gates the two tools that change something outside this process.\n *\n * `send_message` is the only one that touches Messages.app, and it is also\n * every Apple Event this server can send, because sending is the only thing\n * Apple Events can do here — there is no read lane to fall back to and never\n * can be. So with writes off this server remains inert with respect to\n * Messages.app: it opens a file and nothing else.\n *\n * `save_attachment` is gated for a different reason. It sends no Apple Event\n * and changes nothing in Messages; what it does is create a file on the\n * user's disk, which Mail and Notes also treat as a write. The claim above\n * survives it intact.\n */\n allowWrites: boolean;\n};\n\n/**\n * Register the Apple Messages tools.\n *\n * Five reads, always. Two writes, only when `allowWrites` is on — and on this\n * surface the flag carries a permission claim as well as a safety one: with it\n * off no Apple Event is ever sent, so no Automation grant is ever requested.\n * What is needed either way is Full Disk Access, absolutely — see `diagnostics`.\n *\n * The registered set does NOT vary with whether the store is readable. That is a\n * runtime condition, and MCP clients cache the tool list.\n */\nexport const registerTools = (\n server: McpServer,\n client: AppleMessagesClient,\n ctx: ToolContext,\n): void => {\n registerDiagnosticsTools(server, client);\n registerChatTools(server, client);\n registerMessageTools(server, client);\n if (!ctx.allowWrites) return;\n registerActionTools(server, client);\n registerAttachmentTools(server, client);\n};\n","import {\n registerSurfaceResources,\n type Logger,\n type OsascriptRunner,\n} from \"@mgcrea/mcp-apple-core\";\nimport { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\n\nimport { BUILD_INFO } from \"./build-info.js\";\nimport { AppleMessagesClient } from \"./client/messages.js\";\nimport type { Config } from \"./config.js\";\nimport { MESSAGES_GUIDE } from \"./guide.js\";\nimport { registerPrompts } from \"./prompts.js\";\nimport { buildDiagnostics } from \"./tools/diagnostics.js\";\nimport { registerTools } from \"./tools/index.js\";\n\nexport const SERVER_NAME = BUILD_INFO.name;\nexport const SERVER_VERSION = BUILD_INFO.version;\n\nexport type CreateServerOptions = {\n config: Config;\n logger?: Logger;\n /** Injected by tests so nothing spawns a process or touches real Messages. */\n osascript?: OsascriptRunner;\n /** Injected by tests so discovery never reaches the developer's real home. */\n home?: string;\n /** Injected by tests, so no test reaches the developer's real address book. */\n contacts?: ConstructorParameters<typeof AppleMessagesClient>[0][\"contacts\"];\n};\n\nexport type CreatedServer = {\n server: McpServer;\n client: AppleMessagesClient;\n};\n\n/**\n * Build the server. Side-effect free: it opens no database and reads no file,\n * so a test can construct it freely and every external dependency arrives\n * through an option.\n */\nexport const createServer = (opts: CreateServerOptions): CreatedServer => {\n const { config } = opts;\n const server = new McpServer({ name: SERVER_NAME, version: SERVER_VERSION });\n\n const client = new AppleMessagesClient({\n config,\n ...(opts.logger ? { logger: opts.logger } : {}),\n ...(opts.osascript ? { osascript: opts.osascript } : {}),\n ...(opts.home ? { home: opts.home } : {}),\n ...(opts.contacts === undefined ? {} : { contacts: opts.contacts }),\n });\n\n registerTools(server, client, { allowWrites: config.allowWrites });\n /*\n * One flag, both primitives — see `exposePrompts` in core's config. A prompt\n * embeds its surface guide, so registering prompts without the resources\n * would leave every expansion naming a `cupertino://…/guide` that this\n * server does not serve.\n */\n if (config.exposePrompts) {\n registerPrompts(server, config.allowWrites);\n registerSurfaceResources(server, {\n surface: \"messages\",\n displayName: \"Messages\",\n guide: MESSAGES_GUIDE,\n diagnostics: () => buildDiagnostics(client),\n // No inventory. Chats are unbounded and change constantly, which makes them\n // a query (apple_messages_list_chats) rather than a fixed set worth\n // addressing by URI — a resource that is never the same twice is a tool\n // call wearing a URI.\n });\n }\n\n return { server, client };\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"server-BSHulHkz.js","names":["#col","#date","#body","#messageColumns","#joins","#toRow","#chats","#participants","#config","#logger","#home","#runner","#contacts","#contactsTried","#located","#storeTried","#store","loadContactsConfig","#resolved","#contactsClient","#resolve","#correspondent","#render","#require","resolvePath","#reconcile","#invalidate","#chatFor","loadConfig"],"sources":["../src/build-info.ts","../src/client/dates.ts","../src/client/errors.ts","../src/client/jxa/core.ts","../src/client/jxa/write.ts","../src/client/locate.ts","../src/client/ref.ts","../src/client/typedstream.ts","../src/client/store.ts","../src/client/messages.ts","../src/config.ts","../src/guide.ts","../src/prompts.ts","../src/tools/util.ts","../src/tools/diagnostics.ts","../src/tools/actions.ts","../src/tools/attachments.ts","../src/tools/chats.ts","../src/tools/messages.ts","../src/tools/index.ts","../src/server.ts"],"sourcesContent":["// Build-time / runtime identity for the running server. `name`/`version` are\n// read from package.json at startup (always accurate); `gitCommit` /\n// `gitCommitDate` are injected by tsdown's `define` substitution at build time\n// and fall back to \"unknown\" when running from source (e.g. vitest).\n\nimport { readPackageIdentity, type BuildInfo } from \"@mgcrea/mcp-apple-core\";\n\n// oxlint-disable no-underscore-dangle -- bundler-injected build-time constants.\ndeclare const __GIT_COMMIT__: string;\ndeclare const __GIT_COMMIT_DATE__: string;\n\nconst pkg = readPackageIdentity(new URL(\"../package.json\", import.meta.url), {\n name: \"@mgcrea/mcp-apple-messages\",\n version: \"0.0.0\",\n});\n\nexport type { BuildInfo };\n\nexport const BUILD_INFO: BuildInfo = {\n name: pkg.name,\n version: pkg.version,\n gitCommit: typeof __GIT_COMMIT__ === \"string\" ? __GIT_COMMIT__ : \"unknown\",\n gitCommitDate: typeof __GIT_COMMIT_DATE__ === \"string\" ? __GIT_COMMIT_DATE__ : \"unknown\",\n};\n","/**\n * Messages' dates, which do not fit in a JavaScript number.\n *\n * `docs/messages.md` measured every populated date column as **nanoseconds since\n * 2001-01-01** — eighteen digits, about 7.9e17, two orders of magnitude past\n * `Number.MAX_SAFE_INTEGER`. `node:sqlite` throws on those rather than\n * truncating, which is correct of it and fatal to a naive `SELECT date`.\n *\n * The failure mode is what makes this worth its own module. Wrapped in the usual\n * try/catch the throw is swallowed and the column reports as EMPTY — the probe\n * did exactly that on its first granted run and announced \"no dates present\" for\n * all seven columns across 97,414 messages. A section written to catch a silent\n * 31-year error was itself silently wrong.\n *\n * ## The fix: divide in SQL, not in JavaScript\n *\n * `CAST(date AS REAL) / 1e9` never materialises the integer on the JS side, so\n * there is nothing to throw. Precision is fine: 7.9e8 seconds with a fractional\n * part is comfortably inside a double, and this surface has no use for\n * nanosecond resolution anyway.\n *\n * Every query in `store.ts` uses `APPLE_SECONDS_SQL`. Reading one of these\n * columns any other way is the bug.\n */\n\n/** Seconds between 1970-01-01 and 2001-01-01. Same constant as `packages/core`. */\nimport { CORE_DATA_EPOCH_OFFSET } from \"@mgcrea/mcp-apple-core\";\n\n/**\n * Below this, a value is seconds rather than nanoseconds.\n *\n * Messages switched around macOS 10.13 and old rows were not rewritten, so a\n * store with history from both eras carries both. 1e12 apple-seconds is the year\n * 33,658 and 1e12 apple-nanoseconds is 2001 — no real timestamp is near it in\n * either reading, which is what makes the split safe.\n */\nconst NANOSECOND_FLOOR = 1e12;\n\n/**\n * SQL that yields apple-SECONDS as a REAL, whichever unit the row holds.\n *\n * Done in SQL rather than in JS because the point is to never let the raw\n * integer reach `node:sqlite`'s value conversion.\n */\nexport const appleSecondsSql = (column: string): string =>\n `CASE WHEN ${column} IS NULL THEN NULL` +\n ` WHEN ABS(CAST(${column} AS REAL)) > ${NANOSECOND_FLOOR} THEN CAST(${column} AS REAL) / 1000000000.0` +\n ` ELSE CAST(${column} AS REAL) END`;\n\n/** Apple-seconds to a JS Date. Null in, null out. */\nexport const fromAppleSeconds = (value: number | null): Date | null => {\n if (value === null || !Number.isFinite(value) || value === 0) return null;\n return new Date((value + CORE_DATA_EPOCH_OFFSET) * 1000);\n};\n\n/** A JS Date to apple-seconds, for range bounds. */\nexport const toAppleSeconds = (date: Date): number =>\n date.getTime() / 1000 - CORE_DATA_EPOCH_OFFSET;\n\n/** ISO-8601, or null. What every date field on a result carries. */\nexport const renderInstant = (value: number | null): string | null =>\n fromAppleSeconds(value)?.toISOString() ?? null;\n\nexport { CORE_DATA_EPOCH_OFFSET };\n","/**\n * Messages' error surface. The taxonomy lives in `@mgcrea/mcp-apple-core`; what\n * belongs here is the identity those messages are written against.\n */\n\nimport { AppleAutomationError, type SurfaceContext } from \"@mgcrea/mcp-apple-core\";\n\nexport const MESSAGES_SURFACE: SurfaceContext = {\n appName: \"Messages\",\n envPrefix: \"APPLE_MESSAGES\",\n};\n\n/**\n * Used by exactly one code path, and unused by every read.\n *\n * Messages is the one surface with NO Apple Events read lane at all — measured,\n * not assumed. `docs/messages.md` records every attempt failing, and the reason\n * is peculiar enough to write down: Messages answers \"Application isn't running\"\n * while `NSRunningApplication` reports it running, because it lives as a\n * windowless background process that declines to wake for a script. The liveness\n * check and the app's own answer disagree and neither is lying.\n *\n * The id is the liveness check for `send`, which is the only thing Apple Events\n * can do on this surface — see `client/jxa/core.ts`.\n */\nexport const MESSAGES_BUNDLE_ID = \"com.apple.MobileSMS\";\n\nexport {\n AppleAutomationError as AppleMessagesError,\n IndexUnavailableError,\n PlatformError,\n PreconditionError,\n ProtocolError,\n SchemaDriftError,\n TccDeniedError,\n} from \"@mgcrea/mcp-apple-core\";\n\n/** A message ref no longer resolves — deleted, or the chat was cleared. */\nexport class MessageNotFoundError extends AppleAutomationError {\n override readonly name = \"MessageNotFoundError\";\n\n constructor(ref: string) {\n super(\n `No message for ref \"${ref}\". It was probably deleted since the search ran. Re-run the ` +\n `search to get a current ref.`,\n { ref },\n );\n }\n}\n\n/** A chat ref no longer resolves. */\nexport class ChatNotFoundError extends AppleAutomationError {\n override readonly name = \"ChatNotFoundError\";\n\n constructor(ref: string) {\n super(`No chat for ref \"${ref}\". Use apple_messages_list_chats to get a current ref.`, { ref });\n }\n}\n\n/**\n * The store could not be read.\n *\n * Its own error because this surface fails harder than any other: there is no\n * Apple Events fallback, so without Full Disk Access there is no server at all.\n * `docs/distribution.md`'s \"try before you grant\" was retired partly because of\n * this surface, and the message says so rather than implying a degraded mode\n * that does not exist.\n */\nexport class MessagesUnavailableError extends AppleAutomationError {\n override readonly name = \"MessagesUnavailableError\";\n\n constructor(reason: string) {\n super(reason, {});\n }\n}\n\n/**\n * Messages would not accept any form of recipient for a send.\n *\n * Its own error because the cause is almost never the recipient. Every rung of\n * the ladder in `client/jxa/core.ts` except the first one enumerates something,\n * and enumeration is exactly what this app refuses — so the usual cause of this\n * error is that the chat is new (no guid in the store to address it by) rather\n * than that the person does not exist. The message says so, because \"not found\"\n * would send a caller looking for a typo that is not there.\n */\nexport class SendTargetNotFoundError extends AppleAutomationError {\n override readonly name = \"SendTargetNotFoundError\";\n\n constructor(recipient: string, attempts: readonly string[]) {\n super(\n `Messages would not resolve \"${recipient}\" to a chat or participant, so nothing was sent. ` +\n `This usually means there is no existing conversation with them on this Mac: Messages ` +\n `refuses to enumerate participants for a script, so an existing chat is the only handle ` +\n `this server can address reliably. Open the conversation once in Messages.app and retry.`,\n { recipient, attempts: [...attempts] },\n );\n }\n}\n\n/** Messages accepted the target and then refused the send itself. */\nexport class SendFailedError extends AppleAutomationError {\n override readonly name = \"SendFailedError\";\n\n constructor(message: string, attempts: readonly string[]) {\n super(`Messages refused the send: ${message}`, { attempts: [...attempts] });\n }\n}\n","/**\n * The Apple Events lane for Messages — which exists for exactly one verb.\n *\n * Every script here is a static constant. None may contain a template\n * interpolation: `assertStaticScript` rejects any script containing a dollar\n * sign followed by a brace, including one written inside the JXA source. Use\n * string concatenation in JXA code.\n *\n * Contract, shared with every other surface:\n * - parameters arrive as `JSON.parse(argv[0])`\n * - success returns `JSON.stringify({ok: true, data})`\n * - an application-level failure returns `{ok: false, error: {code, message}}`\n * and still exits 0, so a non-zero exit always means infrastructure.\n *\n * ## There is no read.ts here, and there never can be\n *\n * Adding a send did not add a read lane, and on this surface that is not a\n * policy choice — it is the measurement in `docs/messages.md`:\n *\n * | attempt | result |\n * | ---------------- | ----------------------------------------- |\n * | `chats()` | `Error: Application isn't running.` |\n * | `chats.id()` | `TypeError: M.chats.id is not a function` |\n * | `participants()` | `Error: Application isn't running.` |\n * | `buddies()` | `Error: Application isn't running.` |\n * | messages of chat | `Error: Application isn't running.` |\n *\n * Messages answers \"Application isn't running\" while `NSRunningApplication`\n * reports it running, because it lives as a windowless background process that\n * declines to wake for a script. `test/jxa.test.ts` asserts `read.ts` does not\n * exist, so this cannot be re-added out of helpfulness.\n *\n * The consequence for the code below is concrete: **the target resolution steps\n * that enumerate anything are expected to fail**, which is why they are a ladder\n * and why every rung reports itself. The one rung that does not enumerate —\n * `chats.byId(guid)`, with the guid handed over by the file lane — is the one\n * this design is built around.\n *\n * ## What the dictionary actually offers\n *\n * MEASURED from `sdef /System/Applications/Messages.app` on macOS 26.6. Three\n * commands, and only one of them is a write:\n *\n * send text (or a file) to a participant or a chat\n * login log in to all accounts\n * logout log out of all accounts\n *\n * `login`/`logout` are not exposed as tools: logging a user out of iMessage on\n * every device is not something to do behind a tool call, and there is no read\n * to justify logging in.\n *\n * `send`'s direct parameter is typed `file` OR `text`. **This ships text only.**\n * The file form is one branch away and deliberately not taken: a tool that\n * transfers an arbitrary local path to a remote person is an exfiltration\n * primitive, and unlike the text form its blast radius is not bounded by what\n * the model can say. Recorded here so the omission reads as a decision.\n *\n * ## The `to` parameter, and why the file lane picks the target\n *\n * `send` takes a `participant` or a `chat`. Getting one of those normally means\n * enumerating, which is the thing that does not work here. But the `chat` class\n * carries `id` — \"A guid identifier for this chat\" — and the file lane already\n * holds `chat.guid` for all 1,027 chats on the measured store. So the read lane\n * chooses the target and the write lane addresses it by id, which is the only\n * arrangement where neither lane has to do the thing it cannot.\n *\n * That the two guids are the same string is NOT assumed. It is the exact class\n * of thing this project has been wrong about before — `docs/messages.md` calls\n * the id bridge \"unanswerable by construction\" because Messages returned no\n * identifier to bridge FROM. So `chats.byId` is rung one of a ladder, every rung\n * records why it failed, and the strategy that worked is reported back to the\n * caller in the tool result.\n */\n\n/** The bundle id, which does not match the display name: Messages.app is still MobileSMS. */\nexport const PRELUDE = `\nObjC.import(\"AppKit\");\n\nfunction ok(data) { return JSON.stringify({ ok: true, data: data }); }\nfunction err(code, message, extra) {\n var e = { code: code, message: String(message) };\n if (extra) e.detail = extra;\n return JSON.stringify({ ok: false, error: e });\n}\n\n/** Read one property defensively — every read on this surface is allowed to fail. */\nfunction prop(fn, fallback) {\n try {\n var v = fn();\n return v === undefined ? fallback : v;\n } catch (e) {\n return fallback;\n }\n}\n\nfunction isMessagesRunning() {\n var apps = $.NSRunningApplication.runningApplicationsWithBundleIdentifier(\"com.apple.MobileSMS\");\n return apps.count > 0;\n}\n\n/** The dictionary's service type enumeration: SMS, iMessage, RCS. */\nfunction serviceEnum(name) {\n var s = String(name || \"\").toLowerCase();\n if (s === \"sms\") return \"SMS\";\n if (s === \"rcs\") return \"RCS\";\n return \"iMessage\";\n}\n\n/**\n * Find something \\`send\\` will accept as its \\`to\\`.\n *\n * Ordered cheapest and most-likely-to-work first. Every rung is wrapped, every\n * failure is recorded, and the caller is told which one answered — on a surface\n * whose whole read half is known broken, \"it worked\" without \"how\" is not a\n * result anyone can act on later.\n */\nfunction resolveTarget(M, p, tried) {\n var i;\n\n // 1. The chat guid the file lane read out of chat.db. No enumeration.\n if (p.chatGuid) {\n try {\n var chat = M.chats.byId(p.chatGuid);\n chat.id();\n return { target: chat, strategy: \"chat-guid\", kind: \"chat\" };\n } catch (e) {\n tried.push(\"chat-guid: \" + (e.message || e));\n }\n }\n\n if (!p.handle) return null;\n\n // 2. The guid Messages composes for a one-to-one chat, spelled the way the\n // store spells it: \"iMessage;-;+15551234567\". Constructed rather than read,\n // so it is below the real one and above everything that enumerates.\n var services = p.service ? [serviceEnum(p.service)] : [\"iMessage\", \"SMS\", \"RCS\"];\n for (i = 0; i < services.length; i++) {\n var guess = services[i] + \";-;\" + p.handle;\n try {\n var guessed = M.chats.byId(guess);\n guessed.id();\n return { target: guessed, strategy: \"chat-guid-guess\", kind: \"chat\", guid: guess };\n } catch (e2) {\n tried.push(\"chat-guid-guess(\" + guess + \"): \" + (e2.message || e2));\n }\n }\n\n // 3. A participant reached through its account. This enumerates, so it is\n // expected to fail with \"Application isn't running\" — kept because it is\n // the form every AppleScript example on the internet uses, and because if\n // launching the app does wake the scripting interface, this is what works.\n for (i = 0; i < services.length; i++) {\n try {\n var accounts = M.accounts.whose({ serviceType: services[i] })();\n for (var j = 0; j < accounts.length; j++) {\n try {\n var buddy = accounts[j].participants.whose({ handle: p.handle })()[0];\n if (buddy) {\n buddy.id();\n return { target: buddy, strategy: \"account-participant\", kind: \"participant\" };\n }\n } catch (e4) {\n tried.push(\"account-participant(\" + services[i] + \"): \" + (e4.message || e4));\n }\n }\n } catch (e3) {\n tried.push(\"accounts(\" + services[i] + \"): \" + (e3.message || e3));\n }\n }\n\n // 4. The flat participant list, last because it is the widest enumeration.\n try {\n var flat = M.participants.whose({ handle: p.handle })()[0];\n if (flat) {\n flat.id();\n return { target: flat, strategy: \"participant\", kind: \"participant\" };\n }\n tried.push(\"participant: no participant with that handle\");\n } catch (e5) {\n tried.push(\"participant: \" + (e5.message || e5));\n }\n\n return null;\n}\n`;\n","import { PRELUDE } from \"./core.js\";\n\n/**\n * One script, one verb.\n *\n * `send` is the only mutating command in the Messages dictionary that this\n * server exposes — see `core.ts` for the full list and for why `login`/`logout`\n * and the file form of `send` are left out.\n *\n * ## What this script deliberately does NOT do\n *\n * It does not report success from its own read-back, because there is nothing to\n * read back: `send` returns no value, and every read this app offers fails. A\n * script that answered `{ok: true}` and stopped would be claiming delivery on\n * the strength of a command that did not throw — the exact shape of \"plausible,\n * wrong and silent\" this repo keeps designing against.\n *\n * So the script's answer is deliberately narrow: **the send command was accepted\n * by Messages, and here is how the target was addressed.** Whether a row landed\n * is a question for the file lane, and `client/messages.ts` asks it immediately\n * afterwards by polling chat.db for the outgoing row. That split is the answer\n * to the open question `docs/messages.md` left — \"whether a send should\n * re-resolve by scanning the store for a recent row on the target chat\" — and it\n * is what makes a send reportable at all on a surface with no id bridge.\n */\nexport const SEND_MESSAGE = `${PRELUDE}\nfunction run(argv) {\n var p = JSON.parse(argv[0]);\n var M = Application(\"Messages\");\n\n var wasRunning = isMessagesRunning();\n if (!wasRunning && !p.allowLaunch) {\n return err(\"APP_NOT_RUNNING\", \"Messages is not running.\");\n }\n\n var tried = [];\n var resolved = resolveTarget(M, p, tried);\n if (!resolved) {\n return err(\n \"SEND_TARGET_NOT_FOUND\",\n \"Messages would not resolve a chat or participant for that recipient.\",\n tried\n );\n }\n\n try {\n M.send(p.text, { to: resolved.target });\n } catch (e) {\n return err(\"SEND_FAILED\", e.message || e, tried);\n }\n\n return ok({\n strategy: resolved.strategy,\n targetKind: resolved.kind,\n // Best effort, and allowed to be null: reading the id back is itself a read.\n targetId: prop(function () { return String(resolved.target.id()); }, null),\n launched: !wasRunning,\n attempts: tried\n });\n}\n`;\n","import { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\nimport { describeStore, type StoreFacts } from \"@mgcrea/mcp-apple-core\";\n\n/**\n * Find Messages' store.\n *\n * The easiest locator in the repo: `~/Library/Messages/chat.db` is a constant,\n * with no generated directory name to list (Reminders) and no per-account fan-out\n * (Contacts). `statSync` succeeds on a TCC-protected file while `open` is denied,\n * so this distinguishes \"not there\" from \"not allowed\" with no permission at all\n * — which is most of what diagnostics is for on a surface where the grant is\n * mandatory.\n */\n\nexport const STORE_RELATIVE = join(\"Library\", \"Messages\", \"chat.db\");\n\n/** Attachment bytes live here, referenced by `attachment.filename`. */\nexport const ATTACHMENTS_RELATIVE = join(\"Library\", \"Messages\", \"Attachments\");\n\n/**\n * The directory an attachment path is required to sit inside.\n *\n * Deliberately the Messages root rather than `Attachments` itself. Measured\n * shapes of `attachment.filename` include stickers, which Messages keeps in a\n * sibling directory, so confining to `Attachments` would refuse real files —\n * and confining to nothing at all would let a row in a database this server\n * does not write choose which file gets copied out.\n */\nexport const MESSAGES_ROOT_RELATIVE = join(\"Library\", \"Messages\");\n\nexport type LocateResult = StoreFacts & {\n storePath: string;\n attachmentsPath: string;\n /** Nothing outside this is copied out, whatever the store says. */\n messagesRoot: string;\n reason: string | null;\n};\n\nexport const defaultStorePath = (home: string = homedir()): string => join(home, STORE_RELATIVE);\n\nconst FDA_HINT =\n \"Grant Full Disk Access to the app running this server (System Settings > Privacy & Security > \" +\n \"Full Disk Access) and restart it. Granting it to Messages.app does nothing — the reader needs \" +\n \"the permission. Unlike every other surface here, there is no Apple Events fallback: Messages \" +\n \"has no read path in its scripting dictionary at all, so without this grant there is no server.\";\n\nexport const locateStore = (\n opts: { storePath?: string | undefined; home?: string } = {},\n): LocateResult => {\n const home = opts.home ?? homedir();\n const storePath = opts.storePath ?? defaultStorePath(home);\n const facts = describeStore(storePath);\n\n return {\n ...facts,\n storePath,\n attachmentsPath: join(home, ATTACHMENTS_RELATIVE),\n messagesRoot: join(home, MESSAGES_ROOT_RELATIVE),\n reason: facts.readable\n ? null\n : facts.exists\n ? `Found the Messages store at ${storePath} but cannot read it. ${FDA_HINT}`\n : opts.storePath\n ? `No file at ${storePath}. APPLE_MESSAGES_STORE points at nothing.`\n : `No Messages store at ${storePath}. Has Messages ever been set up on this account?`,\n };\n};\n","import { AppleAutomationError } from \"@mgcrea/mcp-apple-core\";\n\n/**\n * Refs for messages and chats.\n *\n * ## Why a GUID rather than a rowid\n *\n * Both exist, and the rowid is faster to look up. The GUID wins anyway because\n * **rowids are reused.** SQLite hands a deleted row's id to the next insert\n * unless the table is `AUTOINCREMENT`, and Messages deletes constantly — every\n * \"delete this conversation\" frees a block of them. A ref handed to a model in\n * one turn and used two turns later would then resolve to a DIFFERENT message,\n * with no error anywhere. That is the failure this project keeps designing\n * against: plausible, wrong, and silent.\n *\n * The GUID is also what Apple itself joins on — `associated_message_guid` for\n * reactions, `thread_originator_guid` for replies — so it is the identifier the\n * schema already treats as stable.\n *\n * ## Why `m1:` and `mc1:`\n *\n * `c1:` is Calendar's, `r1:` is Reminders', `k1:` is Contacts'. A ref that\n * decodes under two surfaces would be worse than one that decodes under none,\n * so each prefix is claimed once and the version digit keeps a future scheme\n * change additive.\n */\n\nexport const MESSAGE_REF_VERSION = \"m1\";\nexport const CHAT_REF_VERSION = \"mc1\";\n\n/**\n * A GUID is opaque and not always a UUID — measured shapes include\n * `iMessage;-;+15551234567` on chats, which carries semicolons and a phone\n * number. So the tail is greedy and nothing inside it is parsed.\n */\nconst MESSAGE_PATTERN = /^m1:(.+)$/;\nconst CHAT_PATTERN = /^mc1:(.+)$/;\n\nconst otherSurface = (raw: string): string => {\n if (raw.startsWith(\"c1:\")) return \" That one is a Calendar event ref.\";\n if (raw.startsWith(\"r1:\")) return \" That one is a Reminders ref.\";\n if (raw.startsWith(\"k1:\")) return \" That one is a Contacts ref.\";\n if (raw.startsWith(\"mc1:\")) return \" That one is a CHAT ref — this wants a message ref.\";\n if (raw.startsWith(\"m1:\")) return \" That one is a MESSAGE ref — this wants a chat ref.\";\n return \"\";\n};\n\nexport class InvalidMessageRefError extends AppleAutomationError {\n override readonly name = \"InvalidMessageRefError\";\n\n constructor(raw: string, want: \"message\" | \"chat\") {\n const shape = want === \"message\" ? '\"m1:<guid>\"' : '\"mc1:<guid>\"';\n super(\n `\"${raw}\" is not a ${want} ref. Refs come from apple_messages_* results and look like ` +\n `${shape} — they are opaque and must not be constructed by hand.${otherSurface(raw)}`,\n { ref: raw },\n );\n }\n}\n\nexport const encodeMessageRef = (guid: string): string => `${MESSAGE_REF_VERSION}:${guid}`;\nexport const encodeChatRef = (guid: string): string => `${CHAT_REF_VERSION}:${guid}`;\n\nexport const decodeMessageRef = (raw: string): string => {\n const m = MESSAGE_PATTERN.exec(raw.trim());\n if (!m?.[1]) throw new InvalidMessageRefError(raw, \"message\");\n return m[1];\n};\n\nexport const decodeChatRef = (raw: string): string => {\n const m = CHAT_PATTERN.exec(raw.trim());\n if (!m?.[1]) throw new InvalidMessageRefError(raw, \"chat\");\n return m[1];\n};\n","/**\n * NSArchiver `typedstream` reader — enough of it to pull the text out of a\n * Messages `attributedBody` blob.\n *\n * ## Why this has to exist\n *\n * `docs/messages.md` measured it: 97,092 of 97,414 messages carry a blob and\n * only 94,049 carry `text`. **The blob is the norm and `text` is the redundant\n * copy**, not the other way round. 3,043 messages — one in thirty-two — have an\n * empty `text` and content only in here, so a server that reads the column\n * returns nothing for them, silently and with no error to notice.\n *\n * The archive header is `04 0B streamtyped 81 E8 03`. Not `bplist00`, not gzip,\n * and not the protobuf that Notes turned out to hold, so none of the existing\n * decoders apply.\n *\n * ## The format, as far as this reader needs it\n *\n * A stream of tagged values. Integers are a single signed byte unless prefixed:\n *\n * 0x81 int16 follows (little-endian)\n * 0x82 int32 follows\n * 0x83 float or double follows\n * 0x84 START — a new class or object definition\n * 0x85 nil / empty\n * 0x86 END of the current object\n * >=0x92 a back-reference; index = byte - 0x92\n *\n * Strings arrive length-prefixed. Class names and object contents both use that\n * shape, which is why this walks structurally instead of pattern-matching: the\n * same bytes mean different things depending on where you are.\n *\n * ## Honest failure, never a guess\n *\n * The Notes decoder was documented backwards because a `LIMIT 1` sample landed\n * on an outlier, and this project has now been bitten three times by a heuristic\n * that produced a plausible wrong answer. So there is no \"longest printable run\"\n * fallback here. If the structure does not parse, `ok` is false and the caller\n * counts it — and `text` remains available as the answer for the 96.5% of rows\n * that have one.\n *\n * Pure and I/O-free.\n *\n * ## Two copies, deliberately\n *\n * `scripts/lib/typedstream.mjs` is the same reader, and the probe uses it to\n * measure this one against a real store. They are kept in step by hand, as\n * `packages/notes/src/client/protobuf.ts` and `scripts/lib/note-protobuf.mjs`\n * already are: a probe that imported from a package would stop being runnable\n * before that package exists, which is the wrong way round.\n */\n\nconst SIGNATURE = \"streamtyped\";\n\nconst TAG_I16 = 0x81;\nconst TAG_I32 = 0x82;\nconst TAG_DECIMAL = 0x83;\nconst TAG_START = 0x84;\nconst TAG_EMPTY = 0x85;\nconst TAG_END = 0x86;\n/** Anything at or above this is an index into the table of things already seen. */\nconst TAG_REFERENCE = 0x92;\n\n/** A blob larger than this is not a message; refuse rather than chew through it. */\nconst MAX_BLOB_BYTES = 4 * 1024 * 1024;\n/**\n * Structural guard for the walk BEFORE the text is reached — class chains and\n * type encodings, which are a few dozen tokens on every blob measured. Reaching\n * this means the stream is not shaped like an archived attributed string.\n */\nconst MAX_TOKENS = 20_000;\n\nconst utf8 = new TextDecoder(\"utf-8\", { fatal: true });\nconst latin1 = new TextDecoder(\"latin1\");\n\nclass Cursor {\n readonly buf: Uint8Array;\n pos: number;\n\n constructor(buf: Uint8Array) {\n this.buf = buf;\n this.pos = 0;\n }\n get done(): boolean {\n return this.pos >= this.buf.length;\n }\n get remaining(): number {\n return this.buf.length - this.pos;\n }\n byte(): number {\n return this.buf[this.pos++] as number;\n }\n peek(): number {\n return this.buf[this.pos] as number;\n }\n take(n: number): Uint8Array {\n const out = this.buf.subarray(this.pos, this.pos + n);\n this.pos += n;\n return out;\n }\n\n /**\n * A byte at an offset from the cursor. Every caller checks `remaining` first,\n * so the index is in range — this exists to say that once rather than to\n * scatter non-null assertions through the integer readers.\n */\n at(offset: number): number {\n return this.buf[this.pos + offset] as number;\n }\n}\n\n/**\n * A signed integer, which is also how every length in the stream is written.\n *\n * Returns null rather than throwing on a truncated read — a truncated blob is a\n * finding, not a crash.\n */\nconst readInt = (c: Cursor): number | null => {\n if (c.done) return null;\n const b = c.byte();\n if (b === TAG_I16) {\n if (c.remaining < 2) return null;\n const v = c.at(0) | (c.at(1) << 8);\n c.pos += 2;\n // Sign-extend: lengths are positive, but the same encoding carries indices.\n return v > 0x7fff ? v - 0x10000 : v;\n }\n if (b === TAG_I32) {\n if (c.remaining < 4) return null;\n const v = (c.at(0) | (c.at(1) << 8) | (c.at(2) << 16) | (c.at(3) << 24)) >>> 0;\n c.pos += 4;\n return v > 0x7fffffff ? v - 0x100000000 : v;\n }\n if (b === TAG_DECIMAL) {\n // Present in attribute values (font sizes). Skipped deliberately: nothing\n // this reader reports needs the number, and consuming it keeps the walk\n // aligned. Width is not knowable from the tag alone, so this is where an\n // unfamiliar blob is most likely to derail — and it will say so.\n return null;\n }\n return b > 0x7f ? b - 0x100 : b;\n};\n\n/** A length-prefixed string. `null` when the bytes are not valid UTF-8. */\nconst readString = (c: Cursor): string | null => {\n const len = readInt(c);\n if (len === null || len < 0 || len > c.remaining) return null;\n const bytes = c.take(len);\n try {\n return utf8.decode(bytes);\n } catch {\n return null;\n }\n};\n\n/**\n * A length-prefixed UTF-8 payload — the `+` type encoding's value.\n *\n * The length is in BYTES, which is the trap: a 22-character string of accents\n * and kana declares 36. Slicing by character count would desynchronise the walk\n * and corrupt everything after it.\n */\nconst readByteArray = (c: Cursor): string | null => {\n const len = readInt(c);\n if (len === null || len < 0 || len > c.remaining) return null;\n const bytes = c.take(len);\n try {\n return utf8.decode(bytes);\n } catch {\n return null;\n }\n};\n\n/**\n * Validate the archive header and position the cursor after it.\n *\n * The system-version integer that follows the signature is read and discarded —\n * it is 1000 on every blob measured, and pinning it would reject a future macOS\n * for no reason.\n */\nconst readHeader = (c: Cursor): string | null => {\n if (c.remaining < 16) return \"too short to be a typedstream\";\n const version = c.byte();\n const sigLen = c.byte();\n if (sigLen !== SIGNATURE.length) return `bad signature length ${sigLen}`;\n const sig = latin1.decode(c.take(sigLen));\n if (sig !== SIGNATURE) return `not a typedstream (signature ${JSON.stringify(sig)})`;\n readInt(c);\n return version === 4 ? null : `unexpected streamer version ${version}`;\n};\n\n/**\n * Walk the stream, dispatching on the type encoding.\n *\n * The layout, read off real bytes rather than inferred — `NSArchiver` still\n * ships on macOS 26, so `NSAttributedString(string: \"Hello, world\")` archived\n * through it produces exactly what Messages stores, with a plaintext nobody has\n * to guess at:\n *\n * 04 0b \"streamtyped\" 81 e8 03 header\n * 84 01 40 START, type \"@\" — an object\n * 84 84 84 12 \"NSAttributedString\" 00\n * 84 84 08 \"NSObject\" 00 85 92\n * 84 84 84 08 \"NSString\" 01 94\n * 84 01 2b 0c \"Hello, world\" START, type \"+\", then the bytes\n * 86 END\n *\n * So the content is whatever follows the type encoding `+`, and the length is in\n * BYTES of UTF-8 — 0x24 for a 22-character string of accents and kana. Past 127\n * it takes the 0x81 int16 form, measured at 0x81 90 01 for a 400-byte payload.\n *\n * Walking structurally rather than scanning for `84 01 2b` matters: 0x84 is a\n * valid UTF-8 continuation byte, so that sequence can occur INSIDE a message.\n * Consuming each payload by its declared length is what keeps the walk in sync\n * and makes a false positive impossible.\n */\ntype WalkResult = {\n strings: string[];\n classes: string[];\n firstPayloadEnd: number | null;\n error: string | null;\n};\n\nconst walk = (c: Cursor): WalkResult => {\n const strings: string[] = [];\n const classes: string[] = [];\n let firstPayloadEnd: number | null = null;\n let tokens = 0;\n\n while (!c.done) {\n if (++tokens > MAX_TOKENS) {\n return { strings, classes, firstPayloadEnd, error: \"token limit exceeded\" };\n }\n const b = c.peek();\n\n if (b !== TAG_START) {\n // nil, END, a back-reference, a version byte or a scalar. None of them\n // carries text, and all are one byte wide.\n c.pos += 1;\n continue;\n }\n\n c.pos += 1;\n // A class chain: START START START <name>. Nothing to read at this level.\n if (c.peek() === TAG_START) continue;\n\n const before = c.pos;\n const encoding = readString(c);\n if (encoding === null) {\n c.pos = before;\n continue;\n }\n\n if (encoding === \"+\") {\n const value = readByteArray(c);\n if (value === null) {\n return { strings, classes, firstPayloadEnd, error: \"truncated byte array\" };\n }\n strings.push(value);\n firstPayloadEnd = c.pos;\n // STOP HERE. The first `+` payload is the backing store, and nothing past\n // it is decoded by design — so walking on can only fail.\n //\n // It did. Measured over 97,094 real blobs, this reader failed exactly\n // twice, both \"token limit exceeded\", and both had ALREADY read their text\n // (936 and 2,095 bytes) before exhausting the walk on attribute runs. Two\n // messages were being thrown away for work whose result was discarded.\n // Whether anything follows is a position comparison, not a walk.\n return { strings, classes, firstPayloadEnd, error: null };\n }\n\n // A class name rather than a type encoding. Recorded because which classes\n // appear is how the open question in docs/messages.md gets answered.\n if (/^[A-Za-z_][A-Za-z0-9_.]*$/.test(encoding) && encoding.length > 2) classes.push(encoding);\n }\n return { strings, classes, firstPayloadEnd, error: null };\n};\n\n/**\n * The message text.\n *\n * ## Text only, and that is now a measured decision rather than a shortcut\n *\n * `docs/messages.md` left this open: *\"whether `attributedBody` carries\n * formatting that matters, or is only ever a redundant copy of `text` plus\n * attachment placeholders. This decides whether the decoder must preserve\n * structure or merely extract a string.\"*\n *\n * It is the second. Two things settle it:\n *\n * 1. **Attribute values are back-REFERENCED, not inline.** Archiving an\n * attachment-shaped string through the real `NSArchiver` produces\n * `92 84 98 98 22 \"__kIMFileTransferGUIDAttributeName\" 86` — where `98` is an\n * index into the object table rather than a type encoding. Resolving those\n * means reconstructing the whole table, which is materially more code and\n * more ways to be silently wrong.\n * 2. **Nothing needs it.** The one attribute worth having is the file-transfer\n * GUID, and attachments are already reachable relationally — 17,529 rows in\n * their own table, joined through `message_attachment_join`. The blob's copy\n * is redundant, and the placeholder character itself (U+FFFC) survives in the\n * text, so the position is not lost either.\n *\n * So this returns the backing string and says plainly that it stopped there.\n * `hasAttributes` reports whether anything followed it, which is what a future\n * decision to go further would be based on — a count, not a guess.\n */\nexport type DecodedBody =\n | {\n ok: true;\n text: string;\n classes: string[];\n /** Bytes remained after the backing store — attribute runs this does not decode. */\n hasAttributes: boolean;\n error: null;\n }\n | { ok: false; text?: undefined; classes?: string[]; hasAttributes?: undefined; error: string };\n\nexport const decodeAttributedBody = (buffer: Uint8Array | null | undefined): DecodedBody => {\n if (!buffer || buffer.length === 0) return { ok: false, error: \"empty blob\" };\n if (buffer.length > MAX_BLOB_BYTES) return { ok: false, error: \"blob too large\" };\n\n const c = new Cursor(buffer);\n const headerError = readHeader(c);\n if (headerError) return { ok: false, error: headerError };\n\n const { strings, classes, error, firstPayloadEnd } = walk(c);\n if (error) return { ok: false, error, classes };\n if (strings.length === 0) return { ok: false, error: \"no content string found\", classes };\n\n return {\n ok: true,\n text: strings[0] as string,\n classes,\n /** Bytes remained after the backing store — attribute runs this does not decode. */\n hasAttributes: firstPayloadEnd !== null && firstPayloadEnd < buffer.length - 2,\n error: null,\n };\n};\n\n/**\n * A redacted structural outline of one blob.\n *\n * For measuring the format on a machine that has the data, without the report\n * ever carrying a message. Every string is reduced to its length and its\n * character class; only Apple's own constants survive as themselves.\n */\nexport const outline = (buffer: Uint8Array, maxTokens = 60): string[] => {\n const c = new Cursor(buffer);\n const headerError = readHeader(c);\n if (headerError) return [`ERROR ${headerError}`];\n\n const out: string[] = [];\n let tokens = 0;\n while (!c.done && tokens < maxTokens) {\n tokens += 1;\n const b = c.peek();\n if (b === TAG_START) {\n c.pos += 1;\n out.push(\"START\");\n continue;\n }\n if (b === TAG_EMPTY) {\n c.pos += 1;\n out.push(\"nil\");\n continue;\n }\n if (b === TAG_END) {\n c.pos += 1;\n out.push(\"END\");\n continue;\n }\n if (b >= TAG_REFERENCE) {\n c.pos += 1;\n out.push(`ref#${b - TAG_REFERENCE}`);\n continue;\n }\n const before = c.pos;\n const s = readString(c);\n if (s === null) {\n c.pos = before + 1;\n out.push(`byte 0x${b.toString(16).padStart(2, \"0\")}`);\n continue;\n }\n // Apple constants and type encodings are safe to print. Anything else is\n // somebody's message, and only its shape leaves the process.\n if (/^(NS|IM|__kIM|CF)[A-Za-z0-9_]*$/.test(s) || s.length <= 2) out.push(`\"${s}\"`);\n else out.push(`<str len=${s.length} ${/^[\\x20-\\x7e]*$/.test(s) ? \"ascii\" : \"unicode\"}>`);\n }\n if (!c.done) out.push(\"…\");\n return out;\n};\n\nexport const TYPEDSTREAM_SIGNATURE = SIGNATURE;\n","import type { DatabaseSync } from \"node:sqlite\";\n\nimport {\n columnsOf,\n escapeLike,\n fingerprintSchema,\n openReadOnly,\n SchemaDriftError,\n type Logger,\n type ReadOnlyMode,\n} from \"@mgcrea/mcp-apple-core\";\n\nimport { appleSecondsSql } from \"./dates.js\";\nimport { decodeAttributedBody } from \"./typedstream.js\";\n\n/**\n * Messages' file lane — the only lane there is.\n *\n * `docs/messages.md` measured every Apple Events read attempt failing, so unlike\n * every other surface here there is nothing to fall back to. That shapes two\n * things: `SchemaDriftError` is the only fatal condition, and a missing store is\n * reported as a reason rather than as an empty list.\n *\n * ## Three measurements this file is built on\n *\n * **The blob is the norm, and increasingly the only thing there.** 97,094 rows\n * carry `attributedBody` and 94,043 carry `text`; 3,051 have only the blob. That\n * 3.1% is an average over a decade, and it hides the real shape: measured\n * through this server, 2016-2025 are ~99% plain `text` and everything from\n * MARCH 2026 ONWARD is blob-only. A reader that selects `text` would today\n * report that the conversation stopped in February. Every read here goes through\n * `#body()`, which prefers the column and falls back to the decoder — validated\n * at **100.000% agreement across all 94,043 rows** where both exist, with zero failures\n * across all 97,094.\n *\n * **Dates do not fit in a JavaScript number.** See `dates.ts`. Every date column\n * is projected through `appleSecondsSql`, never selected raw.\n *\n * **Reactions are messages.** 2,788 rows carry `associated_message_type != 0`,\n * and a reader that does not filter them renders `Liked \"see you at 8\"` as if\n * somebody had typed it. Filtering is table stakes, not an enhancement.\n */\n\n/** Tables the lane cannot work without. */\nconst REQUIRED = [\"message\", \"chat\", \"handle\"] as const;\n\nconst PROBED_FINGERPRINT = \"87b01c58a631\";\nconst PROBED_MACOS = \"26.6\";\n\n/**\n * A tapback rather than something somebody typed.\n *\n * 2000–2005 add a reaction, 3000–3005 remove one; 0 is an ordinary message.\n * The ranges are Apple's and are not documented anywhere public, so the code\n * treats \"not zero\" as the test and only uses the ranges to LABEL — an\n * unrecognised value is still excluded from the conversation, which is the safe\n * direction.\n */\nconst REACTION_LABELS: Record<number, string> = {\n 2000: \"loved\",\n 2001: \"liked\",\n 2002: \"disliked\",\n 2003: \"laughed\",\n 2004: \"emphasized\",\n 2005: \"questioned\",\n};\n\nexport const reactionLabel = (type: number): string => {\n if (REACTION_LABELS[type]) return REACTION_LABELS[type] as string;\n if (type >= 3000 && type <= 3005) return `removed ${REACTION_LABELS[type - 1000] ?? \"reaction\"}`;\n return `reaction ${type}`;\n};\n\nexport type StoreCapabilities = {\n fingerprint: string;\n messageColumns: Set<string>;\n chatColumns: Set<string>;\n handleColumns: Set<string>;\n attachmentColumns: Set<string>;\n hasAttachments: boolean;\n hasReactions: boolean;\n hasThreads: boolean;\n hasEdits: boolean;\n};\n\nexport type MessageRow = {\n guid: string;\n chatGuid: string | null;\n chatName: string | null;\n handle: string | null;\n service: string | null;\n isFromMe: boolean;\n /** Apple-seconds. Render with `renderInstant`. */\n sentAt: number | null;\n readAt: number | null;\n deliveredAt: number | null;\n editedAt: number | null;\n text: string | null;\n /** Which lane answered — the column, or the decoder. */\n textSource: \"column\" | \"decoded\" | \"none\";\n subject: string | null;\n isRead: boolean;\n isSent: boolean;\n isDelivered: boolean;\n hasAttachments: boolean;\n /** Set when this row is a tapback on another message rather than a message. */\n reactionType: number | null;\n reactionTarget: string | null;\n /** Set when this message is a reply in a thread. */\n threadOriginator: string | null;\n /** Non-zero for a group event — someone joined, left, or renamed the chat. */\n itemType: number | null;\n};\n\nexport type ChatRow = {\n guid: string;\n identifier: string | null;\n displayName: string | null;\n /** 43 is a group chat, 45 is one-to-one. Reported raw beside the boolean. */\n style: number | null;\n isGroup: boolean;\n service: string | null;\n participants: string[];\n messages: number;\n lastMessageAt: number | null;\n};\n\nexport type RangeQuery = {\n chatGuid?: string | undefined;\n fromApple?: number | undefined;\n toApple?: number | undefined;\n includeReactions?: boolean | undefined;\n limit: number;\n};\n\nconst num = (v: unknown): number | null => (typeof v === \"number\" ? v : null);\nconst text = (v: unknown): string | null => (typeof v === \"string\" && v.length > 0 ? v : null);\nconst bool = (v: unknown): boolean => v === 1 || v === true;\n\nexport class MessagesStore {\n readonly db: DatabaseSync;\n readonly mode: string;\n readonly caps: StoreCapabilities;\n\n constructor(db: DatabaseSync, mode: string, caps: StoreCapabilities) {\n this.db = db;\n this.mode = mode;\n this.caps = caps;\n }\n\n /** Project a column, or a typed NULL when this store does not have it. */\n #col(present: Set<string>, table: string, name: string, alias = name): string {\n return present.has(name) ? `${table}.\"${name}\" AS ${alias}` : `NULL AS ${alias}`;\n }\n\n #date(present: Set<string>, table: string, name: string, alias: string): string {\n return present.has(name)\n ? `${appleSecondsSql(`${table}.\"${name}\"`)} AS ${alias}`\n : `NULL AS ${alias}`;\n }\n\n /**\n * The text of a message, from whichever source has it.\n *\n * The column first because it is free, then the decoder. `textSource` is on\n * every result so a caller can see which answered — and so the 3.1% that only\n * the decoder can reach are visible rather than indistinguishable from empty.\n */\n #body(row: Record<string, unknown>): { text: string | null; source: MessageRow[\"textSource\"] } {\n const column = text(row.text);\n if (column !== null) return { text: column, source: \"column\" };\n const blob = row.attributedBody;\n if (blob instanceof Uint8Array && blob.length > 0) {\n const decoded = decodeAttributedBody(blob);\n if (decoded.ok && decoded.text.length > 0) return { text: decoded.text, source: \"decoded\" };\n }\n return { text: null, source: \"none\" };\n }\n\n #messageColumns(): string {\n const m = this.caps.messageColumns;\n return [\n `m.\"guid\" AS guid`,\n `m.\"text\" AS text`,\n m.has(\"attributedBody\") ? `m.\"attributedBody\" AS attributedBody` : `NULL AS attributedBody`,\n this.#col(m, \"m\", \"subject\"),\n this.#col(m, \"m\", \"service\"),\n this.#col(m, \"m\", \"is_from_me\", \"isFromMe\"),\n this.#col(m, \"m\", \"is_read\", \"isRead\"),\n this.#col(m, \"m\", \"is_sent\", \"isSent\"),\n this.#col(m, \"m\", \"is_delivered\", \"isDelivered\"),\n this.#col(m, \"m\", \"cache_has_attachments\", \"hasAttachments\"),\n this.#col(m, \"m\", \"associated_message_type\", \"reactionType\"),\n this.#col(m, \"m\", \"associated_message_guid\", \"reactionTarget\"),\n this.#col(m, \"m\", \"thread_originator_guid\", \"threadOriginator\"),\n this.#col(m, \"m\", \"item_type\", \"itemType\"),\n this.#date(m, \"m\", \"date\", \"sentAt\"),\n this.#date(m, \"m\", \"date_read\", \"readAt\"),\n this.#date(m, \"m\", \"date_delivered\", \"deliveredAt\"),\n this.#date(m, \"m\", \"date_edited\", \"editedAt\"),\n `h.\"id\" AS handle`,\n `c.\"guid\" AS chatGuid`,\n this.#col(this.caps.chatColumns, \"c\", \"display_name\", \"chatName\"),\n ].join(\",\\n \");\n }\n\n #joins(): string {\n return `LEFT JOIN \"handle\" h ON h.\"ROWID\" = m.\"handle_id\"\n LEFT JOIN \"chat_message_join\" cmj ON cmj.\"message_id\" = m.\"ROWID\"\n LEFT JOIN \"chat\" c ON c.\"ROWID\" = cmj.\"chat_id\"`;\n }\n\n #toRow(r: Record<string, unknown>): MessageRow {\n const body = this.#body(r);\n return {\n guid: String(r.guid),\n chatGuid: text(r.chatGuid),\n chatName: text(r.chatName),\n handle: text(r.handle),\n service: text(r.service),\n isFromMe: bool(r.isFromMe),\n sentAt: num(r.sentAt),\n readAt: num(r.readAt),\n deliveredAt: num(r.deliveredAt),\n editedAt: num(r.editedAt),\n text: body.text,\n textSource: body.source,\n subject: text(r.subject),\n isRead: bool(r.isRead),\n isSent: bool(r.isSent),\n isDelivered: bool(r.isDelivered),\n hasAttachments: bool(r.hasAttachments),\n reactionType: num(r.reactionType) || null,\n reactionTarget: text(r.reactionTarget),\n threadOriginator: text(r.threadOriginator),\n itemType: num(r.itemType),\n };\n }\n\n /**\n * A window of messages, newest first.\n *\n * Reactions are excluded by default. They are rows in this table like any\n * other, and 2,788 of them would otherwise appear as messages reading\n * `Liked \"see you at 8\"` — which is not something anybody typed.\n */\n range(q: RangeQuery): MessageRow[] {\n const m = this.caps.messageColumns;\n const where: string[] = [];\n const params: unknown[] = [];\n\n if (q.chatGuid) {\n where.push(`c.\"guid\" = ?`);\n params.push(q.chatGuid);\n }\n if (q.fromApple !== undefined && m.has(\"date\")) {\n where.push(`${appleSecondsSql('m.\"date\"')} >= ?`);\n params.push(q.fromApple);\n }\n if (q.toApple !== undefined && m.has(\"date\")) {\n where.push(`${appleSecondsSql('m.\"date\"')} < ?`);\n params.push(q.toApple);\n }\n if (!q.includeReactions && m.has(\"associated_message_type\")) {\n where.push(`(m.\"associated_message_type\" IS NULL OR m.\"associated_message_type\" = 0)`);\n }\n\n const sql = `\n SELECT ${this.#messageColumns()}\n FROM \"message\" m\n ${this.#joins()}\n ${where.length ? `WHERE ${where.join(\"\\n AND \")}` : \"\"}\n ORDER BY m.\"date\" DESC\n LIMIT ${Math.max(1, Math.trunc(q.limit))}`;\n const rows = this.db.prepare(sql).all(...(params as never[])) as Record<string, unknown>[];\n return rows.map((r) => this.#toRow(r));\n }\n\n /**\n * Text search, in two passes — and the second one is the point.\n *\n * Pass 1 is `LIKE` on the column: measured at **16 ms over 97,416 rows** with\n * 27 existing indexes, so there is no index-vs-scan tradeoff to litigate and\n * no FTS table to build.\n *\n * Pass 2 covers what pass 1 structurally cannot. 3,051 messages have an empty\n * `text` and content only in `attributedBody`, and no amount of SQL reaches\n * inside a blob. Decoding them costs about **6 ms** — the decoder runs at\n * 2 ms per thousand — so completeness here is nearly free, and a search that\n * silently omitted one message in thirty-two would be the worst kind of wrong.\n */\n search(query: string, limit: number, includeReactions = false): MessageRow[] {\n const m = this.caps.messageColumns;\n const needle = `%${escapeLike(query)}%`;\n const reactionFilter =\n !includeReactions && m.has(\"associated_message_type\")\n ? `AND (m.\"associated_message_type\" IS NULL OR m.\"associated_message_type\" = 0)`\n : \"\";\n const cap = Math.max(1, Math.trunc(limit));\n\n const fromColumn = this.db\n .prepare(\n `SELECT ${this.#messageColumns()}\n FROM \"message\" m\n ${this.#joins()}\n WHERE m.\"text\" LIKE ? ESCAPE '\\\\' ${reactionFilter}\n ORDER BY m.\"date\" DESC\n LIMIT ${cap}`,\n )\n .all(needle) as Record<string, unknown>[];\n\n const results = fromColumn.map((r) => this.#toRow(r));\n if (results.length >= cap || !m.has(\"attributedBody\")) return results;\n\n // Only the blob-only rows: 3,051 of 97,416 on the probed store.\n const blobOnly = this.db\n .prepare(\n `SELECT ${this.#messageColumns()}\n FROM \"message\" m\n ${this.#joins()}\n WHERE (m.\"text\" IS NULL OR m.\"text\" = '')\n AND m.\"attributedBody\" IS NOT NULL ${reactionFilter}\n ORDER BY m.\"date\" DESC`,\n )\n .all() as Record<string, unknown>[];\n\n const lowered = query.toLowerCase();\n for (const raw of blobOnly) {\n if (results.length >= cap) break;\n const row = this.#toRow(raw);\n if (row.text && row.text.toLowerCase().includes(lowered)) results.push(row);\n }\n return results.slice(0, cap);\n }\n\n byGuid(guid: string): MessageRow | null {\n const rows = this.db\n .prepare(\n `SELECT ${this.#messageColumns()}\n FROM \"message\" m\n ${this.#joins()}\n WHERE m.\"guid\" = ?\n LIMIT 1`,\n )\n .all(guid) as Record<string, unknown>[];\n return rows[0] ? this.#toRow(rows[0]) : null;\n }\n\n /** Tapbacks aimed at one message. */\n reactionsFor(guid: string): { type: number; label: string; handle: string | null }[] {\n if (!this.caps.hasReactions) return [];\n // Apple prefixes the target with `p:0/` on some rows, so match the tail.\n const rows = this.db\n .prepare(\n `SELECT m.\"associated_message_type\" AS type, h.\"id\" AS handle\n FROM \"message\" m\n LEFT JOIN \"handle\" h ON h.\"ROWID\" = m.\"handle_id\"\n WHERE m.\"associated_message_guid\" LIKE ? ESCAPE '\\\\'\n AND m.\"associated_message_type\" > 0\n ORDER BY m.\"date\" ASC`,\n )\n .all(`%${escapeLike(guid)}`) as Record<string, unknown>[];\n return rows.flatMap((r) => {\n const type = num(r.type);\n if (type === null) return [];\n return [{ type, label: reactionLabel(type), handle: text(r.handle) }];\n });\n }\n\n /**\n * The attachments on one message.\n *\n * `id` is `attachment.guid`, for the reason `ref.ts` gives at length about\n * messages: the ROWID is faster and gets REUSED. Every \"delete this\n * conversation\" frees a block of attachment ids for the next insert, so a\n * caller that listed attachments in one turn and saved one two turns later\n * would write out a different file, silently. The guid is `UNIQUE NOT NULL`\n * in the shipped schema and is what Apple's own sync joins on.\n *\n * `path` is the raw `filename` column, reported so a caller can see WHERE the\n * bytes are before asking for them — it is frequently `~`-prefixed, and it is\n * empty for an attachment iCloud has offloaded.\n */\n attachmentsFor(guid: string): {\n id: string | null;\n path: string | null;\n mimeType: string | null;\n transferName: string | null;\n bytes: number | null;\n isSticker: boolean;\n }[] {\n if (!this.caps.hasAttachments) return [];\n const a = this.caps.attachmentColumns;\n const rows = this.db\n .prepare(\n `SELECT ${this.#col(a, \"a\", \"guid\", \"id\")},\n ${this.#col(a, \"a\", \"filename\", \"path\")},\n ${this.#col(a, \"a\", \"mime_type\", \"mimeType\")},\n ${this.#col(a, \"a\", \"transfer_name\", \"transferName\")},\n ${this.#col(a, \"a\", \"total_bytes\", \"bytes\")},\n ${this.#col(a, \"a\", \"is_sticker\", \"isSticker\")}\n FROM \"attachment\" a\n JOIN \"message_attachment_join\" maj ON maj.\"attachment_id\" = a.\"ROWID\"\n JOIN \"message\" m ON m.\"ROWID\" = maj.\"message_id\"\n WHERE m.\"guid\" = ?`,\n )\n .all(guid) as Record<string, unknown>[];\n return rows.map((r) => ({\n id: text(r.id),\n path: text(r.path),\n mimeType: text(r.mimeType),\n transferName: text(r.transferName),\n bytes: num(r.bytes),\n isSticker: num(r.isSticker) === 1,\n }));\n }\n\n /** One attachment by its guid, wherever it hangs. Null when it is gone. */\n attachmentById(id: string): {\n id: string | null;\n path: string | null;\n mimeType: string | null;\n transferName: string | null;\n bytes: number | null;\n } | null {\n if (!this.caps.hasAttachments) return null;\n const a = this.caps.attachmentColumns;\n if (!a.has(\"guid\")) return null;\n const row = this.db\n .prepare(\n `SELECT ${this.#col(a, \"a\", \"guid\", \"id\")},\n ${this.#col(a, \"a\", \"filename\", \"path\")},\n ${this.#col(a, \"a\", \"mime_type\", \"mimeType\")},\n ${this.#col(a, \"a\", \"transfer_name\", \"transferName\")},\n ${this.#col(a, \"a\", \"total_bytes\", \"bytes\")}\n FROM \"attachment\" a WHERE a.\"guid\" = ? LIMIT 1`,\n )\n .get(id) as Record<string, unknown> | undefined;\n if (!row) return null;\n return {\n id: text(row.id),\n path: text(row.path),\n mimeType: text(row.mimeType),\n transferName: text(row.transferName),\n bytes: num(row.bytes),\n };\n }\n\n chats(limit: number): ChatRow[] {\n return this.#chats(\"\", [], limit);\n }\n\n /** One chat, by the guid a ref carries. Null when it has been deleted. */\n chatByGuid(guid: string): ChatRow | null {\n return this.#chats(`WHERE c.\"guid\" = ?`, [guid], 1)[0] ?? null;\n }\n\n /**\n * The chats that already exist with a set of handles, newest first.\n *\n * This is what makes a send addressable at all. Messages will not enumerate\n * participants for a script, so the write lane cannot look a person up — but\n * it can address a chat by guid, and the guid lives here. The read lane\n * choosing the target for the write lane is the whole arrangement; see\n * `client/jxa/core.ts`.\n *\n * Handles are matched as given. Suffix matching happens a layer up in\n * `client/messages.ts`, where `packages/contacts`' measured `suffixKey` is\n * available and the candidate list is the store's own 1,075 handles.\n */\n chatsForHandles(handles: readonly string[], limit = 10): ChatRow[] {\n if (!handles.length) return [];\n const marks = handles.map(() => \"?\").join(\", \");\n return this.#chats(\n `WHERE c.\"ROWID\" IN (\n SELECT chj.\"chat_id\"\n FROM \"chat_handle_join\" chj\n JOIN \"handle\" h2 ON h2.\"ROWID\" = chj.\"handle_id\"\n WHERE h2.\"id\" IN (${marks}))`,\n [...handles],\n limit,\n );\n }\n\n /**\n * Outgoing messages in a set of chats since an instant — the send's receipt.\n *\n * `docs/messages.md` recorded that Apple Events returns no chat identifier, so\n * a send \"cannot report what it wrote by id\". That is true of the write lane\n * alone and false of the pair: the row Messages writes for an outgoing message\n * is an ordinary row in this table, and a narrow window plus the target chat\n * identifies it. Matching on text as well would be wrong — two identical\n * messages a minute apart are a normal thing to send — so the caller passes a\n * `sinceApple` taken immediately BEFORE the send and takes the oldest match.\n */\n sentSince(chatGuids: readonly string[], sinceApple: number, limit = 10): MessageRow[] {\n const m = this.caps.messageColumns;\n if (!chatGuids.length || !m.has(\"date\")) return [];\n const marks = chatGuids.map(() => \"?\").join(\", \");\n const fromMe = m.has(\"is_from_me\") ? `AND m.\"is_from_me\" = 1` : \"\";\n const rows = this.db\n .prepare(\n `SELECT ${this.#messageColumns()}\n FROM \"message\" m\n ${this.#joins()}\n WHERE c.\"guid\" IN (${marks})\n AND ${appleSecondsSql('m.\"date\"')} >= ?\n ${fromMe}\n ORDER BY m.\"date\" ASC\n LIMIT ${Math.max(1, Math.trunc(limit))}`,\n )\n .all(...([...chatGuids, sinceApple] as never[])) as Record<string, unknown>[];\n return rows.map((r) => this.#toRow(r));\n }\n\n #chats(where: string, params: readonly unknown[], limit: number): ChatRow[] {\n const c = this.caps.chatColumns;\n const rows = this.db\n .prepare(\n `SELECT c.\"ROWID\" AS rowid,\n c.\"guid\" AS guid,\n ${this.#col(c, \"c\", \"chat_identifier\", \"identifier\")},\n ${this.#col(c, \"c\", \"display_name\", \"displayName\")},\n ${this.#col(c, \"c\", \"style\")},\n ${this.#col(c, \"c\", \"service_name\", \"service\")},\n COUNT(cmj.\"message_id\") AS messages,\n ${appleSecondsSql('MAX(m.\"date\")')} AS lastMessageAt\n FROM \"chat\" c\n LEFT JOIN \"chat_message_join\" cmj ON cmj.\"chat_id\" = c.\"ROWID\"\n LEFT JOIN \"message\" m ON m.\"ROWID\" = cmj.\"message_id\"\n ${where}\n GROUP BY c.\"ROWID\"\n ORDER BY MAX(m.\"date\") DESC\n LIMIT ${Math.max(1, Math.trunc(limit))}`,\n )\n .all(...(params as never[])) as Record<string, unknown>[];\n\n const participants = this.#participants(rows.map((r) => Number(r.rowid)));\n return rows.map((r) => {\n const style = num(r.style);\n return {\n guid: String(r.guid),\n identifier: text(r.identifier),\n displayName: text(r.displayName),\n style,\n // 43 is a group chat and 45 is one-to-one, measured. The participant\n // count is the check: a \"one-to-one\" with four people is a drift signal.\n isGroup: style === 43 || (participants.get(Number(r.rowid))?.length ?? 0) > 1,\n service: text(r.service),\n participants: participants.get(Number(r.rowid)) ?? [],\n messages: Number(r.messages ?? 0),\n lastMessageAt: num(r.lastMessageAt),\n };\n });\n }\n\n #participants(chatRowIds: readonly number[]): Map<number, string[]> {\n const out = new Map<number, string[]>();\n if (!chatRowIds.length) return out;\n const marks = chatRowIds.map(() => \"?\").join(\", \");\n const rows = this.db\n .prepare(\n `SELECT chj.\"chat_id\" AS chatId, h.\"id\" AS handle\n FROM \"chat_handle_join\" chj\n JOIN \"handle\" h ON h.\"ROWID\" = chj.\"handle_id\"\n WHERE chj.\"chat_id\" IN (${marks})`,\n )\n .all(...(chatRowIds as never[])) as Record<string, unknown>[];\n for (const r of rows) {\n const id = Number(r.chatId);\n const handle = text(r.handle);\n if (!handle) continue;\n const bucket = out.get(id);\n if (bucket) bucket.push(handle);\n else out.set(id, [handle]);\n }\n return out;\n }\n\n /** Every distinct handle in the store, for a bulk resolve. */\n handles(): string[] {\n const rows = this.db\n .prepare(`SELECT DISTINCT \"id\" AS id FROM \"handle\" WHERE \"id\" IS NOT NULL AND \"id\" <> ''`)\n .all() as Record<string, unknown>[];\n return rows.flatMap((r) => {\n const h = text(r.id);\n return h ? [h] : [];\n });\n }\n\n counts(): { messages: number; chats: number; handles: number; attachments: number } {\n const one = (sql: string): number => {\n try {\n return Number((this.db.prepare(sql).get() as { c: number }).c ?? 0);\n } catch {\n return 0;\n }\n };\n return {\n messages: one(`SELECT COUNT(*) AS c FROM \"message\"`),\n chats: one(`SELECT COUNT(*) AS c FROM \"chat\"`),\n handles: one(`SELECT COUNT(*) AS c FROM \"handle\"`),\n attachments: this.caps.hasAttachments ? one(`SELECT COUNT(*) AS c FROM \"attachment\"`) : 0,\n };\n }\n\n close(): void {\n try {\n this.db.close();\n } catch {\n // Closing a database that already failed is not worth reporting.\n }\n }\n}\n\nexport const introspect = (db: DatabaseSync): StoreCapabilities => {\n const messageColumns = new Set(columnsOf(db, \"message\"));\n const chatColumns = new Set(columnsOf(db, \"chat\"));\n const handleColumns = new Set(columnsOf(db, \"handle\"));\n\n for (const t of REQUIRED) {\n const cols = t === \"message\" ? messageColumns : t === \"chat\" ? chatColumns : handleColumns;\n if (cols.size === 0) {\n throw new SchemaDriftError(\n `This Messages store has no ${t} table. It was probed on macOS ${PROBED_MACOS} with ` +\n `schema fingerprint ${PROBED_FINGERPRINT} (a PROBE fingerprint — compare it against ` +\n `another probe run, not against the one diagnostics reports); re-run ` +\n `\\`pnpm probe:messages\\` to see what changed.`,\n );\n }\n }\n\n const attachmentColumns = new Set(columnsOf(db, \"attachment\"));\n return {\n fingerprint: fingerprintSchema(db),\n messageColumns,\n chatColumns,\n handleColumns,\n attachmentColumns,\n hasAttachments:\n attachmentColumns.size > 0 && columnsOf(db, \"message_attachment_join\").length > 0,\n hasReactions: messageColumns.has(\"associated_message_type\"),\n hasThreads: messageColumns.has(\"thread_originator_guid\"),\n hasEdits: messageColumns.has(\"date_edited\"),\n };\n};\n\nexport const openStore = (\n path: string | null,\n mode: ReadOnlyMode,\n logger?: Logger,\n): MessagesStore | null => {\n if (!path) return null;\n const {\n db,\n mode: used,\n validated,\n } = openReadOnly<StoreCapabilities>(path, mode, {\n label: \"Messages store\",\n envVar: \"APPLE_MESSAGES_INDEX_MODE\",\n validate: introspect,\n fatal: (err) => err instanceof SchemaDriftError,\n onFallback: () =>\n logger?.debug?.(\n \"opened the Messages store with immutable=1, which skips the write-ahead log — recent \" +\n \"messages may be missing until Messages checkpoints. The measured WAL was ~0.5 MB.\",\n ),\n });\n return new MessagesStore(db, used, validated);\n};\n","import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { basename, join, resolve as resolvePath, sep } from \"node:path\";\n\nimport {\n AppleContactsClient,\n emailKey,\n handleKind,\n loadConfig as loadContactsConfig,\n suffixKey,\n type ResolvedHandle,\n} from \"@mgcrea/mcp-apple-contacts\";\nimport {\n createOsascriptRunner,\n IndexUnavailableError,\n withBusyRetry,\n type Logger,\n type OsascriptRunner,\n} from \"@mgcrea/mcp-apple-core\";\n\nimport type { Config } from \"../config.js\";\nimport { renderInstant, toAppleSeconds } from \"./dates.js\";\nimport {\n ChatNotFoundError,\n MESSAGES_SURFACE,\n MessagesUnavailableError,\n PreconditionError,\n SendFailedError,\n SendTargetNotFoundError,\n} from \"./errors.js\";\nimport { SEND_MESSAGE } from \"./jxa/write.js\";\nimport { locateStore, type LocateResult } from \"./locate.js\";\nimport { decodeChatRef, encodeChatRef, encodeMessageRef } from \"./ref.js\";\nimport { openStore, type ChatRow, type MessageRow, type MessagesStore } from \"./store.js\";\n\n/**\n * Messages' one lane, plus the resolver.\n *\n * ## Why this depends on another surface\n *\n * `chat.db` records a correspondent as `+15551234567` and nothing else, so a\n * server built on it alone answers *\"+15551234567 said …\"* — complete, and\n * useless. `packages/contacts` exists because of that, and this is the consumer\n * it was built for. It is a workspace import rather than an MCP call: reaching a\n * function in the same repo through a protocol would be absurd.\n *\n * `docs/contacts.md` measured what to expect, and the numbers set the contract:\n *\n * | denominator | resolved |\n * | ----------------------------- | -------- |\n * | every handle ever seen | 27.6% |\n * | messages in the last year | 97.6% |\n * | the 25 busiest correspondents | 84% |\n *\n * So **`unknown` is a normal outcome, not an error.** About one in six of even\n * the busiest correspondents does not resolve, and a caller that treats that as\n * a failure will be wrong several times on any real inbox. Every rendered\n * correspondent therefore carries both the resolved name and the raw handle.\n *\n * ## Degrading\n *\n * If Contacts cannot be read — its own TCC grant, separate from Full Disk\n * Access, and one the user may simply not have answered — resolution is skipped\n * and handles are returned raw. That is a capability downgrade, reported through\n * `diagnostics`, never a throw: a Messages server with no names is still a\n * Messages server.\n */\n\nexport type CreateClientOptions = {\n config: Config;\n logger?: Logger;\n /** Injected by tests. */\n home?: string;\n /** Injected by tests, so no test reaches the developer's real address book. */\n contacts?: AppleContactsClient | null;\n /** Injected by tests, so no test sends a real message to a real person. */\n osascript?: OsascriptRunner;\n};\n\n/**\n * What a send actually knows, which is less than a caller might assume.\n *\n * `sent` means Messages accepted the command without throwing. `delivered` is\n * not a field here at all, because nothing in either lane reports it at send\n * time. What closes the gap is `message`: the row the file lane found afterwards\n * — a real ref, usable with `apple_messages_get_message` like any other.\n */\nexport type SendResult = {\n sent: true;\n /** Which rung of the ladder in `client/jxa/core.ts` answered. */\n strategy: string;\n targetKind: string;\n /** The chat the file lane chose, when it could choose one. */\n chatRef: string | null;\n chat: string | null;\n to: Correspondent | null;\n /** Whether Messages had to be launched. A launch is visible to the user. */\n launched: boolean;\n /** `matched` | `pending` | `unavailable` — see `#reconcile`. */\n reconciliation: string;\n message: RenderedMessage | null;\n note?: string;\n};\n\nexport type Correspondent = {\n /** Always present. The raw `handle.id` from the store. */\n handle: string | null;\n /** Null when unresolved, which is normal — see above. */\n name: string | null;\n /** `resolved` | `unknown` | `ambiguous` | `shortcode` | `unavailable`. */\n resolution: string;\n};\n\nexport type RenderedMessage = {\n ref: string;\n chatRef: string | null;\n chat: string | null;\n from: Correspondent;\n fromMe: boolean;\n sentAt: string | null;\n editedAt: string | null;\n text: string | null;\n /** `column` | `decoded` | `none` — which lane produced the text. */\n textSource: string;\n subject: string | null;\n service: string | null;\n isRead: boolean;\n hasAttachments: boolean;\n /** Present only when this row is a reply. */\n replyToRef?: string;\n /** Present only when this row is a group event rather than a message. */\n itemType?: number;\n};\n\nexport type RenderedChat = {\n ref: string;\n name: string | null;\n isGroup: boolean;\n service: string | null;\n participants: Correspondent[];\n messages: number;\n lastMessageAt: string | null;\n};\n\nexport class AppleMessagesClient {\n readonly #config: Config;\n readonly #logger: Logger | undefined;\n readonly #home: string | undefined;\n\n #located: LocateResult | null = null;\n #store: MessagesStore | null = null;\n #storeTried = false;\n\n #contacts: AppleContactsClient | null;\n #contactsTried: boolean;\n #resolved = new Map<string, ResolvedHandle>();\n\n /**\n * Built even when writes are off, and that costs nothing.\n *\n * `createOsascriptRunner` spawns no process until something calls it, and with\n * `allowWrites` off nothing does — the send tool is never registered. So this\n * server still sends no Apple Event and still asks for no Automation grant in\n * its default configuration, which is the claim `surfaces.json` makes.\n */\n readonly #runner: OsascriptRunner;\n\n constructor(opts: CreateClientOptions) {\n this.#config = opts.config;\n this.#logger = opts.logger;\n this.#home = opts.home;\n // `null` means \"explicitly none\" (tests); `undefined` means \"make one\".\n this.#contacts = opts.contacts ?? null;\n this.#contactsTried = opts.contacts !== undefined;\n this.#runner =\n opts.osascript ??\n createOsascriptRunner({\n surface: MESSAGES_SURFACE,\n osascriptPath: opts.config.osascriptPath,\n timeoutMs: opts.config.osascriptTimeoutMs,\n ...(opts.logger ? { logger: opts.logger } : {}),\n });\n }\n\n get config(): Config {\n return this.#config;\n }\n\n located(): LocateResult {\n this.#located ??= locateStore({\n storePath: this.#config.storePath,\n ...(this.#home ? { home: this.#home } : {}),\n });\n return this.#located;\n }\n\n store(): MessagesStore | null {\n if (this.#storeTried) return this.#store;\n this.#storeTried = true;\n if (this.#config.indexMode === \"off\") return null;\n const located = this.located();\n if (!located.readable) return null;\n const mode = this.#config.indexMode === \"auto\" ? \"ro\" : this.#config.indexMode;\n this.#store = openStore(located.storePath, mode, this.#logger);\n return this.#store;\n }\n\n /**\n * Never `[]` when the store is missing.\n *\n * An empty list is a valid answer to \"no messages match\", and this surface has\n * no second lane to produce one from. Callers get a reason instead — and on\n * this surface the reason is load-bearing, because the fix is a permission.\n */\n #require(): MessagesStore {\n const store = this.store();\n if (store) return store;\n if (this.#config.indexMode === \"off\") {\n throw new IndexUnavailableError(\n \"The Messages index is disabled (APPLE_MESSAGES_INDEX_MODE=off). This surface has no \" +\n \"Apple Events read lane at all, so nothing can be read until it is re-enabled.\",\n );\n }\n throw new MessagesUnavailableError(\n this.located().reason ?? \"No readable Messages store was found.\",\n );\n }\n\n // ─── contacts ──────────────────────────────────────────────────────────────\n\n #contactsClient(): AppleContactsClient | null {\n if (this.#contactsTried) return this.#contacts;\n this.#contactsTried = true;\n if (!this.#config.resolveContacts) return null;\n try {\n this.#contacts = new AppleContactsClient({\n config: loadContactsConfig({}),\n ...(this.#logger ? { logger: this.#logger } : {}),\n ...(this.#home ? { home: this.#home } : {}),\n });\n } catch (err) {\n this.#logger?.debug?.(`contacts resolver unavailable: ${String(err)}`);\n this.#contacts = null;\n }\n return this.#contacts;\n }\n\n /**\n * Resolve a batch, memoised for the life of the process.\n *\n * Batched because building the lookup walks every contact once (3 ms on the\n * measured store) and every subsequent handle is a map hit. Memoised because a\n * conversation renders the same correspondent hundreds of times.\n */\n #resolve(handles: readonly (string | null)[]): void {\n const wanted = [...new Set(handles.filter((h): h is string => Boolean(h)))].filter(\n (h) => !this.#resolved.has(h),\n );\n if (!wanted.length) return;\n const contacts = this.#contactsClient();\n if (!contacts) return;\n try {\n for (const r of contacts.resolve(wanted).results) this.#resolved.set(r.handle, r);\n } catch (err) {\n // A Contacts failure must not take Messages down with it.\n this.#logger?.debug?.(`resolve failed: ${String(err)}`);\n this.#contacts = null;\n }\n }\n\n #correspondent(handle: string | null): Correspondent {\n if (!handle) return { handle: null, name: null, resolution: \"unknown\" };\n const hit = this.#resolved.get(handle);\n if (!hit) {\n return {\n handle,\n name: null,\n // Distinct from \"unknown\": nobody looked, rather than nobody matched.\n resolution: this.#contacts ? \"unknown\" : \"unavailable\",\n };\n }\n return { handle, name: hit.name, resolution: hit.status };\n }\n\n // ─── reads ─────────────────────────────────────────────────────────────────\n\n #render(rows: readonly MessageRow[]): RenderedMessage[] {\n this.#resolve(rows.map((r) => r.handle));\n return rows.map((r) => ({\n ref: encodeMessageRef(r.guid),\n chatRef: r.chatGuid ? encodeChatRef(r.chatGuid) : null,\n chat: r.chatName,\n from: r.isFromMe\n ? { handle: null, name: \"me\", resolution: \"self\" }\n : this.#correspondent(r.handle),\n fromMe: r.isFromMe,\n sentAt: renderInstant(r.sentAt),\n editedAt: renderInstant(r.editedAt),\n text: r.text,\n textSource: r.textSource,\n subject: r.subject,\n service: r.service,\n isRead: r.isRead,\n hasAttachments: r.hasAttachments,\n ...(r.threadOriginator ? { replyToRef: encodeMessageRef(r.threadOriginator) } : {}),\n ...(r.itemType ? { itemType: r.itemType } : {}),\n }));\n }\n\n listMessages(opts: {\n chatRef?: string | undefined;\n fromApple?: number | undefined;\n toApple?: number | undefined;\n includeReactions?: boolean | undefined;\n limit?: number | undefined;\n }): RenderedMessage[] {\n return this.#render(\n this.#require().range({\n ...(opts.chatRef ? { chatGuid: opts.chatRef } : {}),\n ...(opts.fromApple === undefined ? {} : { fromApple: opts.fromApple }),\n ...(opts.toApple === undefined ? {} : { toApple: opts.toApple }),\n ...(opts.includeReactions === undefined ? {} : { includeReactions: opts.includeReactions }),\n limit: opts.limit ?? this.#config.maxResults,\n }),\n );\n }\n\n searchMessages(query: string, limit?: number): RenderedMessage[] {\n return this.#render(this.#require().search(query, limit ?? this.#config.maxResults));\n }\n\n getMessage(guid: string):\n | (RenderedMessage & {\n reactions: { label: string; from: Correspondent }[];\n attachments: ReturnType<MessagesStore[\"attachmentsFor\"]>;\n })\n | null {\n const store = this.#require();\n const row = store.byGuid(guid);\n if (!row) return null;\n const reactions = store.reactionsFor(guid);\n this.#resolve(reactions.map((r) => r.handle));\n const [rendered] = this.#render([row]);\n return {\n ...(rendered as RenderedMessage),\n reactions: reactions.map((r) => ({ label: r.label, from: this.#correspondent(r.handle) })),\n attachments: store.attachmentsFor(guid),\n };\n }\n\n /**\n * Copy one attachment out of the Messages store onto disk.\n *\n * ## Why this is a copy and not an extraction\n *\n * Mail's equivalent has to parse MIME out of an `.emlx` because the bytes are\n * inside the message file. Messages does not work that way: `attachment` rows\n * point at real files under `~/Library/Messages`, so the work here is finding\n * the row, deciding the path is one we are willing to read, and copying.\n *\n * ## Two boundaries, not one\n *\n * The DESTINATION boundary is the same one Mail and Notes enforce:\n * `attachmentDir` is a confinement, `directory` may only select inside it, and\n * the leaf name is `basename`d because it comes from whoever sent the message.\n *\n * The SOURCE boundary is this surface's own. `filename` comes out of a\n * database this server never writes, and it is a fully-qualified path: taken\n * at face value it names any file the process can read. So it is required to\n * resolve inside the Messages root before a single byte is read. That check\n * has never fired on a real store and is not expected to — it is here so that\n * the day the schema surprises us, the surprise is a refusal.\n */\n async saveAttachment(\n attachmentId: string,\n opts: { directory?: string | undefined; overwrite?: boolean } = {},\n ): Promise<{ path: string; bytes: number; source: string; mimeType: string | null }> {\n const store = this.#require();\n const meta = store.attachmentById(attachmentId);\n if (!meta) {\n throw new PreconditionError(\n `No attachment with id \"${attachmentId}\". Ids come from apple_messages_get_message and ` +\n \"are the attachment's guid; the message may have been deleted since it was listed.\",\n );\n }\n if (!meta.path) {\n throw new PreconditionError(\n \"That attachment has no file path in the store, which normally means iCloud has \" +\n \"offloaded it. Open the conversation in Messages to download it, then try again.\",\n );\n }\n\n const home = this.#home ?? homedir();\n // `~/Library/Messages/…` is the shape the column actually holds — not a\n // shell convention this can leave to something else to expand.\n const expanded = meta.path.startsWith(\"~/\") ? join(home, meta.path.slice(2)) : meta.path;\n const source = resolvePath(expanded);\n const root = resolvePath(this.located().messagesRoot);\n if (source !== root && !source.startsWith(root + sep)) {\n throw new PreconditionError(\n `Refusing to read ${source}: it is outside ${root}, and this tool only copies files ` +\n \"Messages itself stores.\",\n );\n }\n if (!existsSync(source) || !statSync(source).isFile()) {\n throw new PreconditionError(\n `The store names ${source} but there is no file there. Messages prunes attachment bytes ` +\n \"while keeping the row, so this is a normal state for an old conversation.\",\n );\n }\n\n const dest = resolvePath(this.#config.attachmentDir);\n const dir = opts.directory ? resolvePath(dest, opts.directory) : dest;\n if (dir !== dest && !dir.startsWith(dest + sep)) {\n throw new PreconditionError(\n `Refusing to write outside ${dest}. Set APPLE_MESSAGES_ATTACHMENT_DIR to change the ` +\n \"destination.\",\n );\n }\n // The sender chose `transfer_name`, so it is basename'd before it is\n // trusted — path traversal wants exactly that field.\n const name = basename(meta.transferName ?? basename(source));\n const target = resolvePath(join(dir, name));\n if (target !== join(dir, name) || !target.startsWith(dir + sep)) {\n throw new PreconditionError(`Refusing to write outside ${dir}.`);\n }\n if (existsSync(target) && !opts.overwrite) {\n throw new PreconditionError(`${target} already exists; refusing to overwrite it.`);\n }\n\n const bytes = readFileSync(source);\n mkdirSync(dir, { recursive: true });\n writeFileSync(target, bytes, { mode: 0o600 });\n return { path: target, bytes: bytes.length, source, mimeType: meta.mimeType };\n }\n\n listChats(limit?: number): RenderedChat[] {\n const rows: ChatRow[] = this.#require().chats(limit ?? this.#config.maxResults);\n this.#resolve(rows.flatMap((c) => c.participants));\n return rows.map((c) => ({\n ref: encodeChatRef(c.guid),\n // A group chat usually has a name; a one-to-one never does, so fall back\n // to the resolved participant rather than showing a bare guid.\n name:\n c.displayName ??\n (c.participants.length === 1 && c.participants[0]\n ? (this.#correspondent(c.participants[0]).name ?? c.participants[0])\n : null),\n isGroup: c.isGroup,\n service: c.service,\n participants: c.participants.map((h) => this.#correspondent(h)),\n messages: c.messages,\n lastMessageAt: renderInstant(c.lastMessageAt),\n }));\n }\n\n // ─── writes ────────────────────────────────────────────────────────────────\n // One verb, and it is the only thing Apple Events can do on this surface.\n //\n // The shape is unlike every other surface here. Elsewhere a write is a\n // round-trip: change something, re-read it, report what the app stored.\n // Messages cannot re-read — every read through its scripting dictionary fails\n // — so the write lane fires and the FILE lane reports. See `#reconcile`.\n\n /**\n * Pick the chat to address, using the store.\n *\n * Handles are matched the way `packages/contacts` measured them rather than by\n * string equality: a caller who types `06 12 34 56 78` and a store that holds\n * `+33612345678` are the same person, and `suffixKey`'s last-9 rule is what\n * says so. The candidate set is the store's own handles — 1,075 on the\n * measured machine — so this is a scan over a list that is already in memory.\n */\n #chatFor(input: { chatRef?: string | undefined; to?: string | undefined }): {\n guid: string | null;\n chat: ChatRow | null;\n handle: string | null;\n } {\n // `store()`, not `#require()`. A send is the one operation on this surface\n // that can work without the file lane — the JXA ladder's lower rungs guess a\n // chat guid from the handle — so an unreadable store degrades the RESULT\n // (no ref to report) rather than blocking the send. Reads still refuse.\n const store = this.store();\n if (input.chatRef) {\n const guid = decodeChatRef(input.chatRef);\n const chat = store?.chatByGuid(guid) ?? null;\n if (store && !chat) throw new ChatNotFoundError(input.chatRef);\n return { guid, chat, handle: chat?.participants[0] ?? null };\n }\n const to = input.to?.trim();\n if (!to) return { guid: null, chat: null, handle: null };\n if (!store) return { guid: null, chat: null, handle: to };\n\n const kind = handleKind(to);\n const wantedSuffix = kind === \"phone\" ? suffixKey(to) : null;\n const wantedEmail = kind === \"email\" ? emailKey(to) : null;\n const candidates = store.handles().filter((h) => {\n if (h === to) return true;\n if (wantedEmail) return emailKey(h) === wantedEmail;\n if (wantedSuffix) return suffixKey(h) === wantedSuffix;\n return false;\n });\n if (!candidates.length) return { guid: null, chat: null, handle: to };\n\n // A one-to-one chat wins over a group with the same person in it: \"message\n // Alice\" must not land in a six-person thread Alice happens to be in.\n const chats = store.chatsForHandles(candidates, 25);\n const direct = chats.find((c) => !c.isGroup) ?? null;\n return { guid: direct?.guid ?? null, chat: direct, handle: candidates[0] ?? to };\n }\n\n /**\n * Find the row Messages wrote, or say plainly that it was not found.\n *\n * `docs/messages.md` left this as an open question — \"whether a send should\n * re-resolve by scanning the store for a recent row on the target chat\" — and\n * this is the answer, because the alternative is a send that can report\n * nothing at all. Apple Events hands back no identifier of any kind.\n *\n * Three things make the match safe rather than merely plausible:\n *\n * 1. `since` is taken BEFORE the send, so nothing earlier can match.\n * 2. The window is scoped to the target chat.\n * 3. Text is compared when it is available, which separates our row from one\n * the user sent from their phone in the same second. Only when it is not\n * available does the oldest row in the window win.\n *\n * A miss is `pending`, never an error: the send already happened, and iMessage\n * writes its row asynchronously. Reporting a failure there would be the worst\n * possible lie — it would invite a retry that sends the message twice.\n */\n async #reconcile(guid: string, since: number, text: string): Promise<RenderedMessage | null> {\n const deadline = Date.now() + this.#config.sendReconcileMs;\n for (;;) {\n // Reopen: this handle was opened before the send, and the point is to see\n // what another process has written since.\n this.#invalidate();\n const store = this.store();\n if (!store) return null;\n const rows = store.sentSince([guid], since, 10);\n const hit = rows.find((r) => r.text !== null && r.text === text) ?? rows[0];\n if (hit) return this.#render([hit])[0] ?? null;\n if (Date.now() >= deadline) return null;\n await new Promise((resolve) => setTimeout(resolve, 250));\n }\n }\n\n #invalidate(): void {\n this.#store?.close();\n this.#store = null;\n this.#storeTried = false;\n }\n\n /**\n * Send one message. A real one, to a real person, immediately.\n *\n * Everything difficult about this is in `client/jxa/core.ts`; what is left\n * here is choosing the target from the file lane and reconciling afterwards.\n */\n async sendMessage(input: {\n chatRef?: string | undefined;\n to?: string | undefined;\n text: string;\n service?: string | undefined;\n }): Promise<SendResult> {\n const { guid, chat, handle } = this.#chatFor(input);\n const since = toAppleSeconds(new Date(Date.now() - 2_000));\n\n let data: {\n strategy?: unknown;\n targetKind?: unknown;\n launched?: unknown;\n attempts?: unknown;\n };\n try {\n data = await withBusyRetry(() =>\n this.#runner.run<Record<string, unknown>>(SEND_MESSAGE, {\n ...(guid ? { chatGuid: guid } : {}),\n ...(handle ? { handle } : {}),\n ...(input.service ? { service: input.service } : {}),\n text: input.text,\n allowLaunch: true,\n }),\n );\n } catch (err) {\n const details = (err as { details?: { code?: string; detail?: unknown } })?.details;\n const attempts = Array.isArray(details?.detail) ? (details.detail as string[]) : [];\n const message = err instanceof Error ? err.message : String(err);\n if (details?.code === \"SEND_TARGET_NOT_FOUND\") {\n throw new SendTargetNotFoundError(input.chatRef ?? input.to ?? \"\", attempts);\n }\n if (details?.code === \"SEND_FAILED\") throw new SendFailedError(message, attempts);\n throw err;\n }\n\n const message = guid ? await this.#reconcile(guid, since, input.text) : null;\n if (handle) this.#resolve([handle]);\n return {\n sent: true,\n strategy: typeof data.strategy === \"string\" ? data.strategy : \"unknown\",\n targetKind: typeof data.targetKind === \"string\" ? data.targetKind : \"unknown\",\n chatRef: guid ? encodeChatRef(guid) : null,\n chat: chat?.displayName ?? null,\n to: handle ? this.#correspondent(handle) : null,\n launched: data.launched === true,\n reconciliation: message ? \"matched\" : guid ? \"pending\" : \"unavailable\",\n message,\n ...(message\n ? {}\n : {\n note: guid\n ? \"Messages accepted the send, but no matching row had appeared in the store yet. \" +\n \"That is normal for a slow network — re-run apple_messages_list_messages on this \" +\n \"chat rather than sending again.\"\n : \"Messages accepted the send, but there was no existing chat to reconcile it \" +\n \"against, so no ref can be reported for it. Read the chat back once it exists.\",\n }),\n };\n }\n\n /** Bounds for a range query, as apple-seconds. */\n window(from?: Date, to?: Date): { fromApple?: number; toApple?: number } {\n return {\n ...(from ? { fromApple: toAppleSeconds(from) } : {}),\n ...(to ? { toApple: toAppleSeconds(to) } : {}),\n };\n }\n\n status(): {\n located: LocateResult;\n store: { opened: boolean; mode: string | null; fingerprint: string | null };\n counts: ReturnType<MessagesStore[\"counts\"]> | null;\n contacts: { enabled: boolean; available: boolean; resolved: number };\n } {\n const store = this.store();\n return {\n located: this.located(),\n store: {\n opened: Boolean(store),\n mode: store?.mode ?? null,\n fingerprint: store?.caps.fingerprint ?? null,\n },\n counts: store?.counts() ?? null,\n contacts: {\n enabled: this.#config.resolveContacts,\n available: Boolean(this.#contactsClient()),\n resolved: this.#resolved.size,\n },\n };\n }\n\n close(): void {\n this.#store?.close();\n this.#store = null;\n this.#storeTried = false;\n this.#contacts?.close();\n this.#resolved.clear();\n }\n}\n","import { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\nimport {\n BaseConfigSchema,\n parseBool,\n parseConfig,\n parseIntOpt,\n trimmed,\n} from \"@mgcrea/mcp-apple-core\";\nimport { z } from \"zod\";\n\n/**\n * Configuration is environment-only — this server holds no secret at all, its\n * access is the macOS permission the user granted.\n *\n * `allowWrites` is inherited from `BaseConfigSchema` and, since 1.2.0, means\n * something here: it gates the one mutating tool, `send_message`. With it off\n * this server registers no write tool and therefore sends no Apple Event at all,\n * which is what keeps its \"no Automation grant\" claim true by default.\n *\n * `docs/messages.md` recorded why sending went unprobed for so long — \"probing\n * it would mean sending a real message to a real person\" — and left the id\n * bridge open, because Apple Events returns no chat identifier to reconcile\n * against. `sendReconcileMs` is that decision made: the file lane finds the row\n * instead.\n */\nconst ConfigSchema = BaseConfigSchema.extend({\n /** Explicit store path. Bypasses discovery — for tests and forensic copies. */\n storePath: z.string().optional(),\n indexMode: z.enum([\"auto\", \"ro\", \"immutable\", \"off\"]).default(\"auto\"),\n /**\n * Look names up in Contacts.\n *\n * On by default: without it this server answers \"+15551234567 said …\", which\n * is why `packages/contacts` was built. Off is for a machine where the\n * Contacts permission has not been granted and the prompt is unwelcome — the\n * resolver degrades to raw handles either way, so this only decides whether it\n * is attempted.\n */\n resolveContacts: z.boolean().default(true),\n /**\n * The only directory `save_attachment` may write into.\n *\n * A boundary rather than a default: the tool's `directory` argument selects a\n * subdirectory of this and cannot escape it. Saving is write-gated for the\n * same reason it is in Mail and Notes — it puts a file on the user's disk,\n * even though it changes nothing in Messages.\n */\n attachmentDir: z.string().default(join(homedir(), \"Downloads\")),\n /** Window for a range query that names only a start. */\n defaultRangeDays: z.number().int().min(1).max(3_660).default(30),\n /**\n * How long to wait for a sent message to appear in the store.\n *\n * Messages writes the outgoing row asynchronously, so a send that returns\n * instantly has usually not been written yet. Five seconds is generous for a\n * local write and short enough that a tool call does not hang on a bad\n * network; a miss is reported as `pending`, never as a failure. Zero means one\n * immediate check and no polling, which is what the test suite uses.\n */\n sendReconcileMs: z.number().int().min(0).max(60_000).default(5_000),\n}).strict();\n\nexport type Config = z.infer<typeof ConfigSchema>;\n\nexport const loadConfig = (env: NodeJS.ProcessEnv = process.env): Config =>\n parseConfig(ConfigSchema, {\n allowWrites: parseBool(env.APPLE_MESSAGES_ALLOW_WRITES),\n exposePrompts: parseBool(env.APPLE_MESSAGES_EXPOSE_PROMPTS),\n debug: parseBool(env.APPLE_MESSAGES_DEBUG),\n storePath: trimmed(env.APPLE_MESSAGES_STORE),\n indexMode: trimmed(env.APPLE_MESSAGES_INDEX_MODE),\n resolveContacts: parseBool(env.APPLE_MESSAGES_RESOLVE_CONTACTS),\n attachmentDir: trimmed(env.APPLE_MESSAGES_ATTACHMENT_DIR),\n defaultRangeDays: parseIntOpt(env.APPLE_MESSAGES_DEFAULT_RANGE_DAYS),\n sendReconcileMs: parseIntOpt(env.APPLE_MESSAGES_SEND_RECONCILE_MS),\n osascriptPath: trimmed(env.APPLE_MESSAGES_OSASCRIPT_PATH),\n osascriptTimeoutMs: parseIntOpt(env.APPLE_MESSAGES_OSASCRIPT_TIMEOUT_MS),\n maxResults: parseIntOpt(env.APPLE_MESSAGES_MAX_RESULTS),\n });\n","/**\n * The Messages operating manual, served as `cupertino://messages/guide` and\n * embedded ahead of every Messages prompt. Static by design — see the note in\n * the Mail guide.\n */\nexport const MESSAGES_GUIDE = `# Apple Messages — how to drive this server\n\n## Refs are opaque\n\nChats come back as \\`mc1:<guid>\\`, messages as \\`m1:<guid>\\`. Pass them back\nverbatim; never construct one.\n\n## Full Disk Access is mandatory here, unlike every other surface\n\nThere is no Apple Events read lane to fall back on. Measured: Messages answers\n\"Application isn't running\" even while it is running, because it is a windowless\nbackground process that declines to wake for a script. Without the grant this\nserver can do **nothing** on the read side — that is a permission problem, not\nan empty message history.\n\n## Which tool, under which constraint\n\n- **\\`apple_messages_list_chats\\`** first, when you need to know which\n conversation you are looking at.\n- **\\`apple_messages_list_messages\\`** reads one conversation in order.\n- **\\`apple_messages_search_messages\\`** searches across them.\n- **\\`apple_messages_get_message\\`** returns one message with its tapbacks and\n attachments.\n\n## Handles are not names\n\nA chat identifies people by phone number or email address. Names come from\nContacts, which has its own separate permission, so \\`unknown\\` is a normal\noutcome — about one in six of even the busiest correspondents has no card. When\ndiagnostics reports \\`contacts.available: false\\`, **nobody looked at all** and\nevery handle is raw. Do not present a raw handle as though the person is\nunidentified when the truth is that lookup was never possible.\n\n## Two things that look like missing messages and are not\n\n**Recent messages are stored as blobs.** Messages stopped writing the plain\n\\`text\\` column between late February and late March 2026; everything since lives\nonly as an archived blob, which this server decodes. \\`textSource\\` on each result\nsays which lane answered. A reader without the decoder would report that the\nconversation stopped in February — if you ever see history that appears to end\nthere, that is this, not silence.\n\n**Tapbacks are rows in the message table.** They are filtered out of\nconversations and reported on the message they target. A \"liked\" is not a reply\nand should not be summarised as one.\n\n## Sending\n\n\\`apple_messages_send_message\\` exists only when writes are enabled, and it is the\n**only** thing this server can change — the dictionary has no edit, delete,\nmark-as-read or reaction command.\n\nSending is real and immediate. There is no draft state, no undo, and no\nconfirmation step after the call. Confirm the recipient handle with the user\nbefore sending, exactly as it will be used.\n\nMessages hands back no identifier for what it sent, so the sent row is found by\nre-reading the store. \\`reconciliation: \"pending\"\\` means it has not appeared yet.\nThat is **not a failure and must not be retried** — retrying sends the message\ntwice.\n`;\n","import {\n promptArg,\n registerWorkflowPrompt,\n requiredPromptArg,\n type PromptContext,\n} from \"@mgcrea/mcp-apple-core\";\nimport type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\n\nimport { MESSAGES_GUIDE } from \"./guide.js\";\n\nconst CTX: PromptContext = { surface: \"messages\", guide: MESSAGES_GUIDE };\n\n/**\n * Messages' workflow prompts.\n *\n * The send prompt carries the only genuinely irreversible action in this\n * bundle. Everywhere else a mistake leaves a draft, an extra reminder or a\n * wrong-looking event; here it puts words in someone's pocket under the user's\n * name, with no undo and no draft state to catch it.\n */\nexport const registerPrompts = (server: McpServer, allowWrites: boolean): void => {\n registerWorkflowPrompt(server, CTX, {\n name: \"apple_messages_catch_up\",\n title: \"Catch up on a conversation\",\n description:\n \"Read back what has been said in a chat and what is waiting on a reply, with handles \" +\n \"resolved to names where that is actually possible. Read-only.\",\n argsSchema: {\n chat: promptArg(\n \"Who or which chat — a name, a handle, or a chat ref. Omit for recent chats.\",\n ),\n since: promptArg('How far back, e.g. \"yesterday\", \"last week\". Defaults to recent history.'),\n },\n build: ({\n chat,\n since,\n }) => `Catch me up on ${chat ? `the conversation with ${chat}` : \"my recent messages\"}${\n since ? `, covering ${since}` : \"\"\n }.\n\n1. \\`apple_messages_list_chats\\` to find ${\n chat\n ? \"the right conversation. If several match, ask which rather than picking.\"\n : \"what has been active.\"\n }\n2. \\`apple_messages_list_messages\\` over that chat${since ? \" with a date bound\" : \"\"}.\n3. Summarise what was actually said and, most importantly, **what is waiting on\n me** — a question asked and not answered is the thing worth surfacing.\n4. Where a handle has no name, say so and show the handle raw. Do not guess who\n it is from context. If \\`contacts.available\\` is false in diagnostics, say that\n names could not be looked up at all — that is different from these people\n being unknown.\n\nIgnore tapbacks as messages; they are reactions on the message they target and\na \"liked\" is not a reply. If the history appears to stop in early 2026, that is\nthe blob-storage change described in the guide, not the end of the conversation\n— check \\`textSource\\` before reporting a gap.`,\n });\n\n if (!allowWrites) return;\n\n registerWorkflowPrompt(server, CTX, {\n name: \"apple_messages_send\",\n title: \"Send a message\",\n description:\n \"Compose and send an iMessage or SMS. Requires writes. Sending is immediate and cannot be \" +\n \"undone — this prompt confirms the recipient before anything goes out.\",\n argsSchema: {\n to: requiredPromptArg(\"Who to send to — a name, phone number or email address.\"),\n message: promptArg(\"What to say, or the gist of it. Omit to be asked.\"),\n },\n build: ({ to, message }) => `Send a message to ${to}.${\n message ? `\\n\\nWhat it should say: ${message}` : \"\"\n }\n\n1. Work out the exact handle. \\`apple_messages_list_chats\\` to find an existing\n conversation with this person — an established chat is better evidence of the\n right handle than an address book match, because it is the one they actually\n reply on.\n2. Read the last few messages in that chat. It tells you which language they\n write in, how formal they are, and whether something is already pending.\n3. **Show the user the exact handle and the exact text, and wait for them to\n confirm.** There is no draft state here and no undo: the call sends. A\n message to the wrong handle cannot be recalled, and it is sent under the\n user's name.\n4. On confirmation, \\`apple_messages_send_message\\`.\n5. If the result says \\`reconciliation: \"pending\"\\`, **the message was sent.**\n Messages returns no identifier, so the sent row is found by re-reading the\n store and has not appeared yet. Do not retry — retrying sends it twice. Say\n it went out and that confirmation is lagging.`,\n });\n};\n","/**\n * Messages' tool helpers. The generic half lives in `@mgcrea/mcp-apple-core`;\n * re-exported here so tools import from one place.\n */\nexport {\n compact,\n confirmArg,\n fail,\n limitArg,\n ok,\n okText,\n toFailure,\n wrap,\n wrapResult,\n type ToolResult,\n} from \"@mgcrea/mcp-apple-core\";\n\nimport { z } from \"zod\";\n\nexport const chatRefArg = z\n .string()\n .optional()\n .describe(\n 'An opaque chat ref from apple_messages_list_chats (looks like \"mc1:<guid>\"). Do not ' +\n \"construct one by hand.\",\n );\n\nexport const messageRefArg = z\n .string()\n .min(1)\n .describe(\n 'An opaque message ref from a list or search result (looks like \"m1:<guid>\"). Do not ' +\n \"construct one by hand.\",\n );\n\nexport const fromArg = z\n .string()\n .optional()\n .describe('Start of the window, ISO-8601 — \"2026-08-01\" or \"2026-08-01T09:00\".');\n\nexport const toArg = z\n .string()\n .optional()\n .describe(\"End of the window, ISO-8601. Defaults to now.\");\n\nexport const includeReactionsArg = z\n .boolean()\n .optional()\n .describe(\n \"Include tapbacks as if they were messages. Off by default, and you almost never want it \" +\n 'on: a tapback renders as `Liked \"see you at 8\"`, which nobody typed. Use ' +\n \"apple_messages_get_message to see the reactions on a specific message instead.\",\n );\n","import type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\n\nimport { BUILD_INFO } from \"../build-info.js\";\nimport type { AppleMessagesClient } from \"../client/messages.js\";\nimport { wrap } from \"./util.js\";\n\n/**\n * Build the report.\n *\n * Split out of the tool registration so the `cupertino://messages/diagnostics`\n * resource can serve the same bytes. Two renderings of one probe: duplicated,\n * the resource and the tool would drift, and the disagreement would surface as\n * \"the diagnostics lied\" — the one thing this file must never do.\n */\nexport const buildDiagnostics = async (\n client: AppleMessagesClient,\n): Promise<Record<string, unknown>> => {\n const status = client.status();\n const writes = client.config.allowWrites;\n return {\n server: { name: BUILD_INFO.name, version: BUILD_INFO.version },\n // Off means the prompts and the cupertino:// resources are not registered at\n // all. Reported here because this tool still is, so it stays the one place\n // that explains a capability the client cannot see.\n settings: { exposePrompts: client.config.exposePrompts },\n lane: {\n reads: \"file lane (read-only SQLite)\",\n writes: writes\n ? \"apple_messages_send_message, over Apple Events. The only mutating command \" +\n \"the Messages dictionary offers.\"\n : \"off — APPLE_MESSAGES_ALLOW_WRITES is not set, so the send tool is not \" +\n \"registered and this server sends no Apple Event at all.\",\n appleEvents:\n \"no read path exists, and never will. Measured: every read attempt fails, and \" +\n 'Messages answers \"Application isn\\'t running\" even while it is running, because ' +\n \"it is a windowless background process that declines to wake for a script. \" +\n \"Sending is the one thing that works.\",\n },\n store: {\n path: status.located.storePath,\n exists: status.located.exists,\n readable: status.located.readable,\n opened: status.store.opened,\n mode: status.store.mode,\n fingerprint: status.store.fingerprint,\n counts: status.counts,\n reason: status.located.reason,\n },\n contacts: status.contacts,\n caveats: [\n \"Full Disk Access is MANDATORY here, unlike every other surface in this bundle. \" +\n \"There is no Apple Events read lane to fall back to, so without the grant this \" +\n \"server can do nothing at all.\",\n \"Messages stopped writing the plain `text` column between late February and late \" +\n \"March 2026: every message since is stored only as an archived blob, which is \" +\n \"decoded here. `textSource` on each result says which lane answered. Across all \" +\n \"history the blob-only share is about 3%, but for anything recent it is ~100%, \" +\n \"so a reader without the decoder would report that the conversation stopped in \" +\n \"February.\",\n \"Names come from Contacts, which has its own separate permission. `unknown` is a \" +\n \"normal outcome and not an error — about one in six of even the busiest \" +\n \"correspondents has no contact card. When `contacts.available` is false, nobody \" +\n \"looked at all and every handle is raw.\",\n \"Tapbacks are rows in the message table. They are filtered out of conversations by \" +\n \"default and reported on the message they target instead.\",\n writes\n ? \"Sending is real and immediate, and it is the ONLY thing this server can \" +\n \"change — the dictionary has no edit, delete, mark-as-read or reaction \" +\n \"command. Messages hands back no identifier for what it sent, so the sent row \" +\n 'is found by re-reading the store; `reconciliation: \"pending\"` means it has ' +\n \"not appeared yet, which is not a failure and must not be retried.\"\n : \"This server cannot send: APPLE_MESSAGES_ALLOW_WRITES is off. With it on, one \" +\n \"tool appears — apple_messages_send_message — and it is the only mutating \" +\n \"command the Messages dictionary offers.\",\n ],\n };\n};\n\nexport const registerDiagnosticsTools = (server: McpServer, client: AppleMessagesClient): void => {\n server.registerTool(\n \"apple_messages_diagnostics\",\n {\n description:\n \"Report whether the Messages store could be opened, how much is in it, whether names \" +\n \"are being resolved, and what this server cannot do. Start here when a read returns \" +\n \"nothing.\",\n inputSchema: {},\n annotations: { readOnlyHint: true },\n },\n async () => wrap(() => buildDiagnostics(client)),\n );\n};\n","import type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { z } from \"zod\";\n\nimport { PreconditionError } from \"../client/errors.js\";\nimport type { AppleMessagesClient } from \"../client/messages.js\";\nimport { confirmArg, wrap } from \"./util.js\";\n\n/**\n * The one mutating tool, registered only when `allowWrites` is on.\n *\n * Never merely refused at call time: an MCP client caches the tool list, so a\n * tool that exists and says no is a tool the model will keep trying. With writes\n * off this file is not reached and the send tool does not exist.\n *\n * ## One verb, because the dictionary has one\n *\n * `sdef /System/Applications/Messages.app` lists three commands — `send`,\n * `login`, `logout`. There is no edit, no delete, no mark-as-read, no typing\n * indicator, and no reaction: everything else this server can show you, it\n * cannot change. `login`/`logout` are not exposed because logging a user out of\n * iMessage across every device is not something to do behind a tool call.\n *\n * `send`'s direct parameter is typed `file` OR `text` in the dictionary, and\n * only the text form ships. See `client/jxa/core.ts` for why.\n */\n\nconst SIDE_EFFECT =\n \"This sends a REAL message from the user's own iMessage/SMS account, immediately and \" +\n \"irreversibly. There is no unsend, no draft and no preview: the recipient's phone buzzes as \" +\n \"soon as this returns. Messages may be launched to do it, which the user will see. Confirm the \" +\n \"exact wording and the exact recipient with the user before calling this.\";\n\nexport const registerActionTools = (server: McpServer, client: AppleMessagesClient): void => {\n server.registerTool(\n \"apple_messages_send_message\",\n {\n description:\n \"Send a message to an existing conversation or to a phone number / email address. \" +\n SIDE_EFFECT +\n \" Prefer chatRef from apple_messages_list_chats over a raw handle: Messages refuses to \" +\n \"enumerate participants for a script, so an existing conversation is the only target \" +\n \"this server can address reliably — a handle with no conversation on this Mac will \" +\n \"usually fail rather than start a new thread. The result reports which targeting \" +\n \"strategy worked, and reconciles against the message store to hand back a real message \" +\n \"ref: reconciliation `matched` means the sent row was found and `message` is it, \" +\n \"`pending` means Messages accepted the send but has not written the row yet (normal on a \" +\n \"slow network — read the chat back, do NOT send again), and `unavailable` means there \" +\n \"was no existing chat to look in.\",\n inputSchema: {\n chatRef: z\n .string()\n .optional()\n .describe(\n 'An opaque chat ref from apple_messages_list_chats (\"mc1:<guid>\"). The reliable ' +\n \"way to target a send. Do not construct one by hand.\",\n ),\n to: z\n .string()\n .optional()\n .describe(\n \"A phone number or email address, when there is no chat ref. Matched against the \" +\n \"store's own handles by last-9-digits, so local and international spellings of \" +\n \"the same number both work.\",\n ),\n text: z.string().min(1).describe(\"Exactly what to send. Sent verbatim.\"),\n service: z\n .enum([\"imessage\", \"sms\", \"rcs\"])\n .optional()\n .describe(\n \"Which service to prefer when addressing a handle with no existing chat. Ignored \" +\n \"when chatRef is given, since the chat already knows its service.\",\n ),\n confirm: confirmArg,\n },\n annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false },\n },\n async ({ chatRef, to, text, service }) =>\n wrap(async () => {\n // Both, or neither, is a caller error rather than something to guess at:\n // guessing here sends a real message to the wrong person.\n if (Boolean(chatRef) === Boolean(to)) {\n throw new PreconditionError(\n \"Give exactly one of chatRef or to. chatRef targets an existing conversation and is \" +\n \"the reliable form; to addresses a phone number or email directly.\",\n );\n }\n return client.sendMessage({\n ...(chatRef ? { chatRef } : {}),\n ...(to ? { to } : {}),\n ...(service ? { service } : {}),\n text,\n });\n }),\n );\n};\n","import type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { z } from \"zod\";\n\nimport type { AppleMessagesClient } from \"../client/messages.js\";\nimport { wrap } from \"./util.js\";\n\n/**\n * Saving an attachment out of a conversation.\n *\n * ## Why this is not in `actions.ts`\n *\n * That file's argument is that Messages' scripting dictionary has exactly one\n * usable verb, so this server has exactly one mutating tool — and that with\n * writes off it sends no Apple Event at all. Both remain true with this tool\n * registered: it opens a file and writes a file, and never speaks to\n * Messages.app. Filing it next to `send` would quietly weaken a claim\n * `diagnostics` makes about what the write gate buys.\n *\n * ## Why it is behind the write gate anyway\n *\n * It puts a file on the user's disk. Mail and Notes made the same call for the\n * same reason, and the three should not disagree about it: a user who has not\n * turned writes on has not agreed to this server creating files.\n */\nexport const registerAttachmentTools = (server: McpServer, client: AppleMessagesClient): void => {\n server.registerTool(\n \"apple_messages_save_attachment\",\n {\n description:\n \"Save one attachment from a conversation to disk — a photo, a video, a PDF, a voice \" +\n \"memo. Take the `id` from an attachment on apple_messages_get_message. Needs Full Disk \" +\n \"Access, like every read here. It can only write into APPLE_MESSAGES_ATTACHMENT_DIR \" +\n \"(default ~/Downloads) and will not overwrite an existing file unless you ask it to. \" +\n \"Write-gated because it puts a file on the user's disk, even though it changes nothing \" +\n \"in Messages. An attachment iCloud has offloaded has no bytes on this Mac and is \" +\n \"refused with that reason — open the conversation in Messages to pull it down first.\",\n inputSchema: {\n attachmentId: z\n .string()\n .min(1)\n .describe(\n \"The `id` of an attachment from apple_messages_get_message. Opaque; do not \" +\n \"construct one, and do not pass the file path shown next to it.\",\n ),\n directory: z\n .string()\n .optional()\n .describe(\"Override the target directory. Must still resolve inside the configured one.\"),\n overwrite: z.boolean().optional().describe(\"Replace an existing file. Default false.\"),\n },\n annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false },\n },\n async ({ attachmentId, directory, overwrite }) =>\n wrap(async () =>\n client.saveAttachment(attachmentId, {\n ...(directory ? { directory } : {}),\n ...(overwrite ? { overwrite } : {}),\n }),\n ),\n );\n};\n","import type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\n\nimport type { AppleMessagesClient } from \"../client/messages.js\";\nimport { limitArg, wrap } from \"./util.js\";\n\n/**\n * NOTE ON `async` BELOW: core's `wrap` is typed `() => Promise<T>` because most\n * surfaces reach Apple Events. Messages reads synchronous SQLite and never\n * leaves the process, so the thunks are marked async here rather than widening a\n * shared signature for every surface to accommodate one.\n */\nexport const registerChatTools = (server: McpServer, client: AppleMessagesClient): void => {\n server.registerTool(\n \"apple_messages_list_chats\",\n {\n description:\n \"List conversations, most recently active first, with their participants and message \" +\n \"counts. Names come from Contacts where they resolve; where they do not, the raw phone \" +\n \"number or email is shown and `resolution` says why. Use the returned ref to read one \" +\n \"conversation with apple_messages_list_messages.\",\n inputSchema: { limit: limitArg },\n annotations: { readOnlyHint: true },\n },\n async ({ limit }) => wrap(async () => client.listChats(limit)),\n );\n};\n","import type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { z } from \"zod\";\n\nimport type { AppleMessagesClient } from \"../client/messages.js\";\nimport { decodeChatRef, decodeMessageRef } from \"../client/ref.js\";\nimport {\n chatRefArg,\n fail,\n fromArg,\n includeReactionsArg,\n limitArg,\n messageRefArg,\n ok,\n toArg,\n wrap,\n wrapResult,\n} from \"./util.js\";\n\n/** ISO-8601 in, or a clear refusal. No relative grammar on this surface yet. */\nconst parseBound = (raw: string | undefined, field: string): Date | undefined => {\n if (raw === undefined) return undefined;\n const d = new Date(raw);\n if (Number.isNaN(d.getTime())) {\n throw new Error(\n `Could not read ${field} from ${JSON.stringify(raw)}. Use ISO-8601: \"2026-08-01\" or ` +\n `\"2026-08-01T09:00\".`,\n );\n }\n return d;\n};\n\nexport const registerMessageTools = (server: McpServer, client: AppleMessagesClient): void => {\n server.registerTool(\n \"apple_messages_list_messages\",\n {\n description:\n \"Read messages, newest first — the whole store, or one conversation, or a date window. \" +\n \"Tapbacks are excluded by default because they are rows in the same table and would \" +\n \"otherwise read as messages nobody typed.\\n\\n\" +\n \"Every message carries `from.name` where Contacts resolves it and `from.handle` always. \" +\n \"`from.resolution` is worth reading: `unknown` is COMMON AND NOT AN ERROR — measured on a \" +\n \"real store, about one in six of even the busiest correspondents has no contact card.\",\n inputSchema: {\n chatRef: chatRefArg,\n from: fromArg,\n to: toArg,\n includeReactions: includeReactionsArg,\n limit: limitArg,\n },\n annotations: { readOnlyHint: true },\n },\n async ({ chatRef, from, to, includeReactions, limit }) =>\n wrap(async () => {\n const window = client.window(parseBound(from, \"from\"), parseBound(to, \"to\"));\n return client.listMessages({\n ...(chatRef ? { chatRef: decodeChatRef(chatRef) } : {}),\n ...window,\n ...(includeReactions === undefined ? {} : { includeReactions }),\n ...(limit === undefined ? {} : { limit }),\n });\n }),\n );\n\n server.registerTool(\n \"apple_messages_search_messages\",\n {\n description:\n \"Search message text across every conversation. Matches are substring and \" +\n \"case-insensitive.\\n\\n\" +\n \"This searches ALL messages, including the roughly 3% whose text lives only in an \" +\n \"archived blob rather than in a plain column — those are invisible to any SQL query and \" +\n \"are decoded here. `textSource` on each result says which produced it.\",\n inputSchema: {\n query: z.string().min(1).describe(\"Text to look for.\"),\n limit: limitArg,\n },\n annotations: { readOnlyHint: true },\n },\n async ({ query, limit }) => wrap(async () => client.searchMessages(query, limit)),\n );\n\n server.registerTool(\n \"apple_messages_get_message\",\n {\n description:\n \"One message in full, with its tapbacks and attachments. Reactions are reported here \" +\n \"rather than mixed into the conversation, which is the whole reason list_messages \" +\n \"filters them out.\",\n inputSchema: { ref: messageRefArg },\n annotations: { readOnlyHint: true },\n },\n async ({ ref }) =>\n wrapResult(async () => {\n const message = client.getMessage(decodeMessageRef(ref));\n if (!message) {\n return fail(\n `No message for ref \"${ref}\". It was probably deleted since the search ran. ` +\n `Re-run the search to get a current ref.`,\n );\n }\n return ok(message);\n }),\n );\n};\n","import type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\n\nimport type { AppleMessagesClient } from \"../client/messages.js\";\nimport { registerActionTools } from \"./actions.js\";\nimport { registerAttachmentTools } from \"./attachments.js\";\nimport { registerChatTools } from \"./chats.js\";\nimport { registerDiagnosticsTools } from \"./diagnostics.js\";\nimport { registerMessageTools } from \"./messages.js\";\n\nexport type ToolContext = {\n /**\n * Gates the two tools that change something outside this process.\n *\n * `send_message` is the only one that touches Messages.app, and it is also\n * every Apple Event this server can send, because sending is the only thing\n * Apple Events can do here — there is no read lane to fall back to and never\n * can be. So with writes off this server remains inert with respect to\n * Messages.app: it opens a file and nothing else.\n *\n * `save_attachment` is gated for a different reason. It sends no Apple Event\n * and changes nothing in Messages; what it does is create a file on the\n * user's disk, which Mail and Notes also treat as a write. The claim above\n * survives it intact.\n */\n allowWrites: boolean;\n};\n\n/**\n * Register the Apple Messages tools.\n *\n * Five reads, always. Two writes, only when `allowWrites` is on — and on this\n * surface the flag carries a permission claim as well as a safety one: with it\n * off no Apple Event is ever sent, so no Automation grant is ever requested.\n * What is needed either way is Full Disk Access, absolutely — see `diagnostics`.\n *\n * The registered set does NOT vary with whether the store is readable. That is a\n * runtime condition, and MCP clients cache the tool list.\n */\nexport const registerTools = (\n server: McpServer,\n client: AppleMessagesClient,\n ctx: ToolContext,\n): void => {\n registerDiagnosticsTools(server, client);\n registerChatTools(server, client);\n registerMessageTools(server, client);\n if (!ctx.allowWrites) return;\n registerActionTools(server, client);\n registerAttachmentTools(server, client);\n};\n","import {\n registerSurfaceResources,\n type Logger,\n type OsascriptRunner,\n} from \"@mgcrea/mcp-apple-core\";\nimport { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\n\nimport { BUILD_INFO } from \"./build-info.js\";\nimport { AppleMessagesClient } from \"./client/messages.js\";\nimport type { Config } from \"./config.js\";\nimport { MESSAGES_GUIDE } from \"./guide.js\";\nimport { registerPrompts } from \"./prompts.js\";\nimport { buildDiagnostics } from \"./tools/diagnostics.js\";\nimport { registerTools } from \"./tools/index.js\";\n\nexport const SERVER_NAME = BUILD_INFO.name;\nexport const SERVER_VERSION = BUILD_INFO.version;\n\nexport type CreateServerOptions = {\n config: Config;\n logger?: Logger;\n /** Injected by tests so nothing spawns a process or touches real Messages. */\n osascript?: OsascriptRunner;\n /** Injected by tests so discovery never reaches the developer's real home. */\n home?: string;\n /** Injected by tests, so no test reaches the developer's real address book. */\n contacts?: ConstructorParameters<typeof AppleMessagesClient>[0][\"contacts\"];\n};\n\nexport type CreatedServer = {\n server: McpServer;\n client: AppleMessagesClient;\n};\n\n/**\n * Build the server. Side-effect free: it opens no database and reads no file,\n * so a test can construct it freely and every external dependency arrives\n * through an option.\n */\nexport const createServer = (opts: CreateServerOptions): CreatedServer => {\n const { config } = opts;\n const server = new McpServer({ name: SERVER_NAME, version: SERVER_VERSION });\n\n const client = new AppleMessagesClient({\n config,\n ...(opts.logger ? { logger: opts.logger } : {}),\n ...(opts.osascript ? { osascript: opts.osascript } : {}),\n ...(opts.home ? { home: opts.home } : {}),\n ...(opts.contacts === undefined ? {} : { contacts: opts.contacts }),\n });\n\n registerTools(server, client, { allowWrites: config.allowWrites });\n /*\n * One flag, both primitives — see `exposePrompts` in core's config. A prompt\n * embeds its surface guide, so registering prompts without the resources\n * would leave every expansion naming a `cupertino://…/guide` that this\n * server does not serve.\n */\n if (config.exposePrompts) {\n registerPrompts(server, config.allowWrites);\n registerSurfaceResources(server, {\n surface: \"messages\",\n displayName: \"Messages\",\n guide: MESSAGES_GUIDE,\n diagnostics: () => buildDiagnostics(client),\n // No inventory. Chats are unbounded and change constantly, which makes them\n // a query (apple_messages_list_chats) rather than a fixed set worth\n // addressing by URI — a resource that is never the same twice is a tool\n // call wearing a URI.\n });\n }\n\n return { server, client };\n};\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mgcrea/mcp-apple-messages",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "Model Context Protocol server for the macOS Apple Messages app",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"apple",
|
|
@@ -47,8 +47,8 @@
|
|
|
47
47
|
"dependencies": {
|
|
48
48
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
49
49
|
"zod": "^4.4.3",
|
|
50
|
-
"@mgcrea/mcp-apple-contacts": "^1.
|
|
51
|
-
"@mgcrea/mcp-apple-core": "^1.
|
|
50
|
+
"@mgcrea/mcp-apple-contacts": "^1.4.0",
|
|
51
|
+
"@mgcrea/mcp-apple-core": "^1.4.0"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
54
|
"@types/node": "^26.2.0",
|