@mgcrea/mcp-apple-core 1.21.0 → 1.21.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -168,6 +168,15 @@ type InterferenceWatch = {
168
168
  *
169
169
  * Returns null from `check()` when the question could not be asked at all,
170
170
  * which is not the same as "undisturbed" and must not be reported as it.
171
+ *
172
+ * **The host's idle reading counts Cupertino's own events.** It has to — input
173
+ * from any other software disturbs a sequence exactly as a person does — so a
174
+ * sequence that posts a key finds "input" a moment ago on every run. A native
175
+ * Mail reply reported "Someone used this Mac 0.2s ago" for its own ⌘V. The
176
+ * host now also says when it last posted, and input no newer than that is
177
+ * taken as ours. What that cannot see is a person whose input lands BEFORE our
178
+ * last post in the same sequence: masked, the ambiguity hover's idle guard
179
+ * already accepts. A host too old to report it keeps the bare comparison.
171
180
  */
172
181
  declare const watchInterference: (channel: AxChannel) => InterferenceWatch;
173
182
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/errors.ts","../src/ax.ts","../src/build-info.ts","../src/osascript.ts","../src/cli.ts","../src/codes.ts","../src/config.ts","../src/dates.ts","../src/facade.ts","../src/fs.ts","../src/listing.ts","../src/prompts.ts","../src/query.ts","../src/resources.ts","../src/schema.ts","../src/sqlite.ts","../src/tools.ts"],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/errors.ts","../src/ax.ts","../src/build-info.ts","../src/osascript.ts","../src/cli.ts","../src/codes.ts","../src/config.ts","../src/dates.ts","../src/facade.ts","../src/fs.ts","../src/listing.ts","../src/prompts.ts","../src/query.ts","../src/resources.ts","../src/schema.ts","../src/sqlite.ts","../src/tools.ts"],"mappings":"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"}
package/dist/index.js CHANGED
@@ -285,23 +285,41 @@ const openAxChannel = (surface, env = process.env) => {
285
285
  };
286
286
  };
287
287
  /**
288
+ * How much newer than Cupertino's own last posted event an input has to be
289
+ * before it is somebody else's. The host stamps its post just after the events
290
+ * go out, so the two readings of one keystroke differ by the posting alone.
291
+ */
292
+ const OWN_INPUT_TOLERANCE_S = .25;
293
+ /**
288
294
  * Start watching. `check()` answers for the span since this call.
289
295
  *
290
296
  * Returns null from `check()` when the question could not be asked at all,
291
297
  * which is not the same as "undisturbed" and must not be reported as it.
298
+ *
299
+ * **The host's idle reading counts Cupertino's own events.** It has to — input
300
+ * from any other software disturbs a sequence exactly as a person does — so a
301
+ * sequence that posts a key finds "input" a moment ago on every run. A native
302
+ * Mail reply reported "Someone used this Mac 0.2s ago" for its own ⌘V. The
303
+ * host now also says when it last posted, and input no newer than that is
304
+ * taken as ours. What that cannot see is a person whose input lands BEFORE our
305
+ * last post in the same sequence: masked, the ambiguity hover's idle guard
306
+ * already accepts. A host too old to report it keeps the bare comparison.
292
307
  */
293
308
  const watchInterference = (channel) => {
294
309
  const started = Date.now();
295
310
  return { async check() {
296
311
  const elapsedSeconds = (Date.now() - started) / 1e3;
297
312
  try {
298
- const secondsSinceInput = (await channel.call({
313
+ const answer = await channel.call({
299
314
  tool: "apple_desktop_user_activity",
300
315
  args: {}
301
- })).secondsSinceInput;
316
+ });
317
+ const secondsSinceInput = answer.secondsSinceInput;
302
318
  if (typeof secondsSinceInput !== "number") return null;
319
+ const own = answer.secondsSinceOwnInput;
320
+ const ours = typeof own === "number" && secondsSinceInput >= own - OWN_INPUT_TOLERANCE_S;
303
321
  return {
304
- disturbed: secondsSinceInput < elapsedSeconds,
322
+ disturbed: secondsSinceInput < elapsedSeconds && !ours,
305
323
  secondsSinceInput,
306
324
  elapsedSeconds
307
325
  };
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/errors.ts","../src/ax.ts","../src/build-info.ts","../src/listing.ts","../src/cli.ts","../src/codes.ts","../src/config.ts","../src/dates.ts","../src/tools.ts","../src/facade.ts","../src/fs.ts","../src/osascript.ts","../src/resources.ts","../src/prompts.ts","../src/query.ts","../src/schema.ts","../src/sqlite.ts"],"sourcesContent":["/**\n * Error taxonomy shared by every Apple-app server.\n *\n * Every message is written for the person who has to fix it — a TCC denial says\n * which System Settings pane to open, not \"operation failed\".\n *\n * ## Why the surface is a required argument\n *\n * These messages name an app (\"Not authorized to control Mail\") and an\n * environment variable (`APPLE_MAIL_ALLOW_WRITES`). An earlier version made the\n * app name an *optional* parameter defaulting to \"Mail\" — and then never passed\n * it at any call site, while a second mention of Mail stayed hardcoded further\n * down the same string. That is worse than no parameter at all: it looks\n * configurable and is not.\n *\n * So `SurfaceContext` is required wherever it appears in a message. Servers\n * subclass with their own surface bound, which keeps `new MailBusyError()`\n * ergonomic at the call site without letting the context go missing.\n */\n\n/** Identity of the app a server drives, for anything user-facing. */\nexport type SurfaceContext = {\n /** How the app is named to a human, e.g. \"Mail\", \"Notes\". */\n appName: string;\n /** Environment variable prefix for this server, e.g. \"APPLE_MAIL\". */\n envPrefix: string;\n};\n\n/** Base class so `toFailure` can carry structured detail through in one branch. */\nexport class AppleAutomationError extends Error {\n override readonly name: string = \"AppleAutomationError\";\n readonly details: Record<string, unknown> | undefined;\n\n constructor(message: string, details?: Record<string, unknown>) {\n super(message);\n this.details = details;\n }\n}\n\n/** The host process may not send Apple Events to the app (osascript -1743). */\nexport class TccDeniedError extends AppleAutomationError {\n override readonly name: string = \"TccDeniedError\";\n\n constructor(surface: SurfaceContext) {\n super(\n `Not authorized to control ${surface.appName}. Grant it in System Settings > ` +\n `Privacy & Security > Automation > (the app running this server) > ${surface.appName}, ` +\n `then restart the server. If no entry appears, the first attempt was denied before the ` +\n `prompt could be answered — run \\`tccutil reset AppleEvents\\` and try again.`,\n );\n }\n}\n\n/** The app is not running and the operation refuses to launch it. */\nexport class AppNotRunningError extends AppleAutomationError {\n override readonly name: string = \"AppNotRunningError\";\n\n constructor(surface: SurfaceContext) {\n super(\n `${surface.appName} is not running. Read tools do not launch it, because launching it ` +\n `steals focus and can start a sync. Open ${surface.appName} and retry.`,\n );\n }\n}\n\n/** The app was busy and refused the Apple Event (-1712). Retried once before surfacing. */\nexport class AppBusyError extends AppleAutomationError {\n override readonly name: string = \"AppBusyError\";\n\n constructor(surface: SurfaceContext) {\n super(\n `${surface.appName} is busy (probably syncing) and did not answer in time. ` +\n `Retry in a few seconds.`,\n );\n }\n}\n\n/** osascript exceeded its budget and was killed. */\nexport class OsascriptTimeoutError extends AppleAutomationError {\n override readonly name: string = \"OsascriptTimeoutError\";\n\n constructor(timeoutMs: number, surface: SurfaceContext) {\n super(\n `${surface.appName} did not answer within ${timeoutMs}ms. It may be mid-sync, or a ` +\n `permission prompt may be waiting on screen. Raise ` +\n `${surface.envPrefix}_OSASCRIPT_TIMEOUT_MS if this is routine at your data size.`,\n );\n }\n}\n\n/**\n * A write was attempted while writes are disabled. Under the house pattern write\n * tools are not registered at all when `allowWrites` is off, so this is a\n * belt-and-braces guard for the library surface, not a path tools can reach.\n */\nexport class WritesDisabledError extends AppleAutomationError {\n override readonly name: string = \"WritesDisabledError\";\n\n constructor(surface: SurfaceContext) {\n super(\n `Writes are disabled. Set ${surface.envPrefix}_ALLOW_WRITES=1 to enable the mutating tools.`,\n );\n }\n}\n\n/** A read-only index could not be opened, so the file lane is unavailable. */\nexport class IndexUnavailableError extends AppleAutomationError {\n override readonly name: string = \"IndexUnavailableError\";\n}\n\n/** The store's schema is not the one we know how to read. */\nexport class SchemaDriftError extends AppleAutomationError {\n override readonly name: string = \"SchemaDriftError\";\n}\n\n/** The server is not running on macOS, or osascript is missing. */\nexport class PlatformError extends AppleAutomationError {\n override readonly name: string = \"PlatformError\";\n}\n\n/** osascript exited 0 but did not produce the JSON envelope we require. */\nexport class ProtocolError extends AppleAutomationError {\n override readonly name: string = \"ProtocolError\";\n}\n\n/** A local precondition failed before anything was sent to the app. */\nexport class PreconditionError extends AppleAutomationError {\n override readonly name: string = \"PreconditionError\";\n}\n\n/**\n * A date argument could not be read.\n *\n * Refusing is the whole point. The alternative — which is what Mail, Messages\n * and Notes did — is `Date.parse` returning `NaN`, node:sqlite binding it as\n * `NULL`, and the query matching nothing: an empty result that looks like an\n * answer. The message lists the accepted forms because the caller is usually a\n * model that guessed, and the fix is to show it the grammar.\n */\nexport class InvalidDateError extends AppleAutomationError {\n override readonly name: string = \"InvalidDateError\";\n\n constructor(field: string, raw: string, reason: string) {\n super(\n `Could not read ${field} from ${JSON.stringify(raw)}: ${reason}. ` +\n `Accepted: an ISO-8601 date \"2026-08-20\" (a whole day, local) or date-time ` +\n `\"2026-08-20T09:00\" (local unless it carries an offset), a signed offset like ` +\n `\"+2d\", \"-3h\", \"+45m\", \"+1w\", \"today\", \"yesterday\", \"tomorrow 09:00\", ` +\n `\"next monday\" or \"last friday\".`,\n { field, raw },\n );\n }\n}\n","import { connect, type Socket } from \"node:net\";\n\nimport { AppleAutomationError, type SurfaceContext } from \"./errors.js\";\n\n/**\n * Borrowing the app's Accessibility driver.\n *\n * ## Why this exists at all\n *\n * `AXUIElementCopyAttributeValue` reads an attribute in **0.202 ms**. Reaching\n * the same attribute through `osascript` + JXA + System Events costs **47.4 ms**\n * — a 234x difference that is entirely transport, measured both ways on one\n * machine in `docs/desktop.md`. Every Accessibility number this repo recorded\n * before that document was taken through System Events, which is why the lane\n * was closed three times on figures that were pricing the wrong thing.\n *\n * The native call cannot be made from here. Node has no binding for it, and\n * more importantly the grant does not belong to node: TCC attaches\n * Accessibility to the **responsible GUI ancestor**, so it is Cupertino.app\n * that holds it. Hence a channel rather than a port — the app already runs the\n * driver, and `ServerHost` will lend it for one bundle id.\n *\n * ## The second grant this removes, which matters more than the speed\n *\n * Driving a UI through System Events needs Automation-to-System-Events **on top\n * of** Accessibility. Two grants, given in two different System Settings panes,\n * to reach one window. The native driver needs the first and not the second.\n *\n * ## Absence is the fallback, not an error\n *\n * `CUPERTINO_AX_SOCKET` and `CUPERTINO_AX_FOR` are set by `ServerLocator` and by\n * nothing else. A package installed from npm and run by hand has neither, and\n * that is the supported case: these are published artifacts that must work with\n * no app on the machine. `open()` returns null there, and the caller keeps\n * whatever lane it had. It never throws to say \"no app\".\n *\n * ## Lazy, deliberately\n *\n * Nothing connects at construction. `SurfaceCatalog` probes servers to learn\n * what they register, and a probe that opened a socket would make every capability\n * scan depend on the host being ready to answer one.\n */\n\n/** The handshake `ServerHost` expects, and the reply it sends back. */\nconst PROTOCOL = \"cupertino/1\";\nconst LENT_SURFACE = \"desktop\";\nconst OK = \"ok\";\n\n/**\n * How long one call may take.\n *\n * A walk carries its own five-second budget and the AX messaging timeout is two\n * seconds per element, so the ceiling that matters is the app's, not this one's.\n * This is only here so a host that stopped answering fails rather than hanging a\n * tool call forever.\n */\nconst CALL_TIMEOUT_MS = 30_000;\n\nexport type AxCall = {\n /** Tool name, e.g. `apple_desktop_find_elements`. */\n readonly tool: string;\n readonly args: Record<string, unknown>;\n};\n\nexport type AxChannel = {\n /**\n * Call one desktop tool and return its parsed JSON body.\n *\n * Calls are serialised. The wire is one socket carrying newline-delimited\n * JSON-RPC, and the driver's handle store is shared mutable state on the other\n * end, so overlapping two walks would interleave both.\n */\n call(request: AxCall): Promise<unknown>;\n close(): void;\n};\n\n/**\n * The app is there and said no, or stopped answering.\n *\n * Distinct from the null `openAxChannel` returns, which means there is no app\n * to ask — see the note there on why those two must not collapse.\n */\nexport class AxChannelError extends AppleAutomationError {\n override readonly name: string = \"AxChannelError\";\n\n constructor(surface: SurfaceContext, message: string) {\n super(`${surface.appName}: ${message}`, { surface: surface.appName });\n }\n}\n\n/** What the host told us, when it refused the handshake. */\nconst refusal = (line: string): string =>\n line.startsWith(\"err \") ? line.slice(4) : `unexpected handshake reply '${line}'`;\n\n/**\n * Open the channel, or return null when this server is not hosted by the app.\n *\n * Null and a throw are different answers and the difference is the whole\n * contract: null means \"there is no app here, use your other lane\", a throw\n * means \"there is an app and it said no\", which a caller must report rather\n * than paper over.\n */\nexport const openAxChannel = (\n surface: SurfaceContext,\n env: NodeJS.ProcessEnv = process.env,\n): AxChannel | null => {\n const socketPath = env.CUPERTINO_AX_SOCKET?.trim();\n const identity = env.CUPERTINO_AX_FOR?.trim();\n if (!socketPath || !identity) return null;\n\n let socket: Socket | null = null;\n let pending = Promise.resolve();\n let nextId = 1;\n\n const connectOnce = (): Promise<Socket> =>\n new Promise((resolve, reject) => {\n const s = connect(socketPath);\n let buffer = \"\";\n const onError = (error: Error) => {\n s.destroy();\n reject(new AxChannelError(surface, `cannot reach Cupertino: ${error.message}`));\n };\n s.once(\"error\", onError);\n s.setEncoding(\"utf8\");\n // The handshake reply is one line; everything after it is JSON-RPC, so\n // this listener has to come off before the first call reads anything.\n const onData = (chunk: string) => {\n buffer += chunk;\n const newline = buffer.indexOf(\"\\n\");\n if (newline < 0) return;\n const line = buffer.slice(0, newline).trim();\n s.off(\"data\", onData);\n s.off(\"error\", onError);\n if (line !== OK) {\n s.destroy();\n reject(new AxChannelError(surface, refusal(line)));\n return;\n }\n resolve(s);\n };\n s.on(\"data\", onData);\n s.write(`${PROTOCOL} ${LENT_SURFACE} for=${identity}\\n`);\n });\n\n const request = async (payload: AxCall): Promise<unknown> => {\n socket ??= await connectOnce();\n const live = socket;\n const id = nextId++;\n const line = `${JSON.stringify({\n jsonrpc: \"2.0\",\n id,\n method: \"tools/call\",\n params: { name: payload.tool, arguments: payload.args },\n })}\\n`;\n\n return new Promise((resolve, reject) => {\n let buffer = \"\";\n const timer = setTimeout(() => {\n cleanup();\n reject(\n new AxChannelError(surface, `${payload.tool} did not answer in ${CALL_TIMEOUT_MS}ms`),\n );\n }, CALL_TIMEOUT_MS);\n\n const cleanup = () => {\n clearTimeout(timer);\n live.off(\"data\", onData);\n live.off(\"error\", onError);\n live.off(\"close\", onClose);\n };\n const onError = (error: Error) => {\n cleanup();\n reject(new AxChannelError(surface, `${payload.tool} failed: ${error.message}`));\n };\n const onClose = () => {\n cleanup();\n socket = null;\n reject(\n new AxChannelError(surface, `Cupertino closed the connection during ${payload.tool}`),\n );\n };\n const onData = (chunk: string) => {\n buffer += chunk;\n const newline = buffer.indexOf(\"\\n\");\n if (newline < 0) return;\n const raw = buffer.slice(0, newline);\n cleanup();\n let message: {\n result?: { content?: { text?: string }[]; isError?: boolean };\n error?: { message?: string };\n };\n try {\n message = JSON.parse(raw);\n } catch {\n reject(new AxChannelError(surface, `unparseable reply to ${payload.tool}`));\n return;\n }\n if (message.error) {\n reject(new AxChannelError(surface, message.error.message ?? \"unknown error\"));\n return;\n }\n // The desktop server answers in MCP's content envelope, and its tools\n // put one JSON document in the first text part.\n const text = message.result?.content?.[0]?.text;\n if (text === undefined) {\n reject(new AxChannelError(surface, `empty reply to ${payload.tool}`));\n return;\n }\n // A REFUSED tool is not a JSON-RPC error. It comes back as a normal\n // result carrying `isError: true` and the reason as PLAIN TEXT, so a\n // caller that only checks `message.error` reads a refusal as a success\n // value and carries on. That shipped, and it cost an afternoon: every\n // press through this channel was being refused for want of a write gate\n // and the lane above reported \"the sheet did not appear\", which is a\n // sentence about Maps rather than about permission.\n if (message.result?.isError === true) {\n reject(new AxChannelError(surface, `${payload.tool}: ${text}`));\n return;\n }\n try {\n resolve(JSON.parse(text));\n } catch {\n resolve(text);\n }\n };\n\n live.on(\"data\", onData);\n live.once(\"error\", onError);\n live.once(\"close\", onClose);\n live.write(line);\n });\n };\n\n return {\n call(payload) {\n // Serialised onto one chain. `pending` is replaced with a promise that\n // settles either way, so one failed call does not wedge the queue.\n const result = pending.then(() => request(payload));\n pending = result.then(\n () => undefined,\n () => undefined,\n );\n return result;\n },\n close() {\n socket?.destroy();\n socket = null;\n },\n };\n};\n\n/**\n * Whether a person touched the machine while a sequence was running.\n *\n * Driving an interface is the one capability here that COMPETES with whoever is\n * using the Mac: a keystroke goes to whatever is frontmost and a click goes to a\n * screen point, so somebody typing during a paste does not slow it down, it\n * corrupts it.\n *\n * The comparison is what makes this work. `secondsSinceInput` on its own cannot\n * tell \"they typed just before we started\" from \"they typed into the middle of\n * it\" — but a sequence that ran for five seconds and ends with two seconds since\n * the last input knows the input landed inside it.\n *\n * **This exists because of a misdiagnosis, not a theory.** Two runs of the Maps\n * lane were blamed on interference and turned out to be bugs in the lane; one\n * was blamed on the lane and turned out to be interference. Neither could be\n * told apart from the outside, and a failure that names the wrong cause sends\n * the next hour in the wrong direction.\n */\nexport type Interference = {\n /** True when input arrived after the sequence began. */\n disturbed: boolean;\n secondsSinceInput: number;\n elapsedSeconds: number;\n};\n\nexport type InterferenceWatch = { check(): Promise<Interference | null> };\n\n/**\n * Start watching. `check()` answers for the span since this call.\n *\n * Returns null from `check()` when the question could not be asked at all,\n * which is not the same as \"undisturbed\" and must not be reported as it.\n */\nexport const watchInterference = (channel: AxChannel): InterferenceWatch => {\n const started = Date.now();\n return {\n async check() {\n const elapsedSeconds = (Date.now() - started) / 1000;\n try {\n const answer = (await channel.call({\n tool: \"apple_desktop_user_activity\",\n args: {},\n })) as { secondsSinceInput?: number };\n const secondsSinceInput = answer.secondsSinceInput;\n if (typeof secondsSinceInput !== \"number\") return null;\n return { disturbed: secondsSinceInput < elapsedSeconds, secondsSinceInput, elapsedSeconds };\n } catch {\n return null;\n }\n },\n };\n};\n\n/**\n * The sentence to append to a failure, or \"\" when nothing useful can be said.\n *\n * Deliberately says nothing when the machine was quiet: a failure that reads\n * \"nobody touched it\" invites the reader to stop looking, and the point of this\n * is to send them to the RIGHT place rather than to reassure them.\n */\nexport const interferenceNote = (found: Interference | null): string => {\n if (!found?.disturbed) return \"\";\n return (\n ` Someone used this Mac ${found.secondsSinceInput.toFixed(1)}s ago, during the ` +\n `${found.elapsedSeconds.toFixed(1)}s this took — a keystroke or click lands wherever the ` +\n `focus is, so that alone can explain this. Retry with the machine idle before looking further.`\n );\n};\n","import { readFileSync } from \"node:fs\";\n\nexport type PackageIdentity = { name: string; version: string };\n\nexport type BuildInfo = PackageIdentity & {\n gitCommit: string;\n gitCommitDate: string;\n};\n\n/**\n * Read a package's own name and version at startup, so they are always accurate\n * rather than baked in at build time.\n *\n * Callers pass their own `new URL(\"../package.json\", import.meta.url)`: resolving\n * it here would find *this* package, not theirs. The git fields stay with the\n * caller too, because `__GIT_COMMIT__` is substituted by whichever bundler build\n * compiles the file that mentions it.\n */\nexport const readPackageIdentity = (\n packageJsonUrl: URL,\n fallback: PackageIdentity,\n): PackageIdentity => {\n try {\n return JSON.parse(readFileSync(packageJsonUrl, \"utf8\")) as PackageIdentity;\n } catch {\n return fallback;\n }\n};\n","import type { Transport } from \"@modelcontextprotocol/sdk/shared/transport.js\";\nimport type { JSONRPCMessage } from \"@modelcontextprotocol/sdk/types.js\";\n\n/**\n * Trimming the SDK's own boilerplate out of `tools/list`.\n *\n * ## What this drops\n *\n * The SDK builds each tool's `inputSchema` from its zod shape at listing time,\n * and the generator stamps every one with\n * `\"$schema\": \"http://json-schema.org/draft-07/schema#\"`. Measured across the\n * eight servers with writes on, that one constant is 4,836 B of a 106,157 B\n * listing — 4.6%, paid by every client on every connect, to name a JSON Schema\n * draft the client already has to assume in order to read the rest of the\n * document. Nothing in the protocol reads it and no client needs it, so it is\n * the rare cut that is free rather than a trade.\n *\n * ## What this deliberately does NOT drop\n *\n * `\"execution\": {\"taskSupport\": \"forbidden\"}` is another 3,720 B (3.5%) of\n * identical constant — `registerTool` hardcodes it on every tool — and it looks\n * like the same kind of waste. It is not, and the difference is worth the\n * paragraph so nobody \"finishes the job\" later.\n *\n * Server-side the two spellings are the same: the SDK's `tools/call` path\n * branches only on `'required'` and `'optional'`, so an absent `execution` and\n * an explicit `'forbidden'` both fall through to the normal handler. Client-side\n * they are not. `taskSupport` is declared `.optional()` with no default, so\n * absence means \"unspecified\" rather than \"forbidden\", and a task-capable client\n * reading a listing with no `execution` is entitled to try task augmentation on\n * a tool that was registered without a task handler. `'forbidden'` is the value\n * that tells it not to. Dropping it would trade 930 tokens for a behavioural\n * change on a path nothing here tests.\n *\n * ## Why it is done to the outgoing frame\n *\n * The alternative seams are worse. The schema is generated inside the SDK, so\n * there is no option to pass; overriding the `tools/list` request handler means\n * reaching into `Server._requestHandlers`, a private field, and re-implementing\n * the listing it already builds. Wrapping `Transport.send` is public API, is\n * indifferent to how the listing was produced, and costs nothing on the frames\n * it does not match — every non-listing message is returned by identity below.\n */\n\n/** The one key removed, spelled once. */\nconst GENERATED_SCHEMA_KEY = \"$schema\";\n\n/**\n * A schema object without its `$schema` stamp, or the value unchanged.\n *\n * Returns the ORIGINAL reference when there is nothing to do, which is what\n * lets `trimToolListing` decide by identity whether it needs to rebuild\n * anything at all.\n */\nconst withoutSchemaKey = (schema: unknown): unknown => {\n if (schema === null || typeof schema !== \"object\" || Array.isArray(schema)) return schema;\n if (!(GENERATED_SCHEMA_KEY in schema)) return schema;\n const rest = { ...(schema as Record<string, unknown>) };\n delete rest[GENERATED_SCHEMA_KEY];\n return rest;\n};\n\n/**\n * Strip generated boilerplate from a `tools/list` reply, passing every other\n * message through untouched.\n *\n * Copies rather than mutates. The SDK hands out the registered tool's own\n * schema object, and deleting a key from it would edit the server's state from\n * a function whose job is to shape one reply.\n */\nexport const trimToolListing = (message: JSONRPCMessage): JSONRPCMessage => {\n if (!(\"result\" in message)) return message;\n const result = message.result as { tools?: unknown } | undefined;\n const tools = result?.tools;\n if (!Array.isArray(tools)) return message;\n\n let changed = false;\n const trimmed = tools.map((tool) => {\n if (tool === null || typeof tool !== \"object\") return tool;\n const entry = tool as Record<string, unknown>;\n const inputSchema = withoutSchemaKey(entry[\"inputSchema\"]);\n const outputSchema = withoutSchemaKey(entry[\"outputSchema\"]);\n if (inputSchema === entry[\"inputSchema\"] && outputSchema === entry[\"outputSchema\"]) return tool;\n changed = true;\n return {\n ...entry,\n ...(entry[\"inputSchema\"] === undefined ? {} : { inputSchema }),\n ...(entry[\"outputSchema\"] === undefined ? {} : { outputSchema }),\n };\n });\n\n if (!changed) return message;\n return { ...message, result: { ...result, tools: trimmed } } as JSONRPCMessage;\n};\n\n/**\n * Wrap a transport so every listing it sends is trimmed on the way out.\n *\n * Mutates and returns the transport it was given rather than proxying it: the\n * SDK's `connect` reaches for `onmessage`, `onclose` and `onerror` on the very\n * object it was handed, and a Proxy or a subclass would have to keep those in\n * sync for no gain.\n */\nexport const withTrimmedListing = <T extends Transport>(transport: T): T => {\n const send = transport.send.bind(transport);\n transport.send = (message, options) => send(trimToolListing(message), options);\n return transport;\n};\n","import type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\n\nimport type { BuildInfo } from \"./build-info.js\";\nimport type { SurfaceContext } from \"./errors.js\";\nimport { withTrimmedListing } from \"./listing.js\";\nimport type { Logger } from \"./osascript.js\";\n\nexport type StdioServerOptions = {\n build: BuildInfo;\n surface: SurfaceContext;\n /** Prefix on every stderr line, e.g. \"apple-mail-mcp\". */\n logPrefix: string;\n /**\n * Build and return the server, plus a one-line summary of the settings it\n * came up with. Called only after the platform guard passes.\n */\n start: (logger: Logger) => Promise<{ server: McpServer; banner: string }>;\n};\n\n/**\n * Boot a server on stdio.\n *\n * The load-bearing rule: **everything goes to stderr**. stdout is the JSON-RPC\n * channel under stdio, and a stray `console.log` there corrupts the protocol —\n * which surfaces as an unintelligible client-side parse error rather than as\n * anything pointing at the log line that caused it.\n */\nexport const runStdioServer = async (opts: StdioServerOptions): Promise<void> => {\n const { build, surface, logPrefix } = opts;\n const debugEnabled = Boolean(process.env[`${surface.envPrefix}_DEBUG`]);\n const logger: Required<Logger> = {\n debug: (...args: unknown[]) => {\n if (debugEnabled) console.error(`[${logPrefix}]`, ...args);\n },\n warn: (...args: unknown[]) => console.error(`[${logPrefix}]`, ...args),\n error: (...args: unknown[]) => console.error(`[${logPrefix}]`, ...args),\n };\n\n logger.warn(\n `${build.name}@${build.version} (git ${build.gitCommit} ${build.gitCommitDate}, node ${process.version})`,\n );\n\n if (process.platform !== \"darwin\") {\n logger.error(\n `fatal: this server drives the macOS ${surface.appName} app and cannot run on ${process.platform}.`,\n );\n process.exit(1);\n }\n\n const { server, banner } = await opts.start(logger);\n await server.connect(withTrimmedListing(new StdioServerTransport()));\n logger.warn(`${logPrefix} connected (${banner})`);\n\n const shutdown = (signal: string): void => {\n logger.warn(`received ${signal}, shutting down`);\n process.exit(0);\n };\n process.on(\"SIGINT\", () => shutdown(\"SIGINT\"));\n process.on(\"SIGTERM\", () => shutdown(\"SIGTERM\"));\n};\n","/**\n * One-time-code extraction, as a pure function over text.\n *\n * No I/O by design: everything here is decided from a string plus one bit about\n * the sender, so the whole thing tests offline against a table.\n *\n * ── WHY THIS SITS IN CORE ────────────────────────────────────────────────────\n *\n * It was written in `packages/messages` and said of itself that it was liftable\n * to Mail. Safari asked second — a code rendered in a page's text is the same\n * problem — and a second caller is the event that settles it, because the two\n * alternatives are both bad. Safari depending on the Messages package would\n * drag `chat.db` and a Contacts dependency across for one pure function. A\n * duplicate would drift silently, and the heuristic is exactly where the\n * mistakes live.\n *\n * This is the first HEURISTIC in core, which has otherwise been plumbing —\n * config, sqlite, osascript, tools, resources. Worth naming rather than\n * sneaking in: what belongs here is a judgement no surface owns, and this one\n * is now owned by two.\n *\n * The table in `test/codes.test.ts` is the asset, not this file. It is\n * SMS-shaped — short machine-written notifications — and a web page is a much\n * richer source of digit runs, so for the Safari caller this heuristic is\n * REUSED rather than re-validated. See docs/safari.md.\n *\n * ── WHY THIS IS NOT A REGEX ──────────────────────────────────────────────────\n *\n * The obvious implementation is `/\\b\\d{4,8}\\b/` and it is wrong in a way that\n * matters more than usual: a caller asks for a login code, gets the last four\n * digits of an order number, and pastes it into an auth prompt. The failure is\n * silent and the retry costs the user an account lockout. So the digit run is\n * the CANDIDATE here, never the answer — it has to survive disqualification and\n * then earn a score.\n *\n * The false positives are not hypothetical. A real inbox carries order numbers,\n * tracking numbers, prices, street numbers, years, flight numbers and phone\n * numbers, and every one of them is a 4-to-8 digit run in a message that also\n * contains the word \"code\" somewhere.\n *\n * ── THE SIGNALS, STRONGEST FIRST ─────────────────────────────────────────────\n *\n * domain-bound `@example.com #123456` — the WebOTP/AutoFill convention\n * Apple and Chrome both parse. Unambiguous by construction:\n * the origin is bound to the code, so there is nothing to\n * guess. When present it wins outright.\n * keyword A code word adjacent to the digits. \"adjacent\" is measured\n * in characters, not words, because the two orders both occur\n * (\"your code is 123456\" and \"123456 is your code\") and a word\n * window would need two passes.\n * shortcode Sender is a shortcode — a bank, a courier, a 2FA sender,\n * never a person. Corroborating only: it raises a weak match\n * to usable, never creates one on its own.\n *\n * ── WHAT `confidence` IS FOR ─────────────────────────────────────────────────\n *\n * The tool reports it, and the tool description tells the model to check the\n * body on anything below \"high\". This mirrors `apple_safari_list_tabs`'\n * `historyMatch`: say how the match was made so the caller is never guessing\n * whether to trust it.\n */\n\n/** A code word. \"code\" carries both English and French, conveniently. */\nconst KEYWORDS = [\n \"code\",\n \"verification\",\n \"verify\",\n \"one-time\",\n \"onetime\",\n \"one time\",\n \"otp\",\n \"passcode\",\n \"pin\",\n \"2fa\",\n \"two-factor\",\n \"authentication\",\n \"authenticate\",\n \"security\",\n \"log in\",\n \"login\",\n \"sign in\",\n \"signin\",\n \"confirm\",\n // French. `vérification` is listed unaccented too because senders strip\n // accents to stay inside one SMS segment.\n \"verification\",\n \"usage unique\",\n \"mot de passe\",\n \"connexion\",\n \"identification\",\n \"securite\",\n \"sécurité\",\n \"vérification\",\n];\n\n/**\n * Phrases where \"code\" means something else entirely.\n *\n * This is a denylist and `docs/surfaces.md` warns that a denylist can never be\n * finished — correctly, and it is used narrowly here because of that. It only\n * ever SUPPRESSES the keyword signal; it never decides the outcome by itself,\n * and a message carrying both \"promo code\" and a real domain-bound code still\n * resolves through the stronger signal.\n */\nconst ANTI_KEYWORDS = [\n \"promo code\",\n \"promotional code\",\n \"discount code\",\n \"coupon code\",\n \"referral code\",\n \"invite code\",\n \"area code\",\n \"zip code\",\n \"postal code\",\n \"qr code\",\n \"barcode\",\n \"bar code\",\n \"country code\",\n \"code promo\",\n \"code postal\",\n \"code de reduction\",\n \"code de réduction\",\n \"code parrainage\",\n];\n\n/** How far from the digits a keyword still counts, in characters. */\nconst NEAR = 32;\nconst ADJACENT = 12;\n\nexport type CodeConfidence = \"high\" | \"medium\" | \"low\";\n\nexport type CodeMatch = {\n /** The digits to type. Never the surrounding text. */\n code: string;\n confidence: CodeConfidence;\n /** Which signal fired: `domain-bound` | `keyword` | `shortcode`. */\n matched: string;\n /** Present only for `domain-bound`: the origin the code is bound to. */\n boundTo?: string;\n};\n\n/**\n * The WebOTP format: a last line of `@host #code`, optionally with `?` params.\n * Anchored to a `@host` so a bare `#1234` (an order number, a hashtag) does not\n * qualify.\n */\nconst DOMAIN_BOUND = /@([a-z0-9][a-z0-9.-]*\\.[a-z]{2,})\\s+#([0-9]{4,8})\\b/i;\n\n/**\n * A maximal run of digits and the separators a phone number or a formatted\n * quantity is allowed to contain. Used to reject, not to match: a span holding\n * more than 8 digits in total is a phone number, an account number or an\n * amount, and every digit run inside it is disqualified along with it.\n */\nconst NUMBER_SPAN = /\\d[\\d\\s().+-]*\\d|\\d+/g;\nconst DIGIT_RUN = /\\d{4,8}/g;\n\nconst normalise = (s: string) => s.toLowerCase().replace(/ /g, \" \");\n\n/** Spans that hold too many digits to be a code. Returns [start, end) pairs. */\nconst disqualifiedSpans = (text: string): [number, number][] => {\n const out: [number, number][] = [];\n for (const m of text.matchAll(NUMBER_SPAN)) {\n const digits = m[0].replace(/\\D/g, \"\").length;\n if (digits > 8) out.push([m.index, m.index + m[0].length]);\n }\n return out;\n};\n\nconst inSpan = (spans: [number, number][], start: number, end: number) =>\n spans.some(([a, b]) => start >= a && end <= b);\n\n/**\n * Rejections that look at the characters touching the digits.\n *\n * Each of these was a real false positive shape before it was a rule; see\n * `test/codes.test.ts`, where every one has a case.\n */\nconst looksLikeSomethingElse = (text: string, start: number, end: number): boolean => {\n const before = text.slice(Math.max(0, start - 12), start);\n const after = text.slice(end, end + 12);\n\n // Currency: \"$1299\", \"€ 1299\", and the grouped/decimal forms \"1,299.00\".\n if (/[$€£¥]\\s*$/.test(before)) return true;\n if (/^[.,]\\d/.test(after)) return true;\n if (/\\d[.,]$/.test(before)) return true;\n\n // Glued to letters — a tracking or reference number like \"AA10123456\".\n // A separator is fine: Google sends \"G-123456\" and the code is the digits.\n if (/[a-z]$/i.test(before)) return true;\n if (/^[a-z]/i.test(after) && !/^[a-z]{0,2}\\b/i.test(after)) return true;\n\n // A percentage or an ordinal is never a code.\n if (/^\\s*%/.test(after)) return true;\n\n return false;\n};\n\n/** 1900-2099. Rejected unless a keyword sits right against it. */\nconst looksLikeYear = (digits: string) => digits.length === 4 && /^(19|20)\\d{2}$/.test(digits);\n\n/**\n * Distance in characters from a digit run to the nearest keyword, or null.\n *\n * Both directions are searched because both orders are common in the wild:\n * \"your code is 123456\" and \"123456 is your Google verification code\".\n *\n * The slice is widened by the longest keyword before searching, and the\n * distance checked afterwards. Slicing to exactly NEAR instead is wrong in a\n * way that is easy to miss: it cuts the keyword in half at the boundary, so\n * \"authentication\" (14 chars) would need to sit 14 characters closer than\n * \"otp\" to register at all. The window bounds the GAP, not the keyword.\n */\nconst LONGEST_KEYWORD = Math.max(...KEYWORDS.map((k) => k.length));\n\nconst keywordDistance = (lower: string, start: number, end: number): number | null => {\n const from = Math.max(0, start - NEAR - LONGEST_KEYWORD);\n const before = lower.slice(from, start);\n const after = lower.slice(end, end + NEAR + LONGEST_KEYWORD);\n\n let best: number | null = null;\n for (const kw of KEYWORDS) {\n const b = before.lastIndexOf(kw);\n if (b !== -1) {\n const d = before.length - (b + kw.length);\n if (d <= NEAR && (best === null || d < best)) best = d;\n }\n const a = after.indexOf(kw);\n if (a !== -1 && a <= NEAR && (best === null || a < best)) best = a;\n }\n return best;\n};\n\n/** True when a code word near the digits is one of the decoy phrases. */\nconst LONGEST_ANTI = Math.max(...ANTI_KEYWORDS.map((k) => k.length));\n\nconst suppressedByAntiKeyword = (lower: string, start: number, end: number): boolean => {\n const window = lower.slice(Math.max(0, start - NEAR - LONGEST_ANTI), end + NEAR + LONGEST_ANTI);\n return ANTI_KEYWORDS.some((k) => window.includes(k));\n};\n\nexport type ExtractOptions = {\n /**\n * Whether the sender is a shortcode. Corroborating only — it raises a weak\n * match to usable and never creates one. `packages/contacts` classifies these\n * and `Correspondent.resolution` carries the verdict.\n *\n * It is ONE caller's corroborating bit and deliberately still named for it. A\n * caller with no sender at all — Safari, reading a page — simply leaves it\n * false, and the consequence is worth knowing rather than working around: on\n * that lane a `low` match can never occur, because the only route to one is\n * this flag. A page with digits and no keyword yields null.\n */\n fromShortcode?: boolean;\n};\n\n/**\n * Pull the one-time code out of a message, or return null.\n *\n * Null is the common and correct answer for most messages, and callers must\n * treat it as \"no code here\" rather than retrying with something looser.\n */\nexport const extractCode = (\n text: string | null | undefined,\n { fromShortcode = false }: ExtractOptions = {},\n): CodeMatch | null => {\n if (!text) return null;\n // A code arrives in a short machine-written notification. Past a few hundred\n // characters this is a newsletter that happens to contain digits, and the\n // scoring below has no way to tell. Cheap, and it removes a whole class.\n if (text.length > 400) return null;\n\n const lower = normalise(text);\n\n // 1. Domain-bound. Unambiguous by construction, so it short-circuits.\n const bound = DOMAIN_BOUND.exec(text);\n const boundHost = bound?.[1];\n const boundCode = bound?.[2];\n if (boundHost && boundCode) {\n return { code: boundCode, confidence: \"high\", matched: \"domain-bound\", boundTo: boundHost };\n }\n\n const dead = disqualifiedSpans(text);\n const candidates: { code: string; confidence: CodeConfidence; matched: string; rank: number }[] =\n [];\n\n for (const m of text.matchAll(DIGIT_RUN)) {\n const digits = m[0];\n const start = m.index;\n const end = start + digits.length;\n\n if (inSpan(dead, start, end)) continue;\n if (looksLikeSomethingElse(text, start, end)) continue;\n\n const distance = keywordDistance(lower, start, end);\n const suppressed = distance !== null && suppressedByAntiKeyword(lower, start, end);\n const keyword = distance !== null && !suppressed;\n\n // A year needs a keyword pressed right against it to count. \"expires 2026\"\n // does not qualify; \"your code is 2026\" does.\n if (looksLikeYear(digits) && !(keyword && distance <= ADJACENT)) continue;\n\n if (keyword) {\n const adjacent = distance <= ADJACENT;\n candidates.push({\n code: digits,\n confidence: adjacent ? \"high\" : \"medium\",\n matched: \"keyword\",\n rank: adjacent ? 0 : 1,\n });\n continue;\n }\n\n // No keyword. A shortcode sender plus a single short line is the last\n // signal worth acting on, and it is deliberately capped at \"low\".\n if (fromShortcode && text.length <= 120) {\n candidates.push({ code: digits, confidence: \"low\", matched: \"shortcode\", rank: 2 });\n }\n }\n\n candidates.sort((a, b) => a.rank - b.rank);\n const [best, second] = candidates;\n if (!best) return null;\n\n // Two equally-ranked candidates means the message holds more than one number\n // this function cannot separate — report the first but never claim \"high\",\n // because picking wrong is the failure this whole file exists to avoid.\n const ambiguous = second !== undefined && second.rank === best.rank && second.code !== best.code;\n return {\n code: best.code,\n confidence: ambiguous && best.confidence === \"high\" ? \"medium\" : best.confidence,\n matched: ambiguous ? `${best.matched}-ambiguous` : best.matched,\n };\n};\n","import { z } from \"zod\";\n\n/**\n * Environment parsing shared by every server.\n *\n * Configuration is environment-only. The sibling servers that also read a\n * `~/.config/<service>/config.json` do so because they hold a private key or an\n * OAuth token; these servers hold no secret at all — their access is the macOS\n * permission the user granted.\n */\n\nexport const trimmed = (v: string | undefined): string | undefined => {\n const t = v?.trim();\n return t ? t : undefined;\n};\n\nexport const parseBool = (v: string | undefined): boolean | undefined => {\n const t = trimmed(v)?.toLowerCase();\n if (t === undefined) return undefined;\n return t === \"1\" || t === \"true\" || t === \"yes\" || t === \"on\";\n};\n\nexport const parseIntOpt = (v: string | undefined): number | undefined => {\n const t = trimmed(v);\n if (t === undefined) return undefined;\n const n = Number(t);\n return Number.isFinite(n) ? n : undefined;\n};\n\nexport const parseList = (v: string | undefined): string[] | undefined => {\n const t = trimmed(v);\n if (t === undefined) return undefined;\n return t\n .split(\",\")\n .map((s) => s.trim())\n .filter(Boolean);\n};\n\n/**\n * Settings every Apple-app server has. Extend it rather than repeating them:\n *\n * const ConfigSchema = BaseConfigSchema.extend({ ... }).strict();\n */\nexport const BaseConfigSchema = z.object({\n allowWrites: z.boolean().default(false),\n /**\n * Register the workflow prompts and the surface resources.\n *\n * ON by default, unlike `allowWrites`, and the difference is the point: the\n * write gate is a SAFETY invariant — off means a mutation cannot be reached\n * even by name — while this is a COST knob, in the same family as\n * `maxResults`. Conflating the two would muddy the one that matters.\n *\n * What it costs, measured across all seven servers with writes on: the\n * prompt and resource listings come to ~3.4k tokens against ~18.5k for the\n * tool definitions, so roughly 18% on top of a bill that is dominated by\n * tools either way. Resource CONTENTS cost nothing until something reads\n * them. The knob exists for hosts that put every listing in the prompt and\n * for people counting bytes; if context is the problem, running fewer\n * servers is the bigger lever by far.\n *\n * One flag for both, not two, because they ship as a pair: every prompt\n * embeds its surface guide, and a prompt naming a `cupertino://…/guide` that\n * nothing serves would be a dangling reference by configuration.\n */\n exposePrompts: z.boolean().default(true),\n /**\n * Serve a searchable index and a dispatcher instead of the full tool list.\n *\n * OFF by default, and a COST knob like `exposePrompts` above rather than a\n * safety gate — but unlike that one it is a knob that TRADES. See\n * `facade.ts` for the mechanism; the trade is that a host's permission rule\n * stops naming the individual tool and starts naming a direction: one rule\n * for this surface's reads, one for its writes.\n *\n * What it buys, measured with writes on: ~26.5k tokens of tool definitions\n * across the eight servers becomes a handful per surface. What it costs\n * besides the permission granularity is a round trip — a model must search\n * before it can call.\n *\n * Worth switching on only for a client that does not already defer tool\n * schemas itself. Claude Code and Claude Desktop do, and gain nothing here\n * while paying both costs, which is why the app declines to write the flag\n * into their config files at all.\n */\n lazyTools: z.boolean().default(false),\n debug: z.boolean().default(false),\n osascriptPath: z.string().default(\"/usr/bin/osascript\"),\n osascriptTimeoutMs: z.number().int().min(1_000).max(600_000).default(30_000),\n maxResults: z.number().int().min(1).max(1_000).default(200),\n});\n\n/**\n * Parse an env-derived object against a schema.\n *\n * Undefined values are dropped so zod's defaults apply, rather than failing on\n * an explicitly-undefined key.\n */\nexport const parseConfig = <T extends z.ZodType>(\n schema: T,\n raw: Record<string, unknown>,\n): z.infer<T> => {\n const compacted = Object.fromEntries(Object.entries(raw).filter(([, v]) => v !== undefined));\n const parsed = schema.safeParse(compacted);\n if (!parsed.success) {\n const issues = parsed.error.issues.map((i) => `${i.path.join(\".\")}: ${i.message}`).join(\"; \");\n throw new Error(`Invalid configuration: ${issues}`);\n }\n return parsed.data as z.infer<T>;\n};\n","/**\n * The one grammar every surface reads its date arguments with.\n *\n * ## Why it is here\n *\n * This shipped as three copies. Reminders wrote it, Calendar repeated it\n * verbatim, and Safari repeated it again with signed offsets and `yesterday`\n * added — that third copy's header says outright that three is the trigger for\n * hoisting and that the copies had already diverged. Meanwhile Mail, Messages\n * and Notes never got a grammar at all: they called `Date.parse` or\n * `new Date()` directly, which is where the bugs were.\n *\n * They were not small bugs, and they were all the same shape — a date argument\n * that produced a confident, plausible, wrong answer:\n *\n * - `Date.parse(\"last week\")` is `NaN`, node:sqlite binds `NaN` as `NULL`,\n * and `date_received >= NULL` matches nothing. A model that wrote a date in\n * words got zero results and no error.\n * - `new Date(\"2026-08-20\")` is UTC midnight but `new Date(\"2026-08-20T00:00\")`\n * is LOCAL midnight, so two spellings a tool description treated as the\n * same thing bounded a query an hour or five apart.\n * - `\"2026-02-30T09:00\"` silently became 2 March, because the rollover guard\n * existed on the date branch and not on the date-time one.\n *\n * ## The rule\n *\n * 1. `YYYY-MM-DD` names a **local calendar day**. As a start bound it is local\n * 00:00 of that day; as an end bound it is local 00:00 of the NEXT day, and\n * every consumer compares with `<`. So `to: \"2026-08-20\"` includes all of\n * the 20th and nothing of the 21st. Resolving it to midnight of the same day\n * is how an end bound quietly excludes the day it names.\n * 2. `YYYY-MM-DD[T ]HH:MM[:SS]` is local wall-clock; with `Z` or `±HH:MM` it is\n * the instant named. A timed bound is used as given, and is exclusive at the\n * end edge like everything else.\n * 3. Relative forms: `±N m|h|d|w`, `today` / `yesterday` / `tomorrow` with an\n * optional `HH:MM`, and `next|last <weekday>`. Days and weeks are calendar\n * arithmetic; hours and minutes are elapsed time. That split is not\n * cosmetic — reversing it drifts every bound by an hour twice a year.\n * 4. Anything else, an empty string, or an instant that does not exist\n * (`2026-02-30`, `T25:00`, `T09:60`) throws `InvalidDateError`. **Nothing\n * here ever returns `NaN`**, because a `NaN` reaches SQLite as `NULL` and a\n * `NULL` bound is an empty result rather than a complaint.\n *\n * The forward-only surfaces inherit the backward forms too. A reminder cannot\n * be due `-7d`, but accepting it costs nothing and refusing it in one surface\n * while accepting it in another is exactly the drift this file replaces.\n */\n\nimport { InvalidDateError } from \"./errors.js\";\n\nexport type DateKind = \"allDay\" | \"timed\";\n\nexport type ParsedDate = {\n kind: DateKind;\n /** The absolute instant, for comparisons and for range bounds. */\n at: Date;\n /**\n * ISO-8601 **with an explicit offset**, e.g. `2026-08-20T09:00:00+02:00`.\n *\n * Always carries the offset so the value is unambiguous once it leaves this\n * process — a bare local string reinterpreted in another zone is the silent\n * failure this module exists to prevent.\n */\n iso: string;\n /** Echoed back in tool results so a caller can see how its input was read. */\n raw: string;\n};\n\nconst DAY_NAMES = [\n \"sunday\",\n \"monday\",\n \"tuesday\",\n \"wednesday\",\n \"thursday\",\n \"friday\",\n \"saturday\",\n] as const;\n\nconst ISO_DATE = /^(\\d{4})-(\\d{2})-(\\d{2})$/;\nconst ISO_DATETIME =\n /^(\\d{4})-(\\d{2})-(\\d{2})[T ](\\d{2}):(\\d{2})(?::(\\d{2}))?(?:\\.\\d+)?(Z|[+-]\\d{2}:?\\d{2})?$/;\nconst OFFSET =\n /^([+-])(\\d+)\\s*(m|min|mins|minute|minutes|h|hr|hrs|hour|hours|d|day|days|w|week|weeks)$/;\nconst DAY_WORD = /^(today|tomorrow|yesterday)(?:\\s+(\\d{1,2}):(\\d{2}))?$/;\nconst RELATIVE_DAY = /^(next|last)\\s+([a-z]+)(?:\\s+(\\d{1,2}):(\\d{2}))?$/;\n\n/** `+02:00` / `-05:00` / `Z` for a given instant, in the system zone. */\nconst offsetOf = (d: Date): string => {\n const mins = -d.getTimezoneOffset();\n if (mins === 0) return \"Z\";\n const sign = mins < 0 ? \"-\" : \"+\";\n const abs = Math.abs(mins);\n return `${sign}${String(Math.floor(abs / 60)).padStart(2, \"0\")}:${String(abs % 60).padStart(2, \"0\")}`;\n};\n\nconst pad = (n: number, w = 2): string => String(n).padStart(w, \"0\");\n\n/**\n * Local wall-clock time rendered with its offset.\n *\n * Deliberately not `toISOString()`, which converts to UTC and would report a\n * 09:00 bound as `07:00Z` — correct as an instant, but unreadable in a tool\n * result whose purpose is confirming what the caller asked for.\n */\nexport const toLocalIso = (d: Date): string =>\n `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}` +\n `T${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}${offsetOf(d)}`;\n\n/** Local midnight on the given calendar day. */\nexport const startOfLocalDay = (d: Date): Date =>\n new Date(d.getFullYear(), d.getMonth(), d.getDate(), 0, 0, 0, 0);\n\n/**\n * The last representable instant of the given calendar day, local.\n *\n * Kept for surfaces that genuinely want an inclusive edge. It is NOT what an\n * end bound resolves to — see `parseBound`.\n */\nexport const endOfLocalDay = (d: Date): Date =>\n new Date(d.getFullYear(), d.getMonth(), d.getDate(), 23, 59, 59, 999);\n\n/** Calendar-aware day arithmetic: same wall-clock time, n days later. */\nexport const addLocalDays = (d: Date, n: number): Date => {\n const out = new Date(d.getTime());\n out.setDate(out.getDate() + n);\n return out;\n};\n\nconst at = (day: Date, hours: number, minutes: number): Date =>\n new Date(day.getFullYear(), day.getMonth(), day.getDate(), hours, minutes, 0, 0);\n\nconst isLeap = (y: number): boolean => (y % 4 === 0 && y % 100 !== 0) || y % 400 === 0;\n\nconst daysInMonth = (y: number, mo: number): number =>\n [31, isLeap(y) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][mo - 1] ?? 0;\n\n/**\n * Reject an instant that does not exist, BEFORE building a Date from it.\n *\n * Checking afterwards does not work, for two reasons found the hard way. An\n * out-of-range hour rolls the DAY over, so `2026-08-20T25:00` comes back as the\n * 21st at 01:00 and a naive date comparison blames the day the caller got\n * right. And a zoned string does not answer `NaN` at all:\n * `new Date(\"2026-02-30T09:00Z\")` is 2 March, so there is nothing to catch.\n *\n * Arithmetic on the components has neither problem and does not depend on the\n * zone. The order matters — time first, because that is what rolled the date.\n */\nconst assertReal = (\n field: string,\n text: string,\n y: number,\n mo: number,\n d: number,\n hh = 0,\n mm = 0,\n ss = 0,\n): void => {\n if (hh > 23 || mm > 59 || ss > 59) {\n throw new InvalidDateError(field, text, `${pad(hh)}:${pad(mm)} is not a time`);\n }\n if (mo < 1 || mo > 12) {\n throw new InvalidDateError(field, text, `there is no month ${pad(mo)}`);\n }\n if (d < 1 || d > daysInMonth(y, mo)) {\n throw new InvalidDateError(field, text, `there is no day ${pad(d)} in month ${pad(mo)}`);\n }\n};\n\nconst result = (kind: DateKind, when: Date, raw: string): ParsedDate => ({\n kind,\n at: when,\n iso: toLocalIso(when),\n raw,\n});\n\n/**\n * Parse one date argument.\n *\n * @param field Named in the error, so a failure says *which* argument was bad.\n * @param raw The caller's string.\n * @param now Injected for hermetic tests, mirroring `loadConfig(env)`.\n */\nexport const parseDate = (field: string, raw: string, now: Date = new Date()): ParsedDate => {\n const text = String(raw ?? \"\").trim();\n if (!text) throw new InvalidDateError(field, String(raw), \"it is empty\");\n const lower = text.toLowerCase();\n\n // ── ISO date-time. An explicit offset means the caller named an instant. ──\n const dt = ISO_DATETIME.exec(text);\n if (dt) {\n const [, y, mo, d, hh, mm, ss, zone] = dt;\n // Before constructing anything, and for the zoned form too: neither NaN nor\n // a component comparison catches these. See `assertReal`.\n assertReal(\n field,\n text,\n Number(y),\n Number(mo),\n Number(d),\n Number(hh),\n Number(mm),\n Number(ss ?? \"0\"),\n );\n const when = zone\n ? new Date(text.replace(\" \", \"T\"))\n : new Date(\n Number(y),\n Number(mo) - 1,\n Number(d),\n Number(hh),\n Number(mm),\n Number(ss ?? \"0\"),\n 0,\n );\n if (Number.isNaN(when.getTime())) {\n throw new InvalidDateError(field, text, \"it is not a real date\");\n }\n return result(\"timed\", when, text);\n }\n\n // ── ISO date. Names a day, so it is all-day. ──\n const only = ISO_DATE.exec(text);\n if (only) {\n const [, y, mo, d] = only;\n // Guard against JS's silent rollover: new Date(2026, 1, 30) is 2 March.\n assertReal(field, text, Number(y), Number(mo), Number(d));\n const when = new Date(Number(y), Number(mo) - 1, Number(d), 0, 0, 0, 0);\n if (Number.isNaN(when.getTime())) {\n throw new InvalidDateError(field, text, \"it is not a real date\");\n }\n return result(\"allDay\", when, text);\n }\n\n // ── Signed offset. Names a duration, so it is timed. ──\n const off = OFFSET.exec(lower);\n if (off) {\n const sign = off[1] === \"-\" ? -1 : 1;\n const n = Number(off[2]) * sign;\n const unit = String(off[3]);\n if (!Number.isFinite(n)) throw new InvalidDateError(field, text, \"the amount is not a number\");\n // Days and weeks are calendar arithmetic; hours and minutes are elapsed time.\n if (unit.startsWith(\"d\")) return result(\"timed\", addLocalDays(now, n), text);\n if (unit.startsWith(\"w\")) return result(\"timed\", addLocalDays(now, n * 7), text);\n const ms = unit.startsWith(\"h\") ? n * 3_600_000 : n * 60_000;\n return result(\"timed\", new Date(now.getTime() + ms), text);\n }\n\n // ── today / yesterday / tomorrow, an optional time promoting it to timed. ──\n const word = DAY_WORD.exec(lower);\n if (word) {\n const shift = word[1] === \"tomorrow\" ? 1 : word[1] === \"yesterday\" ? -1 : 0;\n const day = addLocalDays(now, shift);\n if (word[2] === undefined) return result(\"allDay\", startOfLocalDay(day), text);\n const [hh, mm] = [Number(word[2]), Number(word[3])];\n if (hh > 23 || mm > 59) {\n throw new InvalidDateError(field, text, `${hh}:${word[3]} is not a time`);\n }\n return result(\"timed\", at(day, hh, mm), text);\n }\n\n // ── next/last <weekday>, strictly in that direction: \"last monday\" on a\n // Monday is -7, matching \"next monday\" on a Monday being +7. ──\n const rel = RELATIVE_DAY.exec(lower);\n if (rel) {\n const idx = DAY_NAMES.findIndex((n) => n === rel[2] || n.slice(0, 3) === rel[2]);\n if (idx === -1) {\n throw new InvalidDateError(field, text, `\"${rel[2]}\" is not a day of the week`);\n }\n const forward = rel[1] === \"next\";\n const delta = forward\n ? (idx - now.getDay() + 7) % 7 || 7\n : -((now.getDay() - idx + 7) % 7 || 7);\n const day = addLocalDays(now, delta);\n if (rel[3] === undefined) return result(\"allDay\", startOfLocalDay(day), text);\n const [hh, mm] = [Number(rel[3]), Number(rel[4])];\n if (hh > 23 || mm > 59) {\n throw new InvalidDateError(field, text, `${hh}:${rel[4]} is not a time`);\n }\n return result(\"timed\", at(day, hh, mm), text);\n }\n\n throw new InvalidDateError(field, text, \"it matches none of the accepted forms\");\n};\n\n/**\n * Parse a bound for a range filter.\n *\n * A bare day names the WHOLE day, so which instant it resolves to depends on\n * which edge it is. The start edge is that day's local midnight. The end edge\n * is the NEXT day's local midnight, and every caller compares with `<` — which\n * makes the bound exclusive and the named day fully included.\n *\n * The alternative, 23:59:59.999, was what two of the three copies did, and it\n * is wrong twice: it drops the last millisecond of the day, and more to the\n * point it invites the `<=` that then drops the whole day when somebody writes\n * midnight instead.\n */\nexport const parseBound = (\n field: string,\n raw: string,\n edge: \"start\" | \"end\",\n now: Date = new Date(),\n): Date => {\n const parsed = parseDate(field, raw, now);\n if (parsed.kind !== \"allDay\") return parsed.at;\n return edge === \"end\" ? startOfLocalDay(addLocalDays(parsed.at, 1)) : startOfLocalDay(parsed.at);\n};\n","import { z } from \"zod\";\n\nimport { AppleAutomationError } from \"./errors.js\";\n\nexport type ToolResult = {\n content: { type: \"text\"; text: string }[];\n isError?: boolean;\n};\n\n/**\n * Compact, not pretty-printed.\n *\n * A model does not need the indentation, and it is not free: measured against\n * rows matching these servers' own types, `null, 2` adds 25-41% depending on how\n * many short keys a row carries - worst on the widest lists, which are exactly\n * the responses already big enough to matter. Every tool in every surface\n * returns through here, so this is the one place it is paid.\n */\nexport const ok = (data: unknown): ToolResult => ({\n content: [{ type: \"text\", text: JSON.stringify(data ?? { ok: true }) }],\n});\n\n/**\n * Return text as-is. `ok()` JSON-stringifies, which turns a message body into\n * one escaped \"Hi,\\n\\n…\" line that no one can read.\n */\nexport const okText = (text: string): ToolResult => ({\n content: [{ type: \"text\", text }],\n});\n\nexport const fail = (message: string, extra?: unknown): ToolResult => ({\n content: [\n {\n type: \"text\",\n text: JSON.stringify({ error: message, ...(extra ? { details: extra } : {}) }),\n },\n ],\n isError: true,\n});\n\n/** Render a thrown value as a tool error, preserving whatever detail it carried. */\nexport const toFailure = (err: unknown): ToolResult => {\n if (err instanceof AppleAutomationError) {\n return fail(err.message, { kind: err.name, ...err.details });\n }\n if (err instanceof Error) {\n const details = (err as Error & { details?: unknown }).details;\n return fail(err.message, details);\n }\n return fail(\"Unknown error\", err);\n};\n\n/** Run a tool body, JSON-formatting the result and turning errors into a tool error. */\nexport const wrap = async <T>(fn: () => Promise<T>): Promise<ToolResult> => {\n try {\n return ok(await fn());\n } catch (err) {\n return toFailure(err);\n }\n};\n\n/** Like `wrap`, but the body chooses its own result shape (e.g. a raw body). */\nexport const wrapResult = async (fn: () => Promise<ToolResult>): Promise<ToolResult> => {\n try {\n return await fn();\n } catch (err) {\n return toFailure(err);\n }\n};\n\n/** Drop undefined values so we never send `{mailbox: undefined}` down a lane. */\nexport const compact = <T extends Record<string, unknown>>(obj: T): Partial<T> =>\n Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== undefined)) as Partial<T>;\n\n// ─── shared args ─────────────────────────────────────────────────────────────\n\nexport const limitArg = z\n .number()\n .int()\n .min(1)\n .max(200)\n .optional()\n .describe(\n \"Maximum number of results. Each tool states its own default; \" +\n \"`maxResults` is the ceiling either way.\",\n );\n\n/**\n * Settle a caller's `limit` against the tool's default and the config ceiling.\n *\n * Written out by hand at twenty-odd call sites before this existed, in five\n * different spellings - and three surfaces spelled it `limit ?? maxResults`,\n * with no `Math.min` at all. That made their real default 200 while `limitArg`\n * told every model it was 25, so a model that trusted the description and\n * omitted the argument got eight times the rows it asked for.\n *\n * `fallback` is the tool's own documented default, not a global one: a mailbox\n * listing and a day of events do not want the same number.\n */\nexport const resolveLimit = (\n limit: number | undefined,\n maxResults: number,\n fallback = 25,\n): number => Math.min(limit ?? fallback, maxResults);\n\nexport const confirmArg = z\n .literal(true)\n .describe(\"Must be true. This action changes data and is not undoable from here.\");\n","import type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { z } from \"zod\";\n\nimport { fail, okText, type ToolResult } from \"./tools.js\";\n\n/**\n * Lazy tool loading: a searchable index and a dispatcher, in place of the full\n * `tools/list`.\n *\n * ## Why a facade rather than a smaller listing\n *\n * MCP has no method for fetching a tool's schema later. `inputSchema` is\n * required in a `tools/list` entry and clients validate against it, so a\n * name-only listing is not a cheap server, it is a broken one. An index plus a\n * dispatcher is the only lazy discovery the protocol permits.\n *\n * Measured on the eight servers with writes on, the full listing is ~106 KB\n * (~26.5k tokens), paid by every client on every connect whether or not a tool\n * is ever called. Mail alone is ~25 KB.\n *\n * ## Two dispatchers, split on the write gate\n *\n * Bastion — the sibling project this is modelled on — ships ONE dispatcher, and\n * documents that it therefore cannot carry `readOnlyHint` at all: `true` would\n * be a lie that relaxes the host's confirmation for every write, and `false`\n * would gate the reads along with them. It accepts that, and calls it \"the one\n * switch in the app that trades rather than tightens\".\n *\n * Cupertino should not accept it. `docs/alternatives.md` sells \"unregistered,\n * not refused\" as a differentiator against a competitor whose `--read-only`\n * flag leaves write tools listed, and collapsing every call into one anonymous\n * name would hand that back. So the dispatcher is split on the axis the write\n * gate already uses: `call_tool` reaches reads and is honestly `readOnlyHint:\n * true`; `call_write_tool` reaches writes, is honestly `readOnlyHint: false`\n * and `destructiveHint: true`, and is NOT REGISTERED AT ALL when writes are\n * off. A host still gets a read/write boundary to hang a permission rule on.\n *\n * The cost that remains, and it is real: a host's rule is now per surface and\n * per direction rather than per tool. `apple_mail_send_message` and\n * `apple_mail_move_messages` share one prompt. That is the trade this file\n * makes, and it is why the flag defaults off.\n *\n * ## A write tool is one that disappears when the gate closes\n *\n * Classification does NOT read `annotations.readOnlyHint`. Thirteen mutating\n * tools ship without it — every `create`/`update`/`delete`/`move` tool on\n * Calendar, Notes and Reminders — so trusting it would file\n * `apple_notes_delete_notes` behind the READ dispatcher, silently, with nothing\n * to notice it. Instead the registrar is run twice against throwaway recorders,\n * once with the real `allowWrites` and once with it forced off, and the\n * difference is the write set. That is not a heuristic: it is the repo's own\n * definition, and `each surface's src/tools/index.ts` states the invariant it\n * relies on — \"the registered set is a pure function of `allowWrites` and\n * nothing else\". Registration is side-effect free, so running it twice costs\n * two arrays of closures and touches no client.\n *\n * ## What this deliberately does not copy from Bastion\n *\n * No `nextCursor` walk (registration is in-process, so there are no pages), no\n * catalog cache or invalidation, no `ttlMs`/`cacheScope` annotation, and no\n * pass-through of real tool names for clients holding a stale list. The flag is\n * read from the environment at process launch and cannot change while the\n * process lives, exactly like `allowWrites`, so the listing never varies at\n * runtime and the caching concern that rules out `listChanged` here does not\n * arise.\n */\n\n// ─── recording the registration ──────────────────────────────────────────────\n\ntype ToolConfig = {\n description?: string;\n inputSchema?: Record<string, z.ZodType>;\n annotations?: Record<string, unknown>;\n};\n\ntype ToolHandler = (...args: never[]) => unknown;\n\ntype Declaration = { name: string; config: ToolConfig; handler: ToolHandler };\n\n/**\n * A stand-in server that records what would have been registered.\n *\n * Typed as a whole `McpServer` and cast once, here, rather than narrowed to a\n * `Pick<…, \"registerTool\">` that every surface's `registerTools` would then\n * have to widen its parameter to accept — eight signatures and the ~95\n * functions beneath them, changed to describe a fact that is already true.\n *\n * The cast is safe by inspection, and the inspection is the point: all 95\n * `registerTool` call sites across the eight surfaces call this one method and\n * nothing else, and not one uses its return value. If a registrar ever reaches\n * for `registerPrompt` or `server.server`, it will fail here at runtime rather\n * than quietly registering into a void — which is why this returns a bare\n * object instead of a Proxy that would forward the difference to a real server\n * and half-register a surface.\n */\nconst recorder = (into: Declaration[]): McpServer =>\n ({\n registerTool: (name: string, config: ToolConfig, handler: ToolHandler) => {\n into.push({ name, config, handler });\n return undefined;\n },\n }) as unknown as McpServer;\n\n// ─── the index ───────────────────────────────────────────────────────────────\n\n/** Terms shorter than this are dropped: they match everything and rank nothing. */\nconst SHORTEST_TERM = 3;\n/** How much of a description is printed per row. The whole of it is searched. */\nconst SUMMARY_LIMIT = 160;\nconst SEARCH_LIMIT = 25;\n/**\n * How much bigger a real listing must be than the facade replacing it, in both\n * tools and bytes, before fronting it is a saving rather than a rounding error.\n */\nconst FRONTING_RATIO = 2;\n\n/** Partial matches are low precision by construction, so fewer of them. */\nconst PARTIAL_LIMIT = 10;\nconst INDEX_LIMIT = 200;\n\n/**\n * Rank tiers, best first, SUMMED across the query's terms.\n *\n * Summing rather than taking the best single term is deliberate: a two-word\n * query scored on its luckiest word ranks a tool that matched one term above a\n * tool that matched both.\n */\nconst RANK = { exactName: 0, namePrefix: 1, nameSubstring: 2, summary: 3, tail: 4, missing: 5 };\n\ntype Indexed = {\n name: string;\n summary: string;\n /** name + summary. Decides how WELL a term matched. */\n head: string;\n /** name + the entire description. Decides WHETHER it matched at all. */\n whole: string;\n};\n\nconst summarize = (description: string): string => {\n const flat = description.replace(/\\s+/g, \" \").trim();\n return flat.length <= SUMMARY_LIMIT ? flat : `${flat.slice(0, SUMMARY_LIMIT - 1).trimEnd()}…`;\n};\n\nconst index = (decl: Declaration): Indexed => {\n const description = decl.config.description ?? \"\";\n const summary = summarize(description);\n return {\n name: decl.name,\n summary,\n head: `${decl.name} ${summary}`.toLowerCase(),\n whole: `${decl.name} ${description}`.toLowerCase(),\n };\n};\n\n/**\n * Regular plural fold, guarded.\n *\n * The guards are the whole point: without them `status` searches for `statu`,\n * `class` for `clas` and `focus` for `focu`, and a three-letter term like `ios`\n * loses a third of itself.\n */\nconst singular = (term: string): string => {\n if (term.length <= SHORTEST_TERM) return term;\n if (term.endsWith(\"ss\") || term.endsWith(\"us\")) return term;\n return term.endsWith(\"s\") ? term.slice(0, -1) : term;\n};\n\nconst queryTerms = (query: string): string[] => {\n const seen = new Set<string>();\n const out: string[] = [];\n for (const raw of query.toLowerCase().split(/\\s+/)) {\n const term = raw.trim();\n if (term.length < SHORTEST_TERM || seen.has(term)) continue;\n seen.add(term);\n out.push(term);\n }\n return out;\n};\n\nconst rankTerm = (term: string, entry: Indexed): number => {\n const name = entry.name.toLowerCase();\n const folded = singular(term);\n const hit = (haystack: string): boolean => haystack.includes(term) || haystack.includes(folded);\n if (name === term) return RANK.exactName;\n if (name.startsWith(term)) return RANK.namePrefix;\n if (hit(name)) return RANK.nameSubstring;\n if (hit(entry.head)) return RANK.summary;\n if (hit(entry.whole)) return RANK.tail;\n return RANK.missing;\n};\n\ntype SearchResult = {\n rows: Indexed[];\n /** How many entries matched as well as the best ones, before the cap. */\n matched: number;\n /** Query words no returned row matched. Named back to the caller. */\n missed: string[];\n partial: boolean;\n};\n\n/**\n * Find tools for a query.\n *\n * An empty query returns the server's OWN order rather than an alphabetised\n * one: the registrars group related tools together, and sorting throws that\n * grouping away for no gain.\n *\n * Nothing may cache this keyed on the query — a row's tier is a property of the\n * query AND of the whole catalog it was ranked against.\n */\nconst find = (entries: Indexed[], query: string): SearchResult => {\n const terms = queryTerms(query);\n if (terms.length === 0) {\n return {\n rows: entries.slice(0, INDEX_LIMIT),\n matched: entries.length,\n missed: [],\n partial: false,\n };\n }\n\n const scored = entries.map((entry) => {\n let total = 0;\n let matched = 0;\n const missed: string[] = [];\n for (const term of terms) {\n const rank = rankTerm(term, entry);\n total += rank;\n if (rank === RANK.missing) missed.push(term);\n else matched += 1;\n }\n return { entry, total, matched, missed };\n });\n\n const best = scored.reduce((acc, s) => Math.max(acc, s.matched), 0);\n if (best === 0) return { rows: [], matched: 0, missed: terms, partial: false };\n\n const group = scored\n .filter((s) => s.matched === best)\n .toSorted((a, b) => a.total - b.total || a.entry.name.localeCompare(b.entry.name));\n const partial = best < terms.length;\n // Filtered from the ordered term list, never collected from a Set, so the\n // sentence a caller reads is the same on every run.\n const missed = partial ? terms.filter((t) => group.every((g) => g.missed.includes(t))) : [];\n return {\n rows: group.slice(0, partial ? PARTIAL_LIMIT : SEARCH_LIMIT).map((g) => g.entry),\n matched: group.length,\n missed,\n partial,\n };\n};\n\n/**\n * Suggestions for a name that is not in the catalog.\n *\n * Substring search cannot find a string that appears nowhere, so a typo needs\n * its own answer: shared underscore-separated words first, then the longest\n * common prefix.\n *\n * Both comparisons run on the name with `apple_<surface>_` REMOVED. Every tool\n * on a surface shares that prefix, so comparing whole names makes every tool\n * share two words with every other and \"did you mean\" answers with the first\n * few tools in the catalog — worse than saying nothing, because it reads like a\n * real suggestion. Measured before the fix: `apple_mail_send_messge` suggested\n * `apple_mail_list_accounts`.\n */\nconst nearest = (names: string[], wanted: string, prefix: string): string[] => {\n const strip = (n: string): string =>\n n.toLowerCase().startsWith(`${prefix}_`) ? n.slice(prefix.length + 1) : n;\n const target = strip(wanted);\n const words = new Set(\n target\n .toLowerCase()\n .split(\"_\")\n .filter((w) => w.length >= SHORTEST_TERM),\n );\n const shared = names.filter((n) =>\n strip(n)\n .toLowerCase()\n .split(\"_\")\n .some((w) => words.has(w)),\n );\n if (shared.length > 0) return shared.slice(0, 5);\n\n const common = (n: string): number => {\n const candidate = strip(n).toLowerCase();\n const lower = target.toLowerCase();\n let i = 0;\n while (i < candidate.length && i < lower.length && candidate[i] === lower[i]) i += 1;\n return i;\n };\n const ranked = names.toSorted((a, b) => common(b) - common(a) || a.localeCompare(b));\n /*\n * A one- or two-letter overlap is coincidence, not a near miss. Returning\n * nothing lets the caller read \"no tool named X\" as the whole answer and go\n * to the search tool, which is the move that actually works.\n */\n const bestName = ranked[0];\n if (bestName === undefined || common(bestName) < SHORTEST_TERM) return [];\n return ranked.filter((n) => common(n) >= SHORTEST_TERM).slice(0, 3);\n};\n\n// ─── rendering ───────────────────────────────────────────────────────────────\n\n/** The three names a search result has to point the caller at. */\ntype FacadeNames = { search: string; describe: string; call: string };\n\nconst renderSearch = (result: SearchResult, total: number, names: FacadeNames): string => {\n if (result.rows.length === 0) {\n return `No tool matches. ${total} tools are available — call ${names.search} with no query to list them all.`;\n }\n const rows = result.rows.map((r) => `${r.name} — ${r.summary}`).join(\"\\n\");\n let notice = \"\";\n if (result.partial) {\n // Naming the words that missed is the difference between a caller\n // rephrasing and a caller giving up and asking for the whole listing.\n const which = result.missed.length > 0 ? ` (no match for ${result.missed.join(\", \")})` : \"\";\n notice = `No tool matched every word${which}. Closest:\\n`;\n }\n // Three numbers kept apart on purpose: shown, matched, and the catalog total.\n const footer = `${result.rows.length} of ${total} tools. Read a schema with ${names.describe}, then run it with ${names.call}.`;\n return `${notice}${rows}\\n\\n${footer}`;\n};\n\n/**\n * A tool's declaration as a client would have received it.\n *\n * Built from the recorded zod shape rather than re-derived by hand, so\n * `describe` and a non-lazy listing cannot drift. `$schema` is dropped for the\n * same reason `listing.ts` drops it from `tools/list`.\n */\nconst describe = (decl: Declaration): Record<string, unknown> => {\n const shape = decl.config.inputSchema;\n const schema = shape ? (z.toJSONSchema(z.object(shape)) as Record<string, unknown>) : undefined;\n if (schema) delete schema[\"$schema\"];\n return {\n name: decl.name,\n ...(decl.config.description === undefined ? {} : { description: decl.config.description }),\n ...(schema === undefined ? {} : { inputSchema: schema }),\n ...(decl.config.annotations === undefined ? {} : { annotations: decl.config.annotations }),\n };\n};\n\n// ─── dispatch ────────────────────────────────────────────────────────────────\n\n/**\n * Run a recorded tool, reproducing the validation the SDK would have done.\n *\n * Under a facade the SDK never sees the real call, so it never parses the real\n * arguments. Skipping this would hand every handler unvalidated input — the one\n * way a facade can be actively less safe than the listing it replaced.\n */\nconst invoke = async (decl: Declaration, args: unknown, extra: unknown): Promise<unknown> => {\n const shape = decl.config.inputSchema;\n if (!shape) return (decl.handler as unknown as (e: unknown) => unknown)(extra);\n const parsed = await z.object(shape).safeParseAsync(args ?? {});\n if (!parsed.success) {\n const why = parsed.error.issues\n .map((i) => `${i.path.join(\".\") || \"(root)\"}: ${i.message}`)\n .join(\"; \");\n return fail(`Invalid arguments for ${decl.name}: ${why}`);\n }\n return (decl.handler as unknown as (a: unknown, e: unknown) => unknown)(parsed.data, extra);\n};\n\n// ─── the entry point ─────────────────────────────────────────────────────────\n\nexport type LazyToolsOptions = {\n /** Surface id as in surfaces.json, e.g. \"mail\". Prefixes every facade name. */\n surface: string;\n /** Display name, e.g. \"Mail\". Used only in the facade's own descriptions. */\n displayName: string;\n lazy: boolean;\n allowWrites: boolean;\n};\n\n/**\n * Register a surface's tools, either directly or behind the facade.\n *\n * The callback takes `allowWrites` rather than closing over it so the facade\n * can run it a second time with the gate forced shut and learn which tools are\n * writes. Everything else it needs — the client, the config — it closes over as\n * before.\n */\nexport const withLazyTools = (\n server: McpServer,\n opts: LazyToolsOptions,\n register: (target: McpServer, allowWrites: boolean) => void,\n): void => {\n if (!opts.lazy) {\n register(server, opts.allowWrites);\n return;\n }\n\n const all: Declaration[] = [];\n register(recorder(all), opts.allowWrites);\n\n let writeNames = new Set<string>();\n if (opts.allowWrites) {\n const readsOnly: Declaration[] = [];\n register(recorder(readsOnly), false);\n const readNames = new Set(readsOnly.map((d) => d.name));\n writeNames = new Set(all.filter((d) => !readNames.has(d.name)).map((d) => d.name));\n }\n\n const prefix = `apple_${opts.surface}`;\n\n /*\n * Diagnostics stays eagerly listed. It is the tool every surface guide points\n * at first when something returns `degraded: true` or a permission error, and\n * a model that has just been refused should not have to discover the search\n * tool before it can find out why. All eight cost 3,766 B combined.\n */\n /*\n * Everything the facade registers, as one callable — so it can be measured\n * before it is mounted.\n *\n * A facade is not free: it costs its own declarations, and it buys back only\n * what the real listing would have cost. On a small surface that trade is a\n * loss, and the loss is invisible because both halves look like a working\n * facade. See the floor below.\n */\n const mount = (target: McpServer): void => {\n const eagerName = `${prefix}_diagnostics`;\n const eager = all.filter((d) => d.name === eagerName);\n for (const decl of eager)\n target.registerTool(decl.name, decl.config as never, decl.handler as never);\n\n const lazy = all.filter((d) => d.name !== eagerName);\n const reads = lazy.filter((d) => !writeNames.has(d.name));\n const writes = lazy.filter((d) => writeNames.has(d.name));\n const byName = new Map(lazy.map((d) => [d.name, d]));\n const readIndex = reads.map(index);\n const writeIndex = writes.map(index);\n const allIndex = [...readIndex, ...writeIndex];\n\n const searchName = `${prefix}_search_tools`;\n const describeName = `${prefix}_describe_tool`;\n const callName = `${prefix}_call_tool`;\n const callWriteName = `${prefix}_call_write_tool`;\n\n target.registerTool(\n searchName,\n {\n description:\n `Find ${opts.displayName} tools by what you want to do. This server loads its ` +\n `${allIndex.length} tools on demand: they are not listed up front, and this is how you ` +\n `reach them. Returns matching tool names with a one-line summary each. Call with no ` +\n `query to list everything. Read a schema with ${describeName}, then run it with ` +\n `${callName}` +\n (writes.length > 0 ? ` or ${callWriteName}` : \"\") +\n `.`,\n inputSchema: {\n query: z\n .string()\n .optional()\n .describe(\"What you want to do, e.g. 'search messages' or 'unread'. Omit to list all.\"),\n },\n annotations: { readOnlyHint: true, idempotentHint: true },\n },\n ({ query }) =>\n okText(\n renderSearch(find(allIndex, query ?? \"\"), allIndex.length, {\n search: searchName,\n describe: describeName,\n call: callName,\n }),\n ),\n );\n\n target.registerTool(\n describeName,\n {\n description:\n `Get the full description and input schema of one ${opts.displayName} tool, as it would ` +\n `have appeared in a normal tool listing. Find names with ${searchName} first.`,\n inputSchema: {\n name: z.string().describe(`Exact tool name, e.g. ${lazy[0]?.name ?? callName}.`),\n },\n annotations: { readOnlyHint: true, idempotentHint: true },\n },\n ({ name }) => {\n const decl = byName.get(name);\n if (!decl) {\n const suggestions = nearest([...byName.keys()], name, prefix);\n return fail(\n `No tool named ${name}.` +\n (suggestions.length > 0 ? ` Did you mean: ${suggestions.join(\", \")}?` : \"\"),\n );\n }\n const runner = writeNames.has(name) ? callWriteName : callName;\n return okText(\n `${JSON.stringify(describe(decl))}\\n\\nCall it with ${runner}: {\"name\": ${JSON.stringify(name)}, \"arguments\": {…}}.`,\n );\n },\n );\n\n target.registerTool(\n callName,\n {\n description:\n `Run one of this server's read-only ${opts.displayName} tools. Find a name with ` +\n `${searchName} and its arguments with ${describeName}. Reads only: it cannot reach ` +\n (writes.length > 0\n ? `anything that changes ${opts.displayName} — those go through ${callWriteName}.`\n : `anything that changes ${opts.displayName}, and this server has writes turned off.`),\n inputSchema: {\n name: z.string().describe(\"Exact tool name to run.\"),\n arguments: z\n .record(z.string(), z.unknown())\n .optional()\n .describe(\"That tool's arguments.\"),\n },\n annotations: { readOnlyHint: true },\n },\n async ({ name, arguments: args }, extra) => {\n const decl = byName.get(name);\n if (!decl) {\n const suggestions = nearest([...byName.keys()], name, prefix);\n return fail(\n `No tool named ${name}.` +\n (suggestions.length > 0 ? ` Did you mean: ${suggestions.join(\", \")}?` : \"\"),\n );\n }\n if (writeNames.has(name)) {\n return fail(\n `${name} changes ${opts.displayName}, so it cannot be run through ${callName}. Use ${callWriteName}.`,\n );\n }\n return (await invoke(decl, args, extra)) as ToolResult;\n },\n );\n\n /*\n * Absent, not present-and-empty, when the gate is shut. A dispatcher that\n * exists and refuses everything is exactly the \"registered but refuses\" shape\n * docs/alternatives.md holds against a competitor.\n */\n if (writes.length > 0) {\n // Named in full rather than counted. A permission prompt that says only\n // \"a Mail write tool\" tells the person approving it nothing about the\n // blast radius, and this description is the only place left that can.\n const writeList = writes.map((d) => d.name.slice(prefix.length + 1)).join(\", \");\n target.registerTool(\n callWriteName,\n {\n description:\n `Run one of this server's ${writes.length} ${opts.displayName} tools that CHANGE data ` +\n `— ${writeList}. Find arguments ` +\n `with ${describeName}. Read-only tools go through ${callName} instead.`,\n inputSchema: {\n name: z.string().describe(\"Exact tool name to run.\"),\n arguments: z\n .record(z.string(), z.unknown())\n .optional()\n .describe(\"That tool's arguments.\"),\n },\n annotations: { readOnlyHint: false, destructiveHint: true },\n },\n async ({ name, arguments: args }, extra) => {\n const decl = byName.get(name);\n if (!decl) {\n const suggestions = nearest([...writeNames], name, prefix);\n return fail(\n `No tool named ${name}.` +\n (suggestions.length > 0 ? ` Did you mean: ${suggestions.join(\", \")}?` : \"\"),\n );\n }\n if (!writeNames.has(name)) {\n return fail(`${name} is read-only. Use ${callName}.`);\n }\n return (await invoke(decl, args, extra)) as ToolResult;\n },\n );\n }\n };\n\n /*\n * A listing too small to be worth searching is served whole, whatever the\n * switch says.\n *\n * Both terms have to hold, and the count is the one that decides the real\n * cases. Measured on this repo's own servers with writes on: contacts is 7\n * tools against a 5-declaration facade and messages is 8 — 1.4x and 1.6x,\n * so neither clears the count — while both clear the bytes comfortably\n * (2.6x and 3.8x), because a handful of fat schemas outweighs a facade made\n * of prose. A floor counting bytes alone would front them and buy nothing\n * but a coarser permission prompt: the client stops seeing\n * `apple_contacts_search_contacts` in its approval dialog and starts seeing\n * \"reads on Contacts\".\n *\n * Bytes are the wire entry as `describe` renders it, which is the same unit\n * the ~106 KB measurement at the top of this file is quoted in.\n */\n const facade: Declaration[] = [];\n mount(recorder(facade));\n const wire = (decls: Declaration[]): number =>\n decls.reduce((n, d) => n + JSON.stringify(describe(d)).length, 0);\n\n if (all.length < FRONTING_RATIO * facade.length || wire(all) < FRONTING_RATIO * wire(facade)) {\n register(server, opts.allowWrites);\n return;\n }\n\n mount(server);\n};\n","import { accessSync, constants, statSync } from \"node:fs\";\n\n/**\n * Facts about a TCC-protected file.\n *\n * The subtlety this exists to encode: `statSync` **succeeds** on a\n * TCC-protected file — you get the real size and mtime — and only `open(2)` and\n * `access(2)` are denied. Existence and readability are therefore different\n * questions, and only readability tells you whether Full Disk Access is\n * granted. \"The file is there\" is not evidence.\n */\nexport type FileFacts = {\n exists: boolean;\n readable: boolean;\n size: number | null;\n mtime: string | null;\n};\n\nexport const inspectFile = (path: string): FileFacts => {\n let size: number | null = null;\n let mtime: string | null = null;\n try {\n const st = statSync(path);\n size = st.size;\n mtime = st.mtime.toISOString();\n } catch {\n return { exists: false, readable: false, size: null, mtime: null };\n }\n let readable = false;\n try {\n accessSync(path, constants.R_OK);\n readable = true;\n } catch {\n readable = false;\n }\n return { exists: true, readable, size, mtime };\n};\n\nexport type StoreFacts = FileFacts & {\n /** Whether a `-wal` sits beside it, i.e. whether `immutable=1` COULD miss recent writes. */\n walPresent: boolean;\n walSizeBytes: number | null;\n};\n\n/**\n * Describe a SQLite store and its write-ahead log in one call.\n *\n * The WAL matters because `immutable=1` skips it, so a read can silently miss\n * whatever has not been checkpointed — which is precisely the recent data an\n * agent is usually asked about.\n */\nexport const describeStore = (path: string): StoreFacts => {\n const facts = inspectFile(path);\n const wal = inspectFile(`${path}-wal`);\n return { ...facts, walPresent: wal.exists, walSizeBytes: wal.size };\n};\n","/**\n * The only place in this family of servers that spawns a process.\n *\n * ## Why this is shared rather than copied\n *\n * Two of the guarantees below are security invariants, and an invariant that\n * exists in two copies is one refactor away from existing in one:\n *\n * * `assertStaticScript` is a shell-injection tripwire.\n * * `createQueue` serialises Apple Events, without which -1712 floods.\n *\n * ## Why this is safe\n *\n * `execFile`, never `exec` — there is no shell, so there is no quoting question\n * to get wrong.\n *\n * More importantly: **no caller input is ever interpolated into script text**.\n * The script is a static constant piped to osascript's stdin (`-`), and every\n * variable value arrives as `argv[0]` — a single JSON string that the script\n * parses. Verified against a live Mail: an account name of\n *\n * \"; do shell script \"touch /tmp/pwned\"; //\n *\n * arrives at `run(argv)` as inert data and creates no file. A mailbox named\n * after a shell metacharacter is data, not syntax, and there is no code path\n * where that changes.\n *\n * `assertStaticScript` is the tripwire that keeps it that way: a script string\n * containing `${` means someone reached for a template interpolation, which is\n * exactly the mistake this design exists to prevent.\n */\n\nimport { execFile } from \"node:child_process\";\n\nimport {\n AppBusyError,\n AppNotRunningError,\n OsascriptTimeoutError,\n PlatformError,\n ProtocolError,\n TccDeniedError,\n type SurfaceContext,\n} from \"./errors.js\";\n\nexport type Logger = {\n debug?: (...args: unknown[]) => void;\n warn?: (...args: unknown[]) => void;\n error?: (...args: unknown[]) => void;\n};\n\n/** The envelope every JXA script returns. Application failures come back on exit 0. */\nexport type JxaEnvelope<T> =\n | { ok: true; data: T }\n // Extra keys on `error` are carried through onto the thrown error's details.\n // Messages' send ladder uses this to report which targeting strategies were\n // tried and why each failed, which is the only diagnostic that surface has.\n | { ok: false; error: { code: string; message: string; [key: string]: unknown } };\n\nexport type OsascriptRunner = {\n /** Run a static script with one JSON-serialisable parameter object. */\n run: <T>(script: string, params?: unknown) => Promise<T>;\n};\n\n/**\n * The process boundary, as a seam. Tests substitute this so that everything\n * above it — the queue, the static-script tripwire, argv construction and\n * envelope handling — still runs for real; mocking `run` itself would skip\n * exactly the code these guarantees live in.\n */\nexport type ExecImpl = (\n path: string,\n args: string[],\n script: string,\n timeoutMs: number,\n) => Promise<string>;\n\nexport type OsascriptOptions = {\n osascriptPath: string;\n timeoutMs: number;\n /** Named in every user-facing error this module can throw. */\n surface: SurfaceContext;\n logger?: Logger | undefined;\n exec?: ExecImpl | undefined;\n};\n\nconst MAX_BUFFER = 32 * 1024 * 1024;\n\n/**\n * Reject any script that looks like it was built by interpolation. Crude on\n * purpose: the cost of a false positive is renaming a variable, and the cost of\n * a false negative is a shell injection.\n */\nexport const assertStaticScript = (script: string): void => {\n if (script.includes(\"${\")) {\n throw new PlatformError(\n \"Refusing to run a JXA script containing `${`. Scripts must be static constants; \" +\n \"pass every value through the params object, which arrives as argv[0].\",\n );\n }\n};\n\n/** Map osascript's trailing `(-NNNN)` error code onto something actionable. */\nexport const mapOsaError = (stderr: string, timeoutMs: number, surface: SurfaceContext): Error => {\n const code = /\\((-\\d{3,4})\\)\\s*$/.exec(stderr.trim())?.[1];\n switch (code) {\n case \"-1743\":\n return new TccDeniedError(surface);\n case \"-600\":\n case \"-609\":\n return new AppNotRunningError(surface);\n case \"-1712\":\n return new AppBusyError(surface);\n default:\n break;\n }\n const thrown = /execution error:\\s*(?:Error:\\s*)?(.+?)\\s*\\(-?\\d+\\)\\s*$/m.exec(stderr.trim())?.[1];\n return new ProtocolError(\n thrown ?? stderr.trim().slice(0, 500) ?? `osascript failed (${timeoutMs}ms budget)`,\n );\n};\n\n/**\n * Serialise every invocation. Apple Event dispatch is single-threaded per app:\n * concurrent calls do not finish sooner, they just make -1712 (busy) likelier.\n * Batch within one script instead of parallelising across several.\n */\nconst createQueue = () => {\n let tail: Promise<unknown> = Promise.resolve();\n return <T>(job: () => Promise<T>): Promise<T> => {\n const next = tail.then(job, job);\n tail = next.catch(() => undefined);\n return next;\n };\n};\n\nconst defaultExec =\n (surface: SurfaceContext): ExecImpl =>\n (path, args, script, timeoutMs) =>\n new Promise((resolve, reject) => {\n const child = execFile(\n path,\n args,\n {\n timeout: timeoutMs,\n maxBuffer: MAX_BUFFER,\n killSignal: \"SIGKILL\",\n encoding: \"utf8\",\n // Inherit nothing. osascript needs no environment, and a minimal one\n // removes any question of PATH or locale influencing the run.\n env: { PATH: \"/usr/bin:/bin\" },\n },\n (err, stdout, stderr) => {\n if (!err) {\n resolve(stdout);\n return;\n }\n const killed = (err as NodeJS.ErrnoException & { killed?: boolean }).killed;\n if (killed) {\n reject(new OsascriptTimeoutError(timeoutMs, surface));\n return;\n }\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") {\n reject(\n new PlatformError(\n `${path} not found. This server only runs on macOS with ${surface.appName} installed.`,\n ),\n );\n return;\n }\n reject(mapOsaError(stderr || String(err.message), timeoutMs, surface));\n },\n );\n child.stdin?.end(script);\n });\n\nexport const createOsascriptRunner = (opts: OsascriptOptions): OsascriptRunner => {\n const enqueue = createQueue();\n const exec = opts.exec ?? defaultExec(opts.surface);\n\n const run = async <T>(script: string, params?: unknown): Promise<T> => {\n assertStaticScript(script);\n const args = [\"-l\", \"JavaScript\", \"-\", JSON.stringify(params ?? {})];\n const stdout = await enqueue(() => exec(opts.osascriptPath, args, script, opts.timeoutMs));\n\n let envelope: JxaEnvelope<T>;\n try {\n envelope = JSON.parse(stdout) as JxaEnvelope<T>;\n } catch {\n throw new ProtocolError(`osascript returned non-JSON output: ${stdout.slice(0, 500)}`);\n }\n\n if (!envelope.ok) {\n // Application-level failures come back on exit 0 so that a non-zero exit\n // unambiguously means infrastructure. Re-inflate them into real errors.\n const { code, message, ...rest } = envelope.error;\n // MAIL_NOT_RUNNING predates the generic name and is still emitted by the\n // Mail prelude; both mean the same thing.\n if (code === \"APP_NOT_RUNNING\" || code === \"MAIL_NOT_RUNNING\") {\n throw new AppNotRunningError(opts.surface);\n }\n if (code === \"NOT_AUTHORIZED\") throw new TccDeniedError(opts.surface);\n throw new ProtocolError(message, { code, ...rest });\n }\n\n opts.logger?.debug?.(\"osascript ok\");\n return envelope.data;\n };\n\n return { run };\n};\n\n/** Retry a busy failure once. Apps return -1712 while mid-sync and succeed moments later. */\nexport const withBusyRetry = async <T>(fn: () => Promise<T>, delayMs = 1500): Promise<T> => {\n try {\n return await fn();\n } catch (err) {\n if (!(err instanceof AppBusyError)) throw err;\n await new Promise((r) => setTimeout(r, delayMs));\n return fn();\n }\n};\n","import type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\n\n/**\n * The surface resources.\n *\n * ## Why a resource when a tool already returns this\n *\n * `apple_mail_diagnostics` and `apple_mail_list_accounts` answer these same\n * questions, and they stay. What they cannot do is be *addressed*. A tool result\n * exists only after the model decided to spend a call on it, which means the\n * account list is re-derived every session and the diagnostics report is read\n * after the failure rather than before it. A resource is a URI: a host can\n * attach it, cache it, or let a user paste it in, and none of that costs a tool\n * call or depends on the model guessing that it should look.\n *\n * ## The scheme is `cupertino://`, not `apple://`\n *\n * The tools are named `apple_mail_*` because they say what they drive. A URI\n * scheme is a different kind of claim — it is a namespace, and taking Apple's\n * would read as affiliation this project spends a README line disclaiming. So\n * the authority is the surface id and the scheme is the project's own name, the\n * one already in the bundle identifier.\n *\n * ## Three per surface, and only one of them can fail\n *\n * - `guide` is static text. It needs no permission, touches no store and spawns\n * no process, so it is readable when every other lane is denied — which is\n * exactly the moment its contents are worth reading.\n * - `diagnostics` is the live capability report.\n * - `inventory` is the set of containers you address by name: accounts, and\n * whatever the surface calls its folders. Surfaces with no such containers\n * (Messages, Safari) register two resources rather than inventing a third.\n */\n\nexport const RESOURCE_SCHEME = \"cupertino\";\n\n/** `cupertino://mail/guide`. The one place this string is built. */\nexport const surfaceUri = (surface: string, leaf: string): string =>\n `${RESOURCE_SCHEME}://${surface}/${leaf}`;\n\nexport type ResourceReader = () => Promise<unknown>;\n\nexport type SurfaceResourceOptions = {\n /** Surface id as it appears in surfaces.json, e.g. \"mail\". */\n surface: string;\n /** Display name, e.g. \"Mail\". Used only in resource titles. */\n displayName: string;\n /** The operating manual. Static markdown — see `guide.ts` in each surface. */\n guide: string;\n /** The live capability report, normally the diagnostics tool's own payload. */\n diagnostics: ResourceReader;\n /** The addressable containers. Omitted by surfaces that have none. */\n inventory?: {\n /** What this surface calls them, e.g. \"accounts and mailboxes\". */\n describes: string;\n read: ResourceReader;\n };\n};\n\nconst jsonContents = (uri: string, data: unknown) => ({\n contents: [\n {\n uri,\n mimeType: \"application/json\",\n text: JSON.stringify(data),\n },\n ],\n});\n\n/**\n * Read a resource without ever throwing.\n *\n * A tool that fails returns `isError` and keeps its text; a resource read that\n * throws becomes a JSON-RPC error and keeps nothing. That asymmetry is worst\n * precisely on `diagnostics`, the resource whose whole job is to explain a\n * broken machine: letting a TCC denial replace the report with \"resource read\n * failed\" would delete the answer at the only moment anyone wants it.\n *\n * So a failed read is *data*, shaped like the `degraded` results the tools\n * already return, and the caller can tell an unreadable store from an empty one.\n */\nconst guardedRead = async (surface: string, uri: string, read: ResourceReader) => {\n try {\n return jsonContents(uri, await read());\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n const details = (err as Error & { details?: unknown })?.details;\n return jsonContents(uri, {\n degraded: true,\n error: message,\n ...(err instanceof Error ? { kind: err.name } : {}),\n ...(details ? { details } : {}),\n hint: `Read ${surfaceUri(surface, \"diagnostics\")} for what this server can currently reach.`,\n });\n }\n};\n\n/** Register a surface's guide, diagnostics and (where it has one) inventory. */\nexport const registerSurfaceResources = (server: McpServer, opts: SurfaceResourceOptions): void => {\n const { surface, displayName, guide, diagnostics, inventory } = opts;\n\n const guideUri = surfaceUri(surface, \"guide\");\n server.registerResource(\n `${surface}-guide`,\n guideUri,\n {\n title: `${displayName}: how to drive this server`,\n description:\n `How to use the ${displayName} tools well: what each ref means, which tool to reach for ` +\n \"under which constraint, what a degraded result does and does not say, and what the \" +\n \"write gate is currently hiding. Static text — readable even with every permission denied.\",\n mimeType: \"text/markdown\",\n },\n (uri) => ({ contents: [{ uri: uri.href, mimeType: \"text/markdown\", text: guide }] }),\n );\n\n const diagnosticsUri = surfaceUri(surface, \"diagnostics\");\n server.registerResource(\n `${surface}-diagnostics`,\n diagnosticsUri,\n {\n title: `${displayName}: capabilities and permissions`,\n description:\n `What this ${displayName} server can currently do and why — the same report as the ` +\n \"diagnostics tool, addressable without spending a tool call. Read it before trusting an \" +\n \"empty result.\",\n mimeType: \"application/json\",\n },\n () => guardedRead(surface, diagnosticsUri, diagnostics),\n );\n\n if (!inventory) return;\n\n const inventoryUri = surfaceUri(surface, \"inventory\");\n server.registerResource(\n `${surface}-inventory`,\n inventoryUri,\n {\n title: `${displayName}: ${inventory.describes}`,\n description:\n `The ${inventory.describes} this server can see, spelled exactly as ${displayName} ` +\n \"spells them. These are the names every other tool takes, so reading this first is the \" +\n \"difference between a filter that matches and one that silently matches nothing.\",\n mimeType: \"application/json\",\n },\n () => guardedRead(surface, inventoryUri, inventory.read),\n );\n};\n","import type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport type { GetPromptResult } from \"@modelcontextprotocol/sdk/types.js\";\nimport { z } from \"zod\";\n\nimport { surfaceUri } from \"./resources.js\";\n\n/**\n * The workflow prompts.\n *\n * ## What a prompt is for here\n *\n * The same thing every tool description in this repo is for: holding a\n * constraint the model would otherwise re-derive. A tool holds the ones that\n * are about *one call* — that a body search wants a narrowing filter, that a\n * ref is opaque. A prompt holds the ones that are about the *order of calls*,\n * and those have nowhere else to live. \"Search before you list\", \"read the\n * thread before you answer it\", \"check what exists before you create a\n * duplicate\" are not properties of any single tool, so no single tool\n * description can carry them, and the model rebuilds them from scratch every\n * session — usually correctly, sometimes not, always at a cost.\n *\n * ## Every prompt embeds its surface guide\n *\n * A prompt returns messages, and one of them is the `cupertino://<surface>/guide`\n * resource. That is the coupling that makes both primitives worth more than\n * either alone: the guide is the reference, the prompt is the task, and a host\n * that expands the prompt gets both without the model having to know the guide\n * exists.\n *\n * ## Write-gated prompts follow the tools\n *\n * A prompt that ends in a mutation is registered only when writes are on, for\n * the same reason the mutating tools are: with the gate closed it must not\n * merely refuse, it must be *invisible*. A visible `draft_reply` on a\n * read-only server is an offer the server cannot keep.\n */\n\n/** MCP prompt arguments are strings on the wire. This is the only shape they take. */\nexport const promptArg = (description: string): z.ZodOptional<z.ZodString> =>\n z.string().optional().describe(description);\n\n/** Same, for an argument the prompt is useless without. */\nexport const requiredPromptArg = (description: string): z.ZodString =>\n z.string().min(1).describe(description);\n\nexport type PromptContext = {\n /** Surface id, e.g. \"mail\". */\n surface: string;\n /** The static guide, embedded ahead of every prompt's instruction. */\n guide: string;\n};\n\nexport type WorkflowPrompt<Args extends z.ZodRawShape> = {\n /** Namespaced like the tools, e.g. \"apple_mail_triage\". */\n name: string;\n title: string;\n /** What this does and when to reach for it. Shown in the host's prompt list. */\n description: string;\n argsSchema?: Args;\n /**\n * The instruction. Receives validated arguments; returns the text that does\n * the actual work of ordering the calls.\n */\n build: (args: { [K in keyof Args]: z.infer<Args[K]> }) => string;\n};\n\n/**\n * Register one workflow prompt.\n *\n * Called once per prompt rather than handed an array, because the argument\n * shape is generic and an array of prompts with differing shapes loses the\n * inference that makes `build`'s parameter typed at all.\n */\nexport const registerWorkflowPrompt = <Args extends z.ZodRawShape>(\n server: McpServer,\n ctx: PromptContext,\n prompt: WorkflowPrompt<Args>,\n): void => {\n const guideUri = surfaceUri(ctx.surface, \"guide\");\n\n const result = (instruction: string): GetPromptResult => ({\n messages: [\n {\n role: \"user\",\n content: {\n type: \"resource\",\n resource: { uri: guideUri, mimeType: \"text/markdown\", text: ctx.guide },\n },\n },\n { role: \"user\", content: { type: \"text\", text: instruction } },\n ],\n });\n\n server.registerPrompt(\n prompt.name,\n {\n title: prompt.title,\n description: prompt.description,\n ...(prompt.argsSchema ? { argsSchema: prompt.argsSchema } : {}),\n },\n // The SDK hands an args object only when a schema was declared. Both\n // callback arities are assignable here; the cast keeps one code path.\n ((args: { [K in keyof Args]: z.infer<Args[K]> }) =>\n result(prompt.build(args ?? ({} as never)))) as never,\n );\n};\n","import { z } from \"zod\";\n\n/**\n * Projection and aggregation for the read-only `*_query` tools.\n *\n * These exist to keep answers out of the context window that a model would\n * otherwise have to derive by reading rows. \"Who emailed me most in June\" is\n * one grouped table here; through a plain search it is two hundred message\n * summaries the model has to tally itself, and it pays for every subject,\n * recipient list and flag on the way.\n *\n * Nothing here builds SQL. Callers pass a field name that their surface has\n * already matched against its own allowlist, and the surface maps it to a\n * column expression. A caller's string never reaches a query.\n */\n\nexport const selectArg = z\n .array(z.string().min(1))\n .min(1)\n .optional()\n .describe(\n \"Keep only these fields on each row. Omit for the full row. Naming the two or three \" +\n \"fields you actually need is the cheapest way to shrink a large result.\",\n );\n\n/**\n * Build the `groupBy` arg from the fields a surface can actually group on.\n *\n * Taken as a parameter rather than fixed here because the sensible groupings\n * differ per surface — mail groups by sender, a calendar by day.\n */\nexport const groupByArg = <const F extends readonly [string, ...string[]]>(fields: F, note = \"\") =>\n z\n .enum(fields)\n .optional()\n .describe(\n `Aggregate instead of returning rows: count matches per ${fields.join(\", \")}. ` +\n \"Grouping runs over every match, not just the first `limit` of them — `limit` then \" +\n `caps how many groups come back, ordered by descending count.${note ? ` ${note}` : \"\"}`,\n );\n\nexport type Bucket = {\n /** The grouped value. Null when the underlying field is null (no sender, etc.). */\n key: string | null;\n /** A display form of `key` where one exists — a sender's name, a mailbox's title. */\n label?: string | null;\n count: number;\n} & Record<string, unknown>;\n\nexport type Aggregation = {\n groupedBy: string;\n groups: Bucket[];\n /** How many distinct groups matched, before `limit` cut the list. */\n totalGroups: number;\n /** How many underlying rows were aggregated. NOT capped by `limit`. */\n totalRows: number;\n /** True when `totalGroups` exceeded `limit`, so `groups` is a top-N. */\n truncated: boolean;\n};\n\n/**\n * Assemble the aggregation envelope.\n *\n * Thin on purpose, but it is the one place `truncated` is computed and the one\n * guarantee that `totalRows` is always reported. A grouped result that omits\n * `totalRows` reads as complete whether or not it is, which is the failure this\n * whole shape exists to prevent: a top-N over a truncated page is a confidently\n * wrong answer, and it looks exactly like a right one.\n */\nexport const describeAggregation = (\n groupedBy: string,\n groups: Bucket[],\n totals: { totalGroups: number; totalRows: number },\n): Aggregation => ({\n groupedBy,\n groups,\n totalGroups: totals.totalGroups,\n totalRows: totals.totalRows,\n truncated: totals.totalGroups > groups.length,\n});\n\nexport type Projected<T> = {\n rows: Partial<T>[];\n /**\n * Field names the caller asked for that this surface does not have. Reported\n * rather than dropped: a silent drop looks identical to \"that field was null\n * on every row\", and a model has no way to tell the two apart.\n */\n unknownFields?: string[];\n};\n\n/**\n * Keep only the named fields on each row.\n *\n * `known` is passed explicitly rather than read off the rows because an empty\n * result still has to be able to say a field name was wrong — deriving the key\n * set from the rows would report nothing at all on the case where a typo is\n * most likely to be the reason the result is empty.\n */\nexport const project = <T extends Record<string, unknown>>(\n rows: T[],\n select: string[] | undefined,\n known: readonly string[],\n): Projected<T> => {\n if (!select?.length) return { rows };\n\n const knownSet = new Set(known);\n const wanted = select.filter((f) => knownSet.has(f));\n const unknownFields = select.filter((f) => !knownSet.has(f));\n\n // Every name was wrong. Projecting to {} would hand back a wall of empty\n // objects; the full row plus the complaint is the more useful answer.\n if (!wanted.length) return { rows, unknownFields };\n\n return {\n rows: rows.map((row) => {\n const out: Partial<T> = {};\n for (const field of wanted) {\n if (field in row) out[field as keyof T] = row[field as keyof T];\n }\n return out;\n }),\n ...(unknownFields.length ? { unknownFields } : {}),\n };\n};\n","import { createHash } from \"node:crypto\";\nimport type { DatabaseSync } from \"node:sqlite\";\n\n/**\n * Schema introspection for stores Apple owns and can reshape in any release.\n *\n * Nothing here assumes a column exists. The failure mode being avoided is a\n * `SELECT *` that starts throwing after a system update and takes the whole\n * server down with it.\n */\n\n/** 2001-01-01T00:00:00Z in Unix seconds — the Core Data epoch. */\nexport const CORE_DATA_EPOCH_OFFSET = 978_307_200;\n\nexport const columnsOf = (db: DatabaseSync, table: string): string[] => {\n try {\n return (db.prepare(`PRAGMA table_info(\"${table}\")`).all() as { name: string }[]).map(\n (c) => c.name,\n );\n } catch {\n return [];\n }\n};\n\n/** Every table and its columns, for capability checks that name what is missing. */\nexport const tableMap = (db: DatabaseSync): Record<string, string[]> => {\n const names = (\n db.prepare(\"SELECT name FROM sqlite_master WHERE type = 'table'\").all() as { name: string }[]\n ).map((r) => r.name);\n const tables: Record<string, string[]> = {};\n for (const t of names) tables[t] = columnsOf(db, t);\n return tables;\n};\n\n/**\n * A short hash of the whole DDL. Cheap drift detection: when Apple reshapes the\n * schema this changes, which turns \"why did queries start failing after the\n * update\" into a value you can compare against what was captured.\n */\nexport const fingerprintSchema = (db: DatabaseSync): string => {\n const ddl = db\n .prepare(\"SELECT sql FROM sqlite_master WHERE sql IS NOT NULL ORDER BY type, name\")\n .all() as { sql: string }[];\n return createHash(\"sha256\")\n .update(ddl.map((r) => r.sql).join(\"\\n\"))\n .digest(\"hex\")\n .slice(0, 12);\n};\n\n/**\n * Work out whether a timestamp column is Unix seconds or Core Data seconds by\n * seeing which reading lands near today. Two independent prior-art projects\n * disagree about this for Mail, and hardcoding the wrong one puts every date 31\n * years out — a bug that looks like corruption rather than a unit mismatch.\n */\nexport const detectEpoch = (\n maxTimestamp: number | null,\n now: number = Date.now(),\n): { offset: number; reason: string } => {\n if (maxTimestamp === null || !Number.isFinite(maxTimestamp) || maxTimestamp <= 0) {\n return { offset: 0, reason: \"no dated rows; assuming unix seconds\" };\n }\n const nowSec = now / 1000;\n const tenYears = 10 * 365.25 * 24 * 3600;\n const asUnix = Math.abs(nowSec - maxTimestamp);\n const asCoreData = Math.abs(nowSec - (maxTimestamp + CORE_DATA_EPOCH_OFFSET));\n\n if (asUnix < tenYears && asUnix <= asCoreData) {\n return { offset: 0, reason: \"raw value lands within 10 years of now\" };\n }\n if (asCoreData < tenYears) {\n return {\n offset: CORE_DATA_EPOCH_OFFSET,\n reason: \"value + 978307200 lands within 10 years of now\",\n };\n }\n return {\n offset: 0,\n reason: `neither epoch lands near now (max=${maxTimestamp}); assuming unix`,\n };\n};\n","import { DatabaseSync } from \"node:sqlite\";\n\nimport { IndexUnavailableError } from \"./errors.js\";\n\n/**\n * Read-only access to a store some Apple app owns.\n *\n * Two rules, both load-bearing:\n *\n * 1. **Never write.** The app owns the database, holds it open, and reconciles\n * it against a server. `PRAGMA query_only` makes that structural rather than\n * a matter of everyone remembering.\n * 2. **Prefer `mode=ro` over `immutable=1`.** `immutable=1` tells SQLite the\n * file cannot change and to skip the `-wal` entirely — so a read silently\n * misses anything not yet checkpointed. Measured on a live Mail index: the\n * two modes reported 181427 and 181426 messages minutes after reporting the\n * same number, the difference being one newly-arrived mail. It is a race you\n * lose intermittently and without any error, which is the worst kind.\n */\nexport type ReadOnlyMode = \"auto\" | \"ro\" | \"immutable\" | \"off\";\n\nexport type OpenedStore<T = undefined> = {\n db: DatabaseSync;\n /** Which mode actually opened. `immutable` results are WAL-blind — say so. */\n mode: \"ro\" | \"immutable\";\n /** Whatever `validate` returned, so a capability probe is not run twice. */\n validated: T;\n};\n\n/**\n * SQLite URI filenames need percent-encoding, and Mail's path contains a space\n * (\"Envelope Index\"). `?` and `#` would otherwise be read as URI syntax.\n */\nexport const toFileUri = (path: string, query: string): string =>\n `file:${encodeURI(path).replaceAll(\"?\", \"%3f\").replaceAll(\"#\", \"%23\")}?${query}`;\n\n/** Escape LIKE wildcards so a value containing % or _ searches literally. */\nexport const escapeLike = (value: string): string =>\n value.replaceAll(\"\\\\\", \"\\\\\\\\\").replaceAll(\"%\", \"\\\\%\").replaceAll(\"_\", \"\\\\_\");\n\nexport type OpenOptions<T> = {\n /** Named in the error when `mode` is \"off\", so the message says how to re-enable. */\n envVar?: string | undefined;\n /** What the store is, for the failure message, e.g. \"Mail's search index\". */\n label?: string | undefined;\n /** Appended to the failure message — typically how to grant the missing permission. */\n hint?: string | undefined;\n /**\n * Runs on every attempt; throwing rejects that attempt and tries the next\n * mode. Validating *inside* the ladder rather than after it matters: a store\n * that opens but is unusable should fall through, not be returned.\n */\n validate?: ((db: DatabaseSync) => T) | undefined;\n /**\n * Errors that no other open mode could fix, so the ladder aborts instead of\n * masking them behind a generic \"could not open\".\n */\n fatal?: ((err: unknown) => boolean) | undefined;\n /** Called when the WAL-blind fallback is what actually opened. */\n onFallback?: (() => void) | undefined;\n};\n\nexport const openReadOnly = <T = undefined>(\n path: string,\n mode: ReadOnlyMode,\n opts: OpenOptions<T> = {},\n): OpenedStore<T> => {\n if (mode === \"off\") {\n throw new IndexUnavailableError(\n `The index lane is disabled${opts.envVar ? ` (${opts.envVar}=off)` : \"\"}.`,\n );\n }\n\n const attempts: (\"ro\" | \"immutable\")[] =\n mode === \"auto\" ? [\"ro\", \"immutable\"] : [mode === \"ro\" ? \"ro\" : \"immutable\"];\n\n let lastError: unknown = null;\n for (const attempt of attempts) {\n // Declared outside the try so the catch can close it. `validate` runs\n // against an OPEN handle and every surface passes one — a schema drift or a\n // locked store threw straight past the close, leaking a descriptor onto a\n // file the Apple app itself holds open, on the `ro` attempt AND on the\n // fatal path that rethrows.\n let db: DatabaseSync | undefined;\n try {\n const uri = toFileUri(path, attempt === \"ro\" ? \"mode=ro\" : \"immutable=1\");\n db = new DatabaseSync(uri, { readOnly: true, allowExtension: false });\n // Belt and braces: no caller can issue DML even by accident.\n db.exec(\"PRAGMA query_only = 1\");\n const validated = opts.validate?.(db) as T;\n if (attempt === \"immutable\") opts.onFallback?.();\n return { db, mode: attempt, validated };\n } catch (err) {\n try {\n db?.close();\n } catch {\n // Already closed, or never opened. Nothing to salvage, and the original\n // error is the one worth reporting.\n }\n if (opts.fatal?.(err)) throw err;\n lastError = err;\n }\n }\n\n const message = lastError instanceof Error ? lastError.message : String(lastError);\n throw new IndexUnavailableError(\n `Could not open ${opts.label ?? path} at ${path}: ${message}.${opts.hint ? ` ${opts.hint}` : \"\"}`,\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/errors.ts","../src/ax.ts","../src/build-info.ts","../src/listing.ts","../src/cli.ts","../src/codes.ts","../src/config.ts","../src/dates.ts","../src/tools.ts","../src/facade.ts","../src/fs.ts","../src/osascript.ts","../src/resources.ts","../src/prompts.ts","../src/query.ts","../src/schema.ts","../src/sqlite.ts"],"sourcesContent":["/**\n * Error taxonomy shared by every Apple-app server.\n *\n * Every message is written for the person who has to fix it — a TCC denial says\n * which System Settings pane to open, not \"operation failed\".\n *\n * ## Why the surface is a required argument\n *\n * These messages name an app (\"Not authorized to control Mail\") and an\n * environment variable (`APPLE_MAIL_ALLOW_WRITES`). An earlier version made the\n * app name an *optional* parameter defaulting to \"Mail\" — and then never passed\n * it at any call site, while a second mention of Mail stayed hardcoded further\n * down the same string. That is worse than no parameter at all: it looks\n * configurable and is not.\n *\n * So `SurfaceContext` is required wherever it appears in a message. Servers\n * subclass with their own surface bound, which keeps `new MailBusyError()`\n * ergonomic at the call site without letting the context go missing.\n */\n\n/** Identity of the app a server drives, for anything user-facing. */\nexport type SurfaceContext = {\n /** How the app is named to a human, e.g. \"Mail\", \"Notes\". */\n appName: string;\n /** Environment variable prefix for this server, e.g. \"APPLE_MAIL\". */\n envPrefix: string;\n};\n\n/** Base class so `toFailure` can carry structured detail through in one branch. */\nexport class AppleAutomationError extends Error {\n override readonly name: string = \"AppleAutomationError\";\n readonly details: Record<string, unknown> | undefined;\n\n constructor(message: string, details?: Record<string, unknown>) {\n super(message);\n this.details = details;\n }\n}\n\n/** The host process may not send Apple Events to the app (osascript -1743). */\nexport class TccDeniedError extends AppleAutomationError {\n override readonly name: string = \"TccDeniedError\";\n\n constructor(surface: SurfaceContext) {\n super(\n `Not authorized to control ${surface.appName}. Grant it in System Settings > ` +\n `Privacy & Security > Automation > (the app running this server) > ${surface.appName}, ` +\n `then restart the server. If no entry appears, the first attempt was denied before the ` +\n `prompt could be answered — run \\`tccutil reset AppleEvents\\` and try again.`,\n );\n }\n}\n\n/** The app is not running and the operation refuses to launch it. */\nexport class AppNotRunningError extends AppleAutomationError {\n override readonly name: string = \"AppNotRunningError\";\n\n constructor(surface: SurfaceContext) {\n super(\n `${surface.appName} is not running. Read tools do not launch it, because launching it ` +\n `steals focus and can start a sync. Open ${surface.appName} and retry.`,\n );\n }\n}\n\n/** The app was busy and refused the Apple Event (-1712). Retried once before surfacing. */\nexport class AppBusyError extends AppleAutomationError {\n override readonly name: string = \"AppBusyError\";\n\n constructor(surface: SurfaceContext) {\n super(\n `${surface.appName} is busy (probably syncing) and did not answer in time. ` +\n `Retry in a few seconds.`,\n );\n }\n}\n\n/** osascript exceeded its budget and was killed. */\nexport class OsascriptTimeoutError extends AppleAutomationError {\n override readonly name: string = \"OsascriptTimeoutError\";\n\n constructor(timeoutMs: number, surface: SurfaceContext) {\n super(\n `${surface.appName} did not answer within ${timeoutMs}ms. It may be mid-sync, or a ` +\n `permission prompt may be waiting on screen. Raise ` +\n `${surface.envPrefix}_OSASCRIPT_TIMEOUT_MS if this is routine at your data size.`,\n );\n }\n}\n\n/**\n * A write was attempted while writes are disabled. Under the house pattern write\n * tools are not registered at all when `allowWrites` is off, so this is a\n * belt-and-braces guard for the library surface, not a path tools can reach.\n */\nexport class WritesDisabledError extends AppleAutomationError {\n override readonly name: string = \"WritesDisabledError\";\n\n constructor(surface: SurfaceContext) {\n super(\n `Writes are disabled. Set ${surface.envPrefix}_ALLOW_WRITES=1 to enable the mutating tools.`,\n );\n }\n}\n\n/** A read-only index could not be opened, so the file lane is unavailable. */\nexport class IndexUnavailableError extends AppleAutomationError {\n override readonly name: string = \"IndexUnavailableError\";\n}\n\n/** The store's schema is not the one we know how to read. */\nexport class SchemaDriftError extends AppleAutomationError {\n override readonly name: string = \"SchemaDriftError\";\n}\n\n/** The server is not running on macOS, or osascript is missing. */\nexport class PlatformError extends AppleAutomationError {\n override readonly name: string = \"PlatformError\";\n}\n\n/** osascript exited 0 but did not produce the JSON envelope we require. */\nexport class ProtocolError extends AppleAutomationError {\n override readonly name: string = \"ProtocolError\";\n}\n\n/** A local precondition failed before anything was sent to the app. */\nexport class PreconditionError extends AppleAutomationError {\n override readonly name: string = \"PreconditionError\";\n}\n\n/**\n * A date argument could not be read.\n *\n * Refusing is the whole point. The alternative — which is what Mail, Messages\n * and Notes did — is `Date.parse` returning `NaN`, node:sqlite binding it as\n * `NULL`, and the query matching nothing: an empty result that looks like an\n * answer. The message lists the accepted forms because the caller is usually a\n * model that guessed, and the fix is to show it the grammar.\n */\nexport class InvalidDateError extends AppleAutomationError {\n override readonly name: string = \"InvalidDateError\";\n\n constructor(field: string, raw: string, reason: string) {\n super(\n `Could not read ${field} from ${JSON.stringify(raw)}: ${reason}. ` +\n `Accepted: an ISO-8601 date \"2026-08-20\" (a whole day, local) or date-time ` +\n `\"2026-08-20T09:00\" (local unless it carries an offset), a signed offset like ` +\n `\"+2d\", \"-3h\", \"+45m\", \"+1w\", \"today\", \"yesterday\", \"tomorrow 09:00\", ` +\n `\"next monday\" or \"last friday\".`,\n { field, raw },\n );\n }\n}\n","import { connect, type Socket } from \"node:net\";\n\nimport { AppleAutomationError, type SurfaceContext } from \"./errors.js\";\n\n/**\n * Borrowing the app's Accessibility driver.\n *\n * ## Why this exists at all\n *\n * `AXUIElementCopyAttributeValue` reads an attribute in **0.202 ms**. Reaching\n * the same attribute through `osascript` + JXA + System Events costs **47.4 ms**\n * — a 234x difference that is entirely transport, measured both ways on one\n * machine in `docs/desktop.md`. Every Accessibility number this repo recorded\n * before that document was taken through System Events, which is why the lane\n * was closed three times on figures that were pricing the wrong thing.\n *\n * The native call cannot be made from here. Node has no binding for it, and\n * more importantly the grant does not belong to node: TCC attaches\n * Accessibility to the **responsible GUI ancestor**, so it is Cupertino.app\n * that holds it. Hence a channel rather than a port — the app already runs the\n * driver, and `ServerHost` will lend it for one bundle id.\n *\n * ## The second grant this removes, which matters more than the speed\n *\n * Driving a UI through System Events needs Automation-to-System-Events **on top\n * of** Accessibility. Two grants, given in two different System Settings panes,\n * to reach one window. The native driver needs the first and not the second.\n *\n * ## Absence is the fallback, not an error\n *\n * `CUPERTINO_AX_SOCKET` and `CUPERTINO_AX_FOR` are set by `ServerLocator` and by\n * nothing else. A package installed from npm and run by hand has neither, and\n * that is the supported case: these are published artifacts that must work with\n * no app on the machine. `open()` returns null there, and the caller keeps\n * whatever lane it had. It never throws to say \"no app\".\n *\n * ## Lazy, deliberately\n *\n * Nothing connects at construction. `SurfaceCatalog` probes servers to learn\n * what they register, and a probe that opened a socket would make every capability\n * scan depend on the host being ready to answer one.\n */\n\n/** The handshake `ServerHost` expects, and the reply it sends back. */\nconst PROTOCOL = \"cupertino/1\";\nconst LENT_SURFACE = \"desktop\";\nconst OK = \"ok\";\n\n/**\n * How long one call may take.\n *\n * A walk carries its own five-second budget and the AX messaging timeout is two\n * seconds per element, so the ceiling that matters is the app's, not this one's.\n * This is only here so a host that stopped answering fails rather than hanging a\n * tool call forever.\n */\nconst CALL_TIMEOUT_MS = 30_000;\n\nexport type AxCall = {\n /** Tool name, e.g. `apple_desktop_find_elements`. */\n readonly tool: string;\n readonly args: Record<string, unknown>;\n};\n\nexport type AxChannel = {\n /**\n * Call one desktop tool and return its parsed JSON body.\n *\n * Calls are serialised. The wire is one socket carrying newline-delimited\n * JSON-RPC, and the driver's handle store is shared mutable state on the other\n * end, so overlapping two walks would interleave both.\n */\n call(request: AxCall): Promise<unknown>;\n close(): void;\n};\n\n/**\n * The app is there and said no, or stopped answering.\n *\n * Distinct from the null `openAxChannel` returns, which means there is no app\n * to ask — see the note there on why those two must not collapse.\n */\nexport class AxChannelError extends AppleAutomationError {\n override readonly name: string = \"AxChannelError\";\n\n constructor(surface: SurfaceContext, message: string) {\n super(`${surface.appName}: ${message}`, { surface: surface.appName });\n }\n}\n\n/** What the host told us, when it refused the handshake. */\nconst refusal = (line: string): string =>\n line.startsWith(\"err \") ? line.slice(4) : `unexpected handshake reply '${line}'`;\n\n/**\n * Open the channel, or return null when this server is not hosted by the app.\n *\n * Null and a throw are different answers and the difference is the whole\n * contract: null means \"there is no app here, use your other lane\", a throw\n * means \"there is an app and it said no\", which a caller must report rather\n * than paper over.\n */\nexport const openAxChannel = (\n surface: SurfaceContext,\n env: NodeJS.ProcessEnv = process.env,\n): AxChannel | null => {\n const socketPath = env.CUPERTINO_AX_SOCKET?.trim();\n const identity = env.CUPERTINO_AX_FOR?.trim();\n if (!socketPath || !identity) return null;\n\n let socket: Socket | null = null;\n let pending = Promise.resolve();\n let nextId = 1;\n\n const connectOnce = (): Promise<Socket> =>\n new Promise((resolve, reject) => {\n const s = connect(socketPath);\n let buffer = \"\";\n const onError = (error: Error) => {\n s.destroy();\n reject(new AxChannelError(surface, `cannot reach Cupertino: ${error.message}`));\n };\n s.once(\"error\", onError);\n s.setEncoding(\"utf8\");\n // The handshake reply is one line; everything after it is JSON-RPC, so\n // this listener has to come off before the first call reads anything.\n const onData = (chunk: string) => {\n buffer += chunk;\n const newline = buffer.indexOf(\"\\n\");\n if (newline < 0) return;\n const line = buffer.slice(0, newline).trim();\n s.off(\"data\", onData);\n s.off(\"error\", onError);\n if (line !== OK) {\n s.destroy();\n reject(new AxChannelError(surface, refusal(line)));\n return;\n }\n resolve(s);\n };\n s.on(\"data\", onData);\n s.write(`${PROTOCOL} ${LENT_SURFACE} for=${identity}\\n`);\n });\n\n const request = async (payload: AxCall): Promise<unknown> => {\n socket ??= await connectOnce();\n const live = socket;\n const id = nextId++;\n const line = `${JSON.stringify({\n jsonrpc: \"2.0\",\n id,\n method: \"tools/call\",\n params: { name: payload.tool, arguments: payload.args },\n })}\\n`;\n\n return new Promise((resolve, reject) => {\n let buffer = \"\";\n const timer = setTimeout(() => {\n cleanup();\n reject(\n new AxChannelError(surface, `${payload.tool} did not answer in ${CALL_TIMEOUT_MS}ms`),\n );\n }, CALL_TIMEOUT_MS);\n\n const cleanup = () => {\n clearTimeout(timer);\n live.off(\"data\", onData);\n live.off(\"error\", onError);\n live.off(\"close\", onClose);\n };\n const onError = (error: Error) => {\n cleanup();\n reject(new AxChannelError(surface, `${payload.tool} failed: ${error.message}`));\n };\n const onClose = () => {\n cleanup();\n socket = null;\n reject(\n new AxChannelError(surface, `Cupertino closed the connection during ${payload.tool}`),\n );\n };\n const onData = (chunk: string) => {\n buffer += chunk;\n const newline = buffer.indexOf(\"\\n\");\n if (newline < 0) return;\n const raw = buffer.slice(0, newline);\n cleanup();\n let message: {\n result?: { content?: { text?: string }[]; isError?: boolean };\n error?: { message?: string };\n };\n try {\n message = JSON.parse(raw);\n } catch {\n reject(new AxChannelError(surface, `unparseable reply to ${payload.tool}`));\n return;\n }\n if (message.error) {\n reject(new AxChannelError(surface, message.error.message ?? \"unknown error\"));\n return;\n }\n // The desktop server answers in MCP's content envelope, and its tools\n // put one JSON document in the first text part.\n const text = message.result?.content?.[0]?.text;\n if (text === undefined) {\n reject(new AxChannelError(surface, `empty reply to ${payload.tool}`));\n return;\n }\n // A REFUSED tool is not a JSON-RPC error. It comes back as a normal\n // result carrying `isError: true` and the reason as PLAIN TEXT, so a\n // caller that only checks `message.error` reads a refusal as a success\n // value and carries on. That shipped, and it cost an afternoon: every\n // press through this channel was being refused for want of a write gate\n // and the lane above reported \"the sheet did not appear\", which is a\n // sentence about Maps rather than about permission.\n if (message.result?.isError === true) {\n reject(new AxChannelError(surface, `${payload.tool}: ${text}`));\n return;\n }\n try {\n resolve(JSON.parse(text));\n } catch {\n resolve(text);\n }\n };\n\n live.on(\"data\", onData);\n live.once(\"error\", onError);\n live.once(\"close\", onClose);\n live.write(line);\n });\n };\n\n return {\n call(payload) {\n // Serialised onto one chain. `pending` is replaced with a promise that\n // settles either way, so one failed call does not wedge the queue.\n const result = pending.then(() => request(payload));\n pending = result.then(\n () => undefined,\n () => undefined,\n );\n return result;\n },\n close() {\n socket?.destroy();\n socket = null;\n },\n };\n};\n\n/**\n * Whether a person touched the machine while a sequence was running.\n *\n * Driving an interface is the one capability here that COMPETES with whoever is\n * using the Mac: a keystroke goes to whatever is frontmost and a click goes to a\n * screen point, so somebody typing during a paste does not slow it down, it\n * corrupts it.\n *\n * The comparison is what makes this work. `secondsSinceInput` on its own cannot\n * tell \"they typed just before we started\" from \"they typed into the middle of\n * it\" — but a sequence that ran for five seconds and ends with two seconds since\n * the last input knows the input landed inside it.\n *\n * **This exists because of a misdiagnosis, not a theory.** Two runs of the Maps\n * lane were blamed on interference and turned out to be bugs in the lane; one\n * was blamed on the lane and turned out to be interference. Neither could be\n * told apart from the outside, and a failure that names the wrong cause sends\n * the next hour in the wrong direction.\n */\nexport type Interference = {\n /** True when input arrived after the sequence began. */\n disturbed: boolean;\n secondsSinceInput: number;\n elapsedSeconds: number;\n};\n\nexport type InterferenceWatch = { check(): Promise<Interference | null> };\n\n/**\n * How much newer than Cupertino's own last posted event an input has to be\n * before it is somebody else's. The host stamps its post just after the events\n * go out, so the two readings of one keystroke differ by the posting alone.\n */\nconst OWN_INPUT_TOLERANCE_S = 0.25;\n\n/**\n * Start watching. `check()` answers for the span since this call.\n *\n * Returns null from `check()` when the question could not be asked at all,\n * which is not the same as \"undisturbed\" and must not be reported as it.\n *\n * **The host's idle reading counts Cupertino's own events.** It has to — input\n * from any other software disturbs a sequence exactly as a person does — so a\n * sequence that posts a key finds \"input\" a moment ago on every run. A native\n * Mail reply reported \"Someone used this Mac 0.2s ago\" for its own ⌘V. The\n * host now also says when it last posted, and input no newer than that is\n * taken as ours. What that cannot see is a person whose input lands BEFORE our\n * last post in the same sequence: masked, the ambiguity hover's idle guard\n * already accepts. A host too old to report it keeps the bare comparison.\n */\nexport const watchInterference = (channel: AxChannel): InterferenceWatch => {\n const started = Date.now();\n return {\n async check() {\n const elapsedSeconds = (Date.now() - started) / 1000;\n try {\n const answer = (await channel.call({\n tool: \"apple_desktop_user_activity\",\n args: {},\n })) as { secondsSinceInput?: number; secondsSinceOwnInput?: number | null };\n const secondsSinceInput = answer.secondsSinceInput;\n if (typeof secondsSinceInput !== \"number\") return null;\n const own = answer.secondsSinceOwnInput;\n const ours = typeof own === \"number\" && secondsSinceInput >= own - OWN_INPUT_TOLERANCE_S;\n return {\n disturbed: secondsSinceInput < elapsedSeconds && !ours,\n secondsSinceInput,\n elapsedSeconds,\n };\n } catch {\n return null;\n }\n },\n };\n};\n\n/**\n * The sentence to append to a failure, or \"\" when nothing useful can be said.\n *\n * Deliberately says nothing when the machine was quiet: a failure that reads\n * \"nobody touched it\" invites the reader to stop looking, and the point of this\n * is to send them to the RIGHT place rather than to reassure them.\n */\nexport const interferenceNote = (found: Interference | null): string => {\n if (!found?.disturbed) return \"\";\n return (\n ` Someone used this Mac ${found.secondsSinceInput.toFixed(1)}s ago, during the ` +\n `${found.elapsedSeconds.toFixed(1)}s this took — a keystroke or click lands wherever the ` +\n `focus is, so that alone can explain this. Retry with the machine idle before looking further.`\n );\n};\n","import { readFileSync } from \"node:fs\";\n\nexport type PackageIdentity = { name: string; version: string };\n\nexport type BuildInfo = PackageIdentity & {\n gitCommit: string;\n gitCommitDate: string;\n};\n\n/**\n * Read a package's own name and version at startup, so they are always accurate\n * rather than baked in at build time.\n *\n * Callers pass their own `new URL(\"../package.json\", import.meta.url)`: resolving\n * it here would find *this* package, not theirs. The git fields stay with the\n * caller too, because `__GIT_COMMIT__` is substituted by whichever bundler build\n * compiles the file that mentions it.\n */\nexport const readPackageIdentity = (\n packageJsonUrl: URL,\n fallback: PackageIdentity,\n): PackageIdentity => {\n try {\n return JSON.parse(readFileSync(packageJsonUrl, \"utf8\")) as PackageIdentity;\n } catch {\n return fallback;\n }\n};\n","import type { Transport } from \"@modelcontextprotocol/sdk/shared/transport.js\";\nimport type { JSONRPCMessage } from \"@modelcontextprotocol/sdk/types.js\";\n\n/**\n * Trimming the SDK's own boilerplate out of `tools/list`.\n *\n * ## What this drops\n *\n * The SDK builds each tool's `inputSchema` from its zod shape at listing time,\n * and the generator stamps every one with\n * `\"$schema\": \"http://json-schema.org/draft-07/schema#\"`. Measured across the\n * eight servers with writes on, that one constant is 4,836 B of a 106,157 B\n * listing — 4.6%, paid by every client on every connect, to name a JSON Schema\n * draft the client already has to assume in order to read the rest of the\n * document. Nothing in the protocol reads it and no client needs it, so it is\n * the rare cut that is free rather than a trade.\n *\n * ## What this deliberately does NOT drop\n *\n * `\"execution\": {\"taskSupport\": \"forbidden\"}` is another 3,720 B (3.5%) of\n * identical constant — `registerTool` hardcodes it on every tool — and it looks\n * like the same kind of waste. It is not, and the difference is worth the\n * paragraph so nobody \"finishes the job\" later.\n *\n * Server-side the two spellings are the same: the SDK's `tools/call` path\n * branches only on `'required'` and `'optional'`, so an absent `execution` and\n * an explicit `'forbidden'` both fall through to the normal handler. Client-side\n * they are not. `taskSupport` is declared `.optional()` with no default, so\n * absence means \"unspecified\" rather than \"forbidden\", and a task-capable client\n * reading a listing with no `execution` is entitled to try task augmentation on\n * a tool that was registered without a task handler. `'forbidden'` is the value\n * that tells it not to. Dropping it would trade 930 tokens for a behavioural\n * change on a path nothing here tests.\n *\n * ## Why it is done to the outgoing frame\n *\n * The alternative seams are worse. The schema is generated inside the SDK, so\n * there is no option to pass; overriding the `tools/list` request handler means\n * reaching into `Server._requestHandlers`, a private field, and re-implementing\n * the listing it already builds. Wrapping `Transport.send` is public API, is\n * indifferent to how the listing was produced, and costs nothing on the frames\n * it does not match — every non-listing message is returned by identity below.\n */\n\n/** The one key removed, spelled once. */\nconst GENERATED_SCHEMA_KEY = \"$schema\";\n\n/**\n * A schema object without its `$schema` stamp, or the value unchanged.\n *\n * Returns the ORIGINAL reference when there is nothing to do, which is what\n * lets `trimToolListing` decide by identity whether it needs to rebuild\n * anything at all.\n */\nconst withoutSchemaKey = (schema: unknown): unknown => {\n if (schema === null || typeof schema !== \"object\" || Array.isArray(schema)) return schema;\n if (!(GENERATED_SCHEMA_KEY in schema)) return schema;\n const rest = { ...(schema as Record<string, unknown>) };\n delete rest[GENERATED_SCHEMA_KEY];\n return rest;\n};\n\n/**\n * Strip generated boilerplate from a `tools/list` reply, passing every other\n * message through untouched.\n *\n * Copies rather than mutates. The SDK hands out the registered tool's own\n * schema object, and deleting a key from it would edit the server's state from\n * a function whose job is to shape one reply.\n */\nexport const trimToolListing = (message: JSONRPCMessage): JSONRPCMessage => {\n if (!(\"result\" in message)) return message;\n const result = message.result as { tools?: unknown } | undefined;\n const tools = result?.tools;\n if (!Array.isArray(tools)) return message;\n\n let changed = false;\n const trimmed = tools.map((tool) => {\n if (tool === null || typeof tool !== \"object\") return tool;\n const entry = tool as Record<string, unknown>;\n const inputSchema = withoutSchemaKey(entry[\"inputSchema\"]);\n const outputSchema = withoutSchemaKey(entry[\"outputSchema\"]);\n if (inputSchema === entry[\"inputSchema\"] && outputSchema === entry[\"outputSchema\"]) return tool;\n changed = true;\n return {\n ...entry,\n ...(entry[\"inputSchema\"] === undefined ? {} : { inputSchema }),\n ...(entry[\"outputSchema\"] === undefined ? {} : { outputSchema }),\n };\n });\n\n if (!changed) return message;\n return { ...message, result: { ...result, tools: trimmed } } as JSONRPCMessage;\n};\n\n/**\n * Wrap a transport so every listing it sends is trimmed on the way out.\n *\n * Mutates and returns the transport it was given rather than proxying it: the\n * SDK's `connect` reaches for `onmessage`, `onclose` and `onerror` on the very\n * object it was handed, and a Proxy or a subclass would have to keep those in\n * sync for no gain.\n */\nexport const withTrimmedListing = <T extends Transport>(transport: T): T => {\n const send = transport.send.bind(transport);\n transport.send = (message, options) => send(trimToolListing(message), options);\n return transport;\n};\n","import type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\n\nimport type { BuildInfo } from \"./build-info.js\";\nimport type { SurfaceContext } from \"./errors.js\";\nimport { withTrimmedListing } from \"./listing.js\";\nimport type { Logger } from \"./osascript.js\";\n\nexport type StdioServerOptions = {\n build: BuildInfo;\n surface: SurfaceContext;\n /** Prefix on every stderr line, e.g. \"apple-mail-mcp\". */\n logPrefix: string;\n /**\n * Build and return the server, plus a one-line summary of the settings it\n * came up with. Called only after the platform guard passes.\n */\n start: (logger: Logger) => Promise<{ server: McpServer; banner: string }>;\n};\n\n/**\n * Boot a server on stdio.\n *\n * The load-bearing rule: **everything goes to stderr**. stdout is the JSON-RPC\n * channel under stdio, and a stray `console.log` there corrupts the protocol —\n * which surfaces as an unintelligible client-side parse error rather than as\n * anything pointing at the log line that caused it.\n */\nexport const runStdioServer = async (opts: StdioServerOptions): Promise<void> => {\n const { build, surface, logPrefix } = opts;\n const debugEnabled = Boolean(process.env[`${surface.envPrefix}_DEBUG`]);\n const logger: Required<Logger> = {\n debug: (...args: unknown[]) => {\n if (debugEnabled) console.error(`[${logPrefix}]`, ...args);\n },\n warn: (...args: unknown[]) => console.error(`[${logPrefix}]`, ...args),\n error: (...args: unknown[]) => console.error(`[${logPrefix}]`, ...args),\n };\n\n logger.warn(\n `${build.name}@${build.version} (git ${build.gitCommit} ${build.gitCommitDate}, node ${process.version})`,\n );\n\n if (process.platform !== \"darwin\") {\n logger.error(\n `fatal: this server drives the macOS ${surface.appName} app and cannot run on ${process.platform}.`,\n );\n process.exit(1);\n }\n\n const { server, banner } = await opts.start(logger);\n await server.connect(withTrimmedListing(new StdioServerTransport()));\n logger.warn(`${logPrefix} connected (${banner})`);\n\n const shutdown = (signal: string): void => {\n logger.warn(`received ${signal}, shutting down`);\n process.exit(0);\n };\n process.on(\"SIGINT\", () => shutdown(\"SIGINT\"));\n process.on(\"SIGTERM\", () => shutdown(\"SIGTERM\"));\n};\n","/**\n * One-time-code extraction, as a pure function over text.\n *\n * No I/O by design: everything here is decided from a string plus one bit about\n * the sender, so the whole thing tests offline against a table.\n *\n * ── WHY THIS SITS IN CORE ────────────────────────────────────────────────────\n *\n * It was written in `packages/messages` and said of itself that it was liftable\n * to Mail. Safari asked second — a code rendered in a page's text is the same\n * problem — and a second caller is the event that settles it, because the two\n * alternatives are both bad. Safari depending on the Messages package would\n * drag `chat.db` and a Contacts dependency across for one pure function. A\n * duplicate would drift silently, and the heuristic is exactly where the\n * mistakes live.\n *\n * This is the first HEURISTIC in core, which has otherwise been plumbing —\n * config, sqlite, osascript, tools, resources. Worth naming rather than\n * sneaking in: what belongs here is a judgement no surface owns, and this one\n * is now owned by two.\n *\n * The table in `test/codes.test.ts` is the asset, not this file. It is\n * SMS-shaped — short machine-written notifications — and a web page is a much\n * richer source of digit runs, so for the Safari caller this heuristic is\n * REUSED rather than re-validated. See docs/safari.md.\n *\n * ── WHY THIS IS NOT A REGEX ──────────────────────────────────────────────────\n *\n * The obvious implementation is `/\\b\\d{4,8}\\b/` and it is wrong in a way that\n * matters more than usual: a caller asks for a login code, gets the last four\n * digits of an order number, and pastes it into an auth prompt. The failure is\n * silent and the retry costs the user an account lockout. So the digit run is\n * the CANDIDATE here, never the answer — it has to survive disqualification and\n * then earn a score.\n *\n * The false positives are not hypothetical. A real inbox carries order numbers,\n * tracking numbers, prices, street numbers, years, flight numbers and phone\n * numbers, and every one of them is a 4-to-8 digit run in a message that also\n * contains the word \"code\" somewhere.\n *\n * ── THE SIGNALS, STRONGEST FIRST ─────────────────────────────────────────────\n *\n * domain-bound `@example.com #123456` — the WebOTP/AutoFill convention\n * Apple and Chrome both parse. Unambiguous by construction:\n * the origin is bound to the code, so there is nothing to\n * guess. When present it wins outright.\n * keyword A code word adjacent to the digits. \"adjacent\" is measured\n * in characters, not words, because the two orders both occur\n * (\"your code is 123456\" and \"123456 is your code\") and a word\n * window would need two passes.\n * shortcode Sender is a shortcode — a bank, a courier, a 2FA sender,\n * never a person. Corroborating only: it raises a weak match\n * to usable, never creates one on its own.\n *\n * ── WHAT `confidence` IS FOR ─────────────────────────────────────────────────\n *\n * The tool reports it, and the tool description tells the model to check the\n * body on anything below \"high\". This mirrors `apple_safari_list_tabs`'\n * `historyMatch`: say how the match was made so the caller is never guessing\n * whether to trust it.\n */\n\n/** A code word. \"code\" carries both English and French, conveniently. */\nconst KEYWORDS = [\n \"code\",\n \"verification\",\n \"verify\",\n \"one-time\",\n \"onetime\",\n \"one time\",\n \"otp\",\n \"passcode\",\n \"pin\",\n \"2fa\",\n \"two-factor\",\n \"authentication\",\n \"authenticate\",\n \"security\",\n \"log in\",\n \"login\",\n \"sign in\",\n \"signin\",\n \"confirm\",\n // French. `vérification` is listed unaccented too because senders strip\n // accents to stay inside one SMS segment.\n \"verification\",\n \"usage unique\",\n \"mot de passe\",\n \"connexion\",\n \"identification\",\n \"securite\",\n \"sécurité\",\n \"vérification\",\n];\n\n/**\n * Phrases where \"code\" means something else entirely.\n *\n * This is a denylist and `docs/surfaces.md` warns that a denylist can never be\n * finished — correctly, and it is used narrowly here because of that. It only\n * ever SUPPRESSES the keyword signal; it never decides the outcome by itself,\n * and a message carrying both \"promo code\" and a real domain-bound code still\n * resolves through the stronger signal.\n */\nconst ANTI_KEYWORDS = [\n \"promo code\",\n \"promotional code\",\n \"discount code\",\n \"coupon code\",\n \"referral code\",\n \"invite code\",\n \"area code\",\n \"zip code\",\n \"postal code\",\n \"qr code\",\n \"barcode\",\n \"bar code\",\n \"country code\",\n \"code promo\",\n \"code postal\",\n \"code de reduction\",\n \"code de réduction\",\n \"code parrainage\",\n];\n\n/** How far from the digits a keyword still counts, in characters. */\nconst NEAR = 32;\nconst ADJACENT = 12;\n\nexport type CodeConfidence = \"high\" | \"medium\" | \"low\";\n\nexport type CodeMatch = {\n /** The digits to type. Never the surrounding text. */\n code: string;\n confidence: CodeConfidence;\n /** Which signal fired: `domain-bound` | `keyword` | `shortcode`. */\n matched: string;\n /** Present only for `domain-bound`: the origin the code is bound to. */\n boundTo?: string;\n};\n\n/**\n * The WebOTP format: a last line of `@host #code`, optionally with `?` params.\n * Anchored to a `@host` so a bare `#1234` (an order number, a hashtag) does not\n * qualify.\n */\nconst DOMAIN_BOUND = /@([a-z0-9][a-z0-9.-]*\\.[a-z]{2,})\\s+#([0-9]{4,8})\\b/i;\n\n/**\n * A maximal run of digits and the separators a phone number or a formatted\n * quantity is allowed to contain. Used to reject, not to match: a span holding\n * more than 8 digits in total is a phone number, an account number or an\n * amount, and every digit run inside it is disqualified along with it.\n */\nconst NUMBER_SPAN = /\\d[\\d\\s().+-]*\\d|\\d+/g;\nconst DIGIT_RUN = /\\d{4,8}/g;\n\nconst normalise = (s: string) => s.toLowerCase().replace(/ /g, \" \");\n\n/** Spans that hold too many digits to be a code. Returns [start, end) pairs. */\nconst disqualifiedSpans = (text: string): [number, number][] => {\n const out: [number, number][] = [];\n for (const m of text.matchAll(NUMBER_SPAN)) {\n const digits = m[0].replace(/\\D/g, \"\").length;\n if (digits > 8) out.push([m.index, m.index + m[0].length]);\n }\n return out;\n};\n\nconst inSpan = (spans: [number, number][], start: number, end: number) =>\n spans.some(([a, b]) => start >= a && end <= b);\n\n/**\n * Rejections that look at the characters touching the digits.\n *\n * Each of these was a real false positive shape before it was a rule; see\n * `test/codes.test.ts`, where every one has a case.\n */\nconst looksLikeSomethingElse = (text: string, start: number, end: number): boolean => {\n const before = text.slice(Math.max(0, start - 12), start);\n const after = text.slice(end, end + 12);\n\n // Currency: \"$1299\", \"€ 1299\", and the grouped/decimal forms \"1,299.00\".\n if (/[$€£¥]\\s*$/.test(before)) return true;\n if (/^[.,]\\d/.test(after)) return true;\n if (/\\d[.,]$/.test(before)) return true;\n\n // Glued to letters — a tracking or reference number like \"AA10123456\".\n // A separator is fine: Google sends \"G-123456\" and the code is the digits.\n if (/[a-z]$/i.test(before)) return true;\n if (/^[a-z]/i.test(after) && !/^[a-z]{0,2}\\b/i.test(after)) return true;\n\n // A percentage or an ordinal is never a code.\n if (/^\\s*%/.test(after)) return true;\n\n return false;\n};\n\n/** 1900-2099. Rejected unless a keyword sits right against it. */\nconst looksLikeYear = (digits: string) => digits.length === 4 && /^(19|20)\\d{2}$/.test(digits);\n\n/**\n * Distance in characters from a digit run to the nearest keyword, or null.\n *\n * Both directions are searched because both orders are common in the wild:\n * \"your code is 123456\" and \"123456 is your Google verification code\".\n *\n * The slice is widened by the longest keyword before searching, and the\n * distance checked afterwards. Slicing to exactly NEAR instead is wrong in a\n * way that is easy to miss: it cuts the keyword in half at the boundary, so\n * \"authentication\" (14 chars) would need to sit 14 characters closer than\n * \"otp\" to register at all. The window bounds the GAP, not the keyword.\n */\nconst LONGEST_KEYWORD = Math.max(...KEYWORDS.map((k) => k.length));\n\nconst keywordDistance = (lower: string, start: number, end: number): number | null => {\n const from = Math.max(0, start - NEAR - LONGEST_KEYWORD);\n const before = lower.slice(from, start);\n const after = lower.slice(end, end + NEAR + LONGEST_KEYWORD);\n\n let best: number | null = null;\n for (const kw of KEYWORDS) {\n const b = before.lastIndexOf(kw);\n if (b !== -1) {\n const d = before.length - (b + kw.length);\n if (d <= NEAR && (best === null || d < best)) best = d;\n }\n const a = after.indexOf(kw);\n if (a !== -1 && a <= NEAR && (best === null || a < best)) best = a;\n }\n return best;\n};\n\n/** True when a code word near the digits is one of the decoy phrases. */\nconst LONGEST_ANTI = Math.max(...ANTI_KEYWORDS.map((k) => k.length));\n\nconst suppressedByAntiKeyword = (lower: string, start: number, end: number): boolean => {\n const window = lower.slice(Math.max(0, start - NEAR - LONGEST_ANTI), end + NEAR + LONGEST_ANTI);\n return ANTI_KEYWORDS.some((k) => window.includes(k));\n};\n\nexport type ExtractOptions = {\n /**\n * Whether the sender is a shortcode. Corroborating only — it raises a weak\n * match to usable and never creates one. `packages/contacts` classifies these\n * and `Correspondent.resolution` carries the verdict.\n *\n * It is ONE caller's corroborating bit and deliberately still named for it. A\n * caller with no sender at all — Safari, reading a page — simply leaves it\n * false, and the consequence is worth knowing rather than working around: on\n * that lane a `low` match can never occur, because the only route to one is\n * this flag. A page with digits and no keyword yields null.\n */\n fromShortcode?: boolean;\n};\n\n/**\n * Pull the one-time code out of a message, or return null.\n *\n * Null is the common and correct answer for most messages, and callers must\n * treat it as \"no code here\" rather than retrying with something looser.\n */\nexport const extractCode = (\n text: string | null | undefined,\n { fromShortcode = false }: ExtractOptions = {},\n): CodeMatch | null => {\n if (!text) return null;\n // A code arrives in a short machine-written notification. Past a few hundred\n // characters this is a newsletter that happens to contain digits, and the\n // scoring below has no way to tell. Cheap, and it removes a whole class.\n if (text.length > 400) return null;\n\n const lower = normalise(text);\n\n // 1. Domain-bound. Unambiguous by construction, so it short-circuits.\n const bound = DOMAIN_BOUND.exec(text);\n const boundHost = bound?.[1];\n const boundCode = bound?.[2];\n if (boundHost && boundCode) {\n return { code: boundCode, confidence: \"high\", matched: \"domain-bound\", boundTo: boundHost };\n }\n\n const dead = disqualifiedSpans(text);\n const candidates: { code: string; confidence: CodeConfidence; matched: string; rank: number }[] =\n [];\n\n for (const m of text.matchAll(DIGIT_RUN)) {\n const digits = m[0];\n const start = m.index;\n const end = start + digits.length;\n\n if (inSpan(dead, start, end)) continue;\n if (looksLikeSomethingElse(text, start, end)) continue;\n\n const distance = keywordDistance(lower, start, end);\n const suppressed = distance !== null && suppressedByAntiKeyword(lower, start, end);\n const keyword = distance !== null && !suppressed;\n\n // A year needs a keyword pressed right against it to count. \"expires 2026\"\n // does not qualify; \"your code is 2026\" does.\n if (looksLikeYear(digits) && !(keyword && distance <= ADJACENT)) continue;\n\n if (keyword) {\n const adjacent = distance <= ADJACENT;\n candidates.push({\n code: digits,\n confidence: adjacent ? \"high\" : \"medium\",\n matched: \"keyword\",\n rank: adjacent ? 0 : 1,\n });\n continue;\n }\n\n // No keyword. A shortcode sender plus a single short line is the last\n // signal worth acting on, and it is deliberately capped at \"low\".\n if (fromShortcode && text.length <= 120) {\n candidates.push({ code: digits, confidence: \"low\", matched: \"shortcode\", rank: 2 });\n }\n }\n\n candidates.sort((a, b) => a.rank - b.rank);\n const [best, second] = candidates;\n if (!best) return null;\n\n // Two equally-ranked candidates means the message holds more than one number\n // this function cannot separate — report the first but never claim \"high\",\n // because picking wrong is the failure this whole file exists to avoid.\n const ambiguous = second !== undefined && second.rank === best.rank && second.code !== best.code;\n return {\n code: best.code,\n confidence: ambiguous && best.confidence === \"high\" ? \"medium\" : best.confidence,\n matched: ambiguous ? `${best.matched}-ambiguous` : best.matched,\n };\n};\n","import { z } from \"zod\";\n\n/**\n * Environment parsing shared by every server.\n *\n * Configuration is environment-only. The sibling servers that also read a\n * `~/.config/<service>/config.json` do so because they hold a private key or an\n * OAuth token; these servers hold no secret at all — their access is the macOS\n * permission the user granted.\n */\n\nexport const trimmed = (v: string | undefined): string | undefined => {\n const t = v?.trim();\n return t ? t : undefined;\n};\n\nexport const parseBool = (v: string | undefined): boolean | undefined => {\n const t = trimmed(v)?.toLowerCase();\n if (t === undefined) return undefined;\n return t === \"1\" || t === \"true\" || t === \"yes\" || t === \"on\";\n};\n\nexport const parseIntOpt = (v: string | undefined): number | undefined => {\n const t = trimmed(v);\n if (t === undefined) return undefined;\n const n = Number(t);\n return Number.isFinite(n) ? n : undefined;\n};\n\nexport const parseList = (v: string | undefined): string[] | undefined => {\n const t = trimmed(v);\n if (t === undefined) return undefined;\n return t\n .split(\",\")\n .map((s) => s.trim())\n .filter(Boolean);\n};\n\n/**\n * Settings every Apple-app server has. Extend it rather than repeating them:\n *\n * const ConfigSchema = BaseConfigSchema.extend({ ... }).strict();\n */\nexport const BaseConfigSchema = z.object({\n allowWrites: z.boolean().default(false),\n /**\n * Register the workflow prompts and the surface resources.\n *\n * ON by default, unlike `allowWrites`, and the difference is the point: the\n * write gate is a SAFETY invariant — off means a mutation cannot be reached\n * even by name — while this is a COST knob, in the same family as\n * `maxResults`. Conflating the two would muddy the one that matters.\n *\n * What it costs, measured across all seven servers with writes on: the\n * prompt and resource listings come to ~3.4k tokens against ~18.5k for the\n * tool definitions, so roughly 18% on top of a bill that is dominated by\n * tools either way. Resource CONTENTS cost nothing until something reads\n * them. The knob exists for hosts that put every listing in the prompt and\n * for people counting bytes; if context is the problem, running fewer\n * servers is the bigger lever by far.\n *\n * One flag for both, not two, because they ship as a pair: every prompt\n * embeds its surface guide, and a prompt naming a `cupertino://…/guide` that\n * nothing serves would be a dangling reference by configuration.\n */\n exposePrompts: z.boolean().default(true),\n /**\n * Serve a searchable index and a dispatcher instead of the full tool list.\n *\n * OFF by default, and a COST knob like `exposePrompts` above rather than a\n * safety gate — but unlike that one it is a knob that TRADES. See\n * `facade.ts` for the mechanism; the trade is that a host's permission rule\n * stops naming the individual tool and starts naming a direction: one rule\n * for this surface's reads, one for its writes.\n *\n * What it buys, measured with writes on: ~26.5k tokens of tool definitions\n * across the eight servers becomes a handful per surface. What it costs\n * besides the permission granularity is a round trip — a model must search\n * before it can call.\n *\n * Worth switching on only for a client that does not already defer tool\n * schemas itself. Claude Code and Claude Desktop do, and gain nothing here\n * while paying both costs, which is why the app declines to write the flag\n * into their config files at all.\n */\n lazyTools: z.boolean().default(false),\n debug: z.boolean().default(false),\n osascriptPath: z.string().default(\"/usr/bin/osascript\"),\n osascriptTimeoutMs: z.number().int().min(1_000).max(600_000).default(30_000),\n maxResults: z.number().int().min(1).max(1_000).default(200),\n});\n\n/**\n * Parse an env-derived object against a schema.\n *\n * Undefined values are dropped so zod's defaults apply, rather than failing on\n * an explicitly-undefined key.\n */\nexport const parseConfig = <T extends z.ZodType>(\n schema: T,\n raw: Record<string, unknown>,\n): z.infer<T> => {\n const compacted = Object.fromEntries(Object.entries(raw).filter(([, v]) => v !== undefined));\n const parsed = schema.safeParse(compacted);\n if (!parsed.success) {\n const issues = parsed.error.issues.map((i) => `${i.path.join(\".\")}: ${i.message}`).join(\"; \");\n throw new Error(`Invalid configuration: ${issues}`);\n }\n return parsed.data as z.infer<T>;\n};\n","/**\n * The one grammar every surface reads its date arguments with.\n *\n * ## Why it is here\n *\n * This shipped as three copies. Reminders wrote it, Calendar repeated it\n * verbatim, and Safari repeated it again with signed offsets and `yesterday`\n * added — that third copy's header says outright that three is the trigger for\n * hoisting and that the copies had already diverged. Meanwhile Mail, Messages\n * and Notes never got a grammar at all: they called `Date.parse` or\n * `new Date()` directly, which is where the bugs were.\n *\n * They were not small bugs, and they were all the same shape — a date argument\n * that produced a confident, plausible, wrong answer:\n *\n * - `Date.parse(\"last week\")` is `NaN`, node:sqlite binds `NaN` as `NULL`,\n * and `date_received >= NULL` matches nothing. A model that wrote a date in\n * words got zero results and no error.\n * - `new Date(\"2026-08-20\")` is UTC midnight but `new Date(\"2026-08-20T00:00\")`\n * is LOCAL midnight, so two spellings a tool description treated as the\n * same thing bounded a query an hour or five apart.\n * - `\"2026-02-30T09:00\"` silently became 2 March, because the rollover guard\n * existed on the date branch and not on the date-time one.\n *\n * ## The rule\n *\n * 1. `YYYY-MM-DD` names a **local calendar day**. As a start bound it is local\n * 00:00 of that day; as an end bound it is local 00:00 of the NEXT day, and\n * every consumer compares with `<`. So `to: \"2026-08-20\"` includes all of\n * the 20th and nothing of the 21st. Resolving it to midnight of the same day\n * is how an end bound quietly excludes the day it names.\n * 2. `YYYY-MM-DD[T ]HH:MM[:SS]` is local wall-clock; with `Z` or `±HH:MM` it is\n * the instant named. A timed bound is used as given, and is exclusive at the\n * end edge like everything else.\n * 3. Relative forms: `±N m|h|d|w`, `today` / `yesterday` / `tomorrow` with an\n * optional `HH:MM`, and `next|last <weekday>`. Days and weeks are calendar\n * arithmetic; hours and minutes are elapsed time. That split is not\n * cosmetic — reversing it drifts every bound by an hour twice a year.\n * 4. Anything else, an empty string, or an instant that does not exist\n * (`2026-02-30`, `T25:00`, `T09:60`) throws `InvalidDateError`. **Nothing\n * here ever returns `NaN`**, because a `NaN` reaches SQLite as `NULL` and a\n * `NULL` bound is an empty result rather than a complaint.\n *\n * The forward-only surfaces inherit the backward forms too. A reminder cannot\n * be due `-7d`, but accepting it costs nothing and refusing it in one surface\n * while accepting it in another is exactly the drift this file replaces.\n */\n\nimport { InvalidDateError } from \"./errors.js\";\n\nexport type DateKind = \"allDay\" | \"timed\";\n\nexport type ParsedDate = {\n kind: DateKind;\n /** The absolute instant, for comparisons and for range bounds. */\n at: Date;\n /**\n * ISO-8601 **with an explicit offset**, e.g. `2026-08-20T09:00:00+02:00`.\n *\n * Always carries the offset so the value is unambiguous once it leaves this\n * process — a bare local string reinterpreted in another zone is the silent\n * failure this module exists to prevent.\n */\n iso: string;\n /** Echoed back in tool results so a caller can see how its input was read. */\n raw: string;\n};\n\nconst DAY_NAMES = [\n \"sunday\",\n \"monday\",\n \"tuesday\",\n \"wednesday\",\n \"thursday\",\n \"friday\",\n \"saturday\",\n] as const;\n\nconst ISO_DATE = /^(\\d{4})-(\\d{2})-(\\d{2})$/;\nconst ISO_DATETIME =\n /^(\\d{4})-(\\d{2})-(\\d{2})[T ](\\d{2}):(\\d{2})(?::(\\d{2}))?(?:\\.\\d+)?(Z|[+-]\\d{2}:?\\d{2})?$/;\nconst OFFSET =\n /^([+-])(\\d+)\\s*(m|min|mins|minute|minutes|h|hr|hrs|hour|hours|d|day|days|w|week|weeks)$/;\nconst DAY_WORD = /^(today|tomorrow|yesterday)(?:\\s+(\\d{1,2}):(\\d{2}))?$/;\nconst RELATIVE_DAY = /^(next|last)\\s+([a-z]+)(?:\\s+(\\d{1,2}):(\\d{2}))?$/;\n\n/** `+02:00` / `-05:00` / `Z` for a given instant, in the system zone. */\nconst offsetOf = (d: Date): string => {\n const mins = -d.getTimezoneOffset();\n if (mins === 0) return \"Z\";\n const sign = mins < 0 ? \"-\" : \"+\";\n const abs = Math.abs(mins);\n return `${sign}${String(Math.floor(abs / 60)).padStart(2, \"0\")}:${String(abs % 60).padStart(2, \"0\")}`;\n};\n\nconst pad = (n: number, w = 2): string => String(n).padStart(w, \"0\");\n\n/**\n * Local wall-clock time rendered with its offset.\n *\n * Deliberately not `toISOString()`, which converts to UTC and would report a\n * 09:00 bound as `07:00Z` — correct as an instant, but unreadable in a tool\n * result whose purpose is confirming what the caller asked for.\n */\nexport const toLocalIso = (d: Date): string =>\n `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}` +\n `T${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}${offsetOf(d)}`;\n\n/** Local midnight on the given calendar day. */\nexport const startOfLocalDay = (d: Date): Date =>\n new Date(d.getFullYear(), d.getMonth(), d.getDate(), 0, 0, 0, 0);\n\n/**\n * The last representable instant of the given calendar day, local.\n *\n * Kept for surfaces that genuinely want an inclusive edge. It is NOT what an\n * end bound resolves to — see `parseBound`.\n */\nexport const endOfLocalDay = (d: Date): Date =>\n new Date(d.getFullYear(), d.getMonth(), d.getDate(), 23, 59, 59, 999);\n\n/** Calendar-aware day arithmetic: same wall-clock time, n days later. */\nexport const addLocalDays = (d: Date, n: number): Date => {\n const out = new Date(d.getTime());\n out.setDate(out.getDate() + n);\n return out;\n};\n\nconst at = (day: Date, hours: number, minutes: number): Date =>\n new Date(day.getFullYear(), day.getMonth(), day.getDate(), hours, minutes, 0, 0);\n\nconst isLeap = (y: number): boolean => (y % 4 === 0 && y % 100 !== 0) || y % 400 === 0;\n\nconst daysInMonth = (y: number, mo: number): number =>\n [31, isLeap(y) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][mo - 1] ?? 0;\n\n/**\n * Reject an instant that does not exist, BEFORE building a Date from it.\n *\n * Checking afterwards does not work, for two reasons found the hard way. An\n * out-of-range hour rolls the DAY over, so `2026-08-20T25:00` comes back as the\n * 21st at 01:00 and a naive date comparison blames the day the caller got\n * right. And a zoned string does not answer `NaN` at all:\n * `new Date(\"2026-02-30T09:00Z\")` is 2 March, so there is nothing to catch.\n *\n * Arithmetic on the components has neither problem and does not depend on the\n * zone. The order matters — time first, because that is what rolled the date.\n */\nconst assertReal = (\n field: string,\n text: string,\n y: number,\n mo: number,\n d: number,\n hh = 0,\n mm = 0,\n ss = 0,\n): void => {\n if (hh > 23 || mm > 59 || ss > 59) {\n throw new InvalidDateError(field, text, `${pad(hh)}:${pad(mm)} is not a time`);\n }\n if (mo < 1 || mo > 12) {\n throw new InvalidDateError(field, text, `there is no month ${pad(mo)}`);\n }\n if (d < 1 || d > daysInMonth(y, mo)) {\n throw new InvalidDateError(field, text, `there is no day ${pad(d)} in month ${pad(mo)}`);\n }\n};\n\nconst result = (kind: DateKind, when: Date, raw: string): ParsedDate => ({\n kind,\n at: when,\n iso: toLocalIso(when),\n raw,\n});\n\n/**\n * Parse one date argument.\n *\n * @param field Named in the error, so a failure says *which* argument was bad.\n * @param raw The caller's string.\n * @param now Injected for hermetic tests, mirroring `loadConfig(env)`.\n */\nexport const parseDate = (field: string, raw: string, now: Date = new Date()): ParsedDate => {\n const text = String(raw ?? \"\").trim();\n if (!text) throw new InvalidDateError(field, String(raw), \"it is empty\");\n const lower = text.toLowerCase();\n\n // ── ISO date-time. An explicit offset means the caller named an instant. ──\n const dt = ISO_DATETIME.exec(text);\n if (dt) {\n const [, y, mo, d, hh, mm, ss, zone] = dt;\n // Before constructing anything, and for the zoned form too: neither NaN nor\n // a component comparison catches these. See `assertReal`.\n assertReal(\n field,\n text,\n Number(y),\n Number(mo),\n Number(d),\n Number(hh),\n Number(mm),\n Number(ss ?? \"0\"),\n );\n const when = zone\n ? new Date(text.replace(\" \", \"T\"))\n : new Date(\n Number(y),\n Number(mo) - 1,\n Number(d),\n Number(hh),\n Number(mm),\n Number(ss ?? \"0\"),\n 0,\n );\n if (Number.isNaN(when.getTime())) {\n throw new InvalidDateError(field, text, \"it is not a real date\");\n }\n return result(\"timed\", when, text);\n }\n\n // ── ISO date. Names a day, so it is all-day. ──\n const only = ISO_DATE.exec(text);\n if (only) {\n const [, y, mo, d] = only;\n // Guard against JS's silent rollover: new Date(2026, 1, 30) is 2 March.\n assertReal(field, text, Number(y), Number(mo), Number(d));\n const when = new Date(Number(y), Number(mo) - 1, Number(d), 0, 0, 0, 0);\n if (Number.isNaN(when.getTime())) {\n throw new InvalidDateError(field, text, \"it is not a real date\");\n }\n return result(\"allDay\", when, text);\n }\n\n // ── Signed offset. Names a duration, so it is timed. ──\n const off = OFFSET.exec(lower);\n if (off) {\n const sign = off[1] === \"-\" ? -1 : 1;\n const n = Number(off[2]) * sign;\n const unit = String(off[3]);\n if (!Number.isFinite(n)) throw new InvalidDateError(field, text, \"the amount is not a number\");\n // Days and weeks are calendar arithmetic; hours and minutes are elapsed time.\n if (unit.startsWith(\"d\")) return result(\"timed\", addLocalDays(now, n), text);\n if (unit.startsWith(\"w\")) return result(\"timed\", addLocalDays(now, n * 7), text);\n const ms = unit.startsWith(\"h\") ? n * 3_600_000 : n * 60_000;\n return result(\"timed\", new Date(now.getTime() + ms), text);\n }\n\n // ── today / yesterday / tomorrow, an optional time promoting it to timed. ──\n const word = DAY_WORD.exec(lower);\n if (word) {\n const shift = word[1] === \"tomorrow\" ? 1 : word[1] === \"yesterday\" ? -1 : 0;\n const day = addLocalDays(now, shift);\n if (word[2] === undefined) return result(\"allDay\", startOfLocalDay(day), text);\n const [hh, mm] = [Number(word[2]), Number(word[3])];\n if (hh > 23 || mm > 59) {\n throw new InvalidDateError(field, text, `${hh}:${word[3]} is not a time`);\n }\n return result(\"timed\", at(day, hh, mm), text);\n }\n\n // ── next/last <weekday>, strictly in that direction: \"last monday\" on a\n // Monday is -7, matching \"next monday\" on a Monday being +7. ──\n const rel = RELATIVE_DAY.exec(lower);\n if (rel) {\n const idx = DAY_NAMES.findIndex((n) => n === rel[2] || n.slice(0, 3) === rel[2]);\n if (idx === -1) {\n throw new InvalidDateError(field, text, `\"${rel[2]}\" is not a day of the week`);\n }\n const forward = rel[1] === \"next\";\n const delta = forward\n ? (idx - now.getDay() + 7) % 7 || 7\n : -((now.getDay() - idx + 7) % 7 || 7);\n const day = addLocalDays(now, delta);\n if (rel[3] === undefined) return result(\"allDay\", startOfLocalDay(day), text);\n const [hh, mm] = [Number(rel[3]), Number(rel[4])];\n if (hh > 23 || mm > 59) {\n throw new InvalidDateError(field, text, `${hh}:${rel[4]} is not a time`);\n }\n return result(\"timed\", at(day, hh, mm), text);\n }\n\n throw new InvalidDateError(field, text, \"it matches none of the accepted forms\");\n};\n\n/**\n * Parse a bound for a range filter.\n *\n * A bare day names the WHOLE day, so which instant it resolves to depends on\n * which edge it is. The start edge is that day's local midnight. The end edge\n * is the NEXT day's local midnight, and every caller compares with `<` — which\n * makes the bound exclusive and the named day fully included.\n *\n * The alternative, 23:59:59.999, was what two of the three copies did, and it\n * is wrong twice: it drops the last millisecond of the day, and more to the\n * point it invites the `<=` that then drops the whole day when somebody writes\n * midnight instead.\n */\nexport const parseBound = (\n field: string,\n raw: string,\n edge: \"start\" | \"end\",\n now: Date = new Date(),\n): Date => {\n const parsed = parseDate(field, raw, now);\n if (parsed.kind !== \"allDay\") return parsed.at;\n return edge === \"end\" ? startOfLocalDay(addLocalDays(parsed.at, 1)) : startOfLocalDay(parsed.at);\n};\n","import { z } from \"zod\";\n\nimport { AppleAutomationError } from \"./errors.js\";\n\nexport type ToolResult = {\n content: { type: \"text\"; text: string }[];\n isError?: boolean;\n};\n\n/**\n * Compact, not pretty-printed.\n *\n * A model does not need the indentation, and it is not free: measured against\n * rows matching these servers' own types, `null, 2` adds 25-41% depending on how\n * many short keys a row carries - worst on the widest lists, which are exactly\n * the responses already big enough to matter. Every tool in every surface\n * returns through here, so this is the one place it is paid.\n */\nexport const ok = (data: unknown): ToolResult => ({\n content: [{ type: \"text\", text: JSON.stringify(data ?? { ok: true }) }],\n});\n\n/**\n * Return text as-is. `ok()` JSON-stringifies, which turns a message body into\n * one escaped \"Hi,\\n\\n…\" line that no one can read.\n */\nexport const okText = (text: string): ToolResult => ({\n content: [{ type: \"text\", text }],\n});\n\nexport const fail = (message: string, extra?: unknown): ToolResult => ({\n content: [\n {\n type: \"text\",\n text: JSON.stringify({ error: message, ...(extra ? { details: extra } : {}) }),\n },\n ],\n isError: true,\n});\n\n/** Render a thrown value as a tool error, preserving whatever detail it carried. */\nexport const toFailure = (err: unknown): ToolResult => {\n if (err instanceof AppleAutomationError) {\n return fail(err.message, { kind: err.name, ...err.details });\n }\n if (err instanceof Error) {\n const details = (err as Error & { details?: unknown }).details;\n return fail(err.message, details);\n }\n return fail(\"Unknown error\", err);\n};\n\n/** Run a tool body, JSON-formatting the result and turning errors into a tool error. */\nexport const wrap = async <T>(fn: () => Promise<T>): Promise<ToolResult> => {\n try {\n return ok(await fn());\n } catch (err) {\n return toFailure(err);\n }\n};\n\n/** Like `wrap`, but the body chooses its own result shape (e.g. a raw body). */\nexport const wrapResult = async (fn: () => Promise<ToolResult>): Promise<ToolResult> => {\n try {\n return await fn();\n } catch (err) {\n return toFailure(err);\n }\n};\n\n/** Drop undefined values so we never send `{mailbox: undefined}` down a lane. */\nexport const compact = <T extends Record<string, unknown>>(obj: T): Partial<T> =>\n Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== undefined)) as Partial<T>;\n\n// ─── shared args ─────────────────────────────────────────────────────────────\n\nexport const limitArg = z\n .number()\n .int()\n .min(1)\n .max(200)\n .optional()\n .describe(\n \"Maximum number of results. Each tool states its own default; \" +\n \"`maxResults` is the ceiling either way.\",\n );\n\n/**\n * Settle a caller's `limit` against the tool's default and the config ceiling.\n *\n * Written out by hand at twenty-odd call sites before this existed, in five\n * different spellings - and three surfaces spelled it `limit ?? maxResults`,\n * with no `Math.min` at all. That made their real default 200 while `limitArg`\n * told every model it was 25, so a model that trusted the description and\n * omitted the argument got eight times the rows it asked for.\n *\n * `fallback` is the tool's own documented default, not a global one: a mailbox\n * listing and a day of events do not want the same number.\n */\nexport const resolveLimit = (\n limit: number | undefined,\n maxResults: number,\n fallback = 25,\n): number => Math.min(limit ?? fallback, maxResults);\n\nexport const confirmArg = z\n .literal(true)\n .describe(\"Must be true. This action changes data and is not undoable from here.\");\n","import type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { z } from \"zod\";\n\nimport { fail, okText, type ToolResult } from \"./tools.js\";\n\n/**\n * Lazy tool loading: a searchable index and a dispatcher, in place of the full\n * `tools/list`.\n *\n * ## Why a facade rather than a smaller listing\n *\n * MCP has no method for fetching a tool's schema later. `inputSchema` is\n * required in a `tools/list` entry and clients validate against it, so a\n * name-only listing is not a cheap server, it is a broken one. An index plus a\n * dispatcher is the only lazy discovery the protocol permits.\n *\n * Measured on the eight servers with writes on, the full listing is ~106 KB\n * (~26.5k tokens), paid by every client on every connect whether or not a tool\n * is ever called. Mail alone is ~25 KB.\n *\n * ## Two dispatchers, split on the write gate\n *\n * Bastion — the sibling project this is modelled on — ships ONE dispatcher, and\n * documents that it therefore cannot carry `readOnlyHint` at all: `true` would\n * be a lie that relaxes the host's confirmation for every write, and `false`\n * would gate the reads along with them. It accepts that, and calls it \"the one\n * switch in the app that trades rather than tightens\".\n *\n * Cupertino should not accept it. `docs/alternatives.md` sells \"unregistered,\n * not refused\" as a differentiator against a competitor whose `--read-only`\n * flag leaves write tools listed, and collapsing every call into one anonymous\n * name would hand that back. So the dispatcher is split on the axis the write\n * gate already uses: `call_tool` reaches reads and is honestly `readOnlyHint:\n * true`; `call_write_tool` reaches writes, is honestly `readOnlyHint: false`\n * and `destructiveHint: true`, and is NOT REGISTERED AT ALL when writes are\n * off. A host still gets a read/write boundary to hang a permission rule on.\n *\n * The cost that remains, and it is real: a host's rule is now per surface and\n * per direction rather than per tool. `apple_mail_send_message` and\n * `apple_mail_move_messages` share one prompt. That is the trade this file\n * makes, and it is why the flag defaults off.\n *\n * ## A write tool is one that disappears when the gate closes\n *\n * Classification does NOT read `annotations.readOnlyHint`. Thirteen mutating\n * tools ship without it — every `create`/`update`/`delete`/`move` tool on\n * Calendar, Notes and Reminders — so trusting it would file\n * `apple_notes_delete_notes` behind the READ dispatcher, silently, with nothing\n * to notice it. Instead the registrar is run twice against throwaway recorders,\n * once with the real `allowWrites` and once with it forced off, and the\n * difference is the write set. That is not a heuristic: it is the repo's own\n * definition, and `each surface's src/tools/index.ts` states the invariant it\n * relies on — \"the registered set is a pure function of `allowWrites` and\n * nothing else\". Registration is side-effect free, so running it twice costs\n * two arrays of closures and touches no client.\n *\n * ## What this deliberately does not copy from Bastion\n *\n * No `nextCursor` walk (registration is in-process, so there are no pages), no\n * catalog cache or invalidation, no `ttlMs`/`cacheScope` annotation, and no\n * pass-through of real tool names for clients holding a stale list. The flag is\n * read from the environment at process launch and cannot change while the\n * process lives, exactly like `allowWrites`, so the listing never varies at\n * runtime and the caching concern that rules out `listChanged` here does not\n * arise.\n */\n\n// ─── recording the registration ──────────────────────────────────────────────\n\ntype ToolConfig = {\n description?: string;\n inputSchema?: Record<string, z.ZodType>;\n annotations?: Record<string, unknown>;\n};\n\ntype ToolHandler = (...args: never[]) => unknown;\n\ntype Declaration = { name: string; config: ToolConfig; handler: ToolHandler };\n\n/**\n * A stand-in server that records what would have been registered.\n *\n * Typed as a whole `McpServer` and cast once, here, rather than narrowed to a\n * `Pick<…, \"registerTool\">` that every surface's `registerTools` would then\n * have to widen its parameter to accept — eight signatures and the ~95\n * functions beneath them, changed to describe a fact that is already true.\n *\n * The cast is safe by inspection, and the inspection is the point: all 95\n * `registerTool` call sites across the eight surfaces call this one method and\n * nothing else, and not one uses its return value. If a registrar ever reaches\n * for `registerPrompt` or `server.server`, it will fail here at runtime rather\n * than quietly registering into a void — which is why this returns a bare\n * object instead of a Proxy that would forward the difference to a real server\n * and half-register a surface.\n */\nconst recorder = (into: Declaration[]): McpServer =>\n ({\n registerTool: (name: string, config: ToolConfig, handler: ToolHandler) => {\n into.push({ name, config, handler });\n return undefined;\n },\n }) as unknown as McpServer;\n\n// ─── the index ───────────────────────────────────────────────────────────────\n\n/** Terms shorter than this are dropped: they match everything and rank nothing. */\nconst SHORTEST_TERM = 3;\n/** How much of a description is printed per row. The whole of it is searched. */\nconst SUMMARY_LIMIT = 160;\nconst SEARCH_LIMIT = 25;\n/**\n * How much bigger a real listing must be than the facade replacing it, in both\n * tools and bytes, before fronting it is a saving rather than a rounding error.\n */\nconst FRONTING_RATIO = 2;\n\n/** Partial matches are low precision by construction, so fewer of them. */\nconst PARTIAL_LIMIT = 10;\nconst INDEX_LIMIT = 200;\n\n/**\n * Rank tiers, best first, SUMMED across the query's terms.\n *\n * Summing rather than taking the best single term is deliberate: a two-word\n * query scored on its luckiest word ranks a tool that matched one term above a\n * tool that matched both.\n */\nconst RANK = { exactName: 0, namePrefix: 1, nameSubstring: 2, summary: 3, tail: 4, missing: 5 };\n\ntype Indexed = {\n name: string;\n summary: string;\n /** name + summary. Decides how WELL a term matched. */\n head: string;\n /** name + the entire description. Decides WHETHER it matched at all. */\n whole: string;\n};\n\nconst summarize = (description: string): string => {\n const flat = description.replace(/\\s+/g, \" \").trim();\n return flat.length <= SUMMARY_LIMIT ? flat : `${flat.slice(0, SUMMARY_LIMIT - 1).trimEnd()}…`;\n};\n\nconst index = (decl: Declaration): Indexed => {\n const description = decl.config.description ?? \"\";\n const summary = summarize(description);\n return {\n name: decl.name,\n summary,\n head: `${decl.name} ${summary}`.toLowerCase(),\n whole: `${decl.name} ${description}`.toLowerCase(),\n };\n};\n\n/**\n * Regular plural fold, guarded.\n *\n * The guards are the whole point: without them `status` searches for `statu`,\n * `class` for `clas` and `focus` for `focu`, and a three-letter term like `ios`\n * loses a third of itself.\n */\nconst singular = (term: string): string => {\n if (term.length <= SHORTEST_TERM) return term;\n if (term.endsWith(\"ss\") || term.endsWith(\"us\")) return term;\n return term.endsWith(\"s\") ? term.slice(0, -1) : term;\n};\n\nconst queryTerms = (query: string): string[] => {\n const seen = new Set<string>();\n const out: string[] = [];\n for (const raw of query.toLowerCase().split(/\\s+/)) {\n const term = raw.trim();\n if (term.length < SHORTEST_TERM || seen.has(term)) continue;\n seen.add(term);\n out.push(term);\n }\n return out;\n};\n\nconst rankTerm = (term: string, entry: Indexed): number => {\n const name = entry.name.toLowerCase();\n const folded = singular(term);\n const hit = (haystack: string): boolean => haystack.includes(term) || haystack.includes(folded);\n if (name === term) return RANK.exactName;\n if (name.startsWith(term)) return RANK.namePrefix;\n if (hit(name)) return RANK.nameSubstring;\n if (hit(entry.head)) return RANK.summary;\n if (hit(entry.whole)) return RANK.tail;\n return RANK.missing;\n};\n\ntype SearchResult = {\n rows: Indexed[];\n /** How many entries matched as well as the best ones, before the cap. */\n matched: number;\n /** Query words no returned row matched. Named back to the caller. */\n missed: string[];\n partial: boolean;\n};\n\n/**\n * Find tools for a query.\n *\n * An empty query returns the server's OWN order rather than an alphabetised\n * one: the registrars group related tools together, and sorting throws that\n * grouping away for no gain.\n *\n * Nothing may cache this keyed on the query — a row's tier is a property of the\n * query AND of the whole catalog it was ranked against.\n */\nconst find = (entries: Indexed[], query: string): SearchResult => {\n const terms = queryTerms(query);\n if (terms.length === 0) {\n return {\n rows: entries.slice(0, INDEX_LIMIT),\n matched: entries.length,\n missed: [],\n partial: false,\n };\n }\n\n const scored = entries.map((entry) => {\n let total = 0;\n let matched = 0;\n const missed: string[] = [];\n for (const term of terms) {\n const rank = rankTerm(term, entry);\n total += rank;\n if (rank === RANK.missing) missed.push(term);\n else matched += 1;\n }\n return { entry, total, matched, missed };\n });\n\n const best = scored.reduce((acc, s) => Math.max(acc, s.matched), 0);\n if (best === 0) return { rows: [], matched: 0, missed: terms, partial: false };\n\n const group = scored\n .filter((s) => s.matched === best)\n .toSorted((a, b) => a.total - b.total || a.entry.name.localeCompare(b.entry.name));\n const partial = best < terms.length;\n // Filtered from the ordered term list, never collected from a Set, so the\n // sentence a caller reads is the same on every run.\n const missed = partial ? terms.filter((t) => group.every((g) => g.missed.includes(t))) : [];\n return {\n rows: group.slice(0, partial ? PARTIAL_LIMIT : SEARCH_LIMIT).map((g) => g.entry),\n matched: group.length,\n missed,\n partial,\n };\n};\n\n/**\n * Suggestions for a name that is not in the catalog.\n *\n * Substring search cannot find a string that appears nowhere, so a typo needs\n * its own answer: shared underscore-separated words first, then the longest\n * common prefix.\n *\n * Both comparisons run on the name with `apple_<surface>_` REMOVED. Every tool\n * on a surface shares that prefix, so comparing whole names makes every tool\n * share two words with every other and \"did you mean\" answers with the first\n * few tools in the catalog — worse than saying nothing, because it reads like a\n * real suggestion. Measured before the fix: `apple_mail_send_messge` suggested\n * `apple_mail_list_accounts`.\n */\nconst nearest = (names: string[], wanted: string, prefix: string): string[] => {\n const strip = (n: string): string =>\n n.toLowerCase().startsWith(`${prefix}_`) ? n.slice(prefix.length + 1) : n;\n const target = strip(wanted);\n const words = new Set(\n target\n .toLowerCase()\n .split(\"_\")\n .filter((w) => w.length >= SHORTEST_TERM),\n );\n const shared = names.filter((n) =>\n strip(n)\n .toLowerCase()\n .split(\"_\")\n .some((w) => words.has(w)),\n );\n if (shared.length > 0) return shared.slice(0, 5);\n\n const common = (n: string): number => {\n const candidate = strip(n).toLowerCase();\n const lower = target.toLowerCase();\n let i = 0;\n while (i < candidate.length && i < lower.length && candidate[i] === lower[i]) i += 1;\n return i;\n };\n const ranked = names.toSorted((a, b) => common(b) - common(a) || a.localeCompare(b));\n /*\n * A one- or two-letter overlap is coincidence, not a near miss. Returning\n * nothing lets the caller read \"no tool named X\" as the whole answer and go\n * to the search tool, which is the move that actually works.\n */\n const bestName = ranked[0];\n if (bestName === undefined || common(bestName) < SHORTEST_TERM) return [];\n return ranked.filter((n) => common(n) >= SHORTEST_TERM).slice(0, 3);\n};\n\n// ─── rendering ───────────────────────────────────────────────────────────────\n\n/** The three names a search result has to point the caller at. */\ntype FacadeNames = { search: string; describe: string; call: string };\n\nconst renderSearch = (result: SearchResult, total: number, names: FacadeNames): string => {\n if (result.rows.length === 0) {\n return `No tool matches. ${total} tools are available — call ${names.search} with no query to list them all.`;\n }\n const rows = result.rows.map((r) => `${r.name} — ${r.summary}`).join(\"\\n\");\n let notice = \"\";\n if (result.partial) {\n // Naming the words that missed is the difference between a caller\n // rephrasing and a caller giving up and asking for the whole listing.\n const which = result.missed.length > 0 ? ` (no match for ${result.missed.join(\", \")})` : \"\";\n notice = `No tool matched every word${which}. Closest:\\n`;\n }\n // Three numbers kept apart on purpose: shown, matched, and the catalog total.\n const footer = `${result.rows.length} of ${total} tools. Read a schema with ${names.describe}, then run it with ${names.call}.`;\n return `${notice}${rows}\\n\\n${footer}`;\n};\n\n/**\n * A tool's declaration as a client would have received it.\n *\n * Built from the recorded zod shape rather than re-derived by hand, so\n * `describe` and a non-lazy listing cannot drift. `$schema` is dropped for the\n * same reason `listing.ts` drops it from `tools/list`.\n */\nconst describe = (decl: Declaration): Record<string, unknown> => {\n const shape = decl.config.inputSchema;\n const schema = shape ? (z.toJSONSchema(z.object(shape)) as Record<string, unknown>) : undefined;\n if (schema) delete schema[\"$schema\"];\n return {\n name: decl.name,\n ...(decl.config.description === undefined ? {} : { description: decl.config.description }),\n ...(schema === undefined ? {} : { inputSchema: schema }),\n ...(decl.config.annotations === undefined ? {} : { annotations: decl.config.annotations }),\n };\n};\n\n// ─── dispatch ────────────────────────────────────────────────────────────────\n\n/**\n * Run a recorded tool, reproducing the validation the SDK would have done.\n *\n * Under a facade the SDK never sees the real call, so it never parses the real\n * arguments. Skipping this would hand every handler unvalidated input — the one\n * way a facade can be actively less safe than the listing it replaced.\n */\nconst invoke = async (decl: Declaration, args: unknown, extra: unknown): Promise<unknown> => {\n const shape = decl.config.inputSchema;\n if (!shape) return (decl.handler as unknown as (e: unknown) => unknown)(extra);\n const parsed = await z.object(shape).safeParseAsync(args ?? {});\n if (!parsed.success) {\n const why = parsed.error.issues\n .map((i) => `${i.path.join(\".\") || \"(root)\"}: ${i.message}`)\n .join(\"; \");\n return fail(`Invalid arguments for ${decl.name}: ${why}`);\n }\n return (decl.handler as unknown as (a: unknown, e: unknown) => unknown)(parsed.data, extra);\n};\n\n// ─── the entry point ─────────────────────────────────────────────────────────\n\nexport type LazyToolsOptions = {\n /** Surface id as in surfaces.json, e.g. \"mail\". Prefixes every facade name. */\n surface: string;\n /** Display name, e.g. \"Mail\". Used only in the facade's own descriptions. */\n displayName: string;\n lazy: boolean;\n allowWrites: boolean;\n};\n\n/**\n * Register a surface's tools, either directly or behind the facade.\n *\n * The callback takes `allowWrites` rather than closing over it so the facade\n * can run it a second time with the gate forced shut and learn which tools are\n * writes. Everything else it needs — the client, the config — it closes over as\n * before.\n */\nexport const withLazyTools = (\n server: McpServer,\n opts: LazyToolsOptions,\n register: (target: McpServer, allowWrites: boolean) => void,\n): void => {\n if (!opts.lazy) {\n register(server, opts.allowWrites);\n return;\n }\n\n const all: Declaration[] = [];\n register(recorder(all), opts.allowWrites);\n\n let writeNames = new Set<string>();\n if (opts.allowWrites) {\n const readsOnly: Declaration[] = [];\n register(recorder(readsOnly), false);\n const readNames = new Set(readsOnly.map((d) => d.name));\n writeNames = new Set(all.filter((d) => !readNames.has(d.name)).map((d) => d.name));\n }\n\n const prefix = `apple_${opts.surface}`;\n\n /*\n * Diagnostics stays eagerly listed. It is the tool every surface guide points\n * at first when something returns `degraded: true` or a permission error, and\n * a model that has just been refused should not have to discover the search\n * tool before it can find out why. All eight cost 3,766 B combined.\n */\n /*\n * Everything the facade registers, as one callable — so it can be measured\n * before it is mounted.\n *\n * A facade is not free: it costs its own declarations, and it buys back only\n * what the real listing would have cost. On a small surface that trade is a\n * loss, and the loss is invisible because both halves look like a working\n * facade. See the floor below.\n */\n const mount = (target: McpServer): void => {\n const eagerName = `${prefix}_diagnostics`;\n const eager = all.filter((d) => d.name === eagerName);\n for (const decl of eager)\n target.registerTool(decl.name, decl.config as never, decl.handler as never);\n\n const lazy = all.filter((d) => d.name !== eagerName);\n const reads = lazy.filter((d) => !writeNames.has(d.name));\n const writes = lazy.filter((d) => writeNames.has(d.name));\n const byName = new Map(lazy.map((d) => [d.name, d]));\n const readIndex = reads.map(index);\n const writeIndex = writes.map(index);\n const allIndex = [...readIndex, ...writeIndex];\n\n const searchName = `${prefix}_search_tools`;\n const describeName = `${prefix}_describe_tool`;\n const callName = `${prefix}_call_tool`;\n const callWriteName = `${prefix}_call_write_tool`;\n\n target.registerTool(\n searchName,\n {\n description:\n `Find ${opts.displayName} tools by what you want to do. This server loads its ` +\n `${allIndex.length} tools on demand: they are not listed up front, and this is how you ` +\n `reach them. Returns matching tool names with a one-line summary each. Call with no ` +\n `query to list everything. Read a schema with ${describeName}, then run it with ` +\n `${callName}` +\n (writes.length > 0 ? ` or ${callWriteName}` : \"\") +\n `.`,\n inputSchema: {\n query: z\n .string()\n .optional()\n .describe(\"What you want to do, e.g. 'search messages' or 'unread'. Omit to list all.\"),\n },\n annotations: { readOnlyHint: true, idempotentHint: true },\n },\n ({ query }) =>\n okText(\n renderSearch(find(allIndex, query ?? \"\"), allIndex.length, {\n search: searchName,\n describe: describeName,\n call: callName,\n }),\n ),\n );\n\n target.registerTool(\n describeName,\n {\n description:\n `Get the full description and input schema of one ${opts.displayName} tool, as it would ` +\n `have appeared in a normal tool listing. Find names with ${searchName} first.`,\n inputSchema: {\n name: z.string().describe(`Exact tool name, e.g. ${lazy[0]?.name ?? callName}.`),\n },\n annotations: { readOnlyHint: true, idempotentHint: true },\n },\n ({ name }) => {\n const decl = byName.get(name);\n if (!decl) {\n const suggestions = nearest([...byName.keys()], name, prefix);\n return fail(\n `No tool named ${name}.` +\n (suggestions.length > 0 ? ` Did you mean: ${suggestions.join(\", \")}?` : \"\"),\n );\n }\n const runner = writeNames.has(name) ? callWriteName : callName;\n return okText(\n `${JSON.stringify(describe(decl))}\\n\\nCall it with ${runner}: {\"name\": ${JSON.stringify(name)}, \"arguments\": {…}}.`,\n );\n },\n );\n\n target.registerTool(\n callName,\n {\n description:\n `Run one of this server's read-only ${opts.displayName} tools. Find a name with ` +\n `${searchName} and its arguments with ${describeName}. Reads only: it cannot reach ` +\n (writes.length > 0\n ? `anything that changes ${opts.displayName} — those go through ${callWriteName}.`\n : `anything that changes ${opts.displayName}, and this server has writes turned off.`),\n inputSchema: {\n name: z.string().describe(\"Exact tool name to run.\"),\n arguments: z\n .record(z.string(), z.unknown())\n .optional()\n .describe(\"That tool's arguments.\"),\n },\n annotations: { readOnlyHint: true },\n },\n async ({ name, arguments: args }, extra) => {\n const decl = byName.get(name);\n if (!decl) {\n const suggestions = nearest([...byName.keys()], name, prefix);\n return fail(\n `No tool named ${name}.` +\n (suggestions.length > 0 ? ` Did you mean: ${suggestions.join(\", \")}?` : \"\"),\n );\n }\n if (writeNames.has(name)) {\n return fail(\n `${name} changes ${opts.displayName}, so it cannot be run through ${callName}. Use ${callWriteName}.`,\n );\n }\n return (await invoke(decl, args, extra)) as ToolResult;\n },\n );\n\n /*\n * Absent, not present-and-empty, when the gate is shut. A dispatcher that\n * exists and refuses everything is exactly the \"registered but refuses\" shape\n * docs/alternatives.md holds against a competitor.\n */\n if (writes.length > 0) {\n // Named in full rather than counted. A permission prompt that says only\n // \"a Mail write tool\" tells the person approving it nothing about the\n // blast radius, and this description is the only place left that can.\n const writeList = writes.map((d) => d.name.slice(prefix.length + 1)).join(\", \");\n target.registerTool(\n callWriteName,\n {\n description:\n `Run one of this server's ${writes.length} ${opts.displayName} tools that CHANGE data ` +\n `— ${writeList}. Find arguments ` +\n `with ${describeName}. Read-only tools go through ${callName} instead.`,\n inputSchema: {\n name: z.string().describe(\"Exact tool name to run.\"),\n arguments: z\n .record(z.string(), z.unknown())\n .optional()\n .describe(\"That tool's arguments.\"),\n },\n annotations: { readOnlyHint: false, destructiveHint: true },\n },\n async ({ name, arguments: args }, extra) => {\n const decl = byName.get(name);\n if (!decl) {\n const suggestions = nearest([...writeNames], name, prefix);\n return fail(\n `No tool named ${name}.` +\n (suggestions.length > 0 ? ` Did you mean: ${suggestions.join(\", \")}?` : \"\"),\n );\n }\n if (!writeNames.has(name)) {\n return fail(`${name} is read-only. Use ${callName}.`);\n }\n return (await invoke(decl, args, extra)) as ToolResult;\n },\n );\n }\n };\n\n /*\n * A listing too small to be worth searching is served whole, whatever the\n * switch says.\n *\n * Both terms have to hold, and the count is the one that decides the real\n * cases. Measured on this repo's own servers with writes on: contacts is 7\n * tools against a 5-declaration facade and messages is 8 — 1.4x and 1.6x,\n * so neither clears the count — while both clear the bytes comfortably\n * (2.6x and 3.8x), because a handful of fat schemas outweighs a facade made\n * of prose. A floor counting bytes alone would front them and buy nothing\n * but a coarser permission prompt: the client stops seeing\n * `apple_contacts_search_contacts` in its approval dialog and starts seeing\n * \"reads on Contacts\".\n *\n * Bytes are the wire entry as `describe` renders it, which is the same unit\n * the ~106 KB measurement at the top of this file is quoted in.\n */\n const facade: Declaration[] = [];\n mount(recorder(facade));\n const wire = (decls: Declaration[]): number =>\n decls.reduce((n, d) => n + JSON.stringify(describe(d)).length, 0);\n\n if (all.length < FRONTING_RATIO * facade.length || wire(all) < FRONTING_RATIO * wire(facade)) {\n register(server, opts.allowWrites);\n return;\n }\n\n mount(server);\n};\n","import { accessSync, constants, statSync } from \"node:fs\";\n\n/**\n * Facts about a TCC-protected file.\n *\n * The subtlety this exists to encode: `statSync` **succeeds** on a\n * TCC-protected file — you get the real size and mtime — and only `open(2)` and\n * `access(2)` are denied. Existence and readability are therefore different\n * questions, and only readability tells you whether Full Disk Access is\n * granted. \"The file is there\" is not evidence.\n */\nexport type FileFacts = {\n exists: boolean;\n readable: boolean;\n size: number | null;\n mtime: string | null;\n};\n\nexport const inspectFile = (path: string): FileFacts => {\n let size: number | null = null;\n let mtime: string | null = null;\n try {\n const st = statSync(path);\n size = st.size;\n mtime = st.mtime.toISOString();\n } catch {\n return { exists: false, readable: false, size: null, mtime: null };\n }\n let readable = false;\n try {\n accessSync(path, constants.R_OK);\n readable = true;\n } catch {\n readable = false;\n }\n return { exists: true, readable, size, mtime };\n};\n\nexport type StoreFacts = FileFacts & {\n /** Whether a `-wal` sits beside it, i.e. whether `immutable=1` COULD miss recent writes. */\n walPresent: boolean;\n walSizeBytes: number | null;\n};\n\n/**\n * Describe a SQLite store and its write-ahead log in one call.\n *\n * The WAL matters because `immutable=1` skips it, so a read can silently miss\n * whatever has not been checkpointed — which is precisely the recent data an\n * agent is usually asked about.\n */\nexport const describeStore = (path: string): StoreFacts => {\n const facts = inspectFile(path);\n const wal = inspectFile(`${path}-wal`);\n return { ...facts, walPresent: wal.exists, walSizeBytes: wal.size };\n};\n","/**\n * The only place in this family of servers that spawns a process.\n *\n * ## Why this is shared rather than copied\n *\n * Two of the guarantees below are security invariants, and an invariant that\n * exists in two copies is one refactor away from existing in one:\n *\n * * `assertStaticScript` is a shell-injection tripwire.\n * * `createQueue` serialises Apple Events, without which -1712 floods.\n *\n * ## Why this is safe\n *\n * `execFile`, never `exec` — there is no shell, so there is no quoting question\n * to get wrong.\n *\n * More importantly: **no caller input is ever interpolated into script text**.\n * The script is a static constant piped to osascript's stdin (`-`), and every\n * variable value arrives as `argv[0]` — a single JSON string that the script\n * parses. Verified against a live Mail: an account name of\n *\n * \"; do shell script \"touch /tmp/pwned\"; //\n *\n * arrives at `run(argv)` as inert data and creates no file. A mailbox named\n * after a shell metacharacter is data, not syntax, and there is no code path\n * where that changes.\n *\n * `assertStaticScript` is the tripwire that keeps it that way: a script string\n * containing `${` means someone reached for a template interpolation, which is\n * exactly the mistake this design exists to prevent.\n */\n\nimport { execFile } from \"node:child_process\";\n\nimport {\n AppBusyError,\n AppNotRunningError,\n OsascriptTimeoutError,\n PlatformError,\n ProtocolError,\n TccDeniedError,\n type SurfaceContext,\n} from \"./errors.js\";\n\nexport type Logger = {\n debug?: (...args: unknown[]) => void;\n warn?: (...args: unknown[]) => void;\n error?: (...args: unknown[]) => void;\n};\n\n/** The envelope every JXA script returns. Application failures come back on exit 0. */\nexport type JxaEnvelope<T> =\n | { ok: true; data: T }\n // Extra keys on `error` are carried through onto the thrown error's details.\n // Messages' send ladder uses this to report which targeting strategies were\n // tried and why each failed, which is the only diagnostic that surface has.\n | { ok: false; error: { code: string; message: string; [key: string]: unknown } };\n\nexport type OsascriptRunner = {\n /** Run a static script with one JSON-serialisable parameter object. */\n run: <T>(script: string, params?: unknown) => Promise<T>;\n};\n\n/**\n * The process boundary, as a seam. Tests substitute this so that everything\n * above it — the queue, the static-script tripwire, argv construction and\n * envelope handling — still runs for real; mocking `run` itself would skip\n * exactly the code these guarantees live in.\n */\nexport type ExecImpl = (\n path: string,\n args: string[],\n script: string,\n timeoutMs: number,\n) => Promise<string>;\n\nexport type OsascriptOptions = {\n osascriptPath: string;\n timeoutMs: number;\n /** Named in every user-facing error this module can throw. */\n surface: SurfaceContext;\n logger?: Logger | undefined;\n exec?: ExecImpl | undefined;\n};\n\nconst MAX_BUFFER = 32 * 1024 * 1024;\n\n/**\n * Reject any script that looks like it was built by interpolation. Crude on\n * purpose: the cost of a false positive is renaming a variable, and the cost of\n * a false negative is a shell injection.\n */\nexport const assertStaticScript = (script: string): void => {\n if (script.includes(\"${\")) {\n throw new PlatformError(\n \"Refusing to run a JXA script containing `${`. Scripts must be static constants; \" +\n \"pass every value through the params object, which arrives as argv[0].\",\n );\n }\n};\n\n/** Map osascript's trailing `(-NNNN)` error code onto something actionable. */\nexport const mapOsaError = (stderr: string, timeoutMs: number, surface: SurfaceContext): Error => {\n const code = /\\((-\\d{3,4})\\)\\s*$/.exec(stderr.trim())?.[1];\n switch (code) {\n case \"-1743\":\n return new TccDeniedError(surface);\n case \"-600\":\n case \"-609\":\n return new AppNotRunningError(surface);\n case \"-1712\":\n return new AppBusyError(surface);\n default:\n break;\n }\n const thrown = /execution error:\\s*(?:Error:\\s*)?(.+?)\\s*\\(-?\\d+\\)\\s*$/m.exec(stderr.trim())?.[1];\n return new ProtocolError(\n thrown ?? stderr.trim().slice(0, 500) ?? `osascript failed (${timeoutMs}ms budget)`,\n );\n};\n\n/**\n * Serialise every invocation. Apple Event dispatch is single-threaded per app:\n * concurrent calls do not finish sooner, they just make -1712 (busy) likelier.\n * Batch within one script instead of parallelising across several.\n */\nconst createQueue = () => {\n let tail: Promise<unknown> = Promise.resolve();\n return <T>(job: () => Promise<T>): Promise<T> => {\n const next = tail.then(job, job);\n tail = next.catch(() => undefined);\n return next;\n };\n};\n\nconst defaultExec =\n (surface: SurfaceContext): ExecImpl =>\n (path, args, script, timeoutMs) =>\n new Promise((resolve, reject) => {\n const child = execFile(\n path,\n args,\n {\n timeout: timeoutMs,\n maxBuffer: MAX_BUFFER,\n killSignal: \"SIGKILL\",\n encoding: \"utf8\",\n // Inherit nothing. osascript needs no environment, and a minimal one\n // removes any question of PATH or locale influencing the run.\n env: { PATH: \"/usr/bin:/bin\" },\n },\n (err, stdout, stderr) => {\n if (!err) {\n resolve(stdout);\n return;\n }\n const killed = (err as NodeJS.ErrnoException & { killed?: boolean }).killed;\n if (killed) {\n reject(new OsascriptTimeoutError(timeoutMs, surface));\n return;\n }\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") {\n reject(\n new PlatformError(\n `${path} not found. This server only runs on macOS with ${surface.appName} installed.`,\n ),\n );\n return;\n }\n reject(mapOsaError(stderr || String(err.message), timeoutMs, surface));\n },\n );\n child.stdin?.end(script);\n });\n\nexport const createOsascriptRunner = (opts: OsascriptOptions): OsascriptRunner => {\n const enqueue = createQueue();\n const exec = opts.exec ?? defaultExec(opts.surface);\n\n const run = async <T>(script: string, params?: unknown): Promise<T> => {\n assertStaticScript(script);\n const args = [\"-l\", \"JavaScript\", \"-\", JSON.stringify(params ?? {})];\n const stdout = await enqueue(() => exec(opts.osascriptPath, args, script, opts.timeoutMs));\n\n let envelope: JxaEnvelope<T>;\n try {\n envelope = JSON.parse(stdout) as JxaEnvelope<T>;\n } catch {\n throw new ProtocolError(`osascript returned non-JSON output: ${stdout.slice(0, 500)}`);\n }\n\n if (!envelope.ok) {\n // Application-level failures come back on exit 0 so that a non-zero exit\n // unambiguously means infrastructure. Re-inflate them into real errors.\n const { code, message, ...rest } = envelope.error;\n // MAIL_NOT_RUNNING predates the generic name and is still emitted by the\n // Mail prelude; both mean the same thing.\n if (code === \"APP_NOT_RUNNING\" || code === \"MAIL_NOT_RUNNING\") {\n throw new AppNotRunningError(opts.surface);\n }\n if (code === \"NOT_AUTHORIZED\") throw new TccDeniedError(opts.surface);\n throw new ProtocolError(message, { code, ...rest });\n }\n\n opts.logger?.debug?.(\"osascript ok\");\n return envelope.data;\n };\n\n return { run };\n};\n\n/** Retry a busy failure once. Apps return -1712 while mid-sync and succeed moments later. */\nexport const withBusyRetry = async <T>(fn: () => Promise<T>, delayMs = 1500): Promise<T> => {\n try {\n return await fn();\n } catch (err) {\n if (!(err instanceof AppBusyError)) throw err;\n await new Promise((r) => setTimeout(r, delayMs));\n return fn();\n }\n};\n","import type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\n\n/**\n * The surface resources.\n *\n * ## Why a resource when a tool already returns this\n *\n * `apple_mail_diagnostics` and `apple_mail_list_accounts` answer these same\n * questions, and they stay. What they cannot do is be *addressed*. A tool result\n * exists only after the model decided to spend a call on it, which means the\n * account list is re-derived every session and the diagnostics report is read\n * after the failure rather than before it. A resource is a URI: a host can\n * attach it, cache it, or let a user paste it in, and none of that costs a tool\n * call or depends on the model guessing that it should look.\n *\n * ## The scheme is `cupertino://`, not `apple://`\n *\n * The tools are named `apple_mail_*` because they say what they drive. A URI\n * scheme is a different kind of claim — it is a namespace, and taking Apple's\n * would read as affiliation this project spends a README line disclaiming. So\n * the authority is the surface id and the scheme is the project's own name, the\n * one already in the bundle identifier.\n *\n * ## Three per surface, and only one of them can fail\n *\n * - `guide` is static text. It needs no permission, touches no store and spawns\n * no process, so it is readable when every other lane is denied — which is\n * exactly the moment its contents are worth reading.\n * - `diagnostics` is the live capability report.\n * - `inventory` is the set of containers you address by name: accounts, and\n * whatever the surface calls its folders. Surfaces with no such containers\n * (Messages, Safari) register two resources rather than inventing a third.\n */\n\nexport const RESOURCE_SCHEME = \"cupertino\";\n\n/** `cupertino://mail/guide`. The one place this string is built. */\nexport const surfaceUri = (surface: string, leaf: string): string =>\n `${RESOURCE_SCHEME}://${surface}/${leaf}`;\n\nexport type ResourceReader = () => Promise<unknown>;\n\nexport type SurfaceResourceOptions = {\n /** Surface id as it appears in surfaces.json, e.g. \"mail\". */\n surface: string;\n /** Display name, e.g. \"Mail\". Used only in resource titles. */\n displayName: string;\n /** The operating manual. Static markdown — see `guide.ts` in each surface. */\n guide: string;\n /** The live capability report, normally the diagnostics tool's own payload. */\n diagnostics: ResourceReader;\n /** The addressable containers. Omitted by surfaces that have none. */\n inventory?: {\n /** What this surface calls them, e.g. \"accounts and mailboxes\". */\n describes: string;\n read: ResourceReader;\n };\n};\n\nconst jsonContents = (uri: string, data: unknown) => ({\n contents: [\n {\n uri,\n mimeType: \"application/json\",\n text: JSON.stringify(data),\n },\n ],\n});\n\n/**\n * Read a resource without ever throwing.\n *\n * A tool that fails returns `isError` and keeps its text; a resource read that\n * throws becomes a JSON-RPC error and keeps nothing. That asymmetry is worst\n * precisely on `diagnostics`, the resource whose whole job is to explain a\n * broken machine: letting a TCC denial replace the report with \"resource read\n * failed\" would delete the answer at the only moment anyone wants it.\n *\n * So a failed read is *data*, shaped like the `degraded` results the tools\n * already return, and the caller can tell an unreadable store from an empty one.\n */\nconst guardedRead = async (surface: string, uri: string, read: ResourceReader) => {\n try {\n return jsonContents(uri, await read());\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n const details = (err as Error & { details?: unknown })?.details;\n return jsonContents(uri, {\n degraded: true,\n error: message,\n ...(err instanceof Error ? { kind: err.name } : {}),\n ...(details ? { details } : {}),\n hint: `Read ${surfaceUri(surface, \"diagnostics\")} for what this server can currently reach.`,\n });\n }\n};\n\n/** Register a surface's guide, diagnostics and (where it has one) inventory. */\nexport const registerSurfaceResources = (server: McpServer, opts: SurfaceResourceOptions): void => {\n const { surface, displayName, guide, diagnostics, inventory } = opts;\n\n const guideUri = surfaceUri(surface, \"guide\");\n server.registerResource(\n `${surface}-guide`,\n guideUri,\n {\n title: `${displayName}: how to drive this server`,\n description:\n `How to use the ${displayName} tools well: what each ref means, which tool to reach for ` +\n \"under which constraint, what a degraded result does and does not say, and what the \" +\n \"write gate is currently hiding. Static text — readable even with every permission denied.\",\n mimeType: \"text/markdown\",\n },\n (uri) => ({ contents: [{ uri: uri.href, mimeType: \"text/markdown\", text: guide }] }),\n );\n\n const diagnosticsUri = surfaceUri(surface, \"diagnostics\");\n server.registerResource(\n `${surface}-diagnostics`,\n diagnosticsUri,\n {\n title: `${displayName}: capabilities and permissions`,\n description:\n `What this ${displayName} server can currently do and why — the same report as the ` +\n \"diagnostics tool, addressable without spending a tool call. Read it before trusting an \" +\n \"empty result.\",\n mimeType: \"application/json\",\n },\n () => guardedRead(surface, diagnosticsUri, diagnostics),\n );\n\n if (!inventory) return;\n\n const inventoryUri = surfaceUri(surface, \"inventory\");\n server.registerResource(\n `${surface}-inventory`,\n inventoryUri,\n {\n title: `${displayName}: ${inventory.describes}`,\n description:\n `The ${inventory.describes} this server can see, spelled exactly as ${displayName} ` +\n \"spells them. These are the names every other tool takes, so reading this first is the \" +\n \"difference between a filter that matches and one that silently matches nothing.\",\n mimeType: \"application/json\",\n },\n () => guardedRead(surface, inventoryUri, inventory.read),\n );\n};\n","import type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport type { GetPromptResult } from \"@modelcontextprotocol/sdk/types.js\";\nimport { z } from \"zod\";\n\nimport { surfaceUri } from \"./resources.js\";\n\n/**\n * The workflow prompts.\n *\n * ## What a prompt is for here\n *\n * The same thing every tool description in this repo is for: holding a\n * constraint the model would otherwise re-derive. A tool holds the ones that\n * are about *one call* — that a body search wants a narrowing filter, that a\n * ref is opaque. A prompt holds the ones that are about the *order of calls*,\n * and those have nowhere else to live. \"Search before you list\", \"read the\n * thread before you answer it\", \"check what exists before you create a\n * duplicate\" are not properties of any single tool, so no single tool\n * description can carry them, and the model rebuilds them from scratch every\n * session — usually correctly, sometimes not, always at a cost.\n *\n * ## Every prompt embeds its surface guide\n *\n * A prompt returns messages, and one of them is the `cupertino://<surface>/guide`\n * resource. That is the coupling that makes both primitives worth more than\n * either alone: the guide is the reference, the prompt is the task, and a host\n * that expands the prompt gets both without the model having to know the guide\n * exists.\n *\n * ## Write-gated prompts follow the tools\n *\n * A prompt that ends in a mutation is registered only when writes are on, for\n * the same reason the mutating tools are: with the gate closed it must not\n * merely refuse, it must be *invisible*. A visible `draft_reply` on a\n * read-only server is an offer the server cannot keep.\n */\n\n/** MCP prompt arguments are strings on the wire. This is the only shape they take. */\nexport const promptArg = (description: string): z.ZodOptional<z.ZodString> =>\n z.string().optional().describe(description);\n\n/** Same, for an argument the prompt is useless without. */\nexport const requiredPromptArg = (description: string): z.ZodString =>\n z.string().min(1).describe(description);\n\nexport type PromptContext = {\n /** Surface id, e.g. \"mail\". */\n surface: string;\n /** The static guide, embedded ahead of every prompt's instruction. */\n guide: string;\n};\n\nexport type WorkflowPrompt<Args extends z.ZodRawShape> = {\n /** Namespaced like the tools, e.g. \"apple_mail_triage\". */\n name: string;\n title: string;\n /** What this does and when to reach for it. Shown in the host's prompt list. */\n description: string;\n argsSchema?: Args;\n /**\n * The instruction. Receives validated arguments; returns the text that does\n * the actual work of ordering the calls.\n */\n build: (args: { [K in keyof Args]: z.infer<Args[K]> }) => string;\n};\n\n/**\n * Register one workflow prompt.\n *\n * Called once per prompt rather than handed an array, because the argument\n * shape is generic and an array of prompts with differing shapes loses the\n * inference that makes `build`'s parameter typed at all.\n */\nexport const registerWorkflowPrompt = <Args extends z.ZodRawShape>(\n server: McpServer,\n ctx: PromptContext,\n prompt: WorkflowPrompt<Args>,\n): void => {\n const guideUri = surfaceUri(ctx.surface, \"guide\");\n\n const result = (instruction: string): GetPromptResult => ({\n messages: [\n {\n role: \"user\",\n content: {\n type: \"resource\",\n resource: { uri: guideUri, mimeType: \"text/markdown\", text: ctx.guide },\n },\n },\n { role: \"user\", content: { type: \"text\", text: instruction } },\n ],\n });\n\n server.registerPrompt(\n prompt.name,\n {\n title: prompt.title,\n description: prompt.description,\n ...(prompt.argsSchema ? { argsSchema: prompt.argsSchema } : {}),\n },\n // The SDK hands an args object only when a schema was declared. Both\n // callback arities are assignable here; the cast keeps one code path.\n ((args: { [K in keyof Args]: z.infer<Args[K]> }) =>\n result(prompt.build(args ?? ({} as never)))) as never,\n );\n};\n","import { z } from \"zod\";\n\n/**\n * Projection and aggregation for the read-only `*_query` tools.\n *\n * These exist to keep answers out of the context window that a model would\n * otherwise have to derive by reading rows. \"Who emailed me most in June\" is\n * one grouped table here; through a plain search it is two hundred message\n * summaries the model has to tally itself, and it pays for every subject,\n * recipient list and flag on the way.\n *\n * Nothing here builds SQL. Callers pass a field name that their surface has\n * already matched against its own allowlist, and the surface maps it to a\n * column expression. A caller's string never reaches a query.\n */\n\nexport const selectArg = z\n .array(z.string().min(1))\n .min(1)\n .optional()\n .describe(\n \"Keep only these fields on each row. Omit for the full row. Naming the two or three \" +\n \"fields you actually need is the cheapest way to shrink a large result.\",\n );\n\n/**\n * Build the `groupBy` arg from the fields a surface can actually group on.\n *\n * Taken as a parameter rather than fixed here because the sensible groupings\n * differ per surface — mail groups by sender, a calendar by day.\n */\nexport const groupByArg = <const F extends readonly [string, ...string[]]>(fields: F, note = \"\") =>\n z\n .enum(fields)\n .optional()\n .describe(\n `Aggregate instead of returning rows: count matches per ${fields.join(\", \")}. ` +\n \"Grouping runs over every match, not just the first `limit` of them — `limit` then \" +\n `caps how many groups come back, ordered by descending count.${note ? ` ${note}` : \"\"}`,\n );\n\nexport type Bucket = {\n /** The grouped value. Null when the underlying field is null (no sender, etc.). */\n key: string | null;\n /** A display form of `key` where one exists — a sender's name, a mailbox's title. */\n label?: string | null;\n count: number;\n} & Record<string, unknown>;\n\nexport type Aggregation = {\n groupedBy: string;\n groups: Bucket[];\n /** How many distinct groups matched, before `limit` cut the list. */\n totalGroups: number;\n /** How many underlying rows were aggregated. NOT capped by `limit`. */\n totalRows: number;\n /** True when `totalGroups` exceeded `limit`, so `groups` is a top-N. */\n truncated: boolean;\n};\n\n/**\n * Assemble the aggregation envelope.\n *\n * Thin on purpose, but it is the one place `truncated` is computed and the one\n * guarantee that `totalRows` is always reported. A grouped result that omits\n * `totalRows` reads as complete whether or not it is, which is the failure this\n * whole shape exists to prevent: a top-N over a truncated page is a confidently\n * wrong answer, and it looks exactly like a right one.\n */\nexport const describeAggregation = (\n groupedBy: string,\n groups: Bucket[],\n totals: { totalGroups: number; totalRows: number },\n): Aggregation => ({\n groupedBy,\n groups,\n totalGroups: totals.totalGroups,\n totalRows: totals.totalRows,\n truncated: totals.totalGroups > groups.length,\n});\n\nexport type Projected<T> = {\n rows: Partial<T>[];\n /**\n * Field names the caller asked for that this surface does not have. Reported\n * rather than dropped: a silent drop looks identical to \"that field was null\n * on every row\", and a model has no way to tell the two apart.\n */\n unknownFields?: string[];\n};\n\n/**\n * Keep only the named fields on each row.\n *\n * `known` is passed explicitly rather than read off the rows because an empty\n * result still has to be able to say a field name was wrong — deriving the key\n * set from the rows would report nothing at all on the case where a typo is\n * most likely to be the reason the result is empty.\n */\nexport const project = <T extends Record<string, unknown>>(\n rows: T[],\n select: string[] | undefined,\n known: readonly string[],\n): Projected<T> => {\n if (!select?.length) return { rows };\n\n const knownSet = new Set(known);\n const wanted = select.filter((f) => knownSet.has(f));\n const unknownFields = select.filter((f) => !knownSet.has(f));\n\n // Every name was wrong. Projecting to {} would hand back a wall of empty\n // objects; the full row plus the complaint is the more useful answer.\n if (!wanted.length) return { rows, unknownFields };\n\n return {\n rows: rows.map((row) => {\n const out: Partial<T> = {};\n for (const field of wanted) {\n if (field in row) out[field as keyof T] = row[field as keyof T];\n }\n return out;\n }),\n ...(unknownFields.length ? { unknownFields } : {}),\n };\n};\n","import { createHash } from \"node:crypto\";\nimport type { DatabaseSync } from \"node:sqlite\";\n\n/**\n * Schema introspection for stores Apple owns and can reshape in any release.\n *\n * Nothing here assumes a column exists. The failure mode being avoided is a\n * `SELECT *` that starts throwing after a system update and takes the whole\n * server down with it.\n */\n\n/** 2001-01-01T00:00:00Z in Unix seconds — the Core Data epoch. */\nexport const CORE_DATA_EPOCH_OFFSET = 978_307_200;\n\nexport const columnsOf = (db: DatabaseSync, table: string): string[] => {\n try {\n return (db.prepare(`PRAGMA table_info(\"${table}\")`).all() as { name: string }[]).map(\n (c) => c.name,\n );\n } catch {\n return [];\n }\n};\n\n/** Every table and its columns, for capability checks that name what is missing. */\nexport const tableMap = (db: DatabaseSync): Record<string, string[]> => {\n const names = (\n db.prepare(\"SELECT name FROM sqlite_master WHERE type = 'table'\").all() as { name: string }[]\n ).map((r) => r.name);\n const tables: Record<string, string[]> = {};\n for (const t of names) tables[t] = columnsOf(db, t);\n return tables;\n};\n\n/**\n * A short hash of the whole DDL. Cheap drift detection: when Apple reshapes the\n * schema this changes, which turns \"why did queries start failing after the\n * update\" into a value you can compare against what was captured.\n */\nexport const fingerprintSchema = (db: DatabaseSync): string => {\n const ddl = db\n .prepare(\"SELECT sql FROM sqlite_master WHERE sql IS NOT NULL ORDER BY type, name\")\n .all() as { sql: string }[];\n return createHash(\"sha256\")\n .update(ddl.map((r) => r.sql).join(\"\\n\"))\n .digest(\"hex\")\n .slice(0, 12);\n};\n\n/**\n * Work out whether a timestamp column is Unix seconds or Core Data seconds by\n * seeing which reading lands near today. Two independent prior-art projects\n * disagree about this for Mail, and hardcoding the wrong one puts every date 31\n * years out — a bug that looks like corruption rather than a unit mismatch.\n */\nexport const detectEpoch = (\n maxTimestamp: number | null,\n now: number = Date.now(),\n): { offset: number; reason: string } => {\n if (maxTimestamp === null || !Number.isFinite(maxTimestamp) || maxTimestamp <= 0) {\n return { offset: 0, reason: \"no dated rows; assuming unix seconds\" };\n }\n const nowSec = now / 1000;\n const tenYears = 10 * 365.25 * 24 * 3600;\n const asUnix = Math.abs(nowSec - maxTimestamp);\n const asCoreData = Math.abs(nowSec - (maxTimestamp + CORE_DATA_EPOCH_OFFSET));\n\n if (asUnix < tenYears && asUnix <= asCoreData) {\n return { offset: 0, reason: \"raw value lands within 10 years of now\" };\n }\n if (asCoreData < tenYears) {\n return {\n offset: CORE_DATA_EPOCH_OFFSET,\n reason: \"value + 978307200 lands within 10 years of now\",\n };\n }\n return {\n offset: 0,\n reason: `neither epoch lands near now (max=${maxTimestamp}); assuming unix`,\n };\n};\n","import { DatabaseSync } from \"node:sqlite\";\n\nimport { IndexUnavailableError } from \"./errors.js\";\n\n/**\n * Read-only access to a store some Apple app owns.\n *\n * Two rules, both load-bearing:\n *\n * 1. **Never write.** The app owns the database, holds it open, and reconciles\n * it against a server. `PRAGMA query_only` makes that structural rather than\n * a matter of everyone remembering.\n * 2. **Prefer `mode=ro` over `immutable=1`.** `immutable=1` tells SQLite the\n * file cannot change and to skip the `-wal` entirely — so a read silently\n * misses anything not yet checkpointed. Measured on a live Mail index: the\n * two modes reported 181427 and 181426 messages minutes after reporting the\n * same number, the difference being one newly-arrived mail. It is a race you\n * lose intermittently and without any error, which is the worst kind.\n */\nexport type ReadOnlyMode = \"auto\" | \"ro\" | \"immutable\" | \"off\";\n\nexport type OpenedStore<T = undefined> = {\n db: DatabaseSync;\n /** Which mode actually opened. `immutable` results are WAL-blind — say so. */\n mode: \"ro\" | \"immutable\";\n /** Whatever `validate` returned, so a capability probe is not run twice. */\n validated: T;\n};\n\n/**\n * SQLite URI filenames need percent-encoding, and Mail's path contains a space\n * (\"Envelope Index\"). `?` and `#` would otherwise be read as URI syntax.\n */\nexport const toFileUri = (path: string, query: string): string =>\n `file:${encodeURI(path).replaceAll(\"?\", \"%3f\").replaceAll(\"#\", \"%23\")}?${query}`;\n\n/** Escape LIKE wildcards so a value containing % or _ searches literally. */\nexport const escapeLike = (value: string): string =>\n value.replaceAll(\"\\\\\", \"\\\\\\\\\").replaceAll(\"%\", \"\\\\%\").replaceAll(\"_\", \"\\\\_\");\n\nexport type OpenOptions<T> = {\n /** Named in the error when `mode` is \"off\", so the message says how to re-enable. */\n envVar?: string | undefined;\n /** What the store is, for the failure message, e.g. \"Mail's search index\". */\n label?: string | undefined;\n /** Appended to the failure message — typically how to grant the missing permission. */\n hint?: string | undefined;\n /**\n * Runs on every attempt; throwing rejects that attempt and tries the next\n * mode. Validating *inside* the ladder rather than after it matters: a store\n * that opens but is unusable should fall through, not be returned.\n */\n validate?: ((db: DatabaseSync) => T) | undefined;\n /**\n * Errors that no other open mode could fix, so the ladder aborts instead of\n * masking them behind a generic \"could not open\".\n */\n fatal?: ((err: unknown) => boolean) | undefined;\n /** Called when the WAL-blind fallback is what actually opened. */\n onFallback?: (() => void) | undefined;\n};\n\nexport const openReadOnly = <T = undefined>(\n path: string,\n mode: ReadOnlyMode,\n opts: OpenOptions<T> = {},\n): OpenedStore<T> => {\n if (mode === \"off\") {\n throw new IndexUnavailableError(\n `The index lane is disabled${opts.envVar ? ` (${opts.envVar}=off)` : \"\"}.`,\n );\n }\n\n const attempts: (\"ro\" | \"immutable\")[] =\n mode === \"auto\" ? [\"ro\", \"immutable\"] : [mode === \"ro\" ? \"ro\" : \"immutable\"];\n\n let lastError: unknown = null;\n for (const attempt of attempts) {\n // Declared outside the try so the catch can close it. `validate` runs\n // against an OPEN handle and every surface passes one — a schema drift or a\n // locked store threw straight past the close, leaking a descriptor onto a\n // file the Apple app itself holds open, on the `ro` attempt AND on the\n // fatal path that rethrows.\n let db: DatabaseSync | undefined;\n try {\n const uri = toFileUri(path, attempt === \"ro\" ? \"mode=ro\" : \"immutable=1\");\n db = new DatabaseSync(uri, { readOnly: true, allowExtension: false });\n // Belt and braces: no caller can issue DML even by accident.\n db.exec(\"PRAGMA query_only = 1\");\n const validated = opts.validate?.(db) as T;\n if (attempt === \"immutable\") opts.onFallback?.();\n return { db, mode: attempt, validated };\n } catch (err) {\n try {\n db?.close();\n } catch {\n // Already closed, or never opened. Nothing to salvage, and the original\n // error is the one worth reporting.\n }\n if (opts.fatal?.(err)) throw err;\n lastError = err;\n }\n }\n\n const message = lastError instanceof Error ? lastError.message : String(lastError);\n throw new IndexUnavailableError(\n `Could not open ${opts.label ?? path} at ${path}: ${message}.${opts.hint ? ` ${opts.hint}` : \"\"}`,\n );\n};\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mgcrea/mcp-apple-core",
3
- "version": "1.21.0",
3
+ "version": "1.21.1",
4
4
  "description": "Shared machinery for the Apple-app MCP servers: the osascript boundary, TCC-aware errors, and read-only SQLite access",
5
5
  "keywords": [
6
6
  "apple",