apple-mail-mcp 2.8.2 → 2.8.4

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 (65) hide show
  1. package/README.md +24 -5
  2. package/build/cli.js +12083 -138
  3. package/build/index.js +82686 -1687
  4. package/package.json +3 -4
  5. package/build/cli.d.ts +0 -24
  6. package/build/cli.d.ts.map +0 -1
  7. package/build/index.d.ts +0 -23
  8. package/build/index.d.ts.map +0 -1
  9. package/build/services/appleMailManager.d.ts +0 -680
  10. package/build/services/appleMailManager.d.ts.map +0 -1
  11. package/build/services/appleMailManager.js +0 -3143
  12. package/build/services/fileConfig.d.ts +0 -7
  13. package/build/services/fileConfig.d.ts.map +0 -1
  14. package/build/services/fileConfig.js +0 -52
  15. package/build/services/imapClient.d.ts +0 -312
  16. package/build/services/imapClient.d.ts.map +0 -1
  17. package/build/services/imapClient.js +0 -1023
  18. package/build/services/imapIdle.d.ts +0 -58
  19. package/build/services/imapIdle.d.ts.map +0 -1
  20. package/build/services/imapIdle.js +0 -151
  21. package/build/services/imapMultiAccount.d.ts +0 -124
  22. package/build/services/imapMultiAccount.d.ts.map +0 -1
  23. package/build/services/imapMultiAccount.js +0 -253
  24. package/build/services/messageRouter.d.ts +0 -24
  25. package/build/services/messageRouter.d.ts.map +0 -1
  26. package/build/services/messageRouter.js +0 -31
  27. package/build/services/replyForward.d.ts +0 -87
  28. package/build/services/replyForward.d.ts.map +0 -1
  29. package/build/services/replyForward.js +0 -150
  30. package/build/services/smtpMailer.d.ts +0 -160
  31. package/build/services/smtpMailer.d.ts.map +0 -1
  32. package/build/services/smtpMailer.js +0 -268
  33. package/build/services/templateStore.d.ts +0 -18
  34. package/build/services/templateStore.d.ts.map +0 -1
  35. package/build/services/templateStore.js +0 -91
  36. package/build/tools/doctor.d.ts +0 -23
  37. package/build/tools/doctor.d.ts.map +0 -1
  38. package/build/tools/doctor.js +0 -74
  39. package/build/tools/resourcesAndPrompts.d.ts +0 -14
  40. package/build/tools/resourcesAndPrompts.d.ts.map +0 -1
  41. package/build/tools/resourcesAndPrompts.js +0 -109
  42. package/build/tools/respond.d.ts +0 -48
  43. package/build/tools/respond.d.ts.map +0 -1
  44. package/build/tools/respond.js +0 -95
  45. package/build/tools/thread.d.ts +0 -19
  46. package/build/tools/thread.d.ts.map +0 -1
  47. package/build/tools/thread.js +0 -32
  48. package/build/types.d.ts +0 -434
  49. package/build/types.d.ts.map +0 -1
  50. package/build/types.js +0 -13
  51. package/build/utils/applescript.d.ts +0 -45
  52. package/build/utils/applescript.d.ts.map +0 -1
  53. package/build/utils/applescript.js +0 -446
  54. package/build/utils/attachmentMaterialize.d.ts +0 -9
  55. package/build/utils/attachmentMaterialize.d.ts.map +0 -1
  56. package/build/utils/attachmentMaterialize.js +0 -38
  57. package/build/utils/mimeParse.d.ts +0 -62
  58. package/build/utils/mimeParse.d.ts.map +0 -1
  59. package/build/utils/mimeParse.js +0 -317
  60. package/build/utils/orphan.d.ts +0 -25
  61. package/build/utils/orphan.d.ts.map +0 -1
  62. package/build/utils/orphan.js +0 -26
  63. package/build/utils/serialize.d.ts +0 -30
  64. package/build/utils/serialize.d.ts.map +0 -1
  65. package/build/utils/serialize.js +0 -41
@@ -1,680 +0,0 @@
1
- /**
2
- * Apple Mail Manager
3
- *
4
- * Handles all interactions with Apple Mail via AppleScript.
5
- * This is the core service layer for the MCP server.
6
- *
7
- * Architecture:
8
- * - Text escaping is handled by dedicated helper functions
9
- * - AppleScript generation uses template builders for consistency
10
- * - All public methods return typed results (no raw strings)
11
- * - Error handling is consistent across all operations
12
- *
13
- * @module services/appleMailManager
14
- */
15
- import type { Message, MessageContent, Mailbox, Account, Attachment, HealthCheckResult, MailStats, BatchOperationResult, SyncStatus, RecentlyReceivedStats, MailRule, RuleSpec, AttachmentInput, Contact, EmailTemplate, SerialEmailRecipient, SerialEmailResult, SearchDiagnostics, SearchResult } from "../types.js";
16
- /**
17
- * Merge a per-account SearchDiagnostics into an aggregate (all-accounts) one.
18
- *
19
- * Exported for unit testing.
20
- */
21
- export declare function mergeSearchDiagnostics(into: SearchDiagnostics, from: SearchDiagnostics): void;
22
- /**
23
- * Split a per-account search payload into its message-list portion and parsed
24
- * diagnostics. The AppleScript appends a trailer of the form (using the
25
- * control-character separators defined above):
26
- *
27
- * <messages>{DIAG_MARKER}timedOut=true{DIAG_FIELD_SEP}skipped=Foo (9000){DIAG_ITEM_SEP}{DIAG_FIELD_SEP}notSearched=Bar{DIAG_ITEM_SEP}
28
- *
29
- * `skipped`/`notSearched` are DIAG_ITEM_SEP-separated mailbox names, each prefixed
30
- * with the account name on the way out so the aggregate result is unambiguous.
31
- *
32
- * Exported (pure, no Mail.app dependency) for unit testing — this is the logic
33
- * that turns a swallowed timeout into a visible partial result (issue #24).
34
- */
35
- export declare function splitSearchDiagnostics(output: string, account: string): {
36
- payload: string;
37
- diagnostics: SearchDiagnostics;
38
- };
39
- /**
40
- * True if `resolvedPath` is one of the allowed roots or strictly inside one.
41
- *
42
- * Uses a path-segment boundary check rather than a bare `startsWith`, which
43
- * would let a sibling whose name merely shares the prefix slip through —
44
- * `/Volumes-evil` startsWith `/Volumes`, `/Users/robother` startsWith
45
- * `/Users/rob` (audit finding #12). `resolvedPath` must already be absolute
46
- * (caller passes `resolve(...)` output).
47
- */
48
- export declare function isPathWithinAllowedRoots(resolvedPath: string): boolean;
49
- /**
50
- * Turn a raw mailbox delete/rename failure into an actionable, non-retryable
51
- * message when it's the known server-side-mailbox limitation (#42); otherwise
52
- * return the raw error unchanged.
53
- *
54
- * Exported for unit testing.
55
- */
56
- export declare function describeMailboxOpError(op: "create" | "delete" | "rename", raw: string): string;
57
- /** Env var to pin the default account (matched by account name or email). */
58
- export declare const DEFAULT_ACCOUNT_ENV = "APPLE_MAIL_MCP_DEFAULT_ACCOUNT";
59
- /**
60
- * Choose the account to use when a tool call omits `account`.
61
- *
62
- * Priority: explicit `override` (by name or email) → Mail's default-send
63
- * account *if enabled* → first enabled account → first account → null. The key
64
- * guarantee (issue #47): a **disabled** account is never chosen implicitly — it
65
- * can only be selected via an explicit override (deliberate user intent) or as
66
- * a last resort when no account is enabled. This prevents operations silently
67
- * landing in a configured-but-disabled account (e.g. an unused iCloud account
68
- * that's still addressable via AppleScript).
69
- *
70
- * Pure/exported for unit testing.
71
- */
72
- export declare function chooseDefaultAccount(accounts: Account[], opts?: {
73
- override?: string;
74
- defaultSendEmail?: string;
75
- }): string | null;
76
- export declare function escapeForAppleScript(text: string): string;
77
- /**
78
- * Emits AppleScript that builds a date into the variable `varName` from numeric
79
- * components.
80
- *
81
- * This is locale-independent, unlike `date "May 30, 2026"` string coercion,
82
- * which AppleScript parses using the system locale. On a non-English locale
83
- * (e.g. pt_PT) the English month name throws "Invalid date and time (-30720)";
84
- * because the comparison happens inside the per-message `try` in searchMessages,
85
- * that error is swallowed and every message is skipped, so the search returns
86
- * zero results even when matches exist. See issue #15.
87
- *
88
- * `day` is reset to 1 before assigning month/year so an existing day-of-month
89
- * (e.g. 31) cannot overflow into the next month when the month is changed.
90
- *
91
- * Exported for unit testing.
92
- */
93
- export declare function buildAppleScriptDate(varName: string, d: Date): string;
94
- /**
95
- * Manager class for Apple Mail operations.
96
- *
97
- * Provides methods for:
98
- * - Reading and searching messages
99
- * - Sending emails
100
- * - Managing mailboxes
101
- * - Listing accounts
102
- *
103
- * All operations are synchronous since they rely on AppleScript
104
- * execution via osascript. Error handling is consistent: methods
105
- * return null/false/empty-array on failure rather than throwing.
106
- */
107
- export interface SearchConditionFilters {
108
- query?: string;
109
- from?: string;
110
- subject?: string;
111
- isRead?: boolean;
112
- isFlagged?: boolean;
113
- }
114
- /**
115
- * Build the AppleScript `whose` clause for searchMessages from a filter set.
116
- *
117
- * - `query` is a subject-OR-sender substring match, parenthesized so it groups
118
- * correctly when ANDed with other filters.
119
- * - `from` and `subject` are substring matches (`sender`/`subject` contains).
120
- * - `isRead` / `isFlagged` are boolean status checks.
121
- * - Returns "" when no filters are set. Every interpolated value is escaped.
122
- *
123
- * Exported for unit testing: the bug this addresses (filters declared in the
124
- * tool schema but silently dropped) lived in this logic, so it gets direct
125
- * coverage independent of Mail.app.
126
- */
127
- export declare function buildSearchCondition(filters: SearchConditionFilters): string;
128
- export declare class AppleMailManager {
129
- /**
130
- * Default account used when no account is specified.
131
- */
132
- private defaultAccount;
133
- /**
134
- * TTL cache for expensive AppleScript queries that rarely change.
135
- * Caches account list and per-account mailbox names to avoid
136
- * redundant AppleScript roundtrips on every tool call.
137
- */
138
- private cache;
139
- /** Cache TTL in milliseconds (60 seconds). */
140
- private readonly CACHE_TTL_MS;
141
- /**
142
- * Returns cached accounts or fetches fresh data if cache is expired/empty.
143
- */
144
- private getCachedAccounts;
145
- /**
146
- * Returns cached mailbox names for an account, or fetches fresh.
147
- * This caches only the name list used by resolveMailbox(), not the
148
- * full Mailbox objects with counts (which change frequently).
149
- */
150
- private getCachedMailboxNames;
151
- /**
152
- * Invalidate all caches. Call after operations that change
153
- * mailbox structure (create/delete/rename mailbox).
154
- */
155
- private invalidateCache;
156
- /**
157
- * Reads the live `enabled` flag for an account directly from Mail (bypassing
158
- * the 60 s account cache) so a guard reflects an account that was enabled or
159
- * disabled out-of-band. Returns true/false when known, or null when the probe
160
- * is inconclusive — account not found, or the probe itself failed. Callers
161
- * treat null as "can't tell, don't block".
162
- */
163
- private isAccountEnabled;
164
- /**
165
- * Reads an account's `account type` from Mail (e.g. "imap", "iCloud", "pop",
166
- * ".Mac", or "unknown" for Exchange). Returns the lowercased type string, or
167
- * null when the probe is inconclusive (account not found / probe failed).
168
- * Used to decide whether AppleScript can safely create/delete/rename a
169
- * mailbox on the account (BUG B).
170
- */
171
- private accountTypeOf;
172
- /**
173
- * True when the account stores its mailboxes server-side (IMAP / iCloud /
174
- * Exchange), so AppleScript CANNOT reliably create, delete, or rename its
175
- * folders — those ops must go through the IMAP backend. POP accounts keep
176
- * everything local, so their mailboxes ARE AppleScript-writable.
177
- *
178
- * Returns null when the type can't be determined (fail open: an inconclusive
179
- * probe should not block an operation).
180
- */
181
- private isServerSideAccount;
182
- /**
183
- * Guard for AppleScript create-mailbox on a server-side account (BUG B). When
184
- * the account stores mailboxes server-side, AppleScript can CREATE a folder
185
- * but cannot later delete or rename it — so a bare create would orphan a
186
- * mailbox the server can never remove. If IMAP is configured for the account
187
- * the tool layer routes the op to IMAP before reaching here; if it isn't, we
188
- * refuse rather than create something we can't remove. Returns an error string
189
- * when the op must be refused, else null (POP / local / indeterminate accounts
190
- * fall through to AppleScript).
191
- */
192
- private serverSideCreateGuard;
193
- /**
194
- * Guard for AppleScript-backed structural operations (create / delete / rename
195
- * mailbox). When the target account is disabled in Mail, Mail holds no live
196
- * server session for it, so the operation fails inside Mail with an opaque
197
- * AppleEvent -10000 — and a multi-step op like rename can leave half-built
198
- * state behind (an orphaned destination mailbox). Detect the disabled account
199
- * up front and refuse with an actionable message instead of attempting the
200
- * doomed op.
201
- *
202
- * Returns an error string when the account is known-disabled, else null —
203
- * including when the state can't be determined. We fail open: an inconclusive
204
- * probe never blocks an otherwise-valid operation.
205
- *
206
- * Applies only to the AppleScript backend. Direct-IMAP accounts talk to the
207
- * server independent of Mail's enabled toggle and are routed before reaching
208
- * the manager.
209
- */
210
- private disabledAccountGuard;
211
- /**
212
- * Best-effort rollback for a failed rename: delete a just-created destination
213
- * mailbox, but ONLY if it is empty, so any messages that did move are never
214
- * destroyed. Returns true if the empty orphan was removed.
215
- */
216
- private deleteMailboxIfEmpty;
217
- /**
218
- * Resolves the account to use for an operation when the caller omits one.
219
- *
220
- * Order (see chooseDefaultAccount): the APPLE_MAIL_MCP_DEFAULT_ACCOUNT env
221
- * override → Mail.app's configured default-send account (if enabled) → the
222
- * first enabled account. A disabled account is never chosen implicitly (#47).
223
- */
224
- private resolveAccount;
225
- /**
226
- * Resolves a mailbox name to its actual name in the account.
227
- *
228
- * Different account types (IMAP, Exchange, iCloud) use different
229
- * mailbox naming conventions:
230
- * - IMAP/Gmail: "INBOX", "Sent", "Drafts"
231
- * - Exchange: "Inbox", "Sent Items", "Deleted Items"
232
- * - iCloud: "INBOX", "Sent", "Trash"
233
- *
234
- * This method tries to find a matching mailbox by:
235
- * 1. Exact match
236
- * 2. Case-insensitive match
237
- * 3. Known aliases (e.g., "Sent" -> "Sent Items")
238
- *
239
- * @param mailbox - Requested mailbox name
240
- * @param account - Account to search in
241
- * @returns Actual mailbox name, or original if not found
242
- */
243
- private resolveMailbox;
244
- /**
245
- * Search for messages matching criteria.
246
- *
247
- * @param query - Text to search for in subject or sender
248
- * @param mailbox - Mailbox to search in (e.g., "INBOX")
249
- * @param account - Account to search in
250
- * @param limit - Maximum number of results
251
- * @returns Array of matching messages
252
- */
253
- searchMessages(query?: string, mailbox?: string, account?: string, limit?: number, dateFrom?: string, dateTo?: string, from?: string, subject?: string, isRead?: boolean, isFlagged?: boolean): Message[];
254
- /**
255
- * Search for messages, returning both the matches and diagnostics describing
256
- * how complete the search was.
257
- *
258
- * This is the correctness fix for issue #24. The previous implementation ran
259
- * an unbounded `messages of mb whose <predicate>` over every mailbox in an
260
- * account; on large IMAP/Gmail mailboxes (tens of thousands of messages) that
261
- * single Apple Event exceeded the timeout, the error was swallowed by a `try`,
262
- * and the function returned a clean — but wrong — empty result. Callers/agents
263
- * then confidently reported "no such mail."
264
- *
265
- * Two changes fix that:
266
- * 1. Cheap count-guard: mailboxes larger than the scan threshold are skipped
267
- * (Apple Mail can't search them before timing out anyway) and reported.
268
- * 2. Honest diagnostics: per-account/per-mailbox timeouts are surfaced as a
269
- * `partial` result with the affected scopes named, instead of an empty
270
- * "success."
271
- */
272
- searchMessagesWithDiagnostics(query?: string, mailbox?: string, account?: string, limit?: number, dateFrom?: string, dateTo?: string, from?: string, subject?: string, isRead?: boolean, isFlagged?: boolean): SearchResult;
273
- /**
274
- * Split a per-account search payload into its message list and the DIAG
275
- * trailer, parse both, and return a SearchResult. See searchMessagesWithDiagnostics.
276
- */
277
- private parseSearchResult;
278
- /**
279
- * Get a message by ID.
280
- *
281
- * Note: Mail.app message IDs are unique per mailbox. This method searches
282
- * all mailboxes in all accounts to find the message.
283
- */
284
- getMessageById(id: string, deepAttachmentCheck?: boolean): Message | null;
285
- /**
286
- * Get the content of a message.
287
- *
288
- * @param id - Message ID
289
- * @param includeHtml - When true, also fetch the raw MIME source and extract
290
- * the `text/html` body part into `htmlContent`. This is opt-in because the
291
- * source can be MB-sized (it includes base64 attachments) and the plain-text
292
- * path doesn't need it; fetching it unconditionally was both slow and, worse,
293
- * returned the entire raw MIME blob mislabeled as HTML (#32).
294
- */
295
- getMessageContent(id: string, includeHtml?: boolean): MessageContent | null;
296
- /**
297
- * Get the raw MIME source of a message.
298
- * Used as fallback for attachment extraction when AppleScript
299
- * mail attachments returns empty.
300
- *
301
- * Timeout is 2x the default (120s) because `source of msg` returns
302
- * the entire raw message including base64-encoded attachments —
303
- * a 20MB attachment can take several seconds over Exchange/IMAP.
304
- */
305
- getRawSource(id: string): string | null;
306
- /**
307
- * List messages in a mailbox.
308
- *
309
- * @param mailbox - Mailbox to list from (default: INBOX)
310
- * @param account - Account to list from
311
- * @param limit - Maximum number of messages
312
- * @returns Array of messages
313
- */
314
- listMessages(mailbox?: string, account?: string, limit?: number, from?: string, offset?: number): Message[];
315
- /**
316
- * List messages, returning matches plus coverage diagnostics.
317
- *
318
- * Like `searchMessages`, the unscoped (all-mailboxes) path used to iterate
319
- * `messages of mb` over every mailbox with a swallowing per-mailbox `try`,
320
- * so a large IMAP/Gmail mailbox timed out and the method returned `[]` — a
321
- * false "No messages found." This applies the same #24 discipline: skip
322
- * mailboxes above the scan threshold (reported), enforce a per-account
323
- * wall-clock budget, capture per-mailbox timeouts, and surface all of it as a
324
- * partial result. (by-id lookups don't need this — `whose id is` is indexed
325
- * and returns instantly even on a 44k-message mailbox.)
326
- */
327
- listMessagesWithDiagnostics(mailbox?: string, account?: string, limit?: number, from?: string, offset?: number): SearchResult;
328
- /**
329
- * Parse message list output from AppleScript.
330
- *
331
- * Two emission schemas, disambiguated by length:
332
- * 7 fields: single-mailbox — ...|hasAtt (mailbox from caller)
333
- * 8 fields: all-mailboxes — ...|mailbox|hasAtt
334
- *
335
- * `hasAttachments` here is the fast-path AppleScript count only; it will
336
- * false-negative for MIME-embedded attachments (a known AppleScript
337
- * limitation). Use getMessage or list-attachments for authoritative info.
338
- */
339
- private parseMessageList;
340
- /**
341
- * Send an email.
342
- *
343
- * @param to - Recipient email addresses
344
- * @param subject - Email subject
345
- * @param body - Email body (plain text)
346
- * @param cc - CC recipients
347
- * @param bcc - BCC recipients
348
- * @param account - Account to send from
349
- * @returns true if sent successfully
350
- */
351
- sendEmail(to: string[], subject: string, body: string, cc?: string[], bcc?: string[], account?: string, attachments?: AttachmentInput[]): boolean;
352
- private sendEmailWithPaths;
353
- /**
354
- * Send individual personalized emails to a list of recipients (mail merge).
355
- *
356
- * Replaces {{placeholder}} tokens in subject and body with per-recipient values.
357
- * Each recipient receives their own individual email.
358
- *
359
- * @param recipients - List of recipient objects with email and variable values
360
- * @param subject - Email subject (may contain {{placeholders}})
361
- * @param body - Email body (may contain {{placeholders}})
362
- * @param account - Account to send from
363
- * @param delayMs - Delay between sends in milliseconds (default: 500, max: 10000)
364
- * @returns Array of per-recipient results
365
- */
366
- sendSerialEmail(recipients: SerialEmailRecipient[], subject: string, body: string, account?: string, delayMs?: number): SerialEmailResult[];
367
- /**
368
- * Create a draft email (saved to Drafts folder, not sent).
369
- *
370
- * @param to - Recipient email addresses
371
- * @param subject - Email subject
372
- * @param body - Email body (plain text)
373
- * @param cc - CC recipients
374
- * @param bcc - BCC recipients
375
- * @param account - Account to create draft in
376
- * @returns true if draft created successfully
377
- */
378
- createDraft(to: string[], subject: string, body: string, cc?: string[], bcc?: string[], account?: string, attachments?: AttachmentInput[]): boolean;
379
- private createDraftWithCommands;
380
- /**
381
- * Reply to a message.
382
- *
383
- * @param id - Message ID to reply to
384
- * @param body - Reply body
385
- * @param replyAll - If true, reply to all recipients
386
- * @param send - If true, send immediately; if false, save as draft
387
- * @returns true if reply created/sent successfully
388
- */
389
- replyToMessage(id: string, body: string, replyAll?: boolean, send?: boolean): boolean;
390
- /**
391
- * Forward a message.
392
- *
393
- * @param id - Message ID to forward
394
- * @param to - Recipients to forward to
395
- * @param body - Optional body to prepend
396
- * @param send - If true, send immediately; if false, save as draft
397
- * @returns true if forward created/sent successfully
398
- */
399
- forwardMessage(id: string, to: string[], body?: string, send?: boolean): boolean;
400
- /**
401
- * Helper to find and operate on a message by ID.
402
- */
403
- private findMessageScript;
404
- /**
405
- * Mark a message as read.
406
- */
407
- markAsRead(id: string): boolean;
408
- /**
409
- * Mark a message as unread.
410
- */
411
- markAsUnread(id: string): boolean;
412
- /**
413
- * AppleScript statement(s) to flag a message variable, optionally setting its
414
- * color. `colorIndex` is Apple's flag-index palette (0 red, 1 orange,
415
- * 2 yellow, 3 green, 4 blue, 5 purple, 6 gray); it is validated to 0-6 by the
416
- * schema layer and is a number, so it is safe to interpolate. Omitting it
417
- * applies Mail's default flag without touching the color.
418
- */
419
- private flagOperation;
420
- /**
421
- * Flag a message, optionally with a color (see {@link flagOperation}).
422
- */
423
- flagMessage(id: string, colorIndex?: number): boolean;
424
- /**
425
- * Resolve a message's numeric Mail.app id from its RFC822 Message-ID (the
426
- * backend-independent join key). This bridges an `imap:` id to the numeric id
427
- * required to apply a flag *color* — IMAP flags are colorless, so a smart
428
- * mailbox keyed on flag color can only ever match a message flagged via the
429
- * AppleScript numeric-id path.
430
- *
431
- * The Message-ID is matched both bracketless and `<bracketed>` (Mail returns
432
- * it bracketless; IMAP envelopes carry the brackets). When `accountName` is
433
- * given the search is scoped to that account, checking its INBOX first (swept
434
- * messages live there) to avoid scanning huge All Mail/Archive mailboxes.
435
- *
436
- * @returns the numeric id as a string, or null if no message matches.
437
- */
438
- findNumericIdByMessageId(messageId: string, accountName?: string): string | null;
439
- /**
440
- * Unflag a message.
441
- */
442
- unflagMessage(id: string): boolean;
443
- /**
444
- * Delete a message.
445
- */
446
- deleteMessage(id: string): {
447
- success: boolean;
448
- error?: string;
449
- };
450
- /**
451
- * Classify a failed message mutation (delete/move) into an actionable error.
452
- *
453
- * Mail.app's scripting bridge cannot delete or move drafts, and cannot mutate
454
- * messages in some server-side special mailboxes — it throws `AppleEvent
455
- * handler failed` rather than a useful message (#42). When that pattern is
456
- * seen, look up the message's mailbox (cheap, indexed `whose id is`) to give a
457
- * draft-specific or server-specific hint. Other errors (e.g. "Message not
458
- * found", "ambiguous destination") pass through unchanged.
459
- */
460
- private classifyMessageMutationError;
461
- /**
462
- * Move a message to a different mailbox.
463
- */
464
- /**
465
- * Move a message to a destination mailbox, with full nested-mailbox support.
466
- *
467
- * Resolving the destination as `mailbox "X" of account "Y"` only finds
468
- * top-level mailboxes, so nested destinations (e.g. a "Moore" subfolder)
469
- * silently failed. Instead we walk the target account's full mailbox tree and
470
- * match by name. Resolution is:
471
- * - account-scoped (won't move to a same-named mailbox in another account)
472
- * - ambiguity-aware: if the name matches more than one mailbox in the
473
- * account we refuse to guess and return an error — silently moving mail to
474
- * the wrong folder is worse than failing.
475
- * The source message is located by walking every account's tree breadth-first
476
- * (top-level mailboxes like Inbox are checked first), so messages in nested
477
- * mailboxes are found too.
478
- *
479
- * Returns a result object so batch callers can surface the specific failure
480
- * (destination not found / ambiguous / message not found).
481
- */
482
- private moveMessageInternal;
483
- moveMessage(id: string, mailbox: string, account?: string): {
484
- success: boolean;
485
- error?: string;
486
- };
487
- /**
488
- * Run one operation over many message IDs in a SINGLE osascript invocation.
489
- *
490
- * Previously each batch method looped and called the per-id method, so a
491
- * 100-id batch spawned 100 osascript processes — each one re-resolving
492
- * accounts and walking the whole account→mailbox tree — all serialized
493
- * through the gate (issue #31). This walks the tree exactly once: for each
494
- * mailbox it probes the still-pending IDs with `whose id is` (indexed, so
495
- * effectively free) and applies `operation` to any match, tracking found IDs
496
- * so it can stop early once all are accounted for. Per-id outcomes come back
497
- * as control-char-delimited `id<FS>status` records (status: `ok`,
498
- * `notfound`, or `error:<msg>`), and results are returned in input order.
499
- *
500
- * `setup` runs once before the walk (used by move to resolve the destination);
501
- * it may bail the whole batch by returning a `BATCH_FATAL`-prefixed string.
502
- */
503
- private runBatchOperation;
504
- /**
505
- * Delete multiple messages at once (single tree walk — see runBatchOperation).
506
- */
507
- batchDeleteMessages(ids: string[]): BatchOperationResult[];
508
- /**
509
- * Move multiple messages to a mailbox at once (single tree walk).
510
- *
511
- * The destination is resolved once (account-scoped, ambiguity-aware — a name
512
- * matching more than one mailbox fails the whole batch rather than guessing),
513
- * then every matched message is moved in the same walk.
514
- */
515
- batchMoveMessages(ids: string[], mailbox: string, account?: string): BatchOperationResult[];
516
- /**
517
- * Mark multiple messages as read at once (single tree walk).
518
- */
519
- batchMarkAsRead(ids: string[]): BatchOperationResult[];
520
- /**
521
- * Mark multiple messages as unread at once (single tree walk).
522
- */
523
- batchMarkAsUnread(ids: string[]): BatchOperationResult[];
524
- /**
525
- * Flag multiple messages at once (single tree walk).
526
- */
527
- batchFlagMessages(ids: string[], colorIndex?: number): BatchOperationResult[];
528
- /**
529
- * Unflag multiple messages at once (single tree walk).
530
- */
531
- batchUnflagMessages(ids: string[]): BatchOperationResult[];
532
- /**
533
- * List attachments for a message.
534
- * Tries AppleScript first, falls back to MIME source parsing
535
- * when AppleScript returns empty (known issue across all account types).
536
- */
537
- listAttachments(id: string): Attachment[];
538
- /**
539
- * Save an attachment from a message to disk.
540
- * Tries AppleScript first, falls back to MIME source extraction
541
- * when AppleScript can't find the attachment.
542
- */
543
- saveAttachment(id: string, attachmentName: string, savePath: string): boolean;
544
- /**
545
- * Fetch an attachment's bytes as base64 (B4) — the read counterpart to
546
- * sending inline base64 content. Reuses saveAttachment via a throwaway temp
547
- * dir (under an allowed root), then reads and encodes the file.
548
- */
549
- getAttachmentBase64(id: string, attachmentName: string): {
550
- success: boolean;
551
- base64?: string;
552
- bytes?: number;
553
- error?: string;
554
- };
555
- /**
556
- * List all mailboxes for an account.
557
- */
558
- listMailboxes(account?: string): Mailbox[];
559
- /**
560
- * Get unread count for a mailbox.
561
- */
562
- getUnreadCount(mailbox?: string, account?: string): number;
563
- /**
564
- * Create a new mailbox.
565
- */
566
- createMailbox(name: string, account?: string): {
567
- success: boolean;
568
- error?: string;
569
- };
570
- /**
571
- * Delete a mailbox.
572
- */
573
- deleteMailbox(name: string, account?: string): {
574
- success: boolean;
575
- error?: string;
576
- };
577
- /**
578
- * Rename a mailbox by creating a new one, moving messages, and deleting the old one.
579
- */
580
- renameMailbox(oldName: string, newName: string, account?: string): {
581
- success: boolean;
582
- error?: string;
583
- };
584
- /**
585
- * List all mail accounts (uses cache).
586
- */
587
- listAccounts(): Account[];
588
- /**
589
- * Fetches account list directly from Mail.app via AppleScript.
590
- * Used internally by the cache; prefer getCachedAccounts() or listAccounts().
591
- */
592
- private fetchAccounts;
593
- /**
594
- * Fetches mailbox names for an account directly from Mail.app.
595
- * Used internally by the cache; prefer getCachedMailboxNames().
596
- */
597
- private fetchMailboxNames;
598
- /**
599
- * List all mail rules.
600
- */
601
- listRules(): MailRule[];
602
- /**
603
- * Enable or disable a mail rule.
604
- */
605
- setRuleEnabled(ruleName: string, enabled: boolean): boolean;
606
- /**
607
- * Create a mail rule (B2). Builds conditions (from/to/cc/subject/content with
608
- * a match operator) and actions (mark read/flagged, delete, move to a
609
- * mailbox) on a real Mail.app rule. Returns an error string on failure.
610
- */
611
- createRule(opts: RuleSpec): {
612
- success: boolean;
613
- error?: string;
614
- };
615
- /**
616
- * Delete a mail rule by name (B2). Returns false if no such rule exists.
617
- */
618
- deleteRule(ruleName: string): boolean;
619
- /**
620
- * Search contacts by name or email.
621
- */
622
- searchContacts(query: string): Contact[];
623
- private templateStore;
624
- /**
625
- * List all stored templates.
626
- */
627
- listTemplates(): EmailTemplate[];
628
- /**
629
- * Get a template by ID.
630
- */
631
- getTemplate(id: string): EmailTemplate | null;
632
- /**
633
- * Create or update a template (persisted).
634
- */
635
- saveTemplate(name: string, subject: string, body: string, to?: string[], cc?: string[], id?: string): EmailTemplate;
636
- /**
637
- * Delete a template (persisted).
638
- */
639
- deleteTemplate(id: string): boolean;
640
- /**
641
- * Use a template to create a draft.
642
- */
643
- useTemplate(id: string, overrides?: {
644
- to?: string[];
645
- cc?: string[];
646
- subject?: string;
647
- body?: string;
648
- }): boolean;
649
- /**
650
- * Run health check on Mail.app connectivity.
651
- */
652
- healthCheck(): HealthCheckResult;
653
- /**
654
- * Get mail statistics.
655
- */
656
- getMailStats(): MailStats;
657
- /**
658
- * Get counts of recently received messages.
659
- *
660
- * Counts messages in each account's receiving mailbox for performance
661
- * (scanning all mailboxes is too slow for large accounts): the literal
662
- * "INBOX" for ordinary accounts, or the "All Mail" superset for Gmail-style
663
- * accounts whose literal "INBOX" is an empty virtual shell (BUG A2).
664
- *
665
- * @returns Counts of messages received in last 24h, 7d, and 30d
666
- */
667
- getRecentlyReceivedStats(): RecentlyReceivedStats;
668
- /**
669
- * Get sync status for Mail.app.
670
- *
671
- * Checks for sync activity indicators like:
672
- * - Activity monitor status
673
- * - Network activity status
674
- * - Background refresh indicators
675
- *
676
- * @returns Sync status information
677
- */
678
- getSyncStatus(): SyncStatus;
679
- }
680
- //# sourceMappingURL=appleMailManager.d.ts.map