@mgcrea/mcp-apple-maps 1.14.0 → 1.16.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.
@@ -1,4 +1,4 @@
1
- import { AppleAutomationError, AppleAutomationError as AppleMapsError, BaseConfigSchema, CORE_DATA_EPOCH_OFFSET, IndexUnavailableError, SchemaDriftError, SchemaDriftError as SchemaDriftError$1, columnsOf, compact, describeStore, detectEpoch, escapeLike, fail, fingerprintSchema, inspectFile, limitArg, ok, openReadOnly, parseBool, parseConfig, parseIntOpt, promptArg, readPackageIdentity, registerSurfaceResources, registerWorkflowPrompt, resolveLimit, trimmed, wrap, wrapResult } from "@mgcrea/mcp-apple-core";
1
+ import { AppleAutomationError, AppleAutomationError as AppleMapsError, BaseConfigSchema, CORE_DATA_EPOCH_OFFSET, IndexUnavailableError, SchemaDriftError, SchemaDriftError as SchemaDriftError$1, columnsOf, compact, describeStore, detectEpoch, escapeLike, fail, fingerprintSchema, inspectFile, interferenceNote, limitArg, ok, openAxChannel, openReadOnly, parseBool, parseConfig, parseIntOpt, promptArg, readPackageIdentity, registerSurfaceResources, registerWorkflowPrompt, resolveLimit, trimmed, watchInterference, withLazyTools, wrap, wrapResult } from "@mgcrea/mcp-apple-core";
2
2
  import { readdirSync } from "node:fs";
3
3
  import { homedir } from "node:os";
4
4
  import { join } from "node:path";
@@ -15,8 +15,8 @@ const pkg = readPackageIdentity(new URL("../package.json", import.meta.url), {
15
15
  const BUILD_INFO = {
16
16
  name: pkg.name,
17
17
  version: pkg.version,
18
- gitCommit: "e006496",
19
- gitCommitDate: "2026-09-04T23:14:11+02:00"
18
+ gitCommit: "d70b8e4",
19
+ gitCommitDate: "2026-09-06T12:13:21+02:00"
20
20
  };
21
21
  //#endregion
22
22
  //#region src/client/dates.ts
@@ -238,6 +238,293 @@ const locateStore = (opts = {}) => {
238
238
  };
239
239
  };
240
240
  //#endregion
241
+ //#region src/client/ax.ts
242
+ /**
243
+ * Maps' place card, driven through Cupertino's native Accessibility driver.
244
+ *
245
+ * ## Why this exists beside the SQL lane rather than replacing it
246
+ *
247
+ * `client/write.ts` writes favourites by copying a record Maps minted and
248
+ * INSERTing it. That lane is correct and stays: pressing the card's `AddButton`
249
+ * does not create a favourite. `docs/desktop.md` measured it — the press lands
250
+ * an **unfiled saved place** and `ZFAVORITEITEM` was 26 rows before and 26
251
+ * after. The two lanes address different objects.
252
+ *
253
+ * What this reaches is the set the SQL lane does not offer at all:
254
+ *
255
+ * * **The Places library** — `add_to_places` / `delete_from_places`.
256
+ * * **Guide membership** — `docs/maps.md` lists it as unbuilt: "Adding a place
257
+ * to a collection needs one `Z_6PLACES` join row".
258
+ *
259
+ * ## The safety argument, which is the real reason to prefer it here
260
+ *
261
+ * The store is mirrored by `NSPersistentCloudKitContainer` and mirroring does
262
+ * not wait to be told, so a malformed row is not a local mistake — it reaches
263
+ * every device on the account. `write.ts` carries the rule that keeps SQL safe:
264
+ * never fabricate a place record, only copy one Maps wrote. **A write made
265
+ * through the interface cannot be malformed at all**, because Maps performs it.
266
+ * Where both lanes could do a job, this one has the smaller blast radius.
267
+ *
268
+ * ## The cost, which is the same one the SQL lane pays
269
+ *
270
+ * Reaching the card means opening `maps://?q=<name>&ll=<lat>,<lon>`, so the
271
+ * place is left in **Recents** whether or not anything is kept. That is the
272
+ * mechanism rather than an oversight, and every tool built on this says so.
273
+ */
274
+ const MAPS_BUNDLE = "com.apple.Maps";
275
+ /** Identifiers Maps sets on the card. Unlocalised, unlike every name here. */
276
+ const CARD = {
277
+ add: "AddButton",
278
+ more: "MoreButton",
279
+ card: "PlaceCardViewController",
280
+ addToGuides: "add_to_guides",
281
+ deleteFromPlaces: "delete_from_places",
282
+ addToPlaces: "add_to_places",
283
+ picker: "GuidesPickerView",
284
+ guideRow: "UserGuidesPickerRowCell",
285
+ done: "CardButtonTypeDone",
286
+ close: "CardButtonTypeClose"
287
+ };
288
+ const defaultOpenUrl$1 = (url) => {
289
+ execFileSync("/usr/bin/open", ["-g", url]);
290
+ };
291
+ /**
292
+ * `"Favorites, 3 places"` -> `{ name: "Favorites", places: 3 }`.
293
+ *
294
+ * The count is a bonus and the name is the point, so a row whose shape is not
295
+ * recognised keeps its whole label as the name rather than being dropped. A
296
+ * guide that cannot be matched is worse than one with an odd name.
297
+ */
298
+ const parseGuideRow = (label) => {
299
+ const match = /^(.*),\s*(\d+)\s+places?$/.exec(label);
300
+ if (!match?.[1]) return {
301
+ name: label,
302
+ places: null
303
+ };
304
+ return {
305
+ name: match[1],
306
+ places: Number(match[2])
307
+ };
308
+ };
309
+ const DEFAULT_WAITS = {
310
+ card: 1e4,
311
+ sheet: 4e3
312
+ };
313
+ var MapsAxLane = class MapsAxLane {
314
+ #channel;
315
+ #openUrl;
316
+ #waits;
317
+ constructor(channel, openUrl, waits) {
318
+ this.#channel = channel;
319
+ this.#openUrl = openUrl;
320
+ this.#waits = waits;
321
+ }
322
+ /** Null when this server is not hosted by Cupertino — see `core/src/ax.ts`. */
323
+ static open(env = process.env, openUrl = defaultOpenUrl$1, waits = {}) {
324
+ const channel = openAxChannel(MAPS_SURFACE, env);
325
+ return channel ? new MapsAxLane(channel, openUrl, {
326
+ ...DEFAULT_WAITS,
327
+ ...waits
328
+ }) : null;
329
+ }
330
+ close() {
331
+ this.#channel.close();
332
+ }
333
+ /** Start watching for someone using the Mac during a sequence. */
334
+ watch() {
335
+ return watchInterference(this.#channel);
336
+ }
337
+ #call(tool, args = {}) {
338
+ return this.#channel.call({
339
+ tool: `apple_desktop_${tool}`,
340
+ args
341
+ });
342
+ }
343
+ async #tree() {
344
+ return (await this.#call("ui_tree", {
345
+ bundleId: MAPS_BUNDLE,
346
+ detail: "all",
347
+ maxDepth: 20,
348
+ maxNodes: 4e3,
349
+ budgetSeconds: 20
350
+ })).elements ?? [];
351
+ }
352
+ /**
353
+ * Wait for an element rather than for a duration.
354
+ *
355
+ * `docs/desktop.md` calls this the trap that cost it the most: a card's chrome
356
+ * appears before its content, so no fixed settle time is correct. Cheap here —
357
+ * a whole Maps walk is ~0.17 s.
358
+ */
359
+ async #poll(match, timeoutMs = this.#waits.card) {
360
+ const deadline = Date.now() + timeoutMs;
361
+ let lastError = null;
362
+ for (;;) {
363
+ try {
364
+ const found = (await this.#tree()).find(match);
365
+ if (found) return found;
366
+ lastError = null;
367
+ } catch (error) {
368
+ lastError = error;
369
+ }
370
+ if (Date.now() >= deadline) {
371
+ if (lastError) throw lastError;
372
+ return null;
373
+ }
374
+ await new Promise((resolve) => setTimeout(resolve, 300));
375
+ }
376
+ }
377
+ /**
378
+ * Open a place and wait for its card.
379
+ *
380
+ * The combined `q=` and `ll=` form, because a coordinate positions the map and
381
+ * a NAME selects a place — `write.ts` records the same thing for the same
382
+ * reason.
383
+ */
384
+ async openCard(place) {
385
+ this.#openUrl(`maps://?q=${encodeURIComponent(place.name)}&ll=${place.latitude},${place.longitude}`);
386
+ return await this.#poll((e) => e.id === CARD.add) !== null;
387
+ }
388
+ /**
389
+ * Is this place saved?
390
+ *
391
+ * Read off `AddButton`'s NAME, which is the state bit. `docs/desktop.md`
392
+ * corrected itself on this: `FavoriteButton` reports identical attributes
393
+ * either way, and the sibling carries `"Add"` versus `"Added"`.
394
+ *
395
+ * Localised, and that is a real limit rather than an oversight — there is no
396
+ * unlocalised state anywhere on this card.
397
+ */
398
+ async isSaved() {
399
+ const add = await this.#poll((e) => e.id === CARD.add, this.#waits.sheet);
400
+ if (!add?.name) return null;
401
+ return add.name !== "Add";
402
+ }
403
+ /**
404
+ * Open the card's overflow menu and return its items.
405
+ *
406
+ * POLLED, not settled. The first version asked for the items in the same
407
+ * breath as the press and got an empty list every time — the menu takes on the
408
+ * order of half a second to appear — and then reported "the overflow menu did
409
+ * not open", which is a sentence about Maps rather than about the race. That
410
+ * is this repo's own rule broken in the file that quotes it: **poll for the
411
+ * control, never wait a fixed time for it.**
412
+ */
413
+ async #openMenu() {
414
+ const more = await this.#poll((e) => e.id === CARD.more);
415
+ if (!more) return [];
416
+ await this.#call("press", { handle: more.handle });
417
+ const deadline = Date.now() + this.#waits.sheet;
418
+ for (;;) {
419
+ const items = await this.#call("find_elements", {
420
+ bundleId: MAPS_BUNDLE,
421
+ role: "AXMenuItem"
422
+ });
423
+ if (items.elements?.length) return items.elements;
424
+ if (Date.now() >= deadline) return [];
425
+ await new Promise((resolve) => setTimeout(resolve, 250));
426
+ }
427
+ }
428
+ async #dismiss() {
429
+ await this.#call("key", {
430
+ key: "escape",
431
+ modifiers: []
432
+ });
433
+ }
434
+ /**
435
+ * Save the open place into the Places library.
436
+ *
437
+ * Two presses, not one: `AddButton` raises a **"Name This Location"** sheet
438
+ * and nothing is written until its `Save` is pressed. The sheet REPLACES the
439
+ * window list — a full walk during it returns the sheet and nothing else — so
440
+ * the card's elements are unreachable until it closes, and `Save` carries no
441
+ * `AXIdentifier`, which is why it is addressed by name.
442
+ */
443
+ async savePlace() {
444
+ const add = await this.#poll((e) => e.id === CARD.add);
445
+ if (!add) return false;
446
+ await this.#call("press", { handle: add.handle });
447
+ const save = await this.#poll((e) => e.role === "AXButton" && e.name === "Save", this.#waits.sheet);
448
+ if (!save) return false;
449
+ await this.#call("press", { handle: save.handle });
450
+ return true;
451
+ }
452
+ /**
453
+ * Remove the open place from the Places library.
454
+ *
455
+ * The menu item is `delete_from_places`, and it is only present when the place
456
+ * IS saved — the same slot carries `add_to_places` when it is not. So its
457
+ * absence is a state reading rather than a failure, and this reports which.
458
+ */
459
+ async removePlace() {
460
+ const items = await this.#openMenu();
461
+ if (!items.length) return "no-menu";
462
+ const remove = items.find((e) => e.id === CARD.deleteFromPlaces);
463
+ if (!remove) {
464
+ await this.#dismiss();
465
+ return items.some((e) => e.id === CARD.addToPlaces) ? "not-saved" : "no-menu";
466
+ }
467
+ await this.#call("press", { handle: remove.handle });
468
+ return "removed";
469
+ }
470
+ /**
471
+ * Every guide Maps knows about, read off its own picker.
472
+ *
473
+ * This is the authoritative list, and it is not the same as the store's:
474
+ * `docs/desktop.md` found the picker showing a "Favorites" guide that
475
+ * `apple_maps_list_collections` does not return. Read here as a side effect of
476
+ * being on the way to a write, rather than as a tool of its own — reaching it
477
+ * needs a place card, which needs a `maps://` open, which leaves a Recents
478
+ * entry. That is too much side effect for something calling itself a read.
479
+ *
480
+ * Leaves the picker OPEN, because the caller is normally about to press a row.
481
+ */
482
+ async openGuidePicker() {
483
+ const items = await this.#openMenu();
484
+ const guides = items.find((e) => e.id === CARD.addToGuides);
485
+ if (!guides) {
486
+ if (items.length) await this.#dismiss();
487
+ return null;
488
+ }
489
+ await this.#call("press", { handle: guides.handle });
490
+ if (!await this.#poll((e) => e.id === CARD.picker, this.#waits.sheet)) return null;
491
+ return (await this.#tree()).filter((e) => e.id === CARD.guideRow && e.name).map((e) => parseGuideRow(e.name));
492
+ }
493
+ /** Press one guide row, then Done. Returns false if no row matched. */
494
+ async chooseGuide(name) {
495
+ const wanted = name.toLowerCase();
496
+ const row = (await this.#tree()).find((e) => e.id === CARD.guideRow && parseGuideRow(e.name ?? "").name.toLowerCase() === wanted);
497
+ if (!row) return false;
498
+ await this.#call("press", { handle: row.handle });
499
+ const done = await this.#poll((e) => e.id === CARD.done, this.#waits.sheet);
500
+ if (!done) return false;
501
+ await this.#call("press", { handle: done.handle });
502
+ return true;
503
+ }
504
+ /**
505
+ * Re-open the card and read the state bit, which is the only way to verify.
506
+ *
507
+ * **A write closes the card.** Saving through the naming sheet dismisses the
508
+ * whole place card and returns Maps to its main view, so reading `AddButton`
509
+ * straight after a press finds nothing and reports "the card does not say it
510
+ * is saved" — which is a sentence about the write having failed, when what
511
+ * actually happened is that the thing being read went away.
512
+ *
513
+ * So verification re-opens the place first. That costs another `maps://`,
514
+ * which is free in the sense that matters: the Recents entry this leaves was
515
+ * already left by opening the card in the first place.
516
+ */
517
+ async verifySaved(place) {
518
+ if (!await this.openCard(place)) return null;
519
+ return this.isSaved();
520
+ }
521
+ /** Abandon the picker without filing anything. */
522
+ async cancelGuidePicker() {
523
+ const close = await this.#poll((e) => e.id === CARD.close, this.#waits.sheet);
524
+ if (close) await this.#call("press", { handle: close.handle });
525
+ }
526
+ };
527
+ //#endregion
241
528
  //#region src/client/ref.ts
242
529
  /**
243
530
  * Refs for places and collections.
@@ -1381,6 +1668,14 @@ const summariseEntity = (e) => ({
1381
1668
  });
1382
1669
  var AppleMapsClient = class {
1383
1670
  #config;
1671
+ /**
1672
+ * The interface lane, when this server is hosted by Cupertino.
1673
+ *
1674
+ * Null when it is not, which is the supported case rather than a fault: the
1675
+ * npm package has to work with no app on the machine. `tools/index.ts` skips
1676
+ * registering the tools that need it.
1677
+ */
1678
+ #ax;
1384
1679
  #logger;
1385
1680
  #home;
1386
1681
  #located = null;
@@ -1388,12 +1683,16 @@ var AppleMapsClient = class {
1388
1683
  #storeError = null;
1389
1684
  constructor(opts) {
1390
1685
  this.#config = opts.config;
1686
+ this.#ax = opts.ax !== void 0 ? opts.ax : MapsAxLane.open();
1391
1687
  this.#logger = opts.logger;
1392
1688
  this.#home = opts.home;
1393
1689
  }
1394
1690
  get config() {
1395
1691
  return this.#config;
1396
1692
  }
1693
+ get axLane() {
1694
+ return this.#ax;
1695
+ }
1397
1696
  located() {
1398
1697
  this.#located ??= locateStore({
1399
1698
  storePath: this.#config.storePath,
@@ -1595,6 +1894,7 @@ const ConfigSchema = BaseConfigSchema.extend({
1595
1894
  const loadConfig = (env = process.env) => parseConfig(ConfigSchema, {
1596
1895
  allowWrites: parseBool(env.APPLE_MAPS_ALLOW_WRITES),
1597
1896
  exposePrompts: parseBool(env.APPLE_MAPS_EXPOSE_PROMPTS),
1897
+ lazyTools: parseBool(env.APPLE_MAPS_LAZY_TOOLS),
1598
1898
  debug: parseBool(env.APPLE_MAPS_DEBUG),
1599
1899
  storePath: trimmed(env.APPLE_MAPS_STORE),
1600
1900
  indexMode: trimmed(env.APPLE_MAPS_INDEX_MODE),
@@ -1747,7 +2047,13 @@ const buildDiagnostics = async (client) => {
1747
2047
  working: status.store.opened
1748
2048
  },
1749
2049
  appleEvents: "none — Maps is not scriptable",
1750
- writes: "none — this server registers no mutating tool. The store is mirrored to iCloud by NSPersistentCloudKitContainer, so a write is an edit to one replica of a synchronising graph underneath a running app. That was never probed."
2050
+ writes: {
2051
+ enabled: client.config.allowWrites,
2052
+ lane: "SQL, directly into the Core Data store. Maps ships no scripting dictionary and registers no App Intents on macOS, so there is no lane where the app performs the write on our behalf — this is the only surface here that writes its own store.",
2053
+ seeding: "A place is only real to Maps if it carries a ZMAPITEMSTORAGE blob, which this repo cannot generate. Opening maps://?q=<name>&ll=<lat>,<lon> through LaunchServices makes Maps mint one, and that record is copied. The place is therefore left in Recents whether or not the favourite is kept, and resolving it can take tens of seconds.",
2054
+ blastRadius: "The store is mirrored by NSPersistentCloudKitContainer, and mirroring does not wait to be told. A malformed row is not a local mistake: it reaches every device on the account as soon as Maps next runs. Hence the rule in client/write.ts — never fabricate a place record, only ever copy one Maps wrote.",
2055
+ tools: client.config.allowWrites ? ["apple_maps_add_favorite", "apple_maps_remove_favorite"] : []
2056
+ }
1751
2057
  },
1752
2058
  store: {
1753
2059
  path: located.storePath,
@@ -1785,6 +2091,138 @@ const registerDiagnosticsTools = (server, client) => {
1785
2091
  }, async () => wrap(() => buildDiagnostics(client)));
1786
2092
  };
1787
2093
  //#endregion
2094
+ //#region src/tools/ax-writes.ts
2095
+ /**
2096
+ * The tools that go through Maps' own interface rather than its store.
2097
+ *
2098
+ * ## Why these are not in `writes.ts`
2099
+ *
2100
+ * Different lane, different objects, different failure modes. `writes.ts` writes
2101
+ * SQL into the Core Data store and reaches **favourites**; these press controls
2102
+ * on a place card and reach the **Places library** and **guide membership** —
2103
+ * two sets the SQL lane does not offer at all. `docs/maps.md` lists guide
2104
+ * membership as unbuilt for exactly that reason.
2105
+ *
2106
+ * ## Registered only when the app is hosting this server
2107
+ *
2108
+ * The Accessibility grant belongs to Cupertino.app, not to node, so a package
2109
+ * installed from npm and run by hand has no lane here and these tools are not
2110
+ * registered at all — the same rule the write gate follows, so a host is never
2111
+ * told about a tool that cannot work for it.
2112
+ *
2113
+ * ## What every description has to say
2114
+ *
2115
+ * **It opens Maps and leaves the place in Recents.** Reaching a card means
2116
+ * opening `maps://`, which is how the SQL lane seeds a record too. There is no
2117
+ * way to have the card without the Recents entry.
2118
+ *
2119
+ * **It moves the user's screen.** Unlike a SQL write, this brings a window
2120
+ * forward and presses things in it. That is worth stating plainly.
2121
+ */
2122
+ const noCard = (query, disturbed) => fail(`Maps did not show a place card for "${query}" within 10s. Nothing was changed. The coordinates may not resolve to a place Maps recognises, or Maps may not have finished loading it.` + disturbed);
2123
+ const registerAxWriteTools = (server, lane) => {
2124
+ const place = {
2125
+ query: z.string().min(1).describe("The place's name, as you would type it into Maps' search field."),
2126
+ latitude: z.number().min(-90).max(90).describe("Latitude of the place."),
2127
+ longitude: z.number().min(-180).max(180).describe("Longitude of the place.")
2128
+ };
2129
+ const openCard = async (args) => lane.openCard({
2130
+ name: args.query,
2131
+ latitude: args.latitude,
2132
+ longitude: args.longitude
2133
+ });
2134
+ server.registerTool("apple_maps_save_place", {
2135
+ description: "Save a place into Maps' Places library, by pressing Add on its card. This is NOT a favourite: the Pinned list is a different set, written by apple_maps_add_favorite. Maps performs the write itself, so it cannot produce a malformed record. SIDE EFFECTS: opens Maps, brings its window forward, and leaves the place in the user's Recents. Syncs to the user's other Apple devices. Needs Accessibility for Cupertino.",
2136
+ inputSchema: place,
2137
+ annotations: {
2138
+ readOnlyHint: false,
2139
+ destructiveHint: false,
2140
+ idempotentHint: true
2141
+ }
2142
+ }, async (args) => wrapResult(async () => {
2143
+ const watch = lane.watch();
2144
+ const disturbed = async () => interferenceNote(await watch.check());
2145
+ if (!await openCard(args)) return noCard(args.query, await disturbed());
2146
+ if (await lane.isSaved() === true) return ok({
2147
+ saved: true,
2148
+ alreadySaved: true,
2149
+ query: args.query
2150
+ });
2151
+ if (!await lane.savePlace()) return fail(`The card for "${args.query}" opened but the naming sheet did not, so nothing was saved. The card may still be on screen.` + await disturbed());
2152
+ return await lane.verifySaved({
2153
+ name: args.query,
2154
+ latitude: args.latitude,
2155
+ longitude: args.longitude
2156
+ }) === true ? ok({
2157
+ saved: true,
2158
+ alreadySaved: false,
2159
+ query: args.query
2160
+ }) : fail(`"${args.query}" was pressed through the save sheet but its card does not report it as saved, so this MUST NOT be reported as done. Check Maps.` + await disturbed());
2161
+ }));
2162
+ server.registerTool("apple_maps_remove_saved_place", {
2163
+ description: "Remove a place from Maps' Places library, through the card's overflow menu. This does NOT touch favourites — use apple_maps_remove_favorite for those. SIDE EFFECTS: opens Maps, brings its window forward, and leaves the place in the user's Recents. Syncs to the user's other Apple devices. Needs Accessibility for Cupertino.",
2164
+ inputSchema: place,
2165
+ annotations: {
2166
+ readOnlyHint: false,
2167
+ destructiveHint: true,
2168
+ idempotentHint: true
2169
+ }
2170
+ }, async (args) => wrapResult(async () => {
2171
+ const watch = lane.watch();
2172
+ const disturbed = async () => interferenceNote(await watch.check());
2173
+ if (!await openCard(args)) return noCard(args.query, await disturbed());
2174
+ const outcome = await lane.removePlace();
2175
+ if (outcome === "not-saved") return ok({
2176
+ removed: false,
2177
+ wasSaved: false,
2178
+ query: args.query
2179
+ });
2180
+ if (outcome === "no-menu") return fail(`The card for "${args.query}" opened but its overflow menu did not, so nothing was removed.` + await disturbed());
2181
+ return await lane.verifySaved({
2182
+ name: args.query,
2183
+ latitude: args.latitude,
2184
+ longitude: args.longitude
2185
+ }) === false ? ok({
2186
+ removed: true,
2187
+ wasSaved: true,
2188
+ query: args.query
2189
+ }) : fail(`"${args.query}" was pressed through Delete from Places but its card still reports it as saved, so this MUST NOT be reported as done. Check Maps.` + await disturbed());
2190
+ }));
2191
+ server.registerTool("apple_maps_add_place_to_guide", {
2192
+ description: "File a place into one of Maps' Guides, through the card's Add to Guides picker. This is the write apple_maps_list_collections has no counterpart for. IT CANNOT CONFIRM ITSELF: Maps does not expose whether the place ended up in the guide, so the result says filed: \"unverified\" and it must not be reported as done. The guide must already exist; when the name does not match, the refusal lists every guide Maps offers, which is the authoritative list and can differ from what apple_maps_list_collections returns. SIDE EFFECTS: opens Maps, brings its window forward, and leaves the place in the user's Recents. Syncs to the user's other Apple devices. Needs Accessibility for Cupertino.",
2193
+ inputSchema: {
2194
+ ...place,
2195
+ guide: z.string().min(1).describe("Name of the guide to file it into, as Maps shows it.")
2196
+ },
2197
+ annotations: {
2198
+ readOnlyHint: false,
2199
+ destructiveHint: false,
2200
+ idempotentHint: true
2201
+ }
2202
+ }, async (args) => wrapResult(async () => {
2203
+ const watch = lane.watch();
2204
+ const disturbed = async () => interferenceNote(await watch.check());
2205
+ if (!await openCard(args)) return noCard(args.query, await disturbed());
2206
+ const guides = await lane.openGuidePicker();
2207
+ if (guides === null) return fail(`The card for "${args.query}" opened but its Add to Guides picker did not, so nothing was filed.` + await disturbed());
2208
+ if (!await lane.chooseGuide(args.guide)) {
2209
+ await lane.cancelGuidePicker();
2210
+ return fail(`Maps offers no guide called "${args.guide}", so nothing was filed. It offers: ${guides.map((g) => g.name).join(", ")}.`, { guides: compactGuides(guides) });
2211
+ }
2212
+ return ok({
2213
+ filed: "unverified",
2214
+ query: args.query,
2215
+ guide: args.guide,
2216
+ guides: compactGuides(guides),
2217
+ note: "The guide row and then Done were pressed. Maps does not expose whether the place ended up in the guide — the picker's counts do not refresh in the accessibility tree — so this MUST NOT be reported to the user as filed. Check Maps."
2218
+ });
2219
+ }));
2220
+ };
2221
+ const compactGuides = (guides) => guides.map((g) => g.places === null ? { name: g.name } : {
2222
+ name: g.name,
2223
+ places: g.places
2224
+ });
2225
+ //#endregion
1788
2226
  //#region src/tools/places.ts
1789
2227
  /**
1790
2228
  * The place tools.
@@ -2040,6 +2478,8 @@ const registerTools = (server, client, ctx) => {
2040
2478
  registerPlaceTools(server, client);
2041
2479
  if (!ctx.allowWrites) return;
2042
2480
  registerWriteTools(server, client);
2481
+ const ax = client.axLane;
2482
+ if (ax) registerAxWriteTools(server, ax);
2043
2483
  };
2044
2484
  //#endregion
2045
2485
  //#region src/server.ts
@@ -2065,7 +2505,12 @@ const createServer = (opts) => {
2065
2505
  ...opts.logger ? { logger: opts.logger } : {},
2066
2506
  ...opts.home ? { home: opts.home } : {}
2067
2507
  });
2068
- registerTools(server, client, { allowWrites: config.allowWrites });
2508
+ withLazyTools(server, {
2509
+ surface: "maps",
2510
+ displayName: "Maps",
2511
+ lazy: config.lazyTools,
2512
+ allowWrites: config.allowWrites
2513
+ }, (target, allowWrites) => registerTools(target, client, { allowWrites }));
2069
2514
  if (config.exposePrompts) {
2070
2515
  registerPrompts(server);
2071
2516
  registerSurfaceResources(server, {
@@ -2083,4 +2528,4 @@ const createServer = (opts) => {
2083
2528
  //#endregion
2084
2529
  export { fromStoreTime as A, MAPS_SURFACE as C, UndatableStoreError as D, SchemaDriftError$1 as E, resolveEpoch as M, BUILD_INFO as N, APPLE_SECONDS as O, MAPS_BUNDLE_ID as S, PlaceNotFoundError as T, defaultDirectory as _, loadConfig as a, AppleMapsError as b, introspect as c, InvalidMapsRefError as d, PLACE_REF_VERSION as f, encodePlaceRef as g, encodeCollectionRef as h, registerTools as i, renderInstant as j, CORE_DATA_EPOCH_OFFSET as k, openStore as l, decodePlaceRef as m, SERVER_VERSION as n, AppleMapsClient as o, decodeCollectionRef as p, createServer as r, MapsStore as s, SERVER_NAME as t, COLLECTION_REF_VERSION as u, defaultStorePath as v, MapsStoreUnavailableError as w, IndexUnavailableError as x, locateStore as y };
2085
2530
 
2086
- //# sourceMappingURL=server-D6WxZtcV.js.map
2531
+ //# sourceMappingURL=server-ip7LF8Un.js.map