@l22-io/orchard-mcp 0.3.2 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -8
- package/build/bridge.js +18 -0
- 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 +57 -10
- 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 +11 -8
- 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 +794 -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,846 @@
|
|
|
1
|
+
import ArgumentParser
|
|
2
|
+
import Foundation
|
|
3
|
+
|
|
4
|
+
@main
|
|
5
|
+
struct AppleBridge: AsyncParsableCommand {
|
|
6
|
+
// Strip --output <path> from arguments before ArgumentParser sees them.
|
|
7
|
+
// This allows any subcommand to write to a file instead of stdout,
|
|
8
|
+
// needed for .app bundle mode on macOS Sequoia where stdout is not capturable.
|
|
9
|
+
static func main() async {
|
|
10
|
+
var args = Array(CommandLine.arguments.dropFirst())
|
|
11
|
+
if let idx = args.firstIndex(of: "--output"), idx + 1 < args.count {
|
|
12
|
+
JSONOutput.outputPath = args[idx + 1]
|
|
13
|
+
args.remove(at: idx + 1)
|
|
14
|
+
args.remove(at: idx)
|
|
15
|
+
}
|
|
16
|
+
do {
|
|
17
|
+
var command = try Self.parseAsRoot(args)
|
|
18
|
+
if var asyncCommand = command as? AsyncParsableCommand {
|
|
19
|
+
try await asyncCommand.run()
|
|
20
|
+
} else {
|
|
21
|
+
try command.run()
|
|
22
|
+
}
|
|
23
|
+
} catch {
|
|
24
|
+
Self.exit(withError: error)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
static let configuration = CommandConfiguration(
|
|
29
|
+
commandName: "apple-bridge",
|
|
30
|
+
abstract: "Native macOS bridge for Apple Calendar, Mail, Reminders, Numbers, Pages, and Keynote.",
|
|
31
|
+
version: "0.5.0",
|
|
32
|
+
subcommands: [
|
|
33
|
+
Calendars.self,
|
|
34
|
+
Events.self,
|
|
35
|
+
Search.self,
|
|
36
|
+
MailAccounts.self,
|
|
37
|
+
MailUnread.self,
|
|
38
|
+
MailSearchCmd.self,
|
|
39
|
+
MailMessage.self,
|
|
40
|
+
MailFlagged.self,
|
|
41
|
+
MailCreateDraft.self,
|
|
42
|
+
MailSaveAttachment.self,
|
|
43
|
+
ReminderLists.self,
|
|
44
|
+
RemindersCmd.self,
|
|
45
|
+
RemindersToday.self,
|
|
46
|
+
ReminderCreateList.self,
|
|
47
|
+
ReminderCreate.self,
|
|
48
|
+
ReminderComplete.self,
|
|
49
|
+
ReminderDelete.self,
|
|
50
|
+
ReminderDeleteList.self,
|
|
51
|
+
FileList.self,
|
|
52
|
+
FileInfo.self,
|
|
53
|
+
FileSearchCmd.self,
|
|
54
|
+
FileRead.self,
|
|
55
|
+
FileMove.self,
|
|
56
|
+
FileCopy.self,
|
|
57
|
+
FileCreateFolder.self,
|
|
58
|
+
FileTrash.self,
|
|
59
|
+
Doctor.self,
|
|
60
|
+
// Numbers
|
|
61
|
+
NumbersSearch.self,
|
|
62
|
+
NumbersRead.self,
|
|
63
|
+
NumbersWrite.self,
|
|
64
|
+
NumbersCreate.self,
|
|
65
|
+
NumbersListSheets.self,
|
|
66
|
+
NumbersAddSheet.self,
|
|
67
|
+
NumbersRemoveSheet.self,
|
|
68
|
+
NumbersGetFormulas.self,
|
|
69
|
+
NumbersExport.self,
|
|
70
|
+
NumbersInfo.self,
|
|
71
|
+
// Pages
|
|
72
|
+
PagesSearch.self,
|
|
73
|
+
PagesRead.self,
|
|
74
|
+
PagesWrite.self,
|
|
75
|
+
PagesCreate.self,
|
|
76
|
+
PagesFindReplace.self,
|
|
77
|
+
PagesInsertTable.self,
|
|
78
|
+
PagesListSections.self,
|
|
79
|
+
PagesExport.self,
|
|
80
|
+
PagesInfo.self,
|
|
81
|
+
// Keynote
|
|
82
|
+
KeynoteSearch.self,
|
|
83
|
+
KeynoteRead.self,
|
|
84
|
+
KeynoteCreate.self,
|
|
85
|
+
KeynoteAddSlide.self,
|
|
86
|
+
KeynoteEditSlide.self,
|
|
87
|
+
KeynoteRemoveSlide.self,
|
|
88
|
+
KeynoteReorderSlides.self,
|
|
89
|
+
KeynoteListSlides.self,
|
|
90
|
+
KeynoteListThemes.self,
|
|
91
|
+
KeynoteExport.self,
|
|
92
|
+
KeynoteInfo.self,
|
|
93
|
+
// Notes
|
|
94
|
+
NotesListFolders.self,
|
|
95
|
+
NotesListNotes.self,
|
|
96
|
+
NotesSearchCmd.self,
|
|
97
|
+
NotesRead.self,
|
|
98
|
+
// Contacts
|
|
99
|
+
ContactsListGroups.self,
|
|
100
|
+
ContactsSearchCmd.self,
|
|
101
|
+
ContactsRead.self,
|
|
102
|
+
]
|
|
103
|
+
)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// MARK: - Calendar Subcommands
|
|
107
|
+
|
|
108
|
+
struct Calendars: AsyncParsableCommand {
|
|
109
|
+
static let configuration = CommandConfiguration(
|
|
110
|
+
abstract: "List all calendars with account info."
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
func run() async throws {
|
|
114
|
+
await CalendarBridge.listCalendars()
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
struct Events: AsyncParsableCommand {
|
|
119
|
+
static let configuration = CommandConfiguration(
|
|
120
|
+
abstract: "List events in a date range. Recurring events are expanded."
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
@Option(name: .long, help: "Start date (ISO 8601, e.g. 2026-02-17 or 2026-02-17T00:00:00Z)")
|
|
124
|
+
var start: String
|
|
125
|
+
|
|
126
|
+
@Option(name: .long, help: "End date (ISO 8601)")
|
|
127
|
+
var end: String
|
|
128
|
+
|
|
129
|
+
@Option(name: .long, help: "Filter by calendar ID (from 'calendars' subcommand)")
|
|
130
|
+
var calendar: String?
|
|
131
|
+
|
|
132
|
+
func run() async throws {
|
|
133
|
+
await CalendarBridge.listEvents(startISO: start, endISO: end, calendarID: calendar)
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
struct Search: AsyncParsableCommand {
|
|
138
|
+
static let configuration = CommandConfiguration(
|
|
139
|
+
abstract: "Search events by title, notes, or location within a date range."
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
@Argument(help: "Search query")
|
|
143
|
+
var query: String
|
|
144
|
+
|
|
145
|
+
@Option(name: .long, help: "Start date (ISO 8601)")
|
|
146
|
+
var start: String
|
|
147
|
+
|
|
148
|
+
@Option(name: .long, help: "End date (ISO 8601)")
|
|
149
|
+
var end: String
|
|
150
|
+
|
|
151
|
+
func run() async throws {
|
|
152
|
+
await CalendarBridge.searchEvents(query: query, startISO: start, endISO: end)
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// MARK: - Mail Subcommands
|
|
157
|
+
|
|
158
|
+
struct MailAccounts: AsyncParsableCommand {
|
|
159
|
+
static let configuration = CommandConfiguration(
|
|
160
|
+
commandName: "mail-accounts",
|
|
161
|
+
abstract: "List all mail accounts with mailboxes and unread counts."
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
func run() async throws {
|
|
165
|
+
MailBridge.listAccounts()
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
struct MailUnread: AsyncParsableCommand {
|
|
170
|
+
static let configuration = CommandConfiguration(
|
|
171
|
+
commandName: "mail-unread",
|
|
172
|
+
abstract: "Unread summary per account with recent message subjects."
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
@Option(name: .long, help: "Max unread messages to return per account (default: 10)")
|
|
176
|
+
var limit: Int = 10
|
|
177
|
+
|
|
178
|
+
func run() async throws {
|
|
179
|
+
MailBridge.unreadSummary(limit: limit)
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
struct MailSearchCmd: AsyncParsableCommand {
|
|
184
|
+
static let configuration = CommandConfiguration(
|
|
185
|
+
commandName: "mail-search",
|
|
186
|
+
abstract: "Search messages by subject, sender, body, or all fields."
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
@Option(name: .long, help: "Search query (matches subject and sender)")
|
|
190
|
+
var query: String
|
|
191
|
+
|
|
192
|
+
@Option(name: .long, help: "Filter to specific account name")
|
|
193
|
+
var account: String?
|
|
194
|
+
|
|
195
|
+
@Option(name: .long, help: "Mailbox to search in (default: inbox)")
|
|
196
|
+
var mailbox: String?
|
|
197
|
+
|
|
198
|
+
@Option(name: .long, help: "Max results to return (default: 20)")
|
|
199
|
+
var limit: Int = 20
|
|
200
|
+
|
|
201
|
+
@Option(name: .long, help: "Fields to search: subject, sender, body, all (default: all)")
|
|
202
|
+
var searchIn: String = "all"
|
|
203
|
+
|
|
204
|
+
@Option(name: .long, help: "Number of results to skip for pagination")
|
|
205
|
+
var offset: Int?
|
|
206
|
+
|
|
207
|
+
func run() async throws {
|
|
208
|
+
MailBridge.search(query: query, account: account, mailbox: mailbox, limit: limit, searchIn: searchIn, offset: offset)
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
struct MailMessage: AsyncParsableCommand {
|
|
213
|
+
static let configuration = CommandConfiguration(
|
|
214
|
+
commandName: "mail-message",
|
|
215
|
+
abstract: "Get full message content by message ID."
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
@Option(name: .long, help: "Message ID (from mail-search or mail-unread)")
|
|
219
|
+
var id: String
|
|
220
|
+
|
|
221
|
+
@Option(name: .long, help: "Max body characters to return (default: 4000, 0 = unlimited)")
|
|
222
|
+
var maxBodyLength: Int = 4000
|
|
223
|
+
|
|
224
|
+
func run() async throws {
|
|
225
|
+
MailBridge.readMessage(messageId: id, maxBodyLength: maxBodyLength)
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
struct MailFlagged: AsyncParsableCommand {
|
|
230
|
+
static let configuration = CommandConfiguration(
|
|
231
|
+
commandName: "mail-flagged",
|
|
232
|
+
abstract: "List flagged messages across all accounts."
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
@Option(name: .long, help: "Max results to return (default: 20)")
|
|
236
|
+
var limit: Int = 20
|
|
237
|
+
|
|
238
|
+
@Option(name: .long, help: "Number of results to skip for pagination")
|
|
239
|
+
var offset: Int?
|
|
240
|
+
|
|
241
|
+
func run() async throws {
|
|
242
|
+
MailBridge.flagged(limit: limit, offset: offset)
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
struct MailCreateDraft: AsyncParsableCommand {
|
|
247
|
+
static let configuration = CommandConfiguration(
|
|
248
|
+
commandName: "mail-create-draft",
|
|
249
|
+
abstract: "Create a draft email in Mail.app."
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
@Option(name: .long, help: "Recipient email addresses (comma-separated)")
|
|
253
|
+
var to: String
|
|
254
|
+
|
|
255
|
+
@Option(name: .long, help: "CC email addresses (comma-separated)")
|
|
256
|
+
var cc: String?
|
|
257
|
+
|
|
258
|
+
@Option(name: .long, help: "BCC email addresses (comma-separated)")
|
|
259
|
+
var bcc: String?
|
|
260
|
+
|
|
261
|
+
@Option(name: .long, help: "Email subject")
|
|
262
|
+
var subject: String
|
|
263
|
+
|
|
264
|
+
@Option(name: .long, help: "Email body text")
|
|
265
|
+
var body: String
|
|
266
|
+
|
|
267
|
+
@Option(name: .long, help: "Sender email address (from mail-accounts)")
|
|
268
|
+
var account: String?
|
|
269
|
+
|
|
270
|
+
func run() async throws {
|
|
271
|
+
let toAddrs = to.components(separatedBy: ",").map { $0.trimmingCharacters(in: .whitespaces) }
|
|
272
|
+
let ccAddrs = cc?.components(separatedBy: ",").map { $0.trimmingCharacters(in: .whitespaces) }
|
|
273
|
+
let bccAddrs = bcc?.components(separatedBy: ",").map { $0.trimmingCharacters(in: .whitespaces) }
|
|
274
|
+
MailBridge.createDraft(to: toAddrs, cc: ccAddrs, bcc: bccAddrs, subject: subject, body: body, account: account)
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
struct MailSaveAttachment: AsyncParsableCommand {
|
|
279
|
+
static let configuration = CommandConfiguration(
|
|
280
|
+
commandName: "mail-save-attachment",
|
|
281
|
+
abstract: "Save a message attachment to disk."
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
@Option(name: .long, help: "Message ID (from mail-search or mail-unread)")
|
|
285
|
+
var id: String
|
|
286
|
+
|
|
287
|
+
@Option(name: .long, help: "Attachment index (0-based, from mail-message output)")
|
|
288
|
+
var index: Int
|
|
289
|
+
|
|
290
|
+
@Option(name: .long, help: "Output directory")
|
|
291
|
+
var path: String = "/tmp/orchard-mcp-attachments"
|
|
292
|
+
|
|
293
|
+
func run() async throws {
|
|
294
|
+
MailBridge.saveAttachment(messageId: id, index: index, outputDir: path)
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// MARK: - Reminders Subcommands
|
|
299
|
+
|
|
300
|
+
struct ReminderLists: AsyncParsableCommand {
|
|
301
|
+
static let configuration = CommandConfiguration(
|
|
302
|
+
commandName: "reminder-lists",
|
|
303
|
+
abstract: "List all reminder lists with account and color."
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
func run() async throws {
|
|
307
|
+
await RemindersBridge.listLists()
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
struct RemindersCmd: AsyncParsableCommand {
|
|
312
|
+
static let configuration = CommandConfiguration(
|
|
313
|
+
commandName: "reminders",
|
|
314
|
+
abstract: "List reminders with optional filters."
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
@Option(name: .long, help: "Filter to a specific list name")
|
|
318
|
+
var list: String?
|
|
319
|
+
|
|
320
|
+
@Option(name: .long, help: "Filter: incomplete (default), completed, overdue, dueToday, all")
|
|
321
|
+
var filter: String = "incomplete"
|
|
322
|
+
|
|
323
|
+
@Option(name: .long, help: "Max reminders to return (default: 50)")
|
|
324
|
+
var limit: Int = 50
|
|
325
|
+
|
|
326
|
+
func run() async throws {
|
|
327
|
+
await RemindersBridge.listReminders(listName: list, filter: filter, limit: limit)
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
struct RemindersToday: AsyncParsableCommand {
|
|
332
|
+
static let configuration = CommandConfiguration(
|
|
333
|
+
commandName: "reminders-today",
|
|
334
|
+
abstract: "Incomplete reminders due today plus overdue across all lists."
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
func run() async throws {
|
|
338
|
+
await RemindersBridge.today()
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
struct ReminderCreateList: AsyncParsableCommand {
|
|
343
|
+
static let configuration = CommandConfiguration(
|
|
344
|
+
commandName: "reminder-create-list",
|
|
345
|
+
abstract: "Create a new reminder list."
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
@Option(name: .long, help: "Name for the new list")
|
|
349
|
+
var name: String
|
|
350
|
+
|
|
351
|
+
func run() async throws {
|
|
352
|
+
await RemindersBridge.createList(name: name)
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
struct ReminderCreate: AsyncParsableCommand {
|
|
357
|
+
static let configuration = CommandConfiguration(
|
|
358
|
+
commandName: "reminder-create",
|
|
359
|
+
abstract: "Create a new reminder in a list."
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
@Option(name: .long, help: "List name to add the reminder to")
|
|
363
|
+
var list: String
|
|
364
|
+
|
|
365
|
+
@Option(name: .long, help: "Reminder title")
|
|
366
|
+
var title: String
|
|
367
|
+
|
|
368
|
+
@Option(name: .long, help: "Due date (ISO 8601, e.g. 2026-02-18 or 2026-02-18T10:00:00Z)")
|
|
369
|
+
var due: String?
|
|
370
|
+
|
|
371
|
+
@Option(name: .long, help: "Priority: 0=none, 1=high, 5=medium, 9=low (default: 0)")
|
|
372
|
+
var priority: Int = 0
|
|
373
|
+
|
|
374
|
+
@Option(name: .long, help: "Notes for the reminder")
|
|
375
|
+
var notes: String?
|
|
376
|
+
|
|
377
|
+
func run() async throws {
|
|
378
|
+
await RemindersBridge.createReminder(listName: list, title: title, dueDate: due, priority: priority, notes: notes)
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
struct ReminderComplete: AsyncParsableCommand {
|
|
383
|
+
static let configuration = CommandConfiguration(
|
|
384
|
+
commandName: "reminder-complete",
|
|
385
|
+
abstract: "Mark a reminder as completed."
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
@Option(name: .long, help: "Reminder ID (from reminders command output)")
|
|
389
|
+
var id: String
|
|
390
|
+
|
|
391
|
+
func run() async throws {
|
|
392
|
+
await RemindersBridge.completeReminder(id: id)
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
struct ReminderDelete: AsyncParsableCommand {
|
|
397
|
+
static let configuration = CommandConfiguration(
|
|
398
|
+
commandName: "reminder-delete",
|
|
399
|
+
abstract: "Delete a reminder."
|
|
400
|
+
)
|
|
401
|
+
|
|
402
|
+
@Option(name: .long, help: "Reminder ID (from reminders command output)")
|
|
403
|
+
var id: String
|
|
404
|
+
|
|
405
|
+
func run() async throws {
|
|
406
|
+
await RemindersBridge.deleteReminder(id: id)
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
struct ReminderDeleteList: AsyncParsableCommand {
|
|
411
|
+
static let configuration = CommandConfiguration(
|
|
412
|
+
commandName: "reminder-delete-list",
|
|
413
|
+
abstract: "Delete a reminder list."
|
|
414
|
+
)
|
|
415
|
+
|
|
416
|
+
@Option(name: .long, help: "List ID (from reminder-lists command output)")
|
|
417
|
+
var id: String
|
|
418
|
+
|
|
419
|
+
@Flag(name: .long, help: "Delete even if the list has reminders")
|
|
420
|
+
var force: Bool = false
|
|
421
|
+
|
|
422
|
+
func run() async throws {
|
|
423
|
+
await RemindersBridge.deleteList(id: id, force: force)
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// MARK: - Files & Folders Subcommands
|
|
428
|
+
|
|
429
|
+
struct FileList: ParsableCommand {
|
|
430
|
+
static let configuration = CommandConfiguration(
|
|
431
|
+
commandName: "file-list",
|
|
432
|
+
abstract: "List directory contents with metadata."
|
|
433
|
+
)
|
|
434
|
+
|
|
435
|
+
@Option(name: .long, help: "Directory path (relative to ~ or absolute)")
|
|
436
|
+
var path: String = "."
|
|
437
|
+
|
|
438
|
+
@Flag(name: .long, help: "List recursively")
|
|
439
|
+
var recursive: Bool = false
|
|
440
|
+
|
|
441
|
+
@Option(name: .long, help: "Max recursion depth (default: 3)")
|
|
442
|
+
var depth: Int = 3
|
|
443
|
+
|
|
444
|
+
func run() throws {
|
|
445
|
+
FilesBridge.list(path: path, recursive: recursive, depth: depth)
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
struct FileInfo: ParsableCommand {
|
|
450
|
+
static let configuration = CommandConfiguration(
|
|
451
|
+
commandName: "file-info",
|
|
452
|
+
abstract: "Get detailed file or folder metadata."
|
|
453
|
+
)
|
|
454
|
+
|
|
455
|
+
@Option(name: .long, help: "File path (relative to ~ or absolute)")
|
|
456
|
+
var path: String
|
|
457
|
+
|
|
458
|
+
func run() throws {
|
|
459
|
+
FilesBridge.info(path: path)
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
struct FileSearchCmd: ParsableCommand {
|
|
464
|
+
static let configuration = CommandConfiguration(
|
|
465
|
+
commandName: "file-search",
|
|
466
|
+
abstract: "Search files using Spotlight."
|
|
467
|
+
)
|
|
468
|
+
|
|
469
|
+
@Option(name: .long, help: "Search query (Spotlight syntax)")
|
|
470
|
+
var query: String
|
|
471
|
+
|
|
472
|
+
@Option(name: .long, help: "Filter by kind: folder, image, pdf, document, audio, video, presentation, spreadsheet")
|
|
473
|
+
var kind: String?
|
|
474
|
+
|
|
475
|
+
@Option(name: .long, help: "Search scope directory (relative to ~ or absolute)")
|
|
476
|
+
var scope: String?
|
|
477
|
+
|
|
478
|
+
func run() throws {
|
|
479
|
+
FilesBridge.search(query: query, kind: kind, scope: scope)
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
struct FileRead: ParsableCommand {
|
|
484
|
+
static let configuration = CommandConfiguration(
|
|
485
|
+
commandName: "file-read",
|
|
486
|
+
abstract: "Read and extract text from a file."
|
|
487
|
+
)
|
|
488
|
+
|
|
489
|
+
@Option(name: .long, help: "File path (relative to ~ or absolute)")
|
|
490
|
+
var path: String
|
|
491
|
+
|
|
492
|
+
func run() throws {
|
|
493
|
+
FilesBridge.read(path: path)
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
struct FileMove: ParsableCommand {
|
|
498
|
+
static let configuration = CommandConfiguration(
|
|
499
|
+
commandName: "file-move",
|
|
500
|
+
abstract: "Move or rename files and folders."
|
|
501
|
+
)
|
|
502
|
+
|
|
503
|
+
@Option(name: .long, help: "JSON array of {\"source\": \"...\", \"destination\": \"...\"} pairs")
|
|
504
|
+
var items: String
|
|
505
|
+
|
|
506
|
+
func run() throws {
|
|
507
|
+
FilesBridge.move(itemsJSON: items)
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
struct FileCopy: ParsableCommand {
|
|
512
|
+
static let configuration = CommandConfiguration(
|
|
513
|
+
commandName: "file-copy",
|
|
514
|
+
abstract: "Copy a file or folder."
|
|
515
|
+
)
|
|
516
|
+
|
|
517
|
+
@Option(name: .long, help: "Source path")
|
|
518
|
+
var source: String
|
|
519
|
+
|
|
520
|
+
@Option(name: .long, help: "Destination path")
|
|
521
|
+
var dest: String
|
|
522
|
+
|
|
523
|
+
func run() throws {
|
|
524
|
+
FilesBridge.copy(source: source, destination: dest)
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
struct FileCreateFolder: ParsableCommand {
|
|
529
|
+
static let configuration = CommandConfiguration(
|
|
530
|
+
commandName: "file-create-folder",
|
|
531
|
+
abstract: "Create a directory with intermediate directories."
|
|
532
|
+
)
|
|
533
|
+
|
|
534
|
+
@Option(name: .long, help: "Directory path to create")
|
|
535
|
+
var path: String
|
|
536
|
+
|
|
537
|
+
func run() throws {
|
|
538
|
+
FilesBridge.createFolder(path: path)
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
struct FileTrash: ParsableCommand {
|
|
543
|
+
static let configuration = CommandConfiguration(
|
|
544
|
+
commandName: "file-trash",
|
|
545
|
+
abstract: "Move a file or folder to Trash."
|
|
546
|
+
)
|
|
547
|
+
|
|
548
|
+
@Option(name: .long, help: "File or folder path to trash")
|
|
549
|
+
var path: String
|
|
550
|
+
|
|
551
|
+
func run() throws {
|
|
552
|
+
FilesBridge.trash(path: path)
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// MARK: - Doctor
|
|
557
|
+
|
|
558
|
+
struct Doctor: AsyncParsableCommand {
|
|
559
|
+
static let configuration = CommandConfiguration(
|
|
560
|
+
abstract: "Check permissions and list accessible resources."
|
|
561
|
+
)
|
|
562
|
+
|
|
563
|
+
func run() async throws {
|
|
564
|
+
await DoctorBridge.run()
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
// MARK: - Numbers Subcommands
|
|
569
|
+
|
|
570
|
+
struct NumbersSearch: ParsableCommand {
|
|
571
|
+
static let configuration = CommandConfiguration(commandName: "numbers-search", abstract: "Search for Numbers spreadsheets using Spotlight.")
|
|
572
|
+
@Option(name: .long, help: "Search query") var query: String
|
|
573
|
+
@Option(name: .long, help: "Max results (default: 20)") var limit: Int = 20
|
|
574
|
+
func run() throws { NumbersBridge.search(query: query, limit: limit) }
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
struct NumbersRead: ParsableCommand {
|
|
578
|
+
static let configuration = CommandConfiguration(commandName: "numbers-read", abstract: "Read cell data from a Numbers spreadsheet as JSON.")
|
|
579
|
+
@Option(name: .long, help: "Path to .numbers file") var file: String
|
|
580
|
+
@Option(name: .long, help: "Sheet name (default: first sheet)") var sheet: String?
|
|
581
|
+
@Option(name: .long, help: "Table name (default: first table)") var table: String?
|
|
582
|
+
@Option(name: .long, help: "Cell range in A1 notation (e.g. A1:C10)") var range: String?
|
|
583
|
+
func run() throws { NumbersBridge.read(file: file, sheet: sheet, table: table, range: range) }
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
struct NumbersWrite: ParsableCommand {
|
|
587
|
+
static let configuration = CommandConfiguration(commandName: "numbers-write", abstract: "Write data to cells in a Numbers spreadsheet.")
|
|
588
|
+
@Option(name: .long, help: "Path to .numbers file") var file: String
|
|
589
|
+
@Option(name: .long, help: "Sheet name (default: first sheet)") var sheet: String?
|
|
590
|
+
@Option(name: .long, help: "Table name (default: first table)") var table: String?
|
|
591
|
+
@Option(name: .long, help: "Starting cell in A1 notation (e.g. A1)") var range: String?
|
|
592
|
+
@Option(name: .long, help: "JSON array of arrays with cell data") var data: String
|
|
593
|
+
func run() throws { NumbersBridge.write(file: file, sheet: sheet, table: table, range: range, dataJSON: data) }
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
struct NumbersCreate: ParsableCommand {
|
|
597
|
+
static let configuration = CommandConfiguration(commandName: "numbers-create", abstract: "Create a new Numbers spreadsheet.")
|
|
598
|
+
@Option(name: .long, help: "Output file path") var file: String
|
|
599
|
+
@Option(name: .long, help: "Initial data as JSON array of arrays") var data: String?
|
|
600
|
+
@Option(name: .long, help: "Template name") var template: String?
|
|
601
|
+
func run() throws { NumbersBridge.create(file: file, dataJSON: data, template: template) }
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
struct NumbersListSheets: ParsableCommand {
|
|
605
|
+
static let configuration = CommandConfiguration(commandName: "numbers-list-sheets", abstract: "List all sheets and tables in a Numbers document.")
|
|
606
|
+
@Option(name: .long, help: "Path to .numbers file") var file: String
|
|
607
|
+
func run() throws { NumbersBridge.listSheets(file: file) }
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
struct NumbersAddSheet: ParsableCommand {
|
|
611
|
+
static let configuration = CommandConfiguration(commandName: "numbers-add-sheet", abstract: "Add a new sheet to a Numbers document.")
|
|
612
|
+
@Option(name: .long, help: "Path to .numbers file") var file: String
|
|
613
|
+
@Option(name: .long, help: "Name for the new sheet") var name: String
|
|
614
|
+
func run() throws { NumbersBridge.addSheet(file: file, name: name) }
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
struct NumbersRemoveSheet: ParsableCommand {
|
|
618
|
+
static let configuration = CommandConfiguration(commandName: "numbers-remove-sheet", abstract: "Remove a sheet from a Numbers document.")
|
|
619
|
+
@Option(name: .long, help: "Path to .numbers file") var file: String
|
|
620
|
+
@Option(name: .long, help: "Sheet name to remove") var name: String
|
|
621
|
+
func run() throws { NumbersBridge.removeSheet(file: file, name: name) }
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
struct NumbersGetFormulas: ParsableCommand {
|
|
625
|
+
static let configuration = CommandConfiguration(commandName: "numbers-get-formulas", abstract: "Read formulas from cells in a Numbers spreadsheet.")
|
|
626
|
+
@Option(name: .long, help: "Path to .numbers file") var file: String
|
|
627
|
+
@Option(name: .long, help: "Sheet name (default: first sheet)") var sheet: String?
|
|
628
|
+
@Option(name: .long, help: "Table name (default: first table)") var table: String?
|
|
629
|
+
@Option(name: .long, help: "Cell range in A1 notation") var range: String?
|
|
630
|
+
func run() throws { NumbersBridge.getFormulas(file: file, sheet: sheet, table: table, range: range) }
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
struct NumbersExport: ParsableCommand {
|
|
634
|
+
static let configuration = CommandConfiguration(commandName: "numbers-export", abstract: "Export a Numbers spreadsheet to CSV, PDF, or Excel.")
|
|
635
|
+
@Option(name: .long, help: "Path to .numbers file") var file: String
|
|
636
|
+
@Option(name: .long, help: "Export format: csv, pdf, xlsx") var format: String
|
|
637
|
+
@Option(name: .long, help: "Output file path (default: same name with new extension)") var dest: String?
|
|
638
|
+
func run() throws { NumbersBridge.export(file: file, format: format, dest: dest) }
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
struct NumbersInfo: ParsableCommand {
|
|
642
|
+
static let configuration = CommandConfiguration(commandName: "numbers-info", abstract: "Get metadata about a Numbers spreadsheet.")
|
|
643
|
+
@Option(name: .long, help: "Path to .numbers file") var file: String
|
|
644
|
+
func run() throws { NumbersBridge.info(file: file) }
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
// MARK: - Pages Subcommands
|
|
648
|
+
|
|
649
|
+
struct PagesSearch: ParsableCommand {
|
|
650
|
+
static let configuration = CommandConfiguration(commandName: "pages-search", abstract: "Search for Pages documents using Spotlight.")
|
|
651
|
+
@Option(name: .long, help: "Search query") var query: String
|
|
652
|
+
@Option(name: .long, help: "Max results (default: 20)") var limit: Int = 20
|
|
653
|
+
func run() throws { PagesBridge.search(query: query, limit: limit) }
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
struct PagesRead: ParsableCommand {
|
|
657
|
+
static let configuration = CommandConfiguration(commandName: "pages-read", abstract: "Read body text from a Pages document.")
|
|
658
|
+
@Option(name: .long, help: "Path to .pages file") var file: String
|
|
659
|
+
func run() throws { PagesBridge.read(file: file) }
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
struct PagesWrite: ParsableCommand {
|
|
663
|
+
static let configuration = CommandConfiguration(commandName: "pages-write", abstract: "Set the body text of a Pages document.")
|
|
664
|
+
@Option(name: .long, help: "Path to .pages file") var file: String
|
|
665
|
+
@Option(name: .long, help: "Text to write") var text: String
|
|
666
|
+
func run() throws { PagesBridge.write(file: file, text: text) }
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
struct PagesCreate: ParsableCommand {
|
|
670
|
+
static let configuration = CommandConfiguration(commandName: "pages-create", abstract: "Create a new Pages document.")
|
|
671
|
+
@Option(name: .long, help: "Output file path") var file: String
|
|
672
|
+
@Option(name: .long, help: "Initial body text") var text: String?
|
|
673
|
+
@Option(name: .long, help: "Template name") var template: String?
|
|
674
|
+
func run() throws { PagesBridge.create(file: file, text: text, template: template) }
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
struct PagesFindReplace: ParsableCommand {
|
|
678
|
+
static let configuration = CommandConfiguration(commandName: "pages-find-replace", abstract: "Find and replace text in a Pages document.")
|
|
679
|
+
@Option(name: .long, help: "Path to .pages file") var file: String
|
|
680
|
+
@Option(name: .long, help: "Text to find") var find: String
|
|
681
|
+
@Option(name: .long, help: "Replacement text") var replace: String
|
|
682
|
+
@Flag(name: .long, help: "Replace all occurrences") var all: Bool = false
|
|
683
|
+
func run() throws { PagesBridge.findReplace(file: file, find: find, replace: replace, all: all) }
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
struct PagesInsertTable: ParsableCommand {
|
|
687
|
+
static let configuration = CommandConfiguration(commandName: "pages-insert-table", abstract: "Insert a table into a Pages document.")
|
|
688
|
+
@Option(name: .long, help: "Path to .pages file") var file: String
|
|
689
|
+
@Option(name: .long, help: "Table data as JSON array of arrays") var data: String
|
|
690
|
+
func run() throws { PagesBridge.insertTable(file: file, dataJSON: data) }
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
struct PagesListSections: ParsableCommand {
|
|
694
|
+
static let configuration = CommandConfiguration(commandName: "pages-list-sections", abstract: "List sections in a Pages document with previews.")
|
|
695
|
+
@Option(name: .long, help: "Path to .pages file") var file: String
|
|
696
|
+
func run() throws { PagesBridge.listSections(file: file) }
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
struct PagesExport: ParsableCommand {
|
|
700
|
+
static let configuration = CommandConfiguration(commandName: "pages-export", abstract: "Export a Pages document to PDF, Word, TXT, or EPUB.")
|
|
701
|
+
@Option(name: .long, help: "Path to .pages file") var file: String
|
|
702
|
+
@Option(name: .long, help: "Export format: pdf, docx, txt, epub") var format: String
|
|
703
|
+
@Option(name: .long, help: "Output file path (default: same name with new extension)") var dest: String?
|
|
704
|
+
func run() throws { PagesBridge.export(file: file, format: format, dest: dest) }
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
struct PagesInfo: ParsableCommand {
|
|
708
|
+
static let configuration = CommandConfiguration(commandName: "pages-info", abstract: "Get metadata about a Pages document.")
|
|
709
|
+
@Option(name: .long, help: "Path to .pages file") var file: String
|
|
710
|
+
func run() throws { PagesBridge.info(file: file) }
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
// MARK: - Keynote Subcommands
|
|
714
|
+
|
|
715
|
+
struct KeynoteSearch: ParsableCommand {
|
|
716
|
+
static let configuration = CommandConfiguration(commandName: "keynote-search", abstract: "Search for Keynote presentations using Spotlight.")
|
|
717
|
+
@Option(name: .long, help: "Search query") var query: String
|
|
718
|
+
@Option(name: .long, help: "Max results (default: 20)") var limit: Int = 20
|
|
719
|
+
func run() throws { KeynoteBridge.search(query: query, limit: limit) }
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
struct KeynoteRead: ParsableCommand {
|
|
723
|
+
static let configuration = CommandConfiguration(commandName: "keynote-read", abstract: "Read slide content from a Keynote presentation.")
|
|
724
|
+
@Option(name: .long, help: "Path to .key file") var file: String
|
|
725
|
+
@Option(name: .long, help: "Slide index (1-based, omit to read all)") var slide: Int?
|
|
726
|
+
func run() throws { KeynoteBridge.read(file: file, slideIndex: slide) }
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
struct KeynoteCreate: ParsableCommand {
|
|
730
|
+
static let configuration = CommandConfiguration(commandName: "keynote-create", abstract: "Create a new Keynote presentation.")
|
|
731
|
+
@Option(name: .long, help: "Output file path") var file: String
|
|
732
|
+
@Option(name: .long, help: "Theme name") var theme: String?
|
|
733
|
+
func run() throws { KeynoteBridge.create(file: file, theme: theme) }
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
struct KeynoteAddSlide: ParsableCommand {
|
|
737
|
+
static let configuration = CommandConfiguration(commandName: "keynote-add-slide", abstract: "Add a slide to a Keynote presentation.")
|
|
738
|
+
@Option(name: .long, help: "Path to .key file") var file: String
|
|
739
|
+
@Option(name: .long, help: "Slide layout name") var layout: String?
|
|
740
|
+
@Option(name: .long, help: "Slide title") var title: String?
|
|
741
|
+
@Option(name: .long, help: "Slide body text") var body: String?
|
|
742
|
+
@Option(name: .long, help: "Presenter notes") var notes: String?
|
|
743
|
+
@Option(name: .long, help: "Insert after this slide index (1-based)") var position: Int?
|
|
744
|
+
func run() throws { KeynoteBridge.addSlide(file: file, layout: layout, title: title, body: body, notes: notes, position: position) }
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
struct KeynoteEditSlide: ParsableCommand {
|
|
748
|
+
static let configuration = CommandConfiguration(commandName: "keynote-edit-slide", abstract: "Edit an existing slide in a Keynote presentation.")
|
|
749
|
+
@Option(name: .long, help: "Path to .key file") var file: String
|
|
750
|
+
@Option(name: .long, help: "Slide index (1-based)") var slide: Int
|
|
751
|
+
@Option(name: .long, help: "New title text") var title: String?
|
|
752
|
+
@Option(name: .long, help: "New body text") var body: String?
|
|
753
|
+
@Option(name: .long, help: "New presenter notes") var notes: String?
|
|
754
|
+
func run() throws { KeynoteBridge.editSlide(file: file, slideIndex: slide, title: title, body: body, notes: notes) }
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
struct KeynoteRemoveSlide: ParsableCommand {
|
|
758
|
+
static let configuration = CommandConfiguration(commandName: "keynote-remove-slide", abstract: "Remove a slide from a Keynote presentation.")
|
|
759
|
+
@Option(name: .long, help: "Path to .key file") var file: String
|
|
760
|
+
@Option(name: .long, help: "Slide index to remove (1-based)") var slide: Int
|
|
761
|
+
func run() throws { KeynoteBridge.removeSlide(file: file, slideIndex: slide) }
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
struct KeynoteReorderSlides: ParsableCommand {
|
|
765
|
+
static let configuration = CommandConfiguration(commandName: "keynote-reorder-slides", abstract: "Move a slide to a new position.")
|
|
766
|
+
@Option(name: .long, help: "Path to .key file") var file: String
|
|
767
|
+
@Option(name: .long, help: "Current slide index (1-based)") var from: Int
|
|
768
|
+
@Option(name: .long, help: "Target slide index (1-based)") var to: Int
|
|
769
|
+
func run() throws { KeynoteBridge.reorderSlides(file: file, from: from, to: to) }
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
struct KeynoteListSlides: ParsableCommand {
|
|
773
|
+
static let configuration = CommandConfiguration(commandName: "keynote-list-slides", abstract: "List all slides in a Keynote presentation.")
|
|
774
|
+
@Option(name: .long, help: "Path to .key file") var file: String
|
|
775
|
+
func run() throws { KeynoteBridge.read(file: file, slideIndex: nil) }
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
struct KeynoteListThemes: ParsableCommand {
|
|
779
|
+
static let configuration = CommandConfiguration(commandName: "keynote-list-themes", abstract: "List all available Keynote themes.")
|
|
780
|
+
func run() throws { KeynoteBridge.listThemes() }
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
struct KeynoteExport: ParsableCommand {
|
|
784
|
+
static let configuration = CommandConfiguration(commandName: "keynote-export", abstract: "Export a Keynote presentation to PDF, PowerPoint, PNG, or JPEG.")
|
|
785
|
+
@Option(name: .long, help: "Path to .key file") var file: String
|
|
786
|
+
@Option(name: .long, help: "Export format: pdf, pptx, png, jpeg") var format: String
|
|
787
|
+
@Option(name: .long, help: "Output file or directory path") var dest: String?
|
|
788
|
+
@Option(name: .long, help: "Export only this slide index (1-based, for image formats)") var slide: Int?
|
|
789
|
+
func run() throws { KeynoteBridge.export(file: file, format: format, dest: dest, slideIndex: slide) }
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
struct KeynoteInfo: ParsableCommand {
|
|
793
|
+
static let configuration = CommandConfiguration(commandName: "keynote-info", abstract: "Get metadata about a Keynote presentation.")
|
|
794
|
+
@Option(name: .long, help: "Path to .key file") var file: String
|
|
795
|
+
func run() throws { KeynoteBridge.info(file: file) }
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
// MARK: - Notes Subcommands
|
|
799
|
+
|
|
800
|
+
struct NotesListFolders: ParsableCommand {
|
|
801
|
+
static let configuration = CommandConfiguration(commandName: "notes-folders", abstract: "List all Notes folders grouped by account.")
|
|
802
|
+
func run() throws { NotesBridge.listFolders() }
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
struct NotesListNotes: ParsableCommand {
|
|
806
|
+
static let configuration = CommandConfiguration(commandName: "notes-list", abstract: "List notes, optionally filtered by folder and account.")
|
|
807
|
+
@Option(name: .long, help: "Folder name") var folder: String?
|
|
808
|
+
@Option(name: .long, help: "Account name (required when folder is ambiguous)") var account: String?
|
|
809
|
+
@Option(name: .long, help: "Max results (default: 50)") var limit: Int = 50
|
|
810
|
+
func run() throws { NotesBridge.listNotes(folder: folder, account: account, limit: limit) }
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
struct NotesSearchCmd: ParsableCommand {
|
|
814
|
+
static let configuration = CommandConfiguration(commandName: "notes-search", abstract: "Search notes by title, body, or both.")
|
|
815
|
+
@Option(name: .long, help: "Search query") var query: String
|
|
816
|
+
@Option(name: .long, help: "Fields to search: title, body, all (default: all)") var searchIn: String = "all"
|
|
817
|
+
@Option(name: .long, help: "Max results (default: 20)") var limit: Int = 20
|
|
818
|
+
func run() throws { NotesBridge.search(query: query, limit: limit, searchIn: searchIn) }
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
struct NotesRead: ParsableCommand {
|
|
822
|
+
static let configuration = CommandConfiguration(commandName: "notes-read", abstract: "Read a note's full content by ID.")
|
|
823
|
+
@Option(name: .long, help: "Note ID (from notes-list or notes-search)") var id: String
|
|
824
|
+
@Option(name: .long, help: "Max body characters (default: 8000, 0 = unlimited)") var maxBodyLength: Int = 8000
|
|
825
|
+
func run() throws { NotesBridge.readNote(id: id, maxBodyLength: maxBodyLength) }
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
// MARK: - Contacts Subcommands
|
|
829
|
+
|
|
830
|
+
struct ContactsListGroups: AsyncParsableCommand {
|
|
831
|
+
static let configuration = CommandConfiguration(commandName: "contacts-groups", abstract: "List all contact groups with member counts.")
|
|
832
|
+
func run() async throws { await ContactsBridge.listGroups() }
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
struct ContactsSearchCmd: AsyncParsableCommand {
|
|
836
|
+
static let configuration = CommandConfiguration(commandName: "contacts-search", abstract: "Search contacts by name, email, or phone.")
|
|
837
|
+
@Option(name: .long, help: "Search query") var query: String
|
|
838
|
+
@Option(name: .long, help: "Max results (default: 20)") var limit: Int = 20
|
|
839
|
+
func run() async throws { await ContactsBridge.search(query: query, limit: limit) }
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
struct ContactsRead: AsyncParsableCommand {
|
|
843
|
+
static let configuration = CommandConfiguration(commandName: "contacts-read", abstract: "Read a contact's full details by ID.")
|
|
844
|
+
@Option(name: .long, help: "Contact ID (from contacts-search)") var id: String
|
|
845
|
+
func run() async throws { await ContactsBridge.readContact(id: id) }
|
|
846
|
+
}
|