@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.
Files changed (43) hide show
  1. package/README.md +11 -8
  2. package/build/bridge.d.ts +13 -2
  3. package/build/bridge.js +140 -23
  4. package/build/bridge.js.map +1 -1
  5. package/build/index.js +11 -1
  6. package/build/index.js.map +1 -1
  7. package/build/tools/contacts.d.ts +2 -0
  8. package/build/tools/contacts.js +37 -0
  9. package/build/tools/contacts.js.map +1 -0
  10. package/build/tools/files.js +4 -1
  11. package/build/tools/files.js.map +1 -1
  12. package/build/tools/keynote.d.ts +2 -0
  13. package/build/tools/keynote.js +198 -0
  14. package/build/tools/keynote.js.map +1 -0
  15. package/build/tools/mail.js +69 -13
  16. package/build/tools/mail.js.map +1 -1
  17. package/build/tools/notes.d.ts +2 -0
  18. package/build/tools/notes.js +83 -0
  19. package/build/tools/notes.js.map +1 -0
  20. package/build/tools/numbers.d.ts +2 -0
  21. package/build/tools/numbers.js +167 -0
  22. package/build/tools/numbers.js.map +1 -0
  23. package/build/tools/pages.d.ts +2 -0
  24. package/build/tools/pages.js +143 -0
  25. package/build/tools/pages.js.map +1 -0
  26. package/package.json +12 -9
  27. package/scripts/postinstall.sh +77 -8
  28. package/swift/.build/AppleBridge.app/Contents/MacOS/apple-bridge +0 -0
  29. package/swift/.build/AppleBridge.app.sha256 +1 -0
  30. package/swift/Package.resolved +14 -0
  31. package/swift/Package.swift +28 -0
  32. package/swift/Sources/AppleBridge/AppleBridge.swift +846 -0
  33. package/swift/Sources/AppleBridge/Calendar.swift +221 -0
  34. package/swift/Sources/AppleBridge/Contacts.swift +225 -0
  35. package/swift/Sources/AppleBridge/Doctor.swift +252 -0
  36. package/swift/Sources/AppleBridge/Files.swift +474 -0
  37. package/swift/Sources/AppleBridge/JSON.swift +57 -0
  38. package/swift/Sources/AppleBridge/Keynote.swift +599 -0
  39. package/swift/Sources/AppleBridge/Mail.swift +854 -0
  40. package/swift/Sources/AppleBridge/Notes.swift +263 -0
  41. package/swift/Sources/AppleBridge/Numbers.swift +601 -0
  42. package/swift/Sources/AppleBridge/Pages.swift +467 -0
  43. 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
+ }