@mgcrea/mcp-apple-maps 0.0.0-bootstrap
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/LICENSE +21 -0
- package/README.md +60 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +31 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +633 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/server-DgIy0w0S.js +2080 -0
- package/dist/server-DgIy0w0S.js.map +1 -0
- package/package.json +71 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,633 @@
|
|
|
1
|
+
import { AppleAutomationError, AppleAutomationError as AppleMapsError, BuildInfo, CORE_DATA_EPOCH_OFFSET, FileFacts, IndexUnavailableError, Logger, ReadOnlyMode, SchemaDriftError, StoreFacts, SurfaceContext } from "@mgcrea/mcp-apple-core";
|
|
2
|
+
import { DatabaseSync } from "node:sqlite";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
|
+
//#region src/build-info.d.ts
|
|
6
|
+
declare const BUILD_INFO: BuildInfo;
|
|
7
|
+
//#endregion
|
|
8
|
+
//#region src/client/dates.d.ts
|
|
9
|
+
/**
|
|
10
|
+
* How this store's timestamps map onto real time.
|
|
11
|
+
*
|
|
12
|
+
* `confident` is the field that matters. `detectEpoch` always returns an offset
|
|
13
|
+
* — it falls back to unix when nothing fits — so the offset alone cannot
|
|
14
|
+
* distinguish "measured as unix" from "gave up and assumed unix". Rendering the
|
|
15
|
+
* second as though it were the first is the failure this module exists to
|
|
16
|
+
* prevent, so the two are kept apart.
|
|
17
|
+
*/
|
|
18
|
+
type Epoch = {
|
|
19
|
+
offset: number;
|
|
20
|
+
reason: string;
|
|
21
|
+
confident: boolean;
|
|
22
|
+
};
|
|
23
|
+
declare const resolveEpoch: (maxTimestamp: number | null, now?: number) => Epoch;
|
|
24
|
+
/** The expectation docs/maps.md carries, used only where no store is open. */
|
|
25
|
+
declare const APPLE_SECONDS: Epoch;
|
|
26
|
+
/** A stored timestamp to a JS Date, or null when it cannot be placed. */
|
|
27
|
+
declare const fromStoreTime: (value: number | null, epoch: Epoch) => Date | null;
|
|
28
|
+
/** ISO-8601, or null. What every date field on a result carries. */
|
|
29
|
+
declare const renderInstant: (value: number | null, epoch: Epoch) => string | null;
|
|
30
|
+
//#endregion
|
|
31
|
+
//#region src/client/errors.d.ts
|
|
32
|
+
declare const MAPS_SURFACE: SurfaceContext;
|
|
33
|
+
/**
|
|
34
|
+
* Maps is one of the few Apple apps whose bundle id matches its display name —
|
|
35
|
+
* unlike `com.apple.iCal`, `com.apple.AddressBook` and `com.apple.MobileSMS`.
|
|
36
|
+
* Stated rather than assumed, because this repo has been caught by the opposite
|
|
37
|
+
* three times.
|
|
38
|
+
*
|
|
39
|
+
* It is recorded here and used by almost nothing. Maps ships **no scripting
|
|
40
|
+
* dictionary** — `/System/Applications/Maps.app` contains no `.sdef`, checked
|
|
41
|
+
* directly rather than inferred from `NSAppleScriptEnabled` — so there is no
|
|
42
|
+
* Apple Events lane to address, and this server never sends one.
|
|
43
|
+
*/
|
|
44
|
+
declare const MAPS_BUNDLE_ID = "com.apple.Maps";
|
|
45
|
+
/**
|
|
46
|
+
* The store could not be read.
|
|
47
|
+
*
|
|
48
|
+
* This surface has **one lane and no fallback**, which puts it with Messages
|
|
49
|
+
* rather than with Safari: without Full Disk Access there is no degraded Maps
|
|
50
|
+
* server, there is no Maps server. So every read throws this rather than
|
|
51
|
+
* returning an empty list — an empty `favorites` reads exactly like a person
|
|
52
|
+
* who has saved no places, and that is the failure this error exists to
|
|
53
|
+
* prevent.
|
|
54
|
+
*
|
|
55
|
+
* The hint names the trap that actually catches people: the store lives at a
|
|
56
|
+
* path with **no file extension**, inside the one directory in Maps' container
|
|
57
|
+
* that Full Disk Access gates. A sweep for `*.db` finds nothing and concludes
|
|
58
|
+
* the data is not on disk. It is.
|
|
59
|
+
*/
|
|
60
|
+
declare class MapsStoreUnavailableError extends AppleAutomationError {
|
|
61
|
+
readonly name = "MapsStoreUnavailableError";
|
|
62
|
+
constructor(reason: string);
|
|
63
|
+
}
|
|
64
|
+
/** A ref no longer resolves — the place was removed, or the store was re-synced. */
|
|
65
|
+
declare class PlaceNotFoundError extends AppleAutomationError {
|
|
66
|
+
readonly name = "PlaceNotFoundError";
|
|
67
|
+
constructor(ref: string);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* The epoch could not be identified from the data.
|
|
71
|
+
*
|
|
72
|
+
* The same discipline `packages/safari` applies, for the same measured reason:
|
|
73
|
+
* a wrong epoch produces dates that are well-formed and wrong by 31 years.
|
|
74
|
+
* `docs/maps.md` records that this store's `ZCREATETIME` read as apple-seconds
|
|
75
|
+
* and that a unix-seconds reading of the same value lands in 1995 while still
|
|
76
|
+
* looking entirely plausible. So the offset is detected from the store at open
|
|
77
|
+
* time and dates are WITHHELD when it cannot be.
|
|
78
|
+
*/
|
|
79
|
+
declare class UndatableStoreError extends AppleAutomationError {
|
|
80
|
+
readonly name = "UndatableStoreError";
|
|
81
|
+
constructor(reason: string);
|
|
82
|
+
}
|
|
83
|
+
//#endregion
|
|
84
|
+
//#region src/client/locate.d.ts
|
|
85
|
+
type LocatedFile = FileFacts & {
|
|
86
|
+
path: string;
|
|
87
|
+
};
|
|
88
|
+
type LocateResult = StoreFacts & {
|
|
89
|
+
directory: string;
|
|
90
|
+
storePath: string | null;
|
|
91
|
+
/** Reported so a reader can see it was considered. Never opened. */
|
|
92
|
+
localCache: LocatedFile;
|
|
93
|
+
/** True when the name came from a directory scan rather than the constant. */
|
|
94
|
+
resolvedByScan: boolean;
|
|
95
|
+
reason: string | null;
|
|
96
|
+
};
|
|
97
|
+
declare const defaultDirectory: (home?: string) => string;
|
|
98
|
+
declare const defaultStorePath: (home?: string) => string;
|
|
99
|
+
declare const locateStore: (opts?: {
|
|
100
|
+
storePath?: string | undefined;
|
|
101
|
+
home?: string;
|
|
102
|
+
/** Injected by tests so a scan never reaches a real directory. */
|
|
103
|
+
readdir?: (path: string) => string[];
|
|
104
|
+
}) => LocateResult;
|
|
105
|
+
//#endregion
|
|
106
|
+
//#region src/config.d.ts
|
|
107
|
+
/**
|
|
108
|
+
* Configuration is environment-only — this server holds no secret at all. Its
|
|
109
|
+
* access is the macOS permission the user granted, which is the whole point.
|
|
110
|
+
*
|
|
111
|
+
* `allowWrites` gates two mutating tools, and gates them harder than elsewhere.
|
|
112
|
+
* Maps has no scripting dictionary and no registered App Intents, so a write is
|
|
113
|
+
* SQL straight into a Core Data store that `NSPersistentCloudKitContainer`
|
|
114
|
+
* mirrors — which means it reaches every device on the account, not just this
|
|
115
|
+
* Mac. That was measured, along with the rule that keeps it safe: never
|
|
116
|
+
* fabricate a place record, only ever copy one Maps wrote itself. See
|
|
117
|
+
* `docs/maps.md` and `client/write.ts`.
|
|
118
|
+
*/
|
|
119
|
+
declare const ConfigSchema: z.ZodObject<{
|
|
120
|
+
allowWrites: z.ZodDefault<z.ZodBoolean>;
|
|
121
|
+
exposePrompts: z.ZodDefault<z.ZodBoolean>;
|
|
122
|
+
debug: z.ZodDefault<z.ZodBoolean>;
|
|
123
|
+
osascriptPath: z.ZodDefault<z.ZodString>;
|
|
124
|
+
osascriptTimeoutMs: z.ZodDefault<z.ZodNumber>;
|
|
125
|
+
maxResults: z.ZodDefault<z.ZodNumber>;
|
|
126
|
+
storePath: z.ZodOptional<z.ZodString>;
|
|
127
|
+
indexMode: z.ZodDefault<z.ZodEnum<{
|
|
128
|
+
auto: "auto";
|
|
129
|
+
immutable: "immutable";
|
|
130
|
+
off: "off";
|
|
131
|
+
ro: "ro";
|
|
132
|
+
}>>;
|
|
133
|
+
}, z.core.$strict>;
|
|
134
|
+
type Config = z.infer<typeof ConfigSchema>;
|
|
135
|
+
declare const loadConfig: (env?: NodeJS.ProcessEnv) => Config;
|
|
136
|
+
//#endregion
|
|
137
|
+
//#region src/client/store.d.ts
|
|
138
|
+
type FieldMap = {
|
|
139
|
+
/**
|
|
140
|
+
* The column holding a stable UUID, or null when this store has none good
|
|
141
|
+
* enough to address rows by. See `resolveIdentifier`.
|
|
142
|
+
*/
|
|
143
|
+
identifier: string | null;
|
|
144
|
+
name: string | null;
|
|
145
|
+
customName: string | null;
|
|
146
|
+
latitude: string | null;
|
|
147
|
+
longitude: string | null;
|
|
148
|
+
address: string | null;
|
|
149
|
+
muid: string | null;
|
|
150
|
+
mapItem: string | null;
|
|
151
|
+
created: string | null;
|
|
152
|
+
modified: string | null;
|
|
153
|
+
};
|
|
154
|
+
type EntityFacts = {
|
|
155
|
+
table: string;
|
|
156
|
+
present: boolean;
|
|
157
|
+
rows: number;
|
|
158
|
+
columns: Set<string>;
|
|
159
|
+
fields: FieldMap;
|
|
160
|
+
};
|
|
161
|
+
/**
|
|
162
|
+
* How an item says which collection it is in, once proved against the oracle.
|
|
163
|
+
*
|
|
164
|
+
* Two shapes because Core Data has two: a scalar foreign key named after the
|
|
165
|
+
* relationship, and a `Z_<ordinal><RELATIONSHIP>` join table for a
|
|
166
|
+
* many-to-many. THIS store uses the second. Both are carried because the
|
|
167
|
+
* resolver proves whichever is there rather than assuming, and a store that
|
|
168
|
+
* changes shape should degrade rather than read the wrong column.
|
|
169
|
+
*/
|
|
170
|
+
type CollectionMembership = {
|
|
171
|
+
kind: "column";
|
|
172
|
+
column: string;
|
|
173
|
+
} | {
|
|
174
|
+
kind: "joinTable";
|
|
175
|
+
table: string;
|
|
176
|
+
collectionColumn: string;
|
|
177
|
+
itemColumn: string;
|
|
178
|
+
};
|
|
179
|
+
type StoreCapabilities = {
|
|
180
|
+
fingerprint: string;
|
|
181
|
+
tables: string[];
|
|
182
|
+
favorites: EntityFacts;
|
|
183
|
+
collections: EntityFacts;
|
|
184
|
+
collectionItems: EntityFacts;
|
|
185
|
+
history: EntityFacts;
|
|
186
|
+
mapItems: EntityFacts;
|
|
187
|
+
/**
|
|
188
|
+
* The proved membership mechanism, or null when nothing reproduced
|
|
189
|
+
* `ZPLACESCOUNT` and collections must therefore list without their places.
|
|
190
|
+
*/
|
|
191
|
+
membership: CollectionMembership | null;
|
|
192
|
+
/**
|
|
193
|
+
* The membership mechanism as one short string, for diagnostics.
|
|
194
|
+
*
|
|
195
|
+
* Kept because `apple_maps_diagnostics` reported `collectionFk` before this
|
|
196
|
+
* was understood, and a field that silently changes meaning is worse than one
|
|
197
|
+
* that widens: it now names a join table as well as a column.
|
|
198
|
+
*/
|
|
199
|
+
collectionFk: string | null;
|
|
200
|
+
epoch: Epoch;
|
|
201
|
+
};
|
|
202
|
+
type PlaceRow = {
|
|
203
|
+
id: number;
|
|
204
|
+
/**
|
|
205
|
+
* The stable identifier, when the store has one. Null means refs on this
|
|
206
|
+
* entity fall back to the row id and are only good for the session.
|
|
207
|
+
*/
|
|
208
|
+
uuid: string | null;
|
|
209
|
+
name: string | null;
|
|
210
|
+
customName: string | null;
|
|
211
|
+
latitude: number | null;
|
|
212
|
+
longitude: number | null;
|
|
213
|
+
address: string | null;
|
|
214
|
+
/**
|
|
215
|
+
* Apple's place id, carried as a STRING.
|
|
216
|
+
*
|
|
217
|
+
* MEASURED on a real store: `-2679868148951248105`. It is a 64-bit integer and
|
|
218
|
+
* `node:sqlite` THROWS on one past `Number.MAX_SAFE_INTEGER` rather than
|
|
219
|
+
* truncating, so reading it as a number failed the whole listing with
|
|
220
|
+
* "Value is too large to be represented as a JavaScript number" — one column
|
|
221
|
+
* taking down every favourite. `docs/surfaces.md` states the rule this broke:
|
|
222
|
+
* read such columns as BigInt or `CAST(... AS TEXT)`.
|
|
223
|
+
*
|
|
224
|
+
* The fixture used a small id, so the offline suite passed. Only the real
|
|
225
|
+
* store has ids of this size.
|
|
226
|
+
*/
|
|
227
|
+
muid: string | null;
|
|
228
|
+
createdRaw: number | null;
|
|
229
|
+
modifiedRaw: number | null;
|
|
230
|
+
/**
|
|
231
|
+
* False when the row has no linked `ZMIXINMAPITEM`.
|
|
232
|
+
*
|
|
233
|
+
* MEASURED: 3 of 23 favourites, and they carry no name and no coordinate
|
|
234
|
+
* either. Almost certainly the unconfigured Home / Work / School slots that
|
|
235
|
+
* Maps creates whether or not anyone fills them in. They are RETURNED rather
|
|
236
|
+
* than filtered, with this flag, because silently dropping rows is how a
|
|
237
|
+
* caller concludes a favourite was deleted.
|
|
238
|
+
*/
|
|
239
|
+
linked: boolean;
|
|
240
|
+
};
|
|
241
|
+
type CollectionRow = {
|
|
242
|
+
id: number;
|
|
243
|
+
uuid: string | null;
|
|
244
|
+
title: string | null;
|
|
245
|
+
placesCount: number | null;
|
|
246
|
+
createdRaw: number | null;
|
|
247
|
+
modifiedRaw: number | null;
|
|
248
|
+
};
|
|
249
|
+
/**
|
|
250
|
+
* How a caller addresses one row.
|
|
251
|
+
*
|
|
252
|
+
* Two shapes rather than one because the store decides which is available, not
|
|
253
|
+
* the caller: a store whose `ZIDENTIFIER` is complete gets durable refs, one
|
|
254
|
+
* without falls back to row ids. Making the difference explicit in the type
|
|
255
|
+
* means a resolver cannot quietly treat a row id as a uuid when the store
|
|
256
|
+
* changed underneath it.
|
|
257
|
+
*/
|
|
258
|
+
type EntityKey = {
|
|
259
|
+
uuid: string;
|
|
260
|
+
} | {
|
|
261
|
+
rowId: number;
|
|
262
|
+
};
|
|
263
|
+
declare const introspect: (db: DatabaseSync, now?: number) => StoreCapabilities;
|
|
264
|
+
declare class MapsStore {
|
|
265
|
+
#private;
|
|
266
|
+
readonly db: DatabaseSync;
|
|
267
|
+
readonly caps: StoreCapabilities;
|
|
268
|
+
readonly path: string;
|
|
269
|
+
readonly mode: ReadOnlyMode;
|
|
270
|
+
constructor(opts: {
|
|
271
|
+
db: DatabaseSync;
|
|
272
|
+
caps: StoreCapabilities;
|
|
273
|
+
path: string;
|
|
274
|
+
mode: ReadOnlyMode;
|
|
275
|
+
});
|
|
276
|
+
/** How many collection items are filed in no collection; null when unknown. */
|
|
277
|
+
unfiledCount(): number | null;
|
|
278
|
+
/** One entity's places, newest first when a date is available. */
|
|
279
|
+
places(kind: "favorite" | "collection-item" | "history", opts: {
|
|
280
|
+
limit: number;
|
|
281
|
+
collectionId?: number | undefined;
|
|
282
|
+
query?: string | undefined;
|
|
283
|
+
/** Only items filed in NO collection. See `#unfiledClause`. */
|
|
284
|
+
unfiled?: boolean | undefined;
|
|
285
|
+
}): {
|
|
286
|
+
rows: PlaceRow[];
|
|
287
|
+
truncated: boolean;
|
|
288
|
+
};
|
|
289
|
+
/** One place by entity and key. */
|
|
290
|
+
place(kind: "favorite" | "collection-item" | "history", key: EntityKey): PlaceRow | null;
|
|
291
|
+
/**
|
|
292
|
+
* A collection key to the row id its items point at.
|
|
293
|
+
*
|
|
294
|
+
* Collection membership is a Core Data foreign key, so it holds `Z_PK` values
|
|
295
|
+
* whatever the ref carries. A uuid ref has to be translated before it can
|
|
296
|
+
* filter items, and this is the one place that happens.
|
|
297
|
+
*/
|
|
298
|
+
collectionRowId(key: EntityKey): number | null;
|
|
299
|
+
collections(opts: {
|
|
300
|
+
limit: number;
|
|
301
|
+
}): {
|
|
302
|
+
rows: CollectionRow[];
|
|
303
|
+
truncated: boolean;
|
|
304
|
+
};
|
|
305
|
+
close(): void;
|
|
306
|
+
}
|
|
307
|
+
declare const openStore: (opts: {
|
|
308
|
+
path: string;
|
|
309
|
+
mode?: ReadOnlyMode;
|
|
310
|
+
logger?: Logger;
|
|
311
|
+
now?: number;
|
|
312
|
+
hint?: string;
|
|
313
|
+
}) => MapsStore;
|
|
314
|
+
//#endregion
|
|
315
|
+
//#region src/client/ref.d.ts
|
|
316
|
+
/**
|
|
317
|
+
* Refs for places and collections.
|
|
318
|
+
*
|
|
319
|
+
* ## Why the ref is not `ZMUID`
|
|
320
|
+
*
|
|
321
|
+
* `ZMUID` is Apple's own place identifier and looks like the better key — it is
|
|
322
|
+
* the same number for the same restaurant across every device. It is not used,
|
|
323
|
+
* for two measured reasons recorded in `docs/maps.md`:
|
|
324
|
+
*
|
|
325
|
+
* * It is populated **20 of 23** favourites. The three without it are the
|
|
326
|
+
* rows that have no linked place at all, but a ref scheme that cannot
|
|
327
|
+
* address three rows in twenty-three is not a ref scheme.
|
|
328
|
+
* * It identifies a PLACE, not an entry. The same café saved as a favourite
|
|
329
|
+
* AND filed in a collection carries one `ZMUID` across both, so a ref built
|
|
330
|
+
* on it could not say which of the two a caller meant.
|
|
331
|
+
*
|
|
332
|
+
* The keys below address the ENTRY, which is what every tool here returns. The
|
|
333
|
+
* kind is carried in the prefix so a favourite ref and a collection-item ref
|
|
334
|
+
* can never be confused for one another.
|
|
335
|
+
*
|
|
336
|
+
* ## What the ref carries: a uuid when the store has one, a row id when it does not
|
|
337
|
+
*
|
|
338
|
+
* `ZIDENTIFIER` — a 16-byte Core Data UUID — is set on every favourite,
|
|
339
|
+
* collection, collection item and recent on a real store, and is distinct on
|
|
340
|
+
* every one. It addresses the ENTRY, survives a delete elsewhere in the table,
|
|
341
|
+
* and survives an iCloud re-sync renumbering rows. It is the ref whenever
|
|
342
|
+
* `store.ts` can confirm total coverage.
|
|
343
|
+
*
|
|
344
|
+
* It was found by watching Maps write, not by reading the schema — see
|
|
345
|
+
* `docs/maps.md`. The read probe never reported it, because a probe can only
|
|
346
|
+
* report columns it thought to look for.
|
|
347
|
+
*
|
|
348
|
+
* When a store has no usable identifier the ref falls back to the Core Data row
|
|
349
|
+
* id, and then the old caveat applies in full: `Z_PK` is reused after a delete
|
|
350
|
+
* and renumbered by a re-sync, so such a ref is only good for the session.
|
|
351
|
+
* `packages/messages` rejected row ids outright for that reason; here they are
|
|
352
|
+
* the degraded mode rather than the design.
|
|
353
|
+
*
|
|
354
|
+
* The two are told apart BY SHAPE — a uuid is 32 hex characters, a row id is
|
|
355
|
+
* decimal — and the resolver refuses to try a uuid against a store with no
|
|
356
|
+
* identifier column rather than reinterpreting it as a number. Silently
|
|
357
|
+
* resolving one key space in the other would find a real but wrong place, which
|
|
358
|
+
* is worse than finding nothing.
|
|
359
|
+
*
|
|
360
|
+
* ## Why `p1:` and `pc1:`
|
|
361
|
+
*
|
|
362
|
+
* `c1:` is Calendar's, `r1:` Reminders', `k1:` Contacts', `n1:` Notes',
|
|
363
|
+
* `m1:`/`mc1:` Messages', `s1:`/`sb1:` Safari's. A ref that decodes under two
|
|
364
|
+
* surfaces is worse than one that decodes under none, so each prefix is claimed
|
|
365
|
+
* once and the version digit keeps a future change additive rather than a
|
|
366
|
+
* silent reinterpretation of refs already sitting in a conversation.
|
|
367
|
+
*/
|
|
368
|
+
declare const PLACE_REF_VERSION = "p1";
|
|
369
|
+
declare const COLLECTION_REF_VERSION = "pc1";
|
|
370
|
+
/** The entity a place ref points into. Carried in the ref, never guessed. */
|
|
371
|
+
type PlaceKind = "favorite" | "collection-item" | "history";
|
|
372
|
+
declare class InvalidMapsRefError extends AppleAutomationError {
|
|
373
|
+
readonly name = "InvalidMapsRefError";
|
|
374
|
+
constructor(raw: string, want: "place" | "collection");
|
|
375
|
+
}
|
|
376
|
+
declare const encodePlaceRef: (kind: PlaceKind, key: EntityKey) => string;
|
|
377
|
+
declare const encodeCollectionRef: (key: EntityKey) => string;
|
|
378
|
+
declare const decodePlaceRef: (raw: string) => {
|
|
379
|
+
kind: PlaceKind;
|
|
380
|
+
key: EntityKey;
|
|
381
|
+
};
|
|
382
|
+
declare const decodeCollectionRef: (raw: string) => EntityKey;
|
|
383
|
+
//#endregion
|
|
384
|
+
//#region src/client/write.d.ts
|
|
385
|
+
/** Injected so tests never launch an application. */
|
|
386
|
+
type OpenUrl = (url: string) => void;
|
|
387
|
+
type AddFavoriteInput = {
|
|
388
|
+
/** What to search for. A name; a bare coordinate does NOT open a place card. */
|
|
389
|
+
query: string;
|
|
390
|
+
latitude?: number | undefined;
|
|
391
|
+
longitude?: number | undefined;
|
|
392
|
+
/** The label to store. Defaults to `query`. */
|
|
393
|
+
name?: string | undefined;
|
|
394
|
+
};
|
|
395
|
+
type AddFavoriteResult = {
|
|
396
|
+
rowId: number;
|
|
397
|
+
uuid: string;
|
|
398
|
+
name: string | null;
|
|
399
|
+
latitude: number | null;
|
|
400
|
+
longitude: number | null;
|
|
401
|
+
/** False when an equivalent favourite already existed and was returned as-is. */
|
|
402
|
+
created: boolean;
|
|
403
|
+
/** True when Maps had to be asked to resolve the place, leaving a Recents entry. */
|
|
404
|
+
seeded: boolean;
|
|
405
|
+
};
|
|
406
|
+
declare class MapsWriter {
|
|
407
|
+
#private;
|
|
408
|
+
constructor(opts: {
|
|
409
|
+
storePath: string;
|
|
410
|
+
openUrl?: OpenUrl;
|
|
411
|
+
seedTimeoutMs?: number;
|
|
412
|
+
});
|
|
413
|
+
/**
|
|
414
|
+
* Add a favourite, in three phases with a connection open for as little of it
|
|
415
|
+
* as possible.
|
|
416
|
+
*
|
|
417
|
+
* THE PHASES ARE THE POINT. A first version opened one read-write handle at the
|
|
418
|
+
* top and held it across the whole seed — including the wait for Maps to
|
|
419
|
+
* resolve a place, which can run to tens of seconds. That hung: Maps is being
|
|
420
|
+
* asked to WRITE the very record being waited for, into a store this process is
|
|
421
|
+
* holding open for writing. Whatever the precise interaction, the shape was a
|
|
422
|
+
* departure from the sequence that had been proven by hand, and the proven
|
|
423
|
+
* sequence never holds a write handle while waiting on the app.
|
|
424
|
+
*
|
|
425
|
+
* 1. READ — look for an existing favourite and an existing place record.
|
|
426
|
+
* 2. SEED — ask Maps to mint a record, holding NO connection at all, and
|
|
427
|
+
* poll with a short-lived read-only handle each time.
|
|
428
|
+
* 3. WRITE — open read-write, insert, close.
|
|
429
|
+
*/
|
|
430
|
+
addFavorite(input: AddFavoriteInput): AddFavoriteResult;
|
|
431
|
+
/**
|
|
432
|
+
* Remove a favourite and the place record it owns.
|
|
433
|
+
*
|
|
434
|
+
* `Z_MAX` is deliberately NOT rolled back. Core Data never reuses a primary
|
|
435
|
+
* key, and decrementing the counter would hand the next insert one that is
|
|
436
|
+
* already spoken for by a row still referenced elsewhere.
|
|
437
|
+
*/
|
|
438
|
+
removeFavorite(key: EntityKey): boolean;
|
|
439
|
+
}
|
|
440
|
+
//#endregion
|
|
441
|
+
//#region src/client/maps.d.ts
|
|
442
|
+
/**
|
|
443
|
+
* The client.
|
|
444
|
+
*
|
|
445
|
+
* ## One lane, and the consequence of that
|
|
446
|
+
*
|
|
447
|
+
* Safari's orchestrator exists to keep two lanes from being mistaken for one
|
|
448
|
+
* another. Maps has no second lane at all — the app ships no scripting
|
|
449
|
+
* dictionary — so this class has the opposite job: making sure the ABSENCE of
|
|
450
|
+
* the one lane is never mistaken for an absence of data.
|
|
451
|
+
*
|
|
452
|
+
* Without Full Disk Access every list would naturally come back `[]`, and an
|
|
453
|
+
* empty `favorites` reads exactly like a person who has saved no places. So a
|
|
454
|
+
* read with no store THROWS a named error; it never returns an empty array.
|
|
455
|
+
* That is the same rule `packages/messages` follows, and for the same reason:
|
|
456
|
+
* both are surfaces where the grant is not an optimisation.
|
|
457
|
+
*/
|
|
458
|
+
type RenderedPlace = {
|
|
459
|
+
ref: string;
|
|
460
|
+
kind: PlaceKind;
|
|
461
|
+
/** The user's own label when they set one, otherwise the place's name. */
|
|
462
|
+
name: string | null;
|
|
463
|
+
/** The place's own name, kept separate so a rename is visible as one. */
|
|
464
|
+
placeName: string | null;
|
|
465
|
+
address: string | null;
|
|
466
|
+
latitude: number | null;
|
|
467
|
+
longitude: number | null;
|
|
468
|
+
/**
|
|
469
|
+
* Apple's place identifier. Stable for the same place across devices, and
|
|
470
|
+
* shared between a favourite and a collection entry for the same place — so
|
|
471
|
+
* it is reported, and never used as the ref. See `ref.ts`.
|
|
472
|
+
*
|
|
473
|
+
* A STRING: it is a 64-bit integer that does not fit in a JS number. A real
|
|
474
|
+
* store returned `-2679868148951248105`.
|
|
475
|
+
*/
|
|
476
|
+
muid: string | null;
|
|
477
|
+
created: string | null;
|
|
478
|
+
modified: string | null;
|
|
479
|
+
/**
|
|
480
|
+
* False for a row with no linked place record. MEASURED: 3 of 23 favourites,
|
|
481
|
+
* carrying no name and no coordinate — almost certainly the unconfigured
|
|
482
|
+
* Home / Work / School slots Maps creates whether or not anyone fills them in.
|
|
483
|
+
*/
|
|
484
|
+
linked: boolean;
|
|
485
|
+
};
|
|
486
|
+
type RenderedCollection = {
|
|
487
|
+
ref: string;
|
|
488
|
+
title: string | null;
|
|
489
|
+
/** Maps' own count, which may include items this server cannot enumerate. */
|
|
490
|
+
placesCount: number | null;
|
|
491
|
+
created: string | null;
|
|
492
|
+
modified: string | null;
|
|
493
|
+
};
|
|
494
|
+
type CreateClientOptions = {
|
|
495
|
+
config: Config;
|
|
496
|
+
logger?: Logger;
|
|
497
|
+
/** Injected by tests so discovery never reaches the developer's real home. */
|
|
498
|
+
home?: string;
|
|
499
|
+
};
|
|
500
|
+
declare class AppleMapsClient {
|
|
501
|
+
#private;
|
|
502
|
+
constructor(opts: CreateClientOptions);
|
|
503
|
+
get config(): Config;
|
|
504
|
+
located(): LocateResult;
|
|
505
|
+
/**
|
|
506
|
+
* A writer bound to the located store, or an error explaining why not.
|
|
507
|
+
*
|
|
508
|
+
* Writes go through their own read-write connection rather than the shared
|
|
509
|
+
* read-only one: `store()` opens with `query_only` and an `immutable` fallback
|
|
510
|
+
* precisely so a read can never mutate by accident, and widening it would
|
|
511
|
+
* throw that guarantee away for every read on the surface.
|
|
512
|
+
*/
|
|
513
|
+
writer(openUrl?: OpenUrl): MapsWriter;
|
|
514
|
+
/** Open the store, once, lazily. Every read goes through here. */
|
|
515
|
+
store(): MapsStore;
|
|
516
|
+
get epoch(): Epoch;
|
|
517
|
+
places(kind: PlaceKind, opts: {
|
|
518
|
+
limit: number;
|
|
519
|
+
collectionId?: number | undefined;
|
|
520
|
+
query?: string | undefined;
|
|
521
|
+
unfiled?: boolean | undefined;
|
|
522
|
+
}): {
|
|
523
|
+
places: RenderedPlace[];
|
|
524
|
+
truncated: boolean;
|
|
525
|
+
datesAvailable: boolean;
|
|
526
|
+
};
|
|
527
|
+
place(kind: PlaceKind, key: EntityKey): RenderedPlace | null;
|
|
528
|
+
/**
|
|
529
|
+
* A collection ref to the row id its items point at, or null when the ref
|
|
530
|
+
* addresses nothing in this store.
|
|
531
|
+
*
|
|
532
|
+
* Collection membership is a Core Data foreign key and always holds `Z_PK`,
|
|
533
|
+
* so a uuid ref has to be translated before it can filter items.
|
|
534
|
+
*/
|
|
535
|
+
collectionRowId(key: EntityKey): number | null;
|
|
536
|
+
collections(opts: {
|
|
537
|
+
limit: number;
|
|
538
|
+
}): {
|
|
539
|
+
collections: RenderedCollection[];
|
|
540
|
+
truncated: boolean;
|
|
541
|
+
/** Null when the membership key was not found — see `store.ts`. */
|
|
542
|
+
itemsEnumerable: boolean;
|
|
543
|
+
/**
|
|
544
|
+
* Saved places filed in no collection, or null when unanswerable.
|
|
545
|
+
*
|
|
546
|
+
* Reported beside the collections because the counts otherwise do not add
|
|
547
|
+
* up and nothing says so: the guides account for 18 places while the store
|
|
548
|
+
* holds 30, and a caller comparing the two has no way to learn where the
|
|
549
|
+
* rest went.
|
|
550
|
+
*/
|
|
551
|
+
unfiled: number | null;
|
|
552
|
+
};
|
|
553
|
+
/**
|
|
554
|
+
* Search every place-bearing entity at once.
|
|
555
|
+
*
|
|
556
|
+
* Three separate queries rather than a UNION, because the three tables have
|
|
557
|
+
* different resolved columns and a UNION would have to flatten them to the
|
|
558
|
+
* narrowest — which on this store means losing history's coordinates, since
|
|
559
|
+
* they live in a differently named column from the other two.
|
|
560
|
+
*/
|
|
561
|
+
search(opts: {
|
|
562
|
+
query: string;
|
|
563
|
+
limit: number;
|
|
564
|
+
}): {
|
|
565
|
+
places: RenderedPlace[];
|
|
566
|
+
truncated: boolean;
|
|
567
|
+
datesAvailable: boolean;
|
|
568
|
+
};
|
|
569
|
+
/** Everything diagnostics needs, with no failure allowed to fail the call. */
|
|
570
|
+
status(): {
|
|
571
|
+
located: LocateResult;
|
|
572
|
+
store: {
|
|
573
|
+
opened: boolean;
|
|
574
|
+
mode: string | null;
|
|
575
|
+
reason: string | null;
|
|
576
|
+
};
|
|
577
|
+
capabilities: Record<string, unknown> | null;
|
|
578
|
+
};
|
|
579
|
+
close(): void;
|
|
580
|
+
}
|
|
581
|
+
//#endregion
|
|
582
|
+
//#region src/server.d.ts
|
|
583
|
+
declare const SERVER_NAME: string;
|
|
584
|
+
declare const SERVER_VERSION: string;
|
|
585
|
+
type CreateServerOptions = {
|
|
586
|
+
config: Config;
|
|
587
|
+
logger?: Logger;
|
|
588
|
+
/** Injected by tests so discovery never reaches the developer's real home. */
|
|
589
|
+
home?: string;
|
|
590
|
+
};
|
|
591
|
+
type CreatedServer = {
|
|
592
|
+
server: McpServer;
|
|
593
|
+
client: AppleMapsClient;
|
|
594
|
+
};
|
|
595
|
+
/**
|
|
596
|
+
* Build the server. Side-effect free: it opens no database and reads no file,
|
|
597
|
+
* so a test can construct it freely and every external dependency arrives
|
|
598
|
+
* through an option.
|
|
599
|
+
*
|
|
600
|
+
* There is no `osascript` seam here, unlike every other surface. This server
|
|
601
|
+
* never spawns one — Maps is not scriptable, so there is no Apple Events lane
|
|
602
|
+
* to inject a fake for. Its absence is the point.
|
|
603
|
+
*/
|
|
604
|
+
declare const createServer: (opts: CreateServerOptions) => CreatedServer;
|
|
605
|
+
//#endregion
|
|
606
|
+
//#region src/tools/index.d.ts
|
|
607
|
+
type ToolContext = {
|
|
608
|
+
/**
|
|
609
|
+
* Gates the two mutating tools, which are NOT registered when it is false —
|
|
610
|
+
* so a host that has not opted in is never told they exist.
|
|
611
|
+
*
|
|
612
|
+
* This surface writes SQL directly into Maps' Core Data store, because Maps
|
|
613
|
+
* has no scripting dictionary and no App Intents to write through. The store
|
|
614
|
+
* is mirrored to iCloud, so a write here reaches every device on the account.
|
|
615
|
+
* `docs/maps.md` carries the measurements; `client/write.ts` carries the rule
|
|
616
|
+
* that makes it safe — never fabricate a place record, only copy one Maps
|
|
617
|
+
* wrote itself.
|
|
618
|
+
*/
|
|
619
|
+
allowWrites: boolean;
|
|
620
|
+
};
|
|
621
|
+
/**
|
|
622
|
+
* Register the Apple Maps tools.
|
|
623
|
+
*
|
|
624
|
+
* The registered set does NOT vary with whether the store opened. That is a
|
|
625
|
+
* runtime condition and MCP clients cache the tool list, so a list that shrank
|
|
626
|
+
* without Full Disk Access would stay shrunk after the grant was given. Every
|
|
627
|
+
* tool is registered on a machine with no grant at all; each fails with an
|
|
628
|
+
* error that says which permission is missing.
|
|
629
|
+
*/
|
|
630
|
+
declare const registerTools: (server: McpServer, client: AppleMapsClient, ctx: ToolContext) => void;
|
|
631
|
+
//#endregion
|
|
632
|
+
export { APPLE_SECONDS, AppleMapsClient, AppleMapsError, BUILD_INFO, type BuildInfo, COLLECTION_REF_VERSION, CORE_DATA_EPOCH_OFFSET, type CollectionRow, type Config, type CreateClientOptions, type CreateServerOptions, type EntityFacts, type Epoch, IndexUnavailableError, InvalidMapsRefError, type LocateResult, MAPS_BUNDLE_ID, MAPS_SURFACE, MapsStore, MapsStoreUnavailableError, PLACE_REF_VERSION, type PlaceKind, PlaceNotFoundError, type PlaceRow, type RenderedCollection, type RenderedPlace, SERVER_NAME, SERVER_VERSION, SchemaDriftError, type StoreCapabilities, type ToolContext, UndatableStoreError, createServer, decodeCollectionRef, decodePlaceRef, defaultDirectory, defaultStorePath, encodeCollectionRef, encodePlaceRef, fromStoreTime, introspect, loadConfig, locateStore, openStore, registerTools, renderInstant, resolveEpoch };
|
|
633
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/build-info.ts","../src/client/dates.ts","../src/client/errors.ts","../src/client/locate.ts","../src/config.ts","../src/client/store.ts","../src/client/ref.ts","../src/client/write.ts","../src/client/maps.ts","../src/server.ts","../src/tools/index.ts"],"mappings":"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"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { A as fromStoreTime, C as MAPS_SURFACE, D as UndatableStoreError, E as SchemaDriftError, M as resolveEpoch, N as BUILD_INFO, O as APPLE_SECONDS, S as MAPS_BUNDLE_ID, T as PlaceNotFoundError, _ as defaultDirectory, a as loadConfig, b as AppleMapsError, c as introspect, d as InvalidMapsRefError, f as PLACE_REF_VERSION, g as encodePlaceRef, h as encodeCollectionRef, i as registerTools, j as renderInstant, k as CORE_DATA_EPOCH_OFFSET, l as openStore, m as decodePlaceRef, n as SERVER_VERSION, o as AppleMapsClient, p as decodeCollectionRef, r as createServer, s as MapsStore, t as SERVER_NAME, u as COLLECTION_REF_VERSION, v as defaultStorePath, w as MapsStoreUnavailableError, x as IndexUnavailableError, y as locateStore } from "./server-DgIy0w0S.js";
|
|
2
|
+
export { APPLE_SECONDS, AppleMapsClient, AppleMapsError, BUILD_INFO, COLLECTION_REF_VERSION, CORE_DATA_EPOCH_OFFSET, IndexUnavailableError, InvalidMapsRefError, MAPS_BUNDLE_ID, MAPS_SURFACE, MapsStore, MapsStoreUnavailableError, PLACE_REF_VERSION, PlaceNotFoundError, SERVER_NAME, SERVER_VERSION, SchemaDriftError, UndatableStoreError, createServer, decodeCollectionRef, decodePlaceRef, defaultDirectory, defaultStorePath, encodeCollectionRef, encodePlaceRef, fromStoreTime, introspect, loadConfig, locateStore, openStore, registerTools, renderInstant, resolveEpoch };
|