apple-mail-mcp 2.15.0 → 2.16.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 +5 -0
- package/build/index.js +86 -22
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -953,6 +953,11 @@ accounts only: the local store is a store, not an account. Nothing selects it
|
|
|
953
953
|
implicitly — omitting `account` still resolves to a real account for every other
|
|
954
954
|
tool.
|
|
955
955
|
|
|
956
|
+
The mail inside them is reachable too: `list-messages` and `search-messages`
|
|
957
|
+
accept `account="On My Mac"`, and `get-message` resolves an id that lives only in
|
|
958
|
+
a local mailbox. An id present **both** in an account mailbox and locally is
|
|
959
|
+
reported as ambiguous rather than silently resolving to the account copy.
|
|
960
|
+
|
|
956
961
|
---
|
|
957
962
|
|
|
958
963
|
#### `get-unread-count`
|
package/build/index.js
CHANGED
|
@@ -78980,7 +78980,7 @@ function describeMailboxOpError(op, raw) {
|
|
|
78980
78980
|
const trimmed = (raw || "").trim();
|
|
78981
78981
|
if (UNSUPPORTED_APPLESCRIPT_OP.test(trimmed)) {
|
|
78982
78982
|
const verb = op.charAt(0).toUpperCase() + op.slice(1);
|
|
78983
|
-
return `Mail.app
|
|
78983
|
+
return `Mail.app's scripting bridge will not ${op} this mailbox. That covers server-side (IMAP / Gmail / Workspace / iCloud / Exchange) mailboxes, and on current macOS it covers local "On My Mac" mailboxes too \u2014 measured 2026-08-16, see #193. ${verb} it in Mail.app directly; for an IMAP-configured account the IMAP path can do it instead. (Mail.app error: ${trimmed})`;
|
|
78984
78984
|
}
|
|
78985
78985
|
return trimmed || `Failed to ${op} mailbox`;
|
|
78986
78986
|
}
|
|
@@ -79170,6 +79170,26 @@ var COUNT_PARTIAL_NOTE = `Mail's count moved by less than this operation account
|
|
|
79170
79170
|
function snapshotKey(entry) {
|
|
79171
79171
|
return `${entry.id}\0${entry.messageId}`;
|
|
79172
79172
|
}
|
|
79173
|
+
function crossCheckRenumbered(disappeared, appeared) {
|
|
79174
|
+
const index = (entries) => {
|
|
79175
|
+
const m = /* @__PURE__ */ new Map();
|
|
79176
|
+
for (const e of entries) {
|
|
79177
|
+
if (!e.messageId) continue;
|
|
79178
|
+
m.set(e.messageId, m.has(e.messageId) ? null : e);
|
|
79179
|
+
}
|
|
79180
|
+
return m;
|
|
79181
|
+
};
|
|
79182
|
+
const gone = index(disappeared);
|
|
79183
|
+
const came = index(appeared);
|
|
79184
|
+
const out = [];
|
|
79185
|
+
for (const [mid, before] of gone) {
|
|
79186
|
+
const after = came.get(mid);
|
|
79187
|
+
if (!before || !after) continue;
|
|
79188
|
+
if (before.id === after.id) continue;
|
|
79189
|
+
out.push({ messageId: mid, before: before.id, after: after.id });
|
|
79190
|
+
}
|
|
79191
|
+
return out;
|
|
79192
|
+
}
|
|
79173
79193
|
function buildAppLevelScript(command) {
|
|
79174
79194
|
return `
|
|
79175
79195
|
tell application "Mail"
|
|
@@ -79845,8 +79865,12 @@ ${indent}end try${this.sanitizeFragment("_uacct", indent)}${this.sanitizeFragmen
|
|
|
79845
79865
|
const afterEntries = this.parseSnapshot(a.payload);
|
|
79846
79866
|
const afterKeys = new Set(afterEntries.map((e) => snapshotKey(e)));
|
|
79847
79867
|
const beforeKeys = new Set(beforeEntries.map((e) => snapshotKey(e)));
|
|
79848
|
-
const
|
|
79849
|
-
const
|
|
79868
|
+
const rawDisappeared = beforeEntries.filter((e) => !afterKeys.has(snapshotKey(e)));
|
|
79869
|
+
const rawAppeared = afterEntries.filter((e) => !beforeKeys.has(snapshotKey(e)));
|
|
79870
|
+
const renumbered = crossCheckRenumbered(rawDisappeared, rawAppeared);
|
|
79871
|
+
const renumberedMids = new Set(renumbered.map((r) => r.messageId));
|
|
79872
|
+
const disappeared = rawDisappeared.filter((e) => !renumberedMids.has(e.messageId));
|
|
79873
|
+
const appeared = rawAppeared.filter((e) => !renumberedMids.has(e.messageId));
|
|
79850
79874
|
const unrequested = disappeared.filter(
|
|
79851
79875
|
(e) => !requestedNumericIds.has(canonicalNumericId(e.id))
|
|
79852
79876
|
);
|
|
@@ -79860,7 +79884,8 @@ ${indent}end try${this.sanitizeFragment("_uacct", indent)}${this.sanitizeFragmen
|
|
|
79860
79884
|
disappeared,
|
|
79861
79885
|
unrequested,
|
|
79862
79886
|
appeared,
|
|
79863
|
-
...countStale.length ? { countStale } : {}
|
|
79887
|
+
...countStale.length ? { countStale } : {},
|
|
79888
|
+
...renumbered.length ? { renumbered } : {}
|
|
79864
79889
|
});
|
|
79865
79890
|
continue;
|
|
79866
79891
|
}
|
|
@@ -79873,6 +79898,7 @@ ${indent}end try${this.sanitizeFragment("_uacct", indent)}${this.sanitizeFragmen
|
|
|
79873
79898
|
mailbox: g.mailbox,
|
|
79874
79899
|
snapshot: "partial",
|
|
79875
79900
|
...countStale.length ? { countStale } : {},
|
|
79901
|
+
...renumbered.length ? { renumbered } : {},
|
|
79876
79902
|
skipReason: `Mail would not read ${holes.map((h) => `${h.ranges} (${h.phase})`).join(", ")} of this mailbox, so the snapshot has a hole in it. ` + (derivable.length > 0 ? `Still derivable and reported: ${derivable.join(" and ")}. ` : `Neither half of the diff is derivable from it. `) + `Anything the unread range could refute is omitted rather than guessed \u2014 an absent field here means "not computable", not "empty".`,
|
|
79877
79903
|
unobserved: holes,
|
|
79878
79904
|
...a.miss === "" ? { disappeared, unrequested } : {},
|
|
@@ -80201,7 +80227,9 @@ ${indent}end try${this.sanitizeFragment("_uacct", indent)}${this.sanitizeFragmen
|
|
|
80201
80227
|
}
|
|
80202
80228
|
return { messages: allMessages.slice(0, limit), diagnostics };
|
|
80203
80229
|
}
|
|
80204
|
-
const
|
|
80230
|
+
const local = isLocalStoreLabel(account);
|
|
80231
|
+
const targetAccount = local ? LOCAL_STORE_LABEL : this.resolveAccount(account);
|
|
80232
|
+
const mbIter = local ? "_mbs" : "mailboxes";
|
|
80205
80233
|
const searchCondition = buildSearchCondition({ query, from, subject, isRead, isFlagged });
|
|
80206
80234
|
let dateSetup = "";
|
|
80207
80235
|
let dateFilter = "";
|
|
@@ -80233,7 +80261,7 @@ ${indent}end try${this.sanitizeFragment("_uacct", indent)}${this.sanitizeFragmen
|
|
|
80233
80261
|
set _wantNames to ${nameList}
|
|
80234
80262
|
set msgCount to 0
|
|
80235
80263
|
set seenIds to {}
|
|
80236
|
-
repeat with mb in
|
|
80264
|
+
repeat with mb in ${mbIter}
|
|
80237
80265
|
if msgCount >= ${limit} then exit repeat
|
|
80238
80266
|
set mbName to ""
|
|
80239
80267
|
try
|
|
@@ -80278,7 +80306,7 @@ ${indent}end try${this.sanitizeFragment("_uacct", indent)}${this.sanitizeFragmen
|
|
|
80278
80306
|
set _skipped to ""
|
|
80279
80307
|
set _notSearched to ""
|
|
80280
80308
|
set _startedAt to current date
|
|
80281
|
-
repeat with mb in
|
|
80309
|
+
repeat with mb in ${mbIter}
|
|
80282
80310
|
if msgCount >= ${limit} then exit repeat
|
|
80283
80311
|
set mbName to ""
|
|
80284
80312
|
try
|
|
@@ -80308,7 +80336,7 @@ ${indent}end try${this.sanitizeFragment("_uacct", indent)}${this.sanitizeFragmen
|
|
|
80308
80336
|
return outputText & "${DIAG_MARKER}timedOut=" & (_timedOut as string) & "${DIAG_FIELD_SEP}skipped=" & _skipped & "${DIAG_FIELD_SEP}notSearched=" & _notSearched
|
|
80309
80337
|
`;
|
|
80310
80338
|
}
|
|
80311
|
-
const script = buildAccountScopedScript(targetAccount, searchCommand);
|
|
80339
|
+
const script = local ? buildAppLevelScript(`${localMailboxBindingFragment()}${searchCommand}`) : buildAccountScopedScript(targetAccount, searchCommand);
|
|
80312
80340
|
const result = executeAppleScript(script, { timeoutMs: SEARCH_ACCOUNT_TIMEOUT_MS });
|
|
80313
80341
|
if (!result.success) {
|
|
80314
80342
|
console.error(`Failed to search messages in "${targetAccount}": ${result.error}`);
|
|
@@ -80419,9 +80447,16 @@ ${indent}end try${this.sanitizeFragment("_uacct", indent)}${this.sanitizeFragmen
|
|
|
80419
80447
|
*/
|
|
80420
80448
|
scopedByIdScript(account, mailbox, id, innerAction) {
|
|
80421
80449
|
const resolved = this.resolveMailbox(mailbox, account);
|
|
80422
|
-
|
|
80423
|
-
|
|
80424
|
-
|
|
80450
|
+
const bind = isLocalStoreLabel(account) ? `${localMailboxBindingFragment()}
|
|
80451
|
+
set targetMb to missing value
|
|
80452
|
+
ignoring case
|
|
80453
|
+
repeat with mb in _mbs
|
|
80454
|
+
if (name of mb) is "${escapeForAppleScript(resolved)}" then
|
|
80455
|
+
set targetMb to mb
|
|
80456
|
+
exit repeat
|
|
80457
|
+
end if
|
|
80458
|
+
end repeat
|
|
80459
|
+
end ignoring` : `set acct to (first account whose name is "${escapeForAppleScript(account)}")
|
|
80425
80460
|
set targetMb to missing value
|
|
80426
80461
|
ignoring case
|
|
80427
80462
|
repeat with mb in mailboxes of acct
|
|
@@ -80430,7 +80465,10 @@ ${indent}end try${this.sanitizeFragment("_uacct", indent)}${this.sanitizeFragmen
|
|
|
80430
80465
|
exit repeat
|
|
80431
80466
|
end if
|
|
80432
80467
|
end repeat
|
|
80433
|
-
end ignoring
|
|
80468
|
+
end ignoring`;
|
|
80469
|
+
return buildAppLevelScript(`
|
|
80470
|
+
try
|
|
80471
|
+
${bind}
|
|
80434
80472
|
if targetMb is not missing value then
|
|
80435
80473
|
set matchingMsgs to (messages of targetMb whose id is ${Number(id)})
|
|
80436
80474
|
if (count of matchingMsgs) > 0 then
|
|
@@ -80494,6 +80532,19 @@ ${indent}end try${this.sanitizeFragment("_uacct", indent)}${this.sanitizeFragmen
|
|
|
80494
80532
|
end try
|
|
80495
80533
|
end repeat
|
|
80496
80534
|
end repeat
|
|
80535
|
+
-- #183: local mailboxes belong to no account, so the walk above cannot
|
|
80536
|
+
-- reach them. Collect into the SAME _hits/_names, which means an id
|
|
80537
|
+
-- present both in an account and locally is now correctly reported as
|
|
80538
|
+
-- ambiguous rather than silently resolving to the account copy.${localMailboxBindingFragment()}
|
|
80539
|
+
repeat with mb in _mbs
|
|
80540
|
+
try
|
|
80541
|
+
set matchingMsgs to (messages of mb whose id is ${Number(id)})
|
|
80542
|
+
if (count of matchingMsgs) > 0 then
|
|
80543
|
+
set end of _hits to item 1 of matchingMsgs
|
|
80544
|
+
set _names to _names & "${LOCAL_STORE_LABEL}/" & (name of mb) & ", "
|
|
80545
|
+
end if
|
|
80546
|
+
end try
|
|
80547
|
+
end repeat
|
|
80497
80548
|
if (count of _hits) is 0 then return "${LOOKUP_ERROR_MARKER}Message not found"
|
|
80498
80549
|
if (count of _hits) > 1 then return "${LOOKUP_ERROR_MARKER}${AMBIGUOUS_ID_PREFIX}${Number(id)} is present in more than one mailbox (" & _names & "); list or search that mailbox first so the read targets the right copy"
|
|
80499
80550
|
if (count of _hits) is 1 then
|
|
@@ -80584,6 +80635,19 @@ ${indent}end try${this.sanitizeFragment("_uacct", indent)}${this.sanitizeFragmen
|
|
|
80584
80635
|
end try
|
|
80585
80636
|
end repeat
|
|
80586
80637
|
end repeat
|
|
80638
|
+
-- #183: local mailboxes belong to no account, so the walk above cannot
|
|
80639
|
+
-- reach them. Collect into the SAME _hits/_names, which means an id
|
|
80640
|
+
-- present both in an account and locally is now correctly reported as
|
|
80641
|
+
-- ambiguous rather than silently resolving to the account copy.${localMailboxBindingFragment()}
|
|
80642
|
+
repeat with mb in _mbs
|
|
80643
|
+
try
|
|
80644
|
+
set matchingMsgs to (messages of mb whose id is ${Number(id)})
|
|
80645
|
+
if (count of matchingMsgs) > 0 then
|
|
80646
|
+
set end of _hits to item 1 of matchingMsgs
|
|
80647
|
+
set _names to _names & "${LOCAL_STORE_LABEL}/" & (name of mb) & ", "
|
|
80648
|
+
end if
|
|
80649
|
+
end try
|
|
80650
|
+
end repeat
|
|
80587
80651
|
if (count of _hits) is 0 then return "${LOOKUP_ERROR_MARKER}Message not found"
|
|
80588
80652
|
if (count of _hits) > 1 then return "${LOOKUP_ERROR_MARKER}${AMBIGUOUS_ID_PREFIX}${Number(id)} is present in more than one mailbox (" & _names & "); list or search that mailbox first so the read targets the right copy"
|
|
80589
80653
|
if (count of _hits) is 1 then
|
|
@@ -80647,10 +80711,12 @@ ${indent}end try${this.sanitizeFragment("_uacct", indent)}${this.sanitizeFragmen
|
|
|
80647
80711
|
}
|
|
80648
80712
|
return { messages: allMessages.slice(0, limit), diagnostics };
|
|
80649
80713
|
}
|
|
80650
|
-
const
|
|
80714
|
+
const local = isLocalStoreLabel(account);
|
|
80715
|
+
const targetAccount = local ? LOCAL_STORE_LABEL : this.resolveAccount(account);
|
|
80651
80716
|
const safeFrom = from ? escapeForAppleScript(from) : "";
|
|
80652
80717
|
const fromFilter = from ? `whose sender contains "${safeFrom}"` : "";
|
|
80653
80718
|
const scanThreshold = getMailboxScanThreshold();
|
|
80719
|
+
const mbIter = local ? "_mbs" : "mailboxes";
|
|
80654
80720
|
let listCommand;
|
|
80655
80721
|
if (mailbox) {
|
|
80656
80722
|
const targetMailbox = this.resolveMailbox(mailbox, targetAccount);
|
|
@@ -80665,7 +80731,7 @@ ${indent}end try${this.sanitizeFragment("_uacct", indent)}${this.sanitizeFragmen
|
|
|
80665
80731
|
set msgCount to 0
|
|
80666
80732
|
set skipped to 0
|
|
80667
80733
|
set seenIds to {}
|
|
80668
|
-
repeat with mb in
|
|
80734
|
+
repeat with mb in ${mbIter}
|
|
80669
80735
|
if msgCount >= ${limit} then exit repeat
|
|
80670
80736
|
set mbName to ""
|
|
80671
80737
|
try
|
|
@@ -80712,7 +80778,7 @@ ${indent}end try${this.sanitizeFragment("_uacct", indent)}${this.sanitizeFragmen
|
|
|
80712
80778
|
set _skipped to ""
|
|
80713
80779
|
set _notSearched to ""
|
|
80714
80780
|
set _startedAt to current date
|
|
80715
|
-
repeat with mb in
|
|
80781
|
+
repeat with mb in ${mbIter}
|
|
80716
80782
|
if msgCount >= ${limit} then exit repeat
|
|
80717
80783
|
set mbName to ""
|
|
80718
80784
|
try
|
|
@@ -80742,7 +80808,7 @@ ${indent}end try${this.sanitizeFragment("_uacct", indent)}${this.sanitizeFragmen
|
|
|
80742
80808
|
return outputText & "${DIAG_MARKER}timedOut=" & (_timedOut as string) & "${DIAG_FIELD_SEP}skipped=" & _skipped & "${DIAG_FIELD_SEP}notSearched=" & _notSearched
|
|
80743
80809
|
`;
|
|
80744
80810
|
}
|
|
80745
|
-
const script = buildAccountScopedScript(targetAccount, listCommand);
|
|
80811
|
+
const script = local ? buildAppLevelScript(`${localMailboxBindingFragment()}${listCommand}`) : buildAccountScopedScript(targetAccount, listCommand);
|
|
80746
80812
|
const result = executeAppleScript(script, { timeoutMs: SEARCH_ACCOUNT_TIMEOUT_MS });
|
|
80747
80813
|
if (!result.success) {
|
|
80748
80814
|
console.error(`Failed to list messages in "${targetAccount}": ${result.error}`);
|
|
@@ -82710,16 +82776,14 @@ end tell`;
|
|
|
82710
82776
|
* Used internally by the cache; prefer getCachedMailboxNames().
|
|
82711
82777
|
*/
|
|
82712
82778
|
fetchMailboxNames(account) {
|
|
82713
|
-
const
|
|
82714
|
-
account,
|
|
82715
|
-
`
|
|
82779
|
+
const body = `
|
|
82716
82780
|
set mbNames to {}
|
|
82717
|
-
repeat with mb in mailboxes
|
|
82781
|
+
repeat with mb in ${isLocalStoreLabel(account) ? "_mbs" : "mailboxes"}
|
|
82718
82782
|
set end of mbNames to name of mb
|
|
82719
82783
|
end repeat
|
|
82720
82784
|
return mbNames
|
|
82721
|
-
|
|
82722
|
-
);
|
|
82785
|
+
`;
|
|
82786
|
+
const script = isLocalStoreLabel(account) ? buildAppLevelScript(`${localMailboxBindingFragment()}${body}`) : buildAccountScopedScript(account, body);
|
|
82723
82787
|
const result = executeAppleScript(script);
|
|
82724
82788
|
if (!result.success || !result.output) {
|
|
82725
82789
|
return [];
|
package/package.json
CHANGED