@l22-io/orchard-mcp 0.3.2 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -8
- package/build/bridge.d.ts +13 -2
- package/build/bridge.js +140 -23
- package/build/bridge.js.map +1 -1
- package/build/index.js +11 -1
- package/build/index.js.map +1 -1
- package/build/tools/contacts.d.ts +2 -0
- package/build/tools/contacts.js +37 -0
- package/build/tools/contacts.js.map +1 -0
- package/build/tools/files.js +4 -1
- package/build/tools/files.js.map +1 -1
- package/build/tools/keynote.d.ts +2 -0
- package/build/tools/keynote.js +198 -0
- package/build/tools/keynote.js.map +1 -0
- package/build/tools/mail.js +69 -13
- package/build/tools/mail.js.map +1 -1
- package/build/tools/notes.d.ts +2 -0
- package/build/tools/notes.js +83 -0
- package/build/tools/notes.js.map +1 -0
- package/build/tools/numbers.d.ts +2 -0
- package/build/tools/numbers.js +167 -0
- package/build/tools/numbers.js.map +1 -0
- package/build/tools/pages.d.ts +2 -0
- package/build/tools/pages.js +143 -0
- package/build/tools/pages.js.map +1 -0
- package/package.json +12 -9
- package/scripts/postinstall.sh +77 -8
- package/swift/.build/AppleBridge.app/Contents/MacOS/apple-bridge +0 -0
- package/swift/.build/AppleBridge.app.sha256 +1 -0
- package/swift/Package.resolved +14 -0
- package/swift/Package.swift +28 -0
- package/swift/Sources/AppleBridge/AppleBridge.swift +846 -0
- package/swift/Sources/AppleBridge/Calendar.swift +221 -0
- package/swift/Sources/AppleBridge/Contacts.swift +225 -0
- package/swift/Sources/AppleBridge/Doctor.swift +252 -0
- package/swift/Sources/AppleBridge/Files.swift +474 -0
- package/swift/Sources/AppleBridge/JSON.swift +57 -0
- package/swift/Sources/AppleBridge/Keynote.swift +599 -0
- package/swift/Sources/AppleBridge/Mail.swift +854 -0
- package/swift/Sources/AppleBridge/Notes.swift +263 -0
- package/swift/Sources/AppleBridge/Numbers.swift +601 -0
- package/swift/Sources/AppleBridge/Pages.swift +467 -0
- package/swift/Sources/AppleBridge/Reminders.swift +347 -0
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
import EventKit
|
|
2
|
+
import Foundation
|
|
3
|
+
|
|
4
|
+
enum RemindersBridge {
|
|
5
|
+
private static let store = EKEventStore()
|
|
6
|
+
|
|
7
|
+
static func requestAccess() async -> Bool {
|
|
8
|
+
do {
|
|
9
|
+
return try await store.requestFullAccessToReminders()
|
|
10
|
+
} catch {
|
|
11
|
+
return false
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
static func authorizationStatus() -> EKAuthorizationStatus {
|
|
16
|
+
return EKEventStore.authorizationStatus(for: .reminder)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
static func listLists() async {
|
|
20
|
+
guard await requestAccess() else {
|
|
21
|
+
JSONOutput.error("Reminders access denied. Grant access in System Settings > Privacy & Security > Reminders.")
|
|
22
|
+
return
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
let calendars = store.calendars(for: .reminder)
|
|
26
|
+
|
|
27
|
+
// Fetch all reminders to count per list
|
|
28
|
+
let predicate = store.predicateForReminders(in: nil)
|
|
29
|
+
let allReminders = await withCheckedContinuation { continuation in
|
|
30
|
+
store.fetchReminders(matching: predicate) { result in
|
|
31
|
+
continuation.resume(returning: result ?? [])
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Count reminders per calendar
|
|
36
|
+
var counts: [String: Int] = [:]
|
|
37
|
+
for rem in allReminders {
|
|
38
|
+
let calId = rem.calendar.calendarIdentifier
|
|
39
|
+
counts[calId, default: 0] += 1
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
let result: [[String: Any]] = calendars.map { cal in
|
|
43
|
+
var dict: [String: Any] = [
|
|
44
|
+
"id": cal.calendarIdentifier,
|
|
45
|
+
"title": cal.title,
|
|
46
|
+
"allowsModify": cal.allowsContentModifications,
|
|
47
|
+
"itemCount": counts[cal.calendarIdentifier] ?? 0
|
|
48
|
+
]
|
|
49
|
+
if let source = cal.source {
|
|
50
|
+
dict["account"] = source.title
|
|
51
|
+
}
|
|
52
|
+
if let color = cal.cgColor {
|
|
53
|
+
let components = color.components ?? []
|
|
54
|
+
if components.count >= 3 {
|
|
55
|
+
let r = Int(components[0] * 255)
|
|
56
|
+
let g = Int(components[1] * 255)
|
|
57
|
+
let b = Int(components[2] * 255)
|
|
58
|
+
dict["color"] = String(format: "#%02x%02x%02x", r, g, b)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return dict
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
JSONOutput.success(result)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
static func listReminders(listName: String?, filter: String, limit: Int) async {
|
|
68
|
+
guard await requestAccess() else {
|
|
69
|
+
JSONOutput.error("Reminders access denied. Grant access in System Settings > Privacy & Security > Reminders.")
|
|
70
|
+
return
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
var calendars: [EKCalendar]? = nil
|
|
74
|
+
if let name = listName {
|
|
75
|
+
let matches = store.calendars(for: .reminder).filter {
|
|
76
|
+
$0.title.localizedCaseInsensitiveCompare(name) == .orderedSame
|
|
77
|
+
}
|
|
78
|
+
if matches.isEmpty {
|
|
79
|
+
JSONOutput.error("Reminder list not found: \(name)")
|
|
80
|
+
return
|
|
81
|
+
}
|
|
82
|
+
calendars = matches
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
let predicate = store.predicateForReminders(in: calendars)
|
|
86
|
+
let allReminders = await withCheckedContinuation { continuation in
|
|
87
|
+
store.fetchReminders(matching: predicate) { result in
|
|
88
|
+
continuation.resume(returning: result ?? [])
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
let now = Date()
|
|
93
|
+
let startOfToday = Calendar.current.startOfDay(for: now)
|
|
94
|
+
let endOfToday = Calendar.current.date(byAdding: .day, value: 1, to: startOfToday)!
|
|
95
|
+
|
|
96
|
+
let filtered: [EKReminder]
|
|
97
|
+
switch filter {
|
|
98
|
+
case "completed":
|
|
99
|
+
filtered = allReminders.filter { $0.isCompleted }
|
|
100
|
+
case "overdue":
|
|
101
|
+
filtered = allReminders.filter { !$0.isCompleted && $0.dueDateComponents != nil && (Calendar.current.date(from: $0.dueDateComponents!) ?? .distantFuture) < startOfToday }
|
|
102
|
+
case "dueToday":
|
|
103
|
+
filtered = allReminders.filter {
|
|
104
|
+
guard !$0.isCompleted, let dc = $0.dueDateComponents, let due = Calendar.current.date(from: dc) else { return false }
|
|
105
|
+
return due >= startOfToday && due < endOfToday
|
|
106
|
+
}
|
|
107
|
+
case "all":
|
|
108
|
+
filtered = allReminders
|
|
109
|
+
default: // "incomplete"
|
|
110
|
+
filtered = allReminders.filter { !$0.isCompleted }
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
let limited = Array(filtered.prefix(limit))
|
|
114
|
+
let result: [[String: Any]] = limited.map { rem in
|
|
115
|
+
formatReminder(rem)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
JSONOutput.success(result)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
static func today() async {
|
|
122
|
+
guard await requestAccess() else {
|
|
123
|
+
JSONOutput.error("Reminders access denied. Grant access in System Settings > Privacy & Security > Reminders.")
|
|
124
|
+
return
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
let predicate = store.predicateForReminders(in: nil)
|
|
128
|
+
let allReminders = await withCheckedContinuation { continuation in
|
|
129
|
+
store.fetchReminders(matching: predicate) { result in
|
|
130
|
+
continuation.resume(returning: result ?? [])
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
let now = Date()
|
|
135
|
+
let startOfToday = Calendar.current.startOfDay(for: now)
|
|
136
|
+
let endOfToday = Calendar.current.date(byAdding: .day, value: 1, to: startOfToday)!
|
|
137
|
+
|
|
138
|
+
let todayAndOverdue = allReminders.filter { rem in
|
|
139
|
+
guard !rem.isCompleted else { return false }
|
|
140
|
+
guard let dc = rem.dueDateComponents, let due = Calendar.current.date(from: dc) else { return false }
|
|
141
|
+
return due < endOfToday
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
let result: [[String: Any]] = todayAndOverdue.map { rem in
|
|
145
|
+
formatReminder(rem)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
JSONOutput.success(result)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
static func createList(name: String) async {
|
|
152
|
+
guard await requestAccess() else {
|
|
153
|
+
JSONOutput.error("Reminders access denied. Grant access in System Settings > Privacy & Security > Reminders.")
|
|
154
|
+
return
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
let existing = store.calendars(for: .reminder).filter {
|
|
158
|
+
$0.title.localizedCaseInsensitiveCompare(name) == .orderedSame
|
|
159
|
+
}
|
|
160
|
+
if !existing.isEmpty {
|
|
161
|
+
JSONOutput.error("A reminder list named '\(name)' already exists.")
|
|
162
|
+
return
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
let cal = EKCalendar(for: .reminder, eventStore: store)
|
|
166
|
+
cal.title = name
|
|
167
|
+
|
|
168
|
+
guard let source = store.defaultCalendarForNewReminders()?.source else {
|
|
169
|
+
JSONOutput.error("No reminder source available.")
|
|
170
|
+
return
|
|
171
|
+
}
|
|
172
|
+
cal.source = source
|
|
173
|
+
|
|
174
|
+
do {
|
|
175
|
+
try store.saveCalendar(cal, commit: true)
|
|
176
|
+
let result: [String: Any] = [
|
|
177
|
+
"id": cal.calendarIdentifier,
|
|
178
|
+
"title": cal.title,
|
|
179
|
+
"account": source.title,
|
|
180
|
+
"allowsModify": cal.allowsContentModifications
|
|
181
|
+
]
|
|
182
|
+
JSONOutput.success(result)
|
|
183
|
+
} catch {
|
|
184
|
+
JSONOutput.error("Failed to create list: \(error.localizedDescription)")
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
static func createReminder(listName: String, title: String, dueDate: String?, priority: Int, notes: String?) async {
|
|
189
|
+
guard await requestAccess() else {
|
|
190
|
+
JSONOutput.error("Reminders access denied. Grant access in System Settings > Privacy & Security > Reminders.")
|
|
191
|
+
return
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
let matches = store.calendars(for: .reminder).filter {
|
|
195
|
+
$0.title.localizedCaseInsensitiveCompare(listName) == .orderedSame
|
|
196
|
+
}
|
|
197
|
+
guard let calendar = matches.first else {
|
|
198
|
+
JSONOutput.error("Reminder list not found: \(listName)")
|
|
199
|
+
return
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
let reminder = EKReminder(eventStore: store)
|
|
203
|
+
reminder.title = title
|
|
204
|
+
reminder.calendar = calendar
|
|
205
|
+
reminder.priority = priority
|
|
206
|
+
|
|
207
|
+
if let notes = notes {
|
|
208
|
+
reminder.notes = notes
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if let dueDateStr = dueDate {
|
|
212
|
+
let formatter = ISO8601DateFormatter()
|
|
213
|
+
formatter.formatOptions = [.withInternetDateTime]
|
|
214
|
+
var parsed: Date? = formatter.date(from: dueDateStr)
|
|
215
|
+
if parsed == nil {
|
|
216
|
+
let df = DateFormatter()
|
|
217
|
+
df.dateFormat = "yyyy-MM-dd"
|
|
218
|
+
df.timeZone = TimeZone.current
|
|
219
|
+
parsed = df.date(from: dueDateStr)
|
|
220
|
+
}
|
|
221
|
+
guard let due = parsed else {
|
|
222
|
+
JSONOutput.error("Invalid date format: \(dueDateStr). Use ISO 8601 (e.g. 2026-02-18T10:00:00Z or 2026-02-18).")
|
|
223
|
+
return
|
|
224
|
+
}
|
|
225
|
+
reminder.dueDateComponents = Calendar.current.dateComponents(
|
|
226
|
+
[.year, .month, .day, .hour, .minute, .second],
|
|
227
|
+
from: due
|
|
228
|
+
)
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
do {
|
|
232
|
+
try store.save(reminder, commit: true)
|
|
233
|
+
JSONOutput.success(formatReminder(reminder))
|
|
234
|
+
} catch {
|
|
235
|
+
JSONOutput.error("Failed to create reminder: \(error.localizedDescription)")
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
static func completeReminder(id: String) async {
|
|
240
|
+
guard await requestAccess() else {
|
|
241
|
+
JSONOutput.error("Reminders access denied. Grant access in System Settings > Privacy & Security > Reminders.")
|
|
242
|
+
return
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
guard let reminder = store.calendarItem(withIdentifier: id) as? EKReminder else {
|
|
246
|
+
JSONOutput.error("Reminder not found with id: \(id)")
|
|
247
|
+
return
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if reminder.isCompleted {
|
|
251
|
+
JSONOutput.error("Reminder is already completed.")
|
|
252
|
+
return
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
reminder.isCompleted = true
|
|
256
|
+
|
|
257
|
+
do {
|
|
258
|
+
try store.save(reminder, commit: true)
|
|
259
|
+
JSONOutput.success(formatReminder(reminder))
|
|
260
|
+
} catch {
|
|
261
|
+
JSONOutput.error("Failed to complete reminder: \(error.localizedDescription)")
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
static func deleteReminder(id: String) async {
|
|
266
|
+
guard await requestAccess() else {
|
|
267
|
+
JSONOutput.error("Reminders access denied. Grant access in System Settings > Privacy & Security > Reminders.")
|
|
268
|
+
return
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
guard let reminder = store.calendarItem(withIdentifier: id) as? EKReminder else {
|
|
272
|
+
JSONOutput.error("Reminder not found with id: \(id)")
|
|
273
|
+
return
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
let info = formatReminder(reminder)
|
|
277
|
+
|
|
278
|
+
do {
|
|
279
|
+
try store.remove(reminder, commit: true)
|
|
280
|
+
JSONOutput.success(info)
|
|
281
|
+
} catch {
|
|
282
|
+
JSONOutput.error("Failed to delete reminder: \(error.localizedDescription)")
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
static func deleteList(id: String, force: Bool) async {
|
|
287
|
+
guard await requestAccess() else {
|
|
288
|
+
JSONOutput.error("Reminders access denied. Grant access in System Settings > Privacy & Security > Reminders.")
|
|
289
|
+
return
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
guard let calendar = store.calendar(withIdentifier: id) else {
|
|
293
|
+
JSONOutput.error("Reminder list not found with id: \(id)")
|
|
294
|
+
return
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if !force {
|
|
298
|
+
let predicate = store.predicateForReminders(in: [calendar])
|
|
299
|
+
let reminders = await withCheckedContinuation { continuation in
|
|
300
|
+
store.fetchReminders(matching: predicate) { result in
|
|
301
|
+
continuation.resume(returning: result ?? [])
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
if !reminders.isEmpty {
|
|
305
|
+
JSONOutput.error("List '\(calendar.title)' has \(reminders.count) reminders. Set force=true to delete anyway.")
|
|
306
|
+
return
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
let info: [String: Any] = [
|
|
311
|
+
"id": calendar.calendarIdentifier,
|
|
312
|
+
"title": calendar.title
|
|
313
|
+
]
|
|
314
|
+
|
|
315
|
+
do {
|
|
316
|
+
try store.removeCalendar(calendar, commit: true)
|
|
317
|
+
JSONOutput.success(info)
|
|
318
|
+
} catch {
|
|
319
|
+
JSONOutput.error("Failed to delete list: \(error.localizedDescription)")
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// MARK: - Helpers
|
|
324
|
+
|
|
325
|
+
private static func formatReminder(_ rem: EKReminder) -> [String: Any] {
|
|
326
|
+
var dict: [String: Any] = [
|
|
327
|
+
"id": rem.calendarItemIdentifier,
|
|
328
|
+
"title": rem.title ?? "(no title)",
|
|
329
|
+
"isCompleted": rem.isCompleted,
|
|
330
|
+
"list": rem.calendar.title,
|
|
331
|
+
"priority": rem.priority
|
|
332
|
+
]
|
|
333
|
+
if let dc = rem.dueDateComponents, let due = Calendar.current.date(from: dc) {
|
|
334
|
+
dict["dueDate"] = iso8601(due)
|
|
335
|
+
}
|
|
336
|
+
if rem.isCompleted, let completed = rem.completionDate {
|
|
337
|
+
dict["completionDate"] = iso8601(completed)
|
|
338
|
+
}
|
|
339
|
+
if let notes = rem.notes, !notes.isEmpty {
|
|
340
|
+
dict["notes"] = notes
|
|
341
|
+
}
|
|
342
|
+
if rem.hasRecurrenceRules {
|
|
343
|
+
dict["hasRecurrence"] = true
|
|
344
|
+
}
|
|
345
|
+
return dict
|
|
346
|
+
}
|
|
347
|
+
}
|