@mgcrea/mcp-apple-core 1.14.0 → 1.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  import { z } from "zod";
2
2
  import { DatabaseSync } from "node:sqlite";
3
3
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4
+ import { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
5
+ import { JSONRPCMessage } from "@modelcontextprotocol/sdk/types.js";
4
6
  //#region src/build-info.d.ts
5
7
  type PackageIdentity = {
6
8
  name: string;
@@ -293,6 +295,7 @@ declare const parseList: (v: string | undefined) => string[] | undefined;
293
295
  declare const BaseConfigSchema: z.ZodObject<{
294
296
  allowWrites: z.ZodDefault<z.ZodBoolean>;
295
297
  exposePrompts: z.ZodDefault<z.ZodBoolean>;
298
+ lazyTools: z.ZodDefault<z.ZodBoolean>;
296
299
  debug: z.ZodDefault<z.ZodBoolean>;
297
300
  osascriptPath: z.ZodDefault<z.ZodString>;
298
301
  osascriptTimeoutMs: z.ZodDefault<z.ZodNumber>;
@@ -306,6 +309,25 @@ declare const BaseConfigSchema: z.ZodObject<{
306
309
  */
307
310
  declare const parseConfig: <T extends z.ZodType>(schema: T, raw: Record<string, unknown>) => z.infer<T>;
308
311
  //#endregion
312
+ //#region src/facade.d.ts
313
+ type LazyToolsOptions = {
314
+ /** Surface id as in surfaces.json, e.g. "mail". Prefixes every facade name. */
315
+ surface: string;
316
+ /** Display name, e.g. "Mail". Used only in the facade's own descriptions. */
317
+ displayName: string;
318
+ lazy: boolean;
319
+ allowWrites: boolean;
320
+ };
321
+ /**
322
+ * Register a surface's tools, either directly or behind the facade.
323
+ *
324
+ * The callback takes `allowWrites` rather than closing over it so the facade
325
+ * can run it a second time with the gate forced shut and learn which tools are
326
+ * writes. Everything else it needs — the client, the config — it closes over as
327
+ * before.
328
+ */
329
+ declare const withLazyTools: (server: McpServer, opts: LazyToolsOptions, register: (target: McpServer, allowWrites: boolean) => void) => void;
330
+ //#endregion
309
331
  //#region src/fs.d.ts
310
332
  /**
311
333
  * Facts about a TCC-protected file.
@@ -337,6 +359,26 @@ type StoreFacts = FileFacts & {
337
359
  */
338
360
  declare const describeStore: (path: string) => StoreFacts;
339
361
  //#endregion
362
+ //#region src/listing.d.ts
363
+ /**
364
+ * Strip generated boilerplate from a `tools/list` reply, passing every other
365
+ * message through untouched.
366
+ *
367
+ * Copies rather than mutates. The SDK hands out the registered tool's own
368
+ * schema object, and deleting a key from it would edit the server's state from
369
+ * a function whose job is to shape one reply.
370
+ */
371
+ declare const trimToolListing: (message: JSONRPCMessage) => JSONRPCMessage;
372
+ /**
373
+ * Wrap a transport so every listing it sends is trimmed on the way out.
374
+ *
375
+ * Mutates and returns the transport it was given rather than proxying it: the
376
+ * SDK's `connect` reaches for `onmessage`, `onclose` and `onerror` on the very
377
+ * object it was handed, and a Proxy or a subclass would have to keep those in
378
+ * sync for no gain.
379
+ */
380
+ declare const withTrimmedListing: <T extends Transport>(transport: T) => T;
381
+ //#endregion
340
382
  //#region src/prompts.d.ts
341
383
  /**
342
384
  * The workflow prompts.
@@ -658,5 +700,5 @@ declare const limitArg: z.ZodOptional<z.ZodNumber>;
658
700
  declare const resolveLimit: (limit: number | undefined, maxResults: number, fallback?: number) => number;
659
701
  declare const confirmArg: z.ZodLiteral<true>;
660
702
  //#endregion
661
- export { type Aggregation, AppBusyError, AppNotRunningError, AppleAutomationError, BaseConfigSchema, type Bucket, type BuildInfo, CORE_DATA_EPOCH_OFFSET, type CodeConfidence, type CodeMatch, type ExecImpl, type ExtractOptions, type FileFacts, IndexUnavailableError, type JxaEnvelope, type Logger, type OpenOptions, type OpenedStore, type OsascriptOptions, type OsascriptRunner, OsascriptTimeoutError, type PackageIdentity, PlatformError, PreconditionError, type Projected, type PromptContext, ProtocolError, RESOURCE_SCHEME, type ReadOnlyMode, type ResourceReader, SchemaDriftError, type StdioServerOptions, type StoreFacts, type SurfaceContext, type SurfaceResourceOptions, TccDeniedError, type ToolResult, type WorkflowPrompt, WritesDisabledError, assertStaticScript, columnsOf, compact, confirmArg, createOsascriptRunner, describeAggregation, describeStore, detectEpoch, escapeLike, extractCode, fail, fingerprintSchema, groupByArg, inspectFile, limitArg, mapOsaError, ok, okText, openReadOnly, parseBool, parseConfig, parseIntOpt, parseList, project, promptArg, readPackageIdentity, registerSurfaceResources, registerWorkflowPrompt, requiredPromptArg, resolveLimit, runStdioServer, selectArg, surfaceUri, tableMap, toFailure, toFileUri, trimmed, withBusyRetry, wrap, wrapResult };
703
+ export { type Aggregation, AppBusyError, AppNotRunningError, AppleAutomationError, BaseConfigSchema, type Bucket, type BuildInfo, CORE_DATA_EPOCH_OFFSET, type CodeConfidence, type CodeMatch, type ExecImpl, type ExtractOptions, type FileFacts, IndexUnavailableError, type JxaEnvelope, type LazyToolsOptions, type Logger, type OpenOptions, type OpenedStore, type OsascriptOptions, type OsascriptRunner, OsascriptTimeoutError, type PackageIdentity, PlatformError, PreconditionError, type Projected, type PromptContext, ProtocolError, RESOURCE_SCHEME, type ReadOnlyMode, type ResourceReader, SchemaDriftError, type StdioServerOptions, type StoreFacts, type SurfaceContext, type SurfaceResourceOptions, TccDeniedError, type ToolResult, type WorkflowPrompt, WritesDisabledError, assertStaticScript, columnsOf, compact, confirmArg, createOsascriptRunner, describeAggregation, describeStore, detectEpoch, escapeLike, extractCode, fail, fingerprintSchema, groupByArg, inspectFile, limitArg, mapOsaError, ok, okText, openReadOnly, parseBool, parseConfig, parseIntOpt, parseList, project, promptArg, readPackageIdentity, registerSurfaceResources, registerWorkflowPrompt, requiredPromptArg, resolveLimit, runStdioServer, selectArg, surfaceUri, tableMap, toFailure, toFileUri, trimToolListing, trimmed, withBusyRetry, withLazyTools, withTrimmedListing, wrap, wrapResult };
662
704
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/build-info.ts","../src/errors.ts","../src/osascript.ts","../src/cli.ts","../src/codes.ts","../src/config.ts","../src/fs.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/build-info.ts","../src/errors.ts","../src/osascript.ts","../src/cli.ts","../src/codes.ts","../src/config.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
@@ -22,6 +22,113 @@ const readPackageIdentity = (packageJsonUrl, fallback) => {
22
22
  }
23
23
  };
24
24
  //#endregion
25
+ //#region src/listing.ts
26
+ /**
27
+ * Trimming the SDK's own boilerplate out of `tools/list`.
28
+ *
29
+ * ## What this drops
30
+ *
31
+ * The SDK builds each tool's `inputSchema` from its zod shape at listing time,
32
+ * and the generator stamps every one with
33
+ * `"$schema": "http://json-schema.org/draft-07/schema#"`. Measured across the
34
+ * eight servers with writes on, that one constant is 4,836 B of a 106,157 B
35
+ * listing — 4.6%, paid by every client on every connect, to name a JSON Schema
36
+ * draft the client already has to assume in order to read the rest of the
37
+ * document. Nothing in the protocol reads it and no client needs it, so it is
38
+ * the rare cut that is free rather than a trade.
39
+ *
40
+ * ## What this deliberately does NOT drop
41
+ *
42
+ * `"execution": {"taskSupport": "forbidden"}` is another 3,720 B (3.5%) of
43
+ * identical constant — `registerTool` hardcodes it on every tool — and it looks
44
+ * like the same kind of waste. It is not, and the difference is worth the
45
+ * paragraph so nobody "finishes the job" later.
46
+ *
47
+ * Server-side the two spellings are the same: the SDK's `tools/call` path
48
+ * branches only on `'required'` and `'optional'`, so an absent `execution` and
49
+ * an explicit `'forbidden'` both fall through to the normal handler. Client-side
50
+ * they are not. `taskSupport` is declared `.optional()` with no default, so
51
+ * absence means "unspecified" rather than "forbidden", and a task-capable client
52
+ * reading a listing with no `execution` is entitled to try task augmentation on
53
+ * a tool that was registered without a task handler. `'forbidden'` is the value
54
+ * that tells it not to. Dropping it would trade 930 tokens for a behavioural
55
+ * change on a path nothing here tests.
56
+ *
57
+ * ## Why it is done to the outgoing frame
58
+ *
59
+ * The alternative seams are worse. The schema is generated inside the SDK, so
60
+ * there is no option to pass; overriding the `tools/list` request handler means
61
+ * reaching into `Server._requestHandlers`, a private field, and re-implementing
62
+ * the listing it already builds. Wrapping `Transport.send` is public API, is
63
+ * indifferent to how the listing was produced, and costs nothing on the frames
64
+ * it does not match — every non-listing message is returned by identity below.
65
+ */
66
+ /** The one key removed, spelled once. */
67
+ const GENERATED_SCHEMA_KEY = "$schema";
68
+ /**
69
+ * A schema object without its `$schema` stamp, or the value unchanged.
70
+ *
71
+ * Returns the ORIGINAL reference when there is nothing to do, which is what
72
+ * lets `trimToolListing` decide by identity whether it needs to rebuild
73
+ * anything at all.
74
+ */
75
+ const withoutSchemaKey = (schema) => {
76
+ if (schema === null || typeof schema !== "object" || Array.isArray(schema)) return schema;
77
+ if (!(GENERATED_SCHEMA_KEY in schema)) return schema;
78
+ const rest = { ...schema };
79
+ delete rest[GENERATED_SCHEMA_KEY];
80
+ return rest;
81
+ };
82
+ /**
83
+ * Strip generated boilerplate from a `tools/list` reply, passing every other
84
+ * message through untouched.
85
+ *
86
+ * Copies rather than mutates. The SDK hands out the registered tool's own
87
+ * schema object, and deleting a key from it would edit the server's state from
88
+ * a function whose job is to shape one reply.
89
+ */
90
+ const trimToolListing = (message) => {
91
+ if (!("result" in message)) return message;
92
+ const result = message.result;
93
+ const tools = result?.tools;
94
+ if (!Array.isArray(tools)) return message;
95
+ let changed = false;
96
+ const trimmed = tools.map((tool) => {
97
+ if (tool === null || typeof tool !== "object") return tool;
98
+ const entry = tool;
99
+ const inputSchema = withoutSchemaKey(entry["inputSchema"]);
100
+ const outputSchema = withoutSchemaKey(entry["outputSchema"]);
101
+ if (inputSchema === entry["inputSchema"] && outputSchema === entry["outputSchema"]) return tool;
102
+ changed = true;
103
+ return {
104
+ ...entry,
105
+ ...entry["inputSchema"] === void 0 ? {} : { inputSchema },
106
+ ...entry["outputSchema"] === void 0 ? {} : { outputSchema }
107
+ };
108
+ });
109
+ if (!changed) return message;
110
+ return {
111
+ ...message,
112
+ result: {
113
+ ...result,
114
+ tools: trimmed
115
+ }
116
+ };
117
+ };
118
+ /**
119
+ * Wrap a transport so every listing it sends is trimmed on the way out.
120
+ *
121
+ * Mutates and returns the transport it was given rather than proxying it: the
122
+ * SDK's `connect` reaches for `onmessage`, `onclose` and `onerror` on the very
123
+ * object it was handed, and a Proxy or a subclass would have to keep those in
124
+ * sync for no gain.
125
+ */
126
+ const withTrimmedListing = (transport) => {
127
+ const send = transport.send.bind(transport);
128
+ transport.send = (message, options) => send(trimToolListing(message), options);
129
+ return transport;
130
+ };
131
+ //#endregion
25
132
  //#region src/cli.ts
26
133
  /**
27
134
  * Boot a server on stdio.
@@ -47,7 +154,7 @@ const runStdioServer = async (opts) => {
47
154
  process.exit(1);
48
155
  }
49
156
  const { server, banner } = await opts.start(logger);
50
- await server.connect(new StdioServerTransport());
157
+ await server.connect(withTrimmedListing(new StdioServerTransport()));
51
158
  logger.warn(`${logPrefix} connected (${banner})`);
52
159
  const shutdown = (signal) => {
53
160
  logger.warn(`received ${signal}, shutting down`);
@@ -373,6 +480,26 @@ const BaseConfigSchema = z.object({
373
480
  * nothing serves would be a dangling reference by configuration.
374
481
  */
375
482
  exposePrompts: z.boolean().default(true),
483
+ /**
484
+ * Serve a searchable index and a dispatcher instead of the full tool list.
485
+ *
486
+ * OFF by default, and a COST knob like `exposePrompts` above rather than a
487
+ * safety gate — but unlike that one it is a knob that TRADES. See
488
+ * `facade.ts` for the mechanism; the trade is that a host's permission rule
489
+ * stops naming the individual tool and starts naming a direction: one rule
490
+ * for this surface's reads, one for its writes.
491
+ *
492
+ * What it buys, measured with writes on: ~26.5k tokens of tool definitions
493
+ * across the eight servers becomes a handful per surface. What it costs
494
+ * besides the permission granularity is a round trip — a model must search
495
+ * before it can call.
496
+ *
497
+ * Worth switching on only for a client that does not already defer tool
498
+ * schemas itself. Claude Code and Claude Desktop do, and gain nothing here
499
+ * while paying both costs, which is why the app declines to write the flag
500
+ * into their config files at all.
501
+ */
502
+ lazyTools: z.boolean().default(false),
376
503
  debug: z.boolean().default(false),
377
504
  osascriptPath: z.string().default("/usr/bin/osascript"),
378
505
  osascriptTimeoutMs: z.number().int().min(1e3).max(6e5).default(3e4),
@@ -464,6 +591,411 @@ var PreconditionError = class extends AppleAutomationError {
464
591
  name = "PreconditionError";
465
592
  };
466
593
  //#endregion
594
+ //#region src/tools.ts
595
+ /**
596
+ * Compact, not pretty-printed.
597
+ *
598
+ * A model does not need the indentation, and it is not free: measured against
599
+ * rows matching these servers' own types, `null, 2` adds 25-41% depending on how
600
+ * many short keys a row carries - worst on the widest lists, which are exactly
601
+ * the responses already big enough to matter. Every tool in every surface
602
+ * returns through here, so this is the one place it is paid.
603
+ */
604
+ const ok = (data) => ({ content: [{
605
+ type: "text",
606
+ text: JSON.stringify(data ?? { ok: true })
607
+ }] });
608
+ /**
609
+ * Return text as-is. `ok()` JSON-stringifies, which turns a message body into
610
+ * one escaped "Hi,\n\n…" line that no one can read.
611
+ */
612
+ const okText = (text) => ({ content: [{
613
+ type: "text",
614
+ text
615
+ }] });
616
+ const fail = (message, extra) => ({
617
+ content: [{
618
+ type: "text",
619
+ text: JSON.stringify({
620
+ error: message,
621
+ ...extra ? { details: extra } : {}
622
+ })
623
+ }],
624
+ isError: true
625
+ });
626
+ /** Render a thrown value as a tool error, preserving whatever detail it carried. */
627
+ const toFailure = (err) => {
628
+ if (err instanceof AppleAutomationError) return fail(err.message, {
629
+ kind: err.name,
630
+ ...err.details
631
+ });
632
+ if (err instanceof Error) {
633
+ const details = err.details;
634
+ return fail(err.message, details);
635
+ }
636
+ return fail("Unknown error", err);
637
+ };
638
+ /** Run a tool body, JSON-formatting the result and turning errors into a tool error. */
639
+ const wrap = async (fn) => {
640
+ try {
641
+ return ok(await fn());
642
+ } catch (err) {
643
+ return toFailure(err);
644
+ }
645
+ };
646
+ /** Like `wrap`, but the body chooses its own result shape (e.g. a raw body). */
647
+ const wrapResult = async (fn) => {
648
+ try {
649
+ return await fn();
650
+ } catch (err) {
651
+ return toFailure(err);
652
+ }
653
+ };
654
+ /** Drop undefined values so we never send `{mailbox: undefined}` down a lane. */
655
+ const compact = (obj) => Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== void 0));
656
+ const limitArg = z.number().int().min(1).max(200).optional().describe("Maximum number of results. Each tool states its own default; `maxResults` is the ceiling either way.");
657
+ /**
658
+ * Settle a caller's `limit` against the tool's default and the config ceiling.
659
+ *
660
+ * Written out by hand at twenty-odd call sites before this existed, in five
661
+ * different spellings - and three surfaces spelled it `limit ?? maxResults`,
662
+ * with no `Math.min` at all. That made their real default 200 while `limitArg`
663
+ * told every model it was 25, so a model that trusted the description and
664
+ * omitted the argument got eight times the rows it asked for.
665
+ *
666
+ * `fallback` is the tool's own documented default, not a global one: a mailbox
667
+ * listing and a day of events do not want the same number.
668
+ */
669
+ const resolveLimit = (limit, maxResults, fallback = 25) => Math.min(limit ?? fallback, maxResults);
670
+ const confirmArg = z.literal(true).describe("Must be true. This action changes data and is not undoable from here.");
671
+ //#endregion
672
+ //#region src/facade.ts
673
+ /**
674
+ * A stand-in server that records what would have been registered.
675
+ *
676
+ * Typed as a whole `McpServer` and cast once, here, rather than narrowed to a
677
+ * `Pick<…, "registerTool">` that every surface's `registerTools` would then
678
+ * have to widen its parameter to accept — eight signatures and the ~95
679
+ * functions beneath them, changed to describe a fact that is already true.
680
+ *
681
+ * The cast is safe by inspection, and the inspection is the point: all 95
682
+ * `registerTool` call sites across the eight surfaces call this one method and
683
+ * nothing else, and not one uses its return value. If a registrar ever reaches
684
+ * for `registerPrompt` or `server.server`, it will fail here at runtime rather
685
+ * than quietly registering into a void — which is why this returns a bare
686
+ * object instead of a Proxy that would forward the difference to a real server
687
+ * and half-register a surface.
688
+ */
689
+ const recorder = (into) => ({ registerTool: (name, config, handler) => {
690
+ into.push({
691
+ name,
692
+ config,
693
+ handler
694
+ });
695
+ } });
696
+ /** Terms shorter than this are dropped: they match everything and rank nothing. */
697
+ const SHORTEST_TERM = 3;
698
+ /** How much of a description is printed per row. The whole of it is searched. */
699
+ const SUMMARY_LIMIT = 160;
700
+ const SEARCH_LIMIT = 25;
701
+ /** Partial matches are low precision by construction, so fewer of them. */
702
+ const PARTIAL_LIMIT = 10;
703
+ const INDEX_LIMIT = 200;
704
+ /**
705
+ * Rank tiers, best first, SUMMED across the query's terms.
706
+ *
707
+ * Summing rather than taking the best single term is deliberate: a two-word
708
+ * query scored on its luckiest word ranks a tool that matched one term above a
709
+ * tool that matched both.
710
+ */
711
+ const RANK = {
712
+ exactName: 0,
713
+ namePrefix: 1,
714
+ nameSubstring: 2,
715
+ summary: 3,
716
+ tail: 4,
717
+ missing: 5
718
+ };
719
+ const summarize = (description) => {
720
+ const flat = description.replace(/\s+/g, " ").trim();
721
+ return flat.length <= SUMMARY_LIMIT ? flat : `${flat.slice(0, 159).trimEnd()}…`;
722
+ };
723
+ const index = (decl) => {
724
+ const description = decl.config.description ?? "";
725
+ const summary = summarize(description);
726
+ return {
727
+ name: decl.name,
728
+ summary,
729
+ head: `${decl.name} ${summary}`.toLowerCase(),
730
+ whole: `${decl.name} ${description}`.toLowerCase()
731
+ };
732
+ };
733
+ /**
734
+ * Regular plural fold, guarded.
735
+ *
736
+ * The guards are the whole point: without them `status` searches for `statu`,
737
+ * `class` for `clas` and `focus` for `focu`, and a three-letter term like `ios`
738
+ * loses a third of itself.
739
+ */
740
+ const singular = (term) => {
741
+ if (term.length <= SHORTEST_TERM) return term;
742
+ if (term.endsWith("ss") || term.endsWith("us")) return term;
743
+ return term.endsWith("s") ? term.slice(0, -1) : term;
744
+ };
745
+ const queryTerms = (query) => {
746
+ const seen = /* @__PURE__ */ new Set();
747
+ const out = [];
748
+ for (const raw of query.toLowerCase().split(/\s+/)) {
749
+ const term = raw.trim();
750
+ if (term.length < SHORTEST_TERM || seen.has(term)) continue;
751
+ seen.add(term);
752
+ out.push(term);
753
+ }
754
+ return out;
755
+ };
756
+ const rankTerm = (term, entry) => {
757
+ const name = entry.name.toLowerCase();
758
+ const folded = singular(term);
759
+ const hit = (haystack) => haystack.includes(term) || haystack.includes(folded);
760
+ if (name === term) return RANK.exactName;
761
+ if (name.startsWith(term)) return RANK.namePrefix;
762
+ if (hit(name)) return RANK.nameSubstring;
763
+ if (hit(entry.head)) return RANK.summary;
764
+ if (hit(entry.whole)) return RANK.tail;
765
+ return RANK.missing;
766
+ };
767
+ /**
768
+ * Find tools for a query.
769
+ *
770
+ * An empty query returns the server's OWN order rather than an alphabetised
771
+ * one: the registrars group related tools together, and sorting throws that
772
+ * grouping away for no gain.
773
+ *
774
+ * Nothing may cache this keyed on the query — a row's tier is a property of the
775
+ * query AND of the whole catalog it was ranked against.
776
+ */
777
+ const find = (entries, query) => {
778
+ const terms = queryTerms(query);
779
+ if (terms.length === 0) return {
780
+ rows: entries.slice(0, INDEX_LIMIT),
781
+ matched: entries.length,
782
+ missed: [],
783
+ partial: false
784
+ };
785
+ const scored = entries.map((entry) => {
786
+ let total = 0;
787
+ let matched = 0;
788
+ const missed = [];
789
+ for (const term of terms) {
790
+ const rank = rankTerm(term, entry);
791
+ total += rank;
792
+ if (rank === RANK.missing) missed.push(term);
793
+ else matched += 1;
794
+ }
795
+ return {
796
+ entry,
797
+ total,
798
+ matched,
799
+ missed
800
+ };
801
+ });
802
+ const best = scored.reduce((acc, s) => Math.max(acc, s.matched), 0);
803
+ if (best === 0) return {
804
+ rows: [],
805
+ matched: 0,
806
+ missed: terms,
807
+ partial: false
808
+ };
809
+ const group = scored.filter((s) => s.matched === best).toSorted((a, b) => a.total - b.total || a.entry.name.localeCompare(b.entry.name));
810
+ const partial = best < terms.length;
811
+ const missed = partial ? terms.filter((t) => group.every((g) => g.missed.includes(t))) : [];
812
+ return {
813
+ rows: group.slice(0, partial ? PARTIAL_LIMIT : SEARCH_LIMIT).map((g) => g.entry),
814
+ matched: group.length,
815
+ missed,
816
+ partial
817
+ };
818
+ };
819
+ /**
820
+ * Suggestions for a name that is not in the catalog.
821
+ *
822
+ * Substring search cannot find a string that appears nowhere, so a typo needs
823
+ * its own answer: shared underscore-separated words first, then the longest
824
+ * common prefix.
825
+ *
826
+ * Both comparisons run on the name with `apple_<surface>_` REMOVED. Every tool
827
+ * on a surface shares that prefix, so comparing whole names makes every tool
828
+ * share two words with every other and "did you mean" answers with the first
829
+ * few tools in the catalog — worse than saying nothing, because it reads like a
830
+ * real suggestion. Measured before the fix: `apple_mail_send_messge` suggested
831
+ * `apple_mail_list_accounts`.
832
+ */
833
+ const nearest = (names, wanted, prefix) => {
834
+ const strip = (n) => n.toLowerCase().startsWith(`${prefix}_`) ? n.slice(prefix.length + 1) : n;
835
+ const target = strip(wanted);
836
+ const words = new Set(target.toLowerCase().split("_").filter((w) => w.length >= SHORTEST_TERM));
837
+ const shared = names.filter((n) => strip(n).toLowerCase().split("_").some((w) => words.has(w)));
838
+ if (shared.length > 0) return shared.slice(0, 5);
839
+ const common = (n) => {
840
+ const candidate = strip(n).toLowerCase();
841
+ const lower = target.toLowerCase();
842
+ let i = 0;
843
+ while (i < candidate.length && i < lower.length && candidate[i] === lower[i]) i += 1;
844
+ return i;
845
+ };
846
+ const ranked = names.toSorted((a, b) => common(b) - common(a) || a.localeCompare(b));
847
+ const bestName = ranked[0];
848
+ if (bestName === void 0 || common(bestName) < SHORTEST_TERM) return [];
849
+ return ranked.filter((n) => common(n) >= SHORTEST_TERM).slice(0, 3);
850
+ };
851
+ const renderSearch = (result, total, names) => {
852
+ if (result.rows.length === 0) return `No tool matches. ${total} tools are available — call ${names.search} with no query to list them all.`;
853
+ const rows = result.rows.map((r) => `${r.name} — ${r.summary}`).join("\n");
854
+ let notice = "";
855
+ if (result.partial) notice = `No tool matched every word${result.missed.length > 0 ? ` (no match for ${result.missed.join(", ")})` : ""}. Closest:\n`;
856
+ const footer = `${result.rows.length} of ${total} tools. Read a schema with ${names.describe}, then run it with ${names.call}.`;
857
+ return `${notice}${rows}\n\n${footer}`;
858
+ };
859
+ /**
860
+ * A tool's declaration as a client would have received it.
861
+ *
862
+ * Built from the recorded zod shape rather than re-derived by hand, so
863
+ * `describe` and a non-lazy listing cannot drift. `$schema` is dropped for the
864
+ * same reason `listing.ts` drops it from `tools/list`.
865
+ */
866
+ const describe = (decl) => {
867
+ const shape = decl.config.inputSchema;
868
+ const schema = shape ? z.toJSONSchema(z.object(shape)) : void 0;
869
+ if (schema) delete schema["$schema"];
870
+ return {
871
+ name: decl.name,
872
+ ...decl.config.description === void 0 ? {} : { description: decl.config.description },
873
+ ...schema === void 0 ? {} : { inputSchema: schema },
874
+ ...decl.config.annotations === void 0 ? {} : { annotations: decl.config.annotations }
875
+ };
876
+ };
877
+ /**
878
+ * Run a recorded tool, reproducing the validation the SDK would have done.
879
+ *
880
+ * Under a facade the SDK never sees the real call, so it never parses the real
881
+ * arguments. Skipping this would hand every handler unvalidated input — the one
882
+ * way a facade can be actively less safe than the listing it replaced.
883
+ */
884
+ const invoke = async (decl, args, extra) => {
885
+ const shape = decl.config.inputSchema;
886
+ if (!shape) return decl.handler(extra);
887
+ const parsed = await z.object(shape).safeParseAsync(args ?? {});
888
+ if (!parsed.success) {
889
+ const why = parsed.error.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ");
890
+ return fail(`Invalid arguments for ${decl.name}: ${why}`);
891
+ }
892
+ return decl.handler(parsed.data, extra);
893
+ };
894
+ /**
895
+ * Register a surface's tools, either directly or behind the facade.
896
+ *
897
+ * The callback takes `allowWrites` rather than closing over it so the facade
898
+ * can run it a second time with the gate forced shut and learn which tools are
899
+ * writes. Everything else it needs — the client, the config — it closes over as
900
+ * before.
901
+ */
902
+ const withLazyTools = (server, opts, register) => {
903
+ if (!opts.lazy) {
904
+ register(server, opts.allowWrites);
905
+ return;
906
+ }
907
+ const all = [];
908
+ register(recorder(all), opts.allowWrites);
909
+ let writeNames = /* @__PURE__ */ new Set();
910
+ if (opts.allowWrites) {
911
+ const readsOnly = [];
912
+ register(recorder(readsOnly), false);
913
+ const readNames = new Set(readsOnly.map((d) => d.name));
914
+ writeNames = new Set(all.filter((d) => !readNames.has(d.name)).map((d) => d.name));
915
+ }
916
+ const prefix = `apple_${opts.surface}`;
917
+ const eagerName = `${prefix}_diagnostics`;
918
+ const eager = all.filter((d) => d.name === eagerName);
919
+ for (const decl of eager) server.registerTool(decl.name, decl.config, decl.handler);
920
+ const lazy = all.filter((d) => d.name !== eagerName);
921
+ const reads = lazy.filter((d) => !writeNames.has(d.name));
922
+ const writes = lazy.filter((d) => writeNames.has(d.name));
923
+ const byName = new Map(lazy.map((d) => [d.name, d]));
924
+ const readIndex = reads.map(index);
925
+ const writeIndex = writes.map(index);
926
+ const allIndex = [...readIndex, ...writeIndex];
927
+ const searchName = `${prefix}_search_tools`;
928
+ const describeName = `${prefix}_describe_tool`;
929
+ const callName = `${prefix}_call_tool`;
930
+ const callWriteName = `${prefix}_call_write_tool`;
931
+ server.registerTool(searchName, {
932
+ description: `Find ${opts.displayName} tools by what you want to do. This server loads its ${allIndex.length} tools on demand: they are not listed up front, and this is how you reach them. Returns matching tool names with a one-line summary each. Call with no query to list everything. Read a schema with ${describeName}, then run it with ${callName}` + (writes.length > 0 ? ` or ${callWriteName}` : "") + `.`,
933
+ inputSchema: { query: z.string().optional().describe("What you want to do, e.g. 'search messages' or 'unread'. Omit to list all.") },
934
+ annotations: {
935
+ readOnlyHint: true,
936
+ idempotentHint: true
937
+ }
938
+ }, ({ query }) => okText(renderSearch(find(allIndex, query ?? ""), allIndex.length, {
939
+ search: searchName,
940
+ describe: describeName,
941
+ call: callName
942
+ })));
943
+ server.registerTool(describeName, {
944
+ description: `Get the full description and input schema of one ${opts.displayName} tool, as it would have appeared in a normal tool listing. Find names with ${searchName} first.`,
945
+ inputSchema: { name: z.string().describe(`Exact tool name, e.g. ${lazy[0]?.name ?? callName}.`) },
946
+ annotations: {
947
+ readOnlyHint: true,
948
+ idempotentHint: true
949
+ }
950
+ }, ({ name }) => {
951
+ const decl = byName.get(name);
952
+ if (!decl) {
953
+ const suggestions = nearest([...byName.keys()], name, prefix);
954
+ return fail(`No tool named ${name}.` + (suggestions.length > 0 ? ` Did you mean: ${suggestions.join(", ")}?` : ""));
955
+ }
956
+ const runner = writeNames.has(name) ? callWriteName : callName;
957
+ return okText(`${JSON.stringify(describe(decl))}\n\nCall it with ${runner}: {"name": ${JSON.stringify(name)}, "arguments": {…}}.`);
958
+ });
959
+ server.registerTool(callName, {
960
+ description: `Run one of this server's read-only ${opts.displayName} tools. Find a name with ${searchName} and its arguments with ${describeName}. Reads only: it cannot reach ` + (writes.length > 0 ? `anything that changes ${opts.displayName} — those go through ${callWriteName}.` : `anything that changes ${opts.displayName}, and this server has writes turned off.`),
961
+ inputSchema: {
962
+ name: z.string().describe("Exact tool name to run."),
963
+ arguments: z.record(z.string(), z.unknown()).optional().describe("That tool's arguments.")
964
+ },
965
+ annotations: { readOnlyHint: true }
966
+ }, async ({ name, arguments: args }, extra) => {
967
+ const decl = byName.get(name);
968
+ if (!decl) {
969
+ const suggestions = nearest([...byName.keys()], name, prefix);
970
+ return fail(`No tool named ${name}.` + (suggestions.length > 0 ? ` Did you mean: ${suggestions.join(", ")}?` : ""));
971
+ }
972
+ if (writeNames.has(name)) return fail(`${name} changes ${opts.displayName}, so it cannot be run through ${callName}. Use ${callWriteName}.`);
973
+ return await invoke(decl, args, extra);
974
+ });
975
+ if (writes.length > 0) {
976
+ const writeList = writes.map((d) => d.name.slice(prefix.length + 1)).join(", ");
977
+ server.registerTool(callWriteName, {
978
+ description: `Run one of this server's ${writes.length} ${opts.displayName} tools that CHANGE data — ${writeList}. Find arguments with ${describeName}. Read-only tools go through ${callName} instead.`,
979
+ inputSchema: {
980
+ name: z.string().describe("Exact tool name to run."),
981
+ arguments: z.record(z.string(), z.unknown()).optional().describe("That tool's arguments.")
982
+ },
983
+ annotations: {
984
+ readOnlyHint: false,
985
+ destructiveHint: true
986
+ }
987
+ }, async ({ name, arguments: args }, extra) => {
988
+ const decl = byName.get(name);
989
+ if (!decl) {
990
+ const suggestions = nearest([...writeNames], name, prefix);
991
+ return fail(`No tool named ${name}.` + (suggestions.length > 0 ? ` Did you mean: ${suggestions.join(", ")}?` : ""));
992
+ }
993
+ if (!writeNames.has(name)) return fail(`${name} is read-only. Use ${callName}.`);
994
+ return await invoke(decl, args, extra);
995
+ });
996
+ }
997
+ };
998
+ //#endregion
467
999
  //#region src/fs.ts
468
1000
  const inspectFile = (path) => {
469
1001
  let size = null;
@@ -966,84 +1498,6 @@ const openReadOnly = (path, mode, opts = {}) => {
966
1498
  throw new IndexUnavailableError(`Could not open ${opts.label ?? path} at ${path}: ${message}.${opts.hint ? ` ${opts.hint}` : ""}`);
967
1499
  };
968
1500
  //#endregion
969
- //#region src/tools.ts
970
- /**
971
- * Compact, not pretty-printed.
972
- *
973
- * A model does not need the indentation, and it is not free: measured against
974
- * rows matching these servers' own types, `null, 2` adds 25-41% depending on how
975
- * many short keys a row carries - worst on the widest lists, which are exactly
976
- * the responses already big enough to matter. Every tool in every surface
977
- * returns through here, so this is the one place it is paid.
978
- */
979
- const ok = (data) => ({ content: [{
980
- type: "text",
981
- text: JSON.stringify(data ?? { ok: true })
982
- }] });
983
- /**
984
- * Return text as-is. `ok()` JSON-stringifies, which turns a message body into
985
- * one escaped "Hi,\n\n…" line that no one can read.
986
- */
987
- const okText = (text) => ({ content: [{
988
- type: "text",
989
- text
990
- }] });
991
- const fail = (message, extra) => ({
992
- content: [{
993
- type: "text",
994
- text: JSON.stringify({
995
- error: message,
996
- ...extra ? { details: extra } : {}
997
- })
998
- }],
999
- isError: true
1000
- });
1001
- /** Render a thrown value as a tool error, preserving whatever detail it carried. */
1002
- const toFailure = (err) => {
1003
- if (err instanceof AppleAutomationError) return fail(err.message, {
1004
- kind: err.name,
1005
- ...err.details
1006
- });
1007
- if (err instanceof Error) {
1008
- const details = err.details;
1009
- return fail(err.message, details);
1010
- }
1011
- return fail("Unknown error", err);
1012
- };
1013
- /** Run a tool body, JSON-formatting the result and turning errors into a tool error. */
1014
- const wrap = async (fn) => {
1015
- try {
1016
- return ok(await fn());
1017
- } catch (err) {
1018
- return toFailure(err);
1019
- }
1020
- };
1021
- /** Like `wrap`, but the body chooses its own result shape (e.g. a raw body). */
1022
- const wrapResult = async (fn) => {
1023
- try {
1024
- return await fn();
1025
- } catch (err) {
1026
- return toFailure(err);
1027
- }
1028
- };
1029
- /** Drop undefined values so we never send `{mailbox: undefined}` down a lane. */
1030
- const compact = (obj) => Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== void 0));
1031
- const limitArg = z.number().int().min(1).max(200).optional().describe("Maximum number of results. Each tool states its own default; `maxResults` is the ceiling either way.");
1032
- /**
1033
- * Settle a caller's `limit` against the tool's default and the config ceiling.
1034
- *
1035
- * Written out by hand at twenty-odd call sites before this existed, in five
1036
- * different spellings - and three surfaces spelled it `limit ?? maxResults`,
1037
- * with no `Math.min` at all. That made their real default 200 while `limitArg`
1038
- * told every model it was 25, so a model that trusted the description and
1039
- * omitted the argument got eight times the rows it asked for.
1040
- *
1041
- * `fallback` is the tool's own documented default, not a global one: a mailbox
1042
- * listing and a day of events do not want the same number.
1043
- */
1044
- const resolveLimit = (limit, maxResults, fallback = 25) => Math.min(limit ?? fallback, maxResults);
1045
- const confirmArg = z.literal(true).describe("Must be true. This action changes data and is not undoable from here.");
1046
- //#endregion
1047
- export { AppBusyError, AppNotRunningError, AppleAutomationError, BaseConfigSchema, CORE_DATA_EPOCH_OFFSET, IndexUnavailableError, OsascriptTimeoutError, PlatformError, PreconditionError, ProtocolError, RESOURCE_SCHEME, SchemaDriftError, TccDeniedError, WritesDisabledError, assertStaticScript, columnsOf, compact, confirmArg, createOsascriptRunner, describeAggregation, describeStore, detectEpoch, escapeLike, extractCode, fail, fingerprintSchema, groupByArg, inspectFile, limitArg, mapOsaError, ok, okText, openReadOnly, parseBool, parseConfig, parseIntOpt, parseList, project, promptArg, readPackageIdentity, registerSurfaceResources, registerWorkflowPrompt, requiredPromptArg, resolveLimit, runStdioServer, selectArg, surfaceUri, tableMap, toFailure, toFileUri, trimmed, withBusyRetry, wrap, wrapResult };
1501
+ export { AppBusyError, AppNotRunningError, AppleAutomationError, BaseConfigSchema, CORE_DATA_EPOCH_OFFSET, IndexUnavailableError, OsascriptTimeoutError, PlatformError, PreconditionError, ProtocolError, RESOURCE_SCHEME, SchemaDriftError, TccDeniedError, WritesDisabledError, assertStaticScript, columnsOf, compact, confirmArg, createOsascriptRunner, describeAggregation, describeStore, detectEpoch, escapeLike, extractCode, fail, fingerprintSchema, groupByArg, inspectFile, limitArg, mapOsaError, ok, okText, openReadOnly, parseBool, parseConfig, parseIntOpt, parseList, project, promptArg, readPackageIdentity, registerSurfaceResources, registerWorkflowPrompt, requiredPromptArg, resolveLimit, runStdioServer, selectArg, surfaceUri, tableMap, toFailure, toFileUri, trimToolListing, trimmed, withBusyRetry, withLazyTools, withTrimmedListing, wrap, wrapResult };
1048
1502
 
1049
1503
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/build-info.ts","../src/cli.ts","../src/codes.ts","../src/config.ts","../src/errors.ts","../src/fs.ts","../src/osascript.ts","../src/resources.ts","../src/prompts.ts","../src/query.ts","../src/schema.ts","../src/sqlite.ts","../src/tools.ts"],"sourcesContent":["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 { 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 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(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 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 * 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","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 try {\n const uri = toFileUri(path, attempt === \"ro\" ? \"mode=ro\" : \"immutable=1\");\n const 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 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","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"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/build-info.ts","../src/listing.ts","../src/cli.ts","../src/codes.ts","../src/config.ts","../src/errors.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":["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 * 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","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/** 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 const eagerName = `${prefix}_diagnostics`;\n const eager = all.filter((d) => d.name === eagerName);\n for (const decl of eager)\n server.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 server.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 server.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 server.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.record(z.string(), z.unknown()).optional().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 server.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","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 try {\n const uri = toFileUri(path, attempt === \"ro\" ? \"mode=ro\" : \"immutable=1\");\n const 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 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.14.0",
3
+ "version": "1.15.0",
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",