@mgcrea/mcp-apple-calendar 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 +72 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +28 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +625 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/server-B2HtiXLF.js +2337 -0
- package/dist/server-B2HtiXLF.js.map +1 -0
- package/package.json +68 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,625 @@
|
|
|
1
|
+
import { AppBusyError as CalendarBusyError, AppNotRunningError as CalendarNotRunningError, AppleAutomationError, AppleAutomationError as AppleCalendarError, BuildInfo, Logger, OsascriptRunner, ReadOnlyMode, StoreFacts, SurfaceContext } from "@mgcrea/mcp-apple-core";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4
|
+
import { DatabaseSync } from "node:sqlite";
|
|
5
|
+
//#region src/build-info.d.ts
|
|
6
|
+
declare const BUILD_INFO: BuildInfo;
|
|
7
|
+
//#endregion
|
|
8
|
+
//#region src/client/store.d.ts
|
|
9
|
+
type StoreCapabilities = {
|
|
10
|
+
fingerprint: string;
|
|
11
|
+
/** Present columns, so an Apple rename degrades one field instead of the lane. */
|
|
12
|
+
itemColumns: Set<string>;
|
|
13
|
+
calendarColumns: Set<string>;
|
|
14
|
+
occurrenceColumns: Set<string>;
|
|
15
|
+
recurrenceColumns: Set<string>;
|
|
16
|
+
storeColumns: Set<string>;
|
|
17
|
+
/**
|
|
18
|
+
* Whether recurrences can be expanded at all.
|
|
19
|
+
*
|
|
20
|
+
* False is a capability downgrade, never a throw: leg 1 of a range query
|
|
21
|
+
* (items carrying no recurrence rule) stays correct at any horizon, so the
|
|
22
|
+
* honest degradation is to answer with it and say that repeating events were
|
|
23
|
+
* not expanded — not to fail, and above all not to quietly return fewer rows.
|
|
24
|
+
*/
|
|
25
|
+
hasOccurrenceCache: boolean;
|
|
26
|
+
hasOccurrenceDays: boolean;
|
|
27
|
+
hasRecurrence: boolean;
|
|
28
|
+
hasExceptionDates: boolean;
|
|
29
|
+
hasLocation: boolean;
|
|
30
|
+
hasAttachments: boolean;
|
|
31
|
+
hasParticipants: boolean;
|
|
32
|
+
hasAlarms: boolean;
|
|
33
|
+
epochOffset: number;
|
|
34
|
+
};
|
|
35
|
+
/** One row of the union, still in store units. Rendering happens above this. */
|
|
36
|
+
type EventRow = {
|
|
37
|
+
itemPk: number;
|
|
38
|
+
uuid: string | null;
|
|
39
|
+
calendarPk: number | null;
|
|
40
|
+
calendarUuid: string | null;
|
|
41
|
+
calendarTitle: string | null;
|
|
42
|
+
summary: string | null;
|
|
43
|
+
description: string | null;
|
|
44
|
+
url: string | null;
|
|
45
|
+
conferenceUrl: string | null;
|
|
46
|
+
locationTitle: string | null;
|
|
47
|
+
startApple: number | null;
|
|
48
|
+
endApple: number | null;
|
|
49
|
+
allDay: boolean;
|
|
50
|
+
startTz: string | null;
|
|
51
|
+
endTz: string | null;
|
|
52
|
+
status: number | null;
|
|
53
|
+
invitationStatus: number | null;
|
|
54
|
+
availability: number | null;
|
|
55
|
+
hasRecurrences: boolean;
|
|
56
|
+
hasAttendees: boolean;
|
|
57
|
+
/** Set on a detached occurrence: which series it broke away from, and when. */
|
|
58
|
+
origItemPk: number | null;
|
|
59
|
+
origDateApple: number | null;
|
|
60
|
+
/** Which leg produced this row. Reported so a caller can see the expansion working. */
|
|
61
|
+
source: "item" | "occurrence";
|
|
62
|
+
};
|
|
63
|
+
type RangeQuery = {
|
|
64
|
+
/** Apple-seconds, inclusive lower bound. */
|
|
65
|
+
fromApple: number;
|
|
66
|
+
/** Apple-seconds, exclusive upper bound. */
|
|
67
|
+
toApple: number;
|
|
68
|
+
/** `Calendar.UUID` values. Empty means every calendar. */
|
|
69
|
+
calendarUuids?: readonly string[];
|
|
70
|
+
limit: number;
|
|
71
|
+
};
|
|
72
|
+
type SearchQuery = RangeQuery & {
|
|
73
|
+
text: string;
|
|
74
|
+
scope: "summary" | "full";
|
|
75
|
+
};
|
|
76
|
+
/** How far the cached expansion actually reaches, in Apple-seconds. */
|
|
77
|
+
type Coverage = {
|
|
78
|
+
fromApple: number;
|
|
79
|
+
toApple: number;
|
|
80
|
+
rows: number;
|
|
81
|
+
} | null;
|
|
82
|
+
type IndexCalendar = {
|
|
83
|
+
uuid: string | null;
|
|
84
|
+
title: string | null;
|
|
85
|
+
color: string | null;
|
|
86
|
+
type: string | null;
|
|
87
|
+
accountName: string | null;
|
|
88
|
+
isSubscribed: boolean;
|
|
89
|
+
isPublished: boolean;
|
|
90
|
+
/**
|
|
91
|
+
* Whether this calendar is shared with other people.
|
|
92
|
+
*
|
|
93
|
+
* Derived from `sharing_status`, and INFERRED rather than measured: the only
|
|
94
|
+
* values observed are 0, 1 and NULL, with 1 on the two calendars known to be
|
|
95
|
+
* shared. The raw value stays on the result so a caller can disagree.
|
|
96
|
+
*
|
|
97
|
+
* It matters because writing to a shared calendar is visible to whoever else
|
|
98
|
+
* is on it, and nothing else in the output says so.
|
|
99
|
+
*/
|
|
100
|
+
isShared: boolean;
|
|
101
|
+
sharingStatus: number | null;
|
|
102
|
+
};
|
|
103
|
+
type IndexAccount = {
|
|
104
|
+
name: string | null;
|
|
105
|
+
type: number | null;
|
|
106
|
+
calendars: number;
|
|
107
|
+
};
|
|
108
|
+
declare class CalendarStore {
|
|
109
|
+
#private;
|
|
110
|
+
readonly db: DatabaseSync;
|
|
111
|
+
readonly mode: string;
|
|
112
|
+
readonly caps: StoreCapabilities;
|
|
113
|
+
constructor(db: DatabaseSync, mode: string, caps: StoreCapabilities);
|
|
114
|
+
/**
|
|
115
|
+
* LEG 1 — items carried by the table itself.
|
|
116
|
+
*
|
|
117
|
+
* Overlap, not containment: `start < to AND COALESCE(end, start) > from`. A
|
|
118
|
+
* naive `BETWEEN` on the start silently drops the all-hands that began at
|
|
119
|
+
* 09:00 when the caller asked about 10:00 onward.
|
|
120
|
+
*
|
|
121
|
+
* Items whose occurrences are expanded in the cache are excluded here and
|
|
122
|
+
* picked up by leg 2, so a repeating event is not also returned once at its
|
|
123
|
+
* master start.
|
|
124
|
+
*/
|
|
125
|
+
rangeItems(q: RangeQuery): EventRow[];
|
|
126
|
+
/**
|
|
127
|
+
* LEG 2 — expanded occurrences.
|
|
128
|
+
*
|
|
129
|
+
* `OccurrenceCache.event_id` -> `CalendarItem.ROWID` was measured at a 100%
|
|
130
|
+
* resolve rate. `occurrence_date` is the start; `occurrence_start_date` exists
|
|
131
|
+
* too and reaches only +256 days against `occurrence_date`'s +724, so using it
|
|
132
|
+
* would silently truncate the far half of the window.
|
|
133
|
+
*/
|
|
134
|
+
rangeOccurrences(q: RangeQuery): EventRow[];
|
|
135
|
+
/**
|
|
136
|
+
* Text search, unbounded in time by default.
|
|
137
|
+
*
|
|
138
|
+
* Searching is the one place a caller legitimately wants all of history, so
|
|
139
|
+
* the window is the caller's to set rather than a default. Runs over items
|
|
140
|
+
* only: an occurrence carries no text of its own, and matching the series once
|
|
141
|
+
* is what a search result should be.
|
|
142
|
+
*/
|
|
143
|
+
searchItems(q: SearchQuery): EventRow[];
|
|
144
|
+
/** One event by its Apple Events uid. The bridge measured 198/198 exact. */
|
|
145
|
+
byUuid(uuid: string): EventRow | null;
|
|
146
|
+
/**
|
|
147
|
+
* How far the expansion reaches.
|
|
148
|
+
*
|
|
149
|
+
* Published with every range result. Measured at -732 to +724 days on the
|
|
150
|
+
* probed store, which is a real expansion rather than a month-view cache —
|
|
151
|
+
* but it is still an edge, and nothing guarantees the next machine's is as
|
|
152
|
+
* deep. A range running past it must say so rather than return a short list.
|
|
153
|
+
*/
|
|
154
|
+
coverage(): Coverage;
|
|
155
|
+
calendars(): IndexCalendar[];
|
|
156
|
+
accounts(): IndexAccount[];
|
|
157
|
+
close(): void;
|
|
158
|
+
}
|
|
159
|
+
declare const introspect: (db: DatabaseSync) => StoreCapabilities;
|
|
160
|
+
declare const openStore: (path: string | null, mode: ReadOnlyMode, logger?: Logger) => CalendarStore | null;
|
|
161
|
+
//#endregion
|
|
162
|
+
//#region src/config.d.ts
|
|
163
|
+
/**
|
|
164
|
+
* Configuration is environment-only — this server holds no secret at all, its
|
|
165
|
+
* access is the macOS permission the user granted.
|
|
166
|
+
*
|
|
167
|
+
* `allowWrites`, `debug`, `osascriptPath`, `osascriptTimeoutMs` and `maxResults`
|
|
168
|
+
* come from `BaseConfigSchema`.
|
|
169
|
+
*
|
|
170
|
+
* Note what is deliberately ABSENT relative to `packages/reminders`: there is no
|
|
171
|
+
* `searchCacheTtlMs` and no degraded-listing cap, because both exist there to
|
|
172
|
+
* manage an Apple Events READ lane. Calendar has none by design
|
|
173
|
+
* (`docs/distribution.md`), and config for a lane that does not exist would
|
|
174
|
+
* advertise a fallback this server cannot provide.
|
|
175
|
+
*/
|
|
176
|
+
declare const ConfigSchema: z.ZodObject<{
|
|
177
|
+
allowWrites: z.ZodDefault<z.ZodBoolean>;
|
|
178
|
+
debug: z.ZodDefault<z.ZodBoolean>;
|
|
179
|
+
osascriptPath: z.ZodDefault<z.ZodString>;
|
|
180
|
+
osascriptTimeoutMs: z.ZodDefault<z.ZodNumber>;
|
|
181
|
+
maxResults: z.ZodDefault<z.ZodNumber>;
|
|
182
|
+
accounts: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
183
|
+
calendars: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
184
|
+
storePath: z.ZodOptional<z.ZodString>;
|
|
185
|
+
indexMode: z.ZodDefault<z.ZodEnum<{
|
|
186
|
+
auto: "auto";
|
|
187
|
+
immutable: "immutable";
|
|
188
|
+
off: "off";
|
|
189
|
+
ro: "ro";
|
|
190
|
+
}>>;
|
|
191
|
+
defaultCalendar: z.ZodOptional<z.ZodString>;
|
|
192
|
+
defaultRangeDays: z.ZodDefault<z.ZodNumber>;
|
|
193
|
+
maxRangeDays: z.ZodDefault<z.ZodNumber>;
|
|
194
|
+
defaultEventDurationMinutes: z.ZodDefault<z.ZodNumber>;
|
|
195
|
+
includeDeclined: z.ZodDefault<z.ZodBoolean>;
|
|
196
|
+
includeCancelled: z.ZodDefault<z.ZodBoolean>;
|
|
197
|
+
timeZone: z.ZodOptional<z.ZodString>;
|
|
198
|
+
}, z.core.$strict>;
|
|
199
|
+
type Config = z.infer<typeof ConfigSchema>;
|
|
200
|
+
/**
|
|
201
|
+
* `env` is a parameter with a default so tests are hermetic — they pass their
|
|
202
|
+
* own object rather than mutating (and having to restore) process.env.
|
|
203
|
+
*/
|
|
204
|
+
declare const loadConfig: (env?: NodeJS.ProcessEnv) => Config;
|
|
205
|
+
//#endregion
|
|
206
|
+
//#region src/client/dates.d.ts
|
|
207
|
+
/**
|
|
208
|
+
* What a tool reports for an event's start or end.
|
|
209
|
+
*
|
|
210
|
+
* A union rather than one struct with an `allDay` flag: an all-day event names
|
|
211
|
+
* a DAY and has no instant, and giving it an `iso` field would invite callers to
|
|
212
|
+
* read one. The type makes that impossible instead of merely discouraged.
|
|
213
|
+
*/
|
|
214
|
+
type EventInstant = {
|
|
215
|
+
allDay: true;
|
|
216
|
+
day: string;
|
|
217
|
+
timeZone: null;
|
|
218
|
+
} | {
|
|
219
|
+
allDay: false;
|
|
220
|
+
iso: string;
|
|
221
|
+
timeZone: string | null;
|
|
222
|
+
};
|
|
223
|
+
//#endregion
|
|
224
|
+
//#region src/client/locate.d.ts
|
|
225
|
+
/**
|
|
226
|
+
* Find Calendar's store.
|
|
227
|
+
*
|
|
228
|
+
* ## Why this is the easy case
|
|
229
|
+
*
|
|
230
|
+
* Reminders keeps its database under a generated directory name, so resolving
|
|
231
|
+
* it means *listing* a protected directory — which is itself the privileged
|
|
232
|
+
* operation, leaving no path to even stat without the grant. Calendar does not:
|
|
233
|
+
*
|
|
234
|
+
* ~/Library/Group Containers/group.com.apple.calendar/Calendar.sqlitedb
|
|
235
|
+
*
|
|
236
|
+
* is a constant. `statSync` succeeds on a TCC-protected file (only `access(2)`
|
|
237
|
+
* is denied — see packages/core/src/fs.ts), so this locator can tell "exists but
|
|
238
|
+
* unreadable" from "not there at all" with no permission whatsoever. Those are
|
|
239
|
+
* different failures with different fixes, and saying so is most of what
|
|
240
|
+
* diagnostics is for.
|
|
241
|
+
*
|
|
242
|
+
* ## Why it still walks the container
|
|
243
|
+
*
|
|
244
|
+
* `docs/calendar.md` recorded per-account stores sitting beside the main one,
|
|
245
|
+
* and the probe picked between them by size. The known filename is *preferred*,
|
|
246
|
+
* so the common case costs one `describeStore` and no listing at all; the walk
|
|
247
|
+
* is a fallback for a machine whose layout differs, and for the day Apple moves
|
|
248
|
+
* the file the way it moved Reminders' out of `~/Library/Reminders`.
|
|
249
|
+
*/
|
|
250
|
+
/** `group.com.apple.calendar`, under `~/Library/Group Containers`. */
|
|
251
|
+
declare const GROUP_CONTAINER = "group.com.apple.calendar";
|
|
252
|
+
/** The observed filename. Preferred when present; not required. */
|
|
253
|
+
declare const STORE_FILENAME = "Calendar.sqlitedb";
|
|
254
|
+
/** Sits beside the store. 32 KB, and its contents are not used by this server. */
|
|
255
|
+
declare const EXTRAS_FILENAME = "Extras.db";
|
|
256
|
+
type StoreCandidate = StoreFacts & {
|
|
257
|
+
path: string;
|
|
258
|
+
};
|
|
259
|
+
type LocateResult = StoreFacts & {
|
|
260
|
+
containerPath: string;
|
|
261
|
+
/** Null when discovery found nothing at all — see `reason`. */
|
|
262
|
+
storePath: string | null;
|
|
263
|
+
/** Every store-shaped file found, largest first. */
|
|
264
|
+
candidates: StoreCandidate[];
|
|
265
|
+
/**
|
|
266
|
+
* Whether the container could be listed.
|
|
267
|
+
*
|
|
268
|
+
* Unlike Reminders this is NOT the permission signal — the store's path is
|
|
269
|
+
* known, so `readable` answers that directly. It is reported because a
|
|
270
|
+
* listable container with no store in it means Calendar was never set up,
|
|
271
|
+
* which is a different conversation from a denied grant.
|
|
272
|
+
*/
|
|
273
|
+
containerListable: boolean;
|
|
274
|
+
/** `Extras.db` beside the store. Recorded so it stops being an unknown. */
|
|
275
|
+
extrasPresent: boolean;
|
|
276
|
+
reason: string | null;
|
|
277
|
+
};
|
|
278
|
+
declare const defaultContainerPath: (home?: string) => string;
|
|
279
|
+
declare const defaultStorePath: (home?: string) => string;
|
|
280
|
+
declare const locateStore: (opts?: {
|
|
281
|
+
storePath?: string | undefined;
|
|
282
|
+
home?: string;
|
|
283
|
+
}) => LocateResult;
|
|
284
|
+
//#endregion
|
|
285
|
+
//#region src/client/recurrence.d.ts
|
|
286
|
+
type ExpansionState = "expanded" | "unavailable";
|
|
287
|
+
//#endregion
|
|
288
|
+
//#region src/client/calendar.d.ts
|
|
289
|
+
/**
|
|
290
|
+
* The lane orchestrator.
|
|
291
|
+
*
|
|
292
|
+
* Thinner than `packages/reminders`' by design. That surface arbitrates between
|
|
293
|
+
* two read lanes and caches an expensive Apple Events bulk fetch; Calendar has
|
|
294
|
+
* exactly one read lane, because `docs/distribution.md` sets the policy for new
|
|
295
|
+
* surfaces — file-lane reads, Apple Events for writes and live state — and
|
|
296
|
+
* `docs/calendar.md` measured why: a range query over Apple Events costs 3.4 s
|
|
297
|
+
* and does not improve with batching.
|
|
298
|
+
*
|
|
299
|
+
* So there is nothing to arbitrate. Either the store opens or the surface says
|
|
300
|
+
* plainly that it cannot answer, and never returns an empty list that reads like
|
|
301
|
+
* an empty calendar.
|
|
302
|
+
*/
|
|
303
|
+
type LaneStatus = {
|
|
304
|
+
/**
|
|
305
|
+
* Always reported, never probed here.
|
|
306
|
+
*
|
|
307
|
+
* Reminders probes its Apple Events lane on every `lanes()` call because that
|
|
308
|
+
* lane answers reads. Calendar's does not: it exists only for writes, which
|
|
309
|
+
* are not implemented yet. Probing it would fire the Automation prompt for a
|
|
310
|
+
* capability the server does not currently have, which is a worse trade than
|
|
311
|
+
* saying so.
|
|
312
|
+
*/
|
|
313
|
+
applescript: "not-used" | "live" | "unavailable";
|
|
314
|
+
index: "live" | "unavailable" | "disabled";
|
|
315
|
+
indexMode: string | null;
|
|
316
|
+
storeFingerprint: string | null;
|
|
317
|
+
reason: string | null;
|
|
318
|
+
};
|
|
319
|
+
type EventSummary = {
|
|
320
|
+
/** Opaque; feed it back to get_event or a write tool. */
|
|
321
|
+
ref: string;
|
|
322
|
+
summary: string | null;
|
|
323
|
+
start: EventInstant | null;
|
|
324
|
+
end: EventInstant | null;
|
|
325
|
+
allDay: boolean;
|
|
326
|
+
calendar: string | null;
|
|
327
|
+
location: string | null;
|
|
328
|
+
/** True when this row is one instance of a repeating event. */
|
|
329
|
+
isOccurrence: boolean;
|
|
330
|
+
/** Present on an occurrence: the ref naming the whole series. */
|
|
331
|
+
seriesRef?: string;
|
|
332
|
+
/** Raw store value, reported because the mapping above is inferred. */
|
|
333
|
+
status: number | null;
|
|
334
|
+
invitationStatus: number | null;
|
|
335
|
+
/** Which lane produced the row. */
|
|
336
|
+
source: EventRow["source"];
|
|
337
|
+
};
|
|
338
|
+
type EventDetail = EventSummary & {
|
|
339
|
+
description: string | null;
|
|
340
|
+
url: string | null;
|
|
341
|
+
conferenceUrl: string | null;
|
|
342
|
+
hasAttendees: boolean;
|
|
343
|
+
hasRecurrences: boolean;
|
|
344
|
+
timeZone: string | null;
|
|
345
|
+
};
|
|
346
|
+
type EventPage = {
|
|
347
|
+
events: EventSummary[];
|
|
348
|
+
expansion: ExpansionState;
|
|
349
|
+
expansionReason?: string;
|
|
350
|
+
/** The window actually queried, echoed so a clamp is visible. */
|
|
351
|
+
window: {
|
|
352
|
+
from: string;
|
|
353
|
+
to: string;
|
|
354
|
+
clamped: boolean;
|
|
355
|
+
};
|
|
356
|
+
coverage: {
|
|
357
|
+
from: string;
|
|
358
|
+
to: string;
|
|
359
|
+
rows: number;
|
|
360
|
+
} | null;
|
|
361
|
+
truncated?: {
|
|
362
|
+
reason: string;
|
|
363
|
+
affects: string;
|
|
364
|
+
uncoveredFrom?: string;
|
|
365
|
+
uncoveredTo?: string;
|
|
366
|
+
};
|
|
367
|
+
dropped?: number;
|
|
368
|
+
};
|
|
369
|
+
type EventFilters = {
|
|
370
|
+
from?: string | undefined;
|
|
371
|
+
to?: string | undefined;
|
|
372
|
+
calendar?: string | undefined;
|
|
373
|
+
includeDeclined?: boolean | undefined;
|
|
374
|
+
includeCancelled?: boolean | undefined;
|
|
375
|
+
limit: number;
|
|
376
|
+
};
|
|
377
|
+
type CreateClientOptions = {
|
|
378
|
+
config: Config;
|
|
379
|
+
logger?: Logger;
|
|
380
|
+
/** Injected by tests so nothing spawns a process or touches a real Calendar. */
|
|
381
|
+
osascript?: OsascriptRunner;
|
|
382
|
+
/** Injected by tests so a relative range resolves against a frozen clock. */
|
|
383
|
+
now?: () => Date;
|
|
384
|
+
};
|
|
385
|
+
/** What a write returns: what Calendar STORED, never what was requested. */
|
|
386
|
+
type WriteResult = {
|
|
387
|
+
ref: string;
|
|
388
|
+
uid: string | null;
|
|
389
|
+
summary: string | null;
|
|
390
|
+
start: string | null;
|
|
391
|
+
end: string | null;
|
|
392
|
+
allDay: boolean;
|
|
393
|
+
calendar: string | null;
|
|
394
|
+
/** Always "apple-events": writes never touch the store. */
|
|
395
|
+
source: "apple-events";
|
|
396
|
+
};
|
|
397
|
+
type CreateEventFields = {
|
|
398
|
+
summary: string;
|
|
399
|
+
calendar?: string | undefined;
|
|
400
|
+
start: string;
|
|
401
|
+
end?: string | undefined;
|
|
402
|
+
durationMinutes?: number | undefined;
|
|
403
|
+
allDay?: boolean | undefined;
|
|
404
|
+
location?: string | undefined;
|
|
405
|
+
description?: string | undefined;
|
|
406
|
+
url?: string | undefined;
|
|
407
|
+
};
|
|
408
|
+
type UpdateEventFields = {
|
|
409
|
+
ref: string;
|
|
410
|
+
summary?: string | undefined;
|
|
411
|
+
start?: string | undefined;
|
|
412
|
+
end?: string | undefined;
|
|
413
|
+
durationMinutes?: number | undefined;
|
|
414
|
+
allDay?: boolean | undefined;
|
|
415
|
+
location?: string | undefined;
|
|
416
|
+
description?: string | undefined;
|
|
417
|
+
url?: string | undefined;
|
|
418
|
+
};
|
|
419
|
+
declare class AppleCalendarClient {
|
|
420
|
+
#private;
|
|
421
|
+
readonly config: Config;
|
|
422
|
+
readonly runner: OsascriptRunner;
|
|
423
|
+
constructor(opts: CreateClientOptions);
|
|
424
|
+
/** Cached: the answer cannot change without the process being restarted anyway. */
|
|
425
|
+
locate(): LocateResult;
|
|
426
|
+
/**
|
|
427
|
+
* The store, opened lazily and at most once.
|
|
428
|
+
*
|
|
429
|
+
* Returns null rather than throwing, because "no index" is a state the caller
|
|
430
|
+
* has to render, not an exception. The reason lives on the locate result.
|
|
431
|
+
*/
|
|
432
|
+
index(): CalendarStore | null;
|
|
433
|
+
/**
|
|
434
|
+
* Drop the open handle so the next read reopens.
|
|
435
|
+
*
|
|
436
|
+
* Called after a write: Calendar owns the store and reconciles it against a
|
|
437
|
+
* server, so an event created over Apple Events lands in the file on the
|
|
438
|
+
* app's schedule, not ours.
|
|
439
|
+
*/
|
|
440
|
+
invalidate(): void;
|
|
441
|
+
listEvents(filters: EventFilters): EventPage;
|
|
442
|
+
/**
|
|
443
|
+
* Text search over events.
|
|
444
|
+
*
|
|
445
|
+
* Runs on items only. An occurrence carries no text of its own, so matching
|
|
446
|
+
* the series once is what a search result should be — expanding it here would
|
|
447
|
+
* bury one answer under fifty identical ones.
|
|
448
|
+
*/
|
|
449
|
+
searchEvents(args: EventFilters & {
|
|
450
|
+
query: string;
|
|
451
|
+
scope?: "summary" | "full" | undefined;
|
|
452
|
+
}): EventPage;
|
|
453
|
+
getEvent(ref: string): EventDetail;
|
|
454
|
+
calendars(): IndexCalendar[];
|
|
455
|
+
accounts(): IndexAccount[];
|
|
456
|
+
createEvent(fields: CreateEventFields): Promise<WriteResult>;
|
|
457
|
+
updateEvent(fields: UpdateEventFields): Promise<WriteResult>;
|
|
458
|
+
/**
|
|
459
|
+
* Delete whole events.
|
|
460
|
+
*
|
|
461
|
+
* Only whole events: Calendar's scripting interface cannot remove a single
|
|
462
|
+
* occurrence of a repeating one. `excludedDates` — the property Calendar.app
|
|
463
|
+
* itself uses for "Delete This Event" — reads back a 1903 sentinel and throws
|
|
464
|
+
* on assignment, measured on macOS 26.6. So an occurrence ref is refused
|
|
465
|
+
* rather than silently deleting the whole series, which is the same shape of
|
|
466
|
+
* refusal `updateEvent` makes and for the same underlying reason.
|
|
467
|
+
*/
|
|
468
|
+
deleteEvents(refs: readonly string[]): Promise<{
|
|
469
|
+
results: unknown[];
|
|
470
|
+
scope: string;
|
|
471
|
+
}>;
|
|
472
|
+
lanes(): LaneStatus;
|
|
473
|
+
}
|
|
474
|
+
//#endregion
|
|
475
|
+
//#region src/client/errors.d.ts
|
|
476
|
+
/**
|
|
477
|
+
* Named in every user-facing error and in the env vars they mention.
|
|
478
|
+
*
|
|
479
|
+
* `appName` is the display name, which is NOT the bundle id: Calendar.app is
|
|
480
|
+
* still `com.apple.iCal` underneath. Every other surface has the two agreeing,
|
|
481
|
+
* so the mismatch is written down in both places it matters.
|
|
482
|
+
*/
|
|
483
|
+
declare const CALENDAR_SURFACE: SurfaceContext;
|
|
484
|
+
/** Calendar's Apple Events target. Not `com.apple.Calendar`, which does not exist. */
|
|
485
|
+
declare const CALENDAR_BUNDLE_ID = "com.apple.iCal";
|
|
486
|
+
/**
|
|
487
|
+
* A date argument could not be understood.
|
|
488
|
+
*
|
|
489
|
+
* Carries the accepted grammar rather than just rejecting, because the caller is
|
|
490
|
+
* usually a model that will retry once and needs to know what shape to retry in.
|
|
491
|
+
*/
|
|
492
|
+
declare class InvalidDateError extends AppleAutomationError {
|
|
493
|
+
readonly name = "InvalidDateError";
|
|
494
|
+
constructor(field: string, raw: string, reason: string);
|
|
495
|
+
}
|
|
496
|
+
/** A CalendarRef no longer resolves — deleted, or moved to another calendar. */
|
|
497
|
+
declare class EventNotFoundError extends AppleAutomationError {
|
|
498
|
+
readonly name = "EventNotFoundError";
|
|
499
|
+
constructor(ref: string);
|
|
500
|
+
}
|
|
501
|
+
/** A calendar was named that Calendar does not have. */
|
|
502
|
+
declare class CalendarNotFoundError extends AppleAutomationError {
|
|
503
|
+
readonly name = "CalendarNotFoundError";
|
|
504
|
+
constructor(name: string, available?: readonly string[]);
|
|
505
|
+
}
|
|
506
|
+
/**
|
|
507
|
+
* A write was aimed at a calendar that cannot accept one.
|
|
508
|
+
*
|
|
509
|
+
* Its own error rather than a generic failure because the cause is almost
|
|
510
|
+
* always structural rather than a mistake: holiday, birthday and subscribed
|
|
511
|
+
* calendars are read-only by nature, and a caller that hits one needs to pick a
|
|
512
|
+
* different target, not retry.
|
|
513
|
+
*/
|
|
514
|
+
declare class CalendarNotWritableError extends AppleAutomationError {
|
|
515
|
+
readonly name = "CalendarNotWritableError";
|
|
516
|
+
constructor(name: string);
|
|
517
|
+
}
|
|
518
|
+
//#endregion
|
|
519
|
+
//#region src/client/ref.d.ts
|
|
520
|
+
/**
|
|
521
|
+
* CalendarRef — the one identifier any tool accepts or returns.
|
|
522
|
+
*
|
|
523
|
+
* Wire format: c1:<calendarUid>/<occurrence>/<eventUid>
|
|
524
|
+
*
|
|
525
|
+
* calendarUid `Calendar.UUID`. Always a UUID in this store.
|
|
526
|
+
* occurrence "-" for a single event or a whole series, otherwise the
|
|
527
|
+
* occurrence start as ISO-8601 basic with offset,
|
|
528
|
+
* e.g. 20260821T090000+0200.
|
|
529
|
+
* eventUid `CalendarItem.UUID`, verbatim, as the greedy tail.
|
|
530
|
+
*
|
|
531
|
+
* The `c1:` prefix follows the same reasoning as Notes' `n1:` and Reminders'
|
|
532
|
+
* `r1:`: if the scheme ever changes, a versioned prefix makes that an additive
|
|
533
|
+
* change instead of a silent reinterpretation of every ref already sitting in a
|
|
534
|
+
* conversation.
|
|
535
|
+
*
|
|
536
|
+
* ## Why the uid is the greedy tail, and why `@` is not a separator
|
|
537
|
+
*
|
|
538
|
+
* `docs/calendar.md` measured the id bridge on an iCloud account, where every
|
|
539
|
+
* uid is a bare UUID. That is a property of the ACCOUNT, not of Calendar: a
|
|
540
|
+
* Google event's uid looks like `abc123def@google.com`, and an Exchange one is
|
|
541
|
+
* a long hex blob. Requiring a UUID here would work perfectly on the machine it
|
|
542
|
+
* was written on and fail completely on anyone else's — so the uid is carried
|
|
543
|
+
* through verbatim and the UUID is only extracted opportunistically.
|
|
544
|
+
*
|
|
545
|
+
* That also rules out `@` as a field separator, which is otherwise the obvious
|
|
546
|
+
* choice for pinning an occurrence to a time.
|
|
547
|
+
*
|
|
548
|
+
* ## Why the calendar uid rides along
|
|
549
|
+
*
|
|
550
|
+
* Calendar's scripting dictionary has no `events.byId()`. Finding an event over
|
|
551
|
+
* Apple Events means either `whose({uid})` — measured at 4.5-7.3 s and, worse,
|
|
552
|
+
* UNSTABLE across runs — or one bulk `cal.events.uid()` fetch and an index in
|
|
553
|
+
* JS, at about 1.8 s. The bulk fetch is only affordable if it is scoped to ONE
|
|
554
|
+
* calendar, so every write narrows by calendar before it scans.
|
|
555
|
+
*
|
|
556
|
+
* That is a concrete thing the file lane hands the write lane: the store knows
|
|
557
|
+
* which calendar an event is in, so Apple Events never has to search for it.
|
|
558
|
+
*/
|
|
559
|
+
declare const REF_VERSION = "c1";
|
|
560
|
+
type CalendarRef = {
|
|
561
|
+
/** `Calendar.UUID` — which calendar to narrow to before scanning. */
|
|
562
|
+
calendarUid: string;
|
|
563
|
+
/** `CalendarItem.UUID`, exactly as stored. This is what resolves. */
|
|
564
|
+
eventUid: string;
|
|
565
|
+
/** Null for a single event or a whole series. */
|
|
566
|
+
occurrenceStart: Date | null;
|
|
567
|
+
/** True when this ref names one occurrence rather than the series. */
|
|
568
|
+
isOccurrence: boolean;
|
|
569
|
+
};
|
|
570
|
+
declare const encodeRef: (calendarUid: string, eventUid: string, occurrenceStart?: Date | null) => string;
|
|
571
|
+
declare const decodeRef: (raw: string) => CalendarRef;
|
|
572
|
+
/** The series a ref belongs to. Identity for a ref that is already a series. */
|
|
573
|
+
declare const seriesRefOf: (ref: CalendarRef) => string;
|
|
574
|
+
/** The bare UUID inside an id, when there is one. Null is legitimate, not an error. */
|
|
575
|
+
declare const uuidOf: (id: string) => string | null;
|
|
576
|
+
//#endregion
|
|
577
|
+
//#region src/server.d.ts
|
|
578
|
+
declare const SERVER_NAME: string;
|
|
579
|
+
declare const SERVER_VERSION: string;
|
|
580
|
+
type CreateServerOptions = {
|
|
581
|
+
config: Config;
|
|
582
|
+
logger?: Logger;
|
|
583
|
+
/** Injected by tests so nothing spawns a process or touches a real Calendar. */
|
|
584
|
+
osascript?: OsascriptRunner;
|
|
585
|
+
/** Injected by tests so a relative range resolves against a frozen clock. */
|
|
586
|
+
now?: () => Date;
|
|
587
|
+
};
|
|
588
|
+
type CreatedServer = {
|
|
589
|
+
server: McpServer;
|
|
590
|
+
client: AppleCalendarClient;
|
|
591
|
+
};
|
|
592
|
+
/**
|
|
593
|
+
* Build the server. Side-effect free: it opens no connection, spawns no
|
|
594
|
+
* process and reads no file, so a test can construct it freely and every
|
|
595
|
+
* external dependency arrives through an option.
|
|
596
|
+
*
|
|
597
|
+
*/
|
|
598
|
+
declare const createServer: (opts: CreateServerOptions) => CreatedServer;
|
|
599
|
+
//#endregion
|
|
600
|
+
//#region src/tools/index.d.ts
|
|
601
|
+
type ToolContext = {
|
|
602
|
+
/**
|
|
603
|
+
* Register the mutating tools too. Off by default — with the flag off they are
|
|
604
|
+
* not merely refused, they are invisible and cannot be called at all.
|
|
605
|
+
*/
|
|
606
|
+
allowWrites: boolean;
|
|
607
|
+
};
|
|
608
|
+
/**
|
|
609
|
+
* Register the Apple Calendar tools.
|
|
610
|
+
*
|
|
611
|
+
* The registered set is a pure function of `allowWrites` and nothing else. In
|
|
612
|
+
* particular it does NOT vary with whether Full Disk Access is granted: that is
|
|
613
|
+
* a runtime condition which can change while the process lives, and MCP clients
|
|
614
|
+
* cache the tool list, so a tool that appears and disappears would leave clients
|
|
615
|
+
* calling names the server no longer has. Tools that need the store instead
|
|
616
|
+
* report their source, or explain what is missing.
|
|
617
|
+
*
|
|
618
|
+
* Writes go through Apple Events, always. Not a preference: `PRAGMA query_only`
|
|
619
|
+
* is set on the store because Calendar owns it, holds it open and reconciles it
|
|
620
|
+
* against a server, so writing to it would corrupt sync state.
|
|
621
|
+
*/
|
|
622
|
+
declare const registerTools: (server: McpServer, client: AppleCalendarClient, ctx: ToolContext) => void;
|
|
623
|
+
//#endregion
|
|
624
|
+
export { AppleCalendarClient, AppleCalendarError, BUILD_INFO, type BuildInfo, CALENDAR_BUNDLE_ID, CALENDAR_SURFACE, CalendarBusyError, CalendarNotFoundError, CalendarNotRunningError, CalendarNotWritableError, type CalendarRef, CalendarStore, type Config, type CreateClientOptions, type CreateServerOptions, EXTRAS_FILENAME, EventNotFoundError, GROUP_CONTAINER, InvalidDateError, type LaneStatus, type LocateResult, REF_VERSION, SERVER_NAME, SERVER_VERSION, STORE_FILENAME, type StoreCandidate, type StoreCapabilities, type ToolContext, createServer, decodeRef, defaultContainerPath, defaultStorePath, encodeRef, introspect, loadConfig, locateStore, openStore, registerTools, seriesRefOf, uuidOf };
|
|
625
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/build-info.ts","../src/client/store.ts","../src/config.ts","../src/client/dates.ts","../src/client/locate.ts","../src/client/recurrence.ts","../src/client/calendar.ts","../src/client/errors.ts","../src/client/ref.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 BUILD_INFO, C as CALENDAR_SURFACE, D as CalendarNotWritableError, E as CalendarNotRunningError, O as EventNotFoundError, S as CALENDAR_BUNDLE_ID, T as CalendarNotFoundError, _ as STORE_FILENAME, a as loadConfig, b as locateStore, c as introspect, d as decodeRef, f as encodeRef, g as GROUP_CONTAINER, h as EXTRAS_FILENAME, i as registerTools, k as InvalidDateError, l as openStore, m as uuidOf, n as SERVER_VERSION, o as AppleCalendarClient, p as seriesRefOf, r as createServer, s as CalendarStore, t as SERVER_NAME, u as REF_VERSION, v as defaultContainerPath, w as CalendarBusyError, x as AppleCalendarError, y as defaultStorePath } from "./server-B2HtiXLF.js";
|
|
2
|
+
export { AppleCalendarClient, AppleCalendarError, BUILD_INFO, CALENDAR_BUNDLE_ID, CALENDAR_SURFACE, CalendarBusyError, CalendarNotFoundError, CalendarNotRunningError, CalendarNotWritableError, CalendarStore, EXTRAS_FILENAME, EventNotFoundError, GROUP_CONTAINER, InvalidDateError, REF_VERSION, SERVER_NAME, SERVER_VERSION, STORE_FILENAME, createServer, decodeRef, defaultContainerPath, defaultStorePath, encodeRef, introspect, loadConfig, locateStore, openStore, registerTools, seriesRefOf, uuidOf };
|