apple-mail-mcp 2.20.9 → 2.20.11
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 +27 -3
- package/build/cli.js +57 -12
- package/build/index.js +20465 -20389
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -237,12 +237,29 @@ Search for messages matching criteria. Searches all accounts by default.
|
|
|
237
237
|
| `account` | string | No | Account to search in (omit to search all accounts) |
|
|
238
238
|
| `isRead` | boolean | No | Filter by read status |
|
|
239
239
|
| `isFlagged` | boolean | No | Filter by flagged status |
|
|
240
|
+
| `flagColor` | string | No | Filter to messages flagged with exactly this color — see [`flag-message`](#flag-message--unflag-message) for the palette. Implies `isFlagged: true`; pairing this with `isFlagged: false` is rejected. See the flag-color note below. |
|
|
240
241
|
| `dateFrom` | string | No | Start date filter (e.g., "January 1, 2026") |
|
|
241
242
|
| `dateTo` | string | No | End date filter (e.g., "March 1, 2026") |
|
|
242
243
|
| `limit` | number | No | Max results, 1–500 (default: 50) |
|
|
243
244
|
| `offset` | number | No | Newest matches to skip, ≥ 0 (for pagination). IMAP accounts only; without a `mailbox`, `offset` + `limit` may not exceed 5,000 |
|
|
244
245
|
|
|
245
|
-
**Returns:** List of matching messages with ID, date, subject, sender,
|
|
246
|
+
**Returns:** List of matching messages with ID, date, subject, sender, read state,
|
|
247
|
+
and `flagColor` when flagged with an explicit color.
|
|
248
|
+
|
|
249
|
+
**Flag color as a label (#286).** Every row from both backends carries
|
|
250
|
+
`flagColor` (the palette name — `red`, `orange`, `yellow`, `green`, `blue`,
|
|
251
|
+
`purple`, `gray`) whenever the message is flagged with an explicit color;
|
|
252
|
+
omitted for an unflagged message or one flagged with Mail's plain default (no
|
|
253
|
+
color chosen). Combined with the `flagColor` filter above, a colored flag
|
|
254
|
+
works as a label across mailboxes: flag a set of messages with
|
|
255
|
+
`flag-message`/`batch-flag-messages`, then find exactly that set later with
|
|
256
|
+
`search-messages`, without moving anything into a dedicated folder. **Known
|
|
257
|
+
limitation, inherent to Apple's `$MailFlagBit0/1/2` IMAP wire encoding, not
|
|
258
|
+
specific to this server:** `red` (palette index 0) sets no keyword bits, so
|
|
259
|
+
over IMAP it is indistinguishable from a plain colorless flag, both when
|
|
260
|
+
reading `flagColor` back and when filtering on it. AppleScript-backed
|
|
261
|
+
accounts are unaffected (Mail's own `flag index` property is `0` for red, `-1`
|
|
262
|
+
for none).
|
|
246
263
|
|
|
247
264
|
⚠️ **Row dates.** Since 2.19.7 every row — on both the IMAP and AppleScript
|
|
248
265
|
backends — carries both `dateSent` (the message's `Date:` header) and
|
|
@@ -392,7 +409,9 @@ List messages in a mailbox.
|
|
|
392
409
|
| `from` | string | No | Filter by sender email address or name |
|
|
393
410
|
| `unreadOnly` | boolean | No | Only show unread messages |
|
|
394
411
|
|
|
395
|
-
**Returns:** List of messages with ID, date, subject, and
|
|
412
|
+
**Returns:** List of messages with ID, date, subject, sender, and `flagColor`
|
|
413
|
+
when flagged with an explicit color (see the flag-color note under
|
|
414
|
+
[`search-messages`](#search-messages), which also supports filtering by it).
|
|
396
415
|
|
|
397
416
|
⚠️ **Row dates.** Since 2.19.7 every row — on both the **IMAP** and
|
|
398
417
|
**AppleScript** backends — carries both `dateSent` (the message's `Date:`
|
|
@@ -995,7 +1014,12 @@ Flag or unflag a message. `flag-message` optionally takes a flag **color**; `unf
|
|
|
995
1014
|
|
|
996
1015
|
**Flag colors** are an Apple Mail feature — the message's `flag index` (0 red, 1 orange, 2 yellow, 3 green, 4 blue, 5 purple, 6 gray), which is the property a Mail smart mailbox can match on. **The color is applied on both routes** (since 2.10.0): AppleScript sets the flag index directly, and for an **IMAP-routed** id (`imap:…`) the color is written as Mail.app's `$MailFlagBit0/1/2` keywords — a 3-bit field holding the same palette index. `\Flagged` on its own really is colorless, but those keywords ride alongside it in an ordinary `UID STORE`, so a smart mailbox keyed on flag color matches an IMAP-flagged message too. You do **not** need to resolve to a numeric id just to color a flag.
|
|
997
1016
|
|
|
998
|
-
To **read** a color
|
|
1017
|
+
To **read** a color back, or **find** messages by color, use
|
|
1018
|
+
[`search-messages`](#search-messages) (or [`list-messages`](#list-messages) for
|
|
1019
|
+
read-back only) — both routes now return `flagColor` (the palette name) on
|
|
1020
|
+
every flagged-with-a-color row, and `search-messages` takes a `flagColor`
|
|
1021
|
+
filter using this same palette (#286). The IMAP route additionally still
|
|
1022
|
+
carries the raw 0-6 `flagColorIndex` for back-compat.
|
|
999
1023
|
|
|
1000
1024
|
---
|
|
1001
1025
|
|
package/build/cli.js
CHANGED
|
@@ -58021,6 +58021,7 @@ var init_auditLog = __esm({
|
|
|
58021
58021
|
// src/services/imapClient.ts
|
|
58022
58022
|
var imapClient_exports = {};
|
|
58023
58023
|
__export(imapClient_exports, {
|
|
58024
|
+
FLAG_COLOR_NAMES: () => FLAG_COLOR_NAMES,
|
|
58024
58025
|
HEADER_WINDOW_BYTES: () => HEADER_WINDOW_BYTES,
|
|
58025
58026
|
IMAP_ENV: () => IMAP_ENV,
|
|
58026
58027
|
LARGE_MAILBOX_MESSAGES: () => LARGE_MAILBOX_MESSAGES,
|
|
@@ -58038,6 +58039,7 @@ __export(imapClient_exports, {
|
|
|
58038
58039
|
decodedSizeWithoutBody: () => decodedSizeWithoutBody,
|
|
58039
58040
|
dropAllPools: () => dropAllPools,
|
|
58040
58041
|
encodeImapId: () => encodeImapId,
|
|
58042
|
+
flagColorName: () => flagColorName,
|
|
58041
58043
|
imapAppendDraft: () => imapAppendDraft,
|
|
58042
58044
|
imapAppendSentCopy: () => imapAppendSentCopy,
|
|
58043
58045
|
imapBatchDelete: () => imapBatchDelete,
|
|
@@ -58324,6 +58326,9 @@ function buildCriteria(a, listMode) {
|
|
|
58324
58326
|
if (a.isFlagged === false) c.unflagged = true;
|
|
58325
58327
|
if (a.dateFrom) c.since = new Date(a.dateFrom);
|
|
58326
58328
|
if (a.dateTo) c.before = new Date(a.dateTo);
|
|
58329
|
+
if (a.flagColorIndex !== void 0) {
|
|
58330
|
+
c.flagged = true;
|
|
58331
|
+
}
|
|
58327
58332
|
return c;
|
|
58328
58333
|
}
|
|
58329
58334
|
function validDate(d) {
|
|
@@ -58366,6 +58371,7 @@ function isoOrEmpty(d) {
|
|
|
58366
58371
|
}
|
|
58367
58372
|
function structuredRow(m, account, path3) {
|
|
58368
58373
|
const env = m.envelope ?? {};
|
|
58374
|
+
const flagColorIndex = mailFlagColorIndex(m.flags);
|
|
58369
58375
|
return {
|
|
58370
58376
|
id: encodeImapId(account, path3, m.uid),
|
|
58371
58377
|
subject: env.subject || "(no subject)",
|
|
@@ -58382,7 +58388,19 @@ function structuredRow(m, account, path3) {
|
|
|
58382
58388
|
dateReceived: isoOrEmpty(validDate(m.internalDate) ?? headerDate(m)),
|
|
58383
58389
|
isRead: m.flags?.has("\\Seen") ?? false,
|
|
58384
58390
|
isFlagged: m.flags?.has("\\Flagged") ?? false,
|
|
58385
|
-
flagColorIndex
|
|
58391
|
+
// flagColorIndex has been computed here since 2.19.x but never reached a
|
|
58392
|
+
// caller in friendly form (#286, @j5pu) — flagColor is the palette name
|
|
58393
|
+
// (e.g. "orange"), omitted whenever the index is (unflagged, or flagged
|
|
58394
|
+
// with Mail's plain default and no explicit color). flagColorIndex stays
|
|
58395
|
+
// for back-compat.
|
|
58396
|
+
// ⚠️ "red" (index 0) sets no $MailFlagBit keyword — it is wire-identical to
|
|
58397
|
+
// a plain colorless flag over IMAP, so a red-flagged message reads back
|
|
58398
|
+
// here with no flagColor (and flagColor: "red" in search-messages also
|
|
58399
|
+
// matches a plain flag). Inherent to Apple's bitfield encoding, not
|
|
58400
|
+
// fixable from the client side; the AppleScript route is unaffected (Mail's
|
|
58401
|
+
// own `flag index` is 0 for red, -1 for none).
|
|
58402
|
+
flagColorIndex,
|
|
58403
|
+
flagColor: flagColorName(flagColorIndex),
|
|
58386
58404
|
mailbox: path3,
|
|
58387
58405
|
account,
|
|
58388
58406
|
// Derived from BODYSTRUCTURE, which the list/search fetch now requests.
|
|
@@ -58461,6 +58479,16 @@ async function searchUids(client, path3, criteria, size) {
|
|
|
58461
58479
|
`IMAP SEARCH on "${path3}"${sized} failed: ` + (cause ? errText(cause) : "the server rejected it or the connection dropped before it answered (on a mailbox this size, usually a server-side timeout)")
|
|
58462
58480
|
);
|
|
58463
58481
|
}
|
|
58482
|
+
async function filterUidsByColor(client, uids, colorIndex) {
|
|
58483
|
+
if (uids.length === 0) return uids;
|
|
58484
|
+
const keep = /* @__PURE__ */ new Set();
|
|
58485
|
+
for await (const msg of client.fetch(uids.join(","), { uid: true, flags: true }, { uid: true })) {
|
|
58486
|
+
if (typeof msg?.uid !== "number") continue;
|
|
58487
|
+
const idx = mailFlagColorIndex(msg.flags);
|
|
58488
|
+
if (colorIndex === 0 ? idx === void 0 : idx === colorIndex) keep.add(msg.uid);
|
|
58489
|
+
}
|
|
58490
|
+
return uids.filter((uid) => keep.has(uid));
|
|
58491
|
+
}
|
|
58464
58492
|
function mergeFetched(into, msg) {
|
|
58465
58493
|
const prev = into.get(msg.uid);
|
|
58466
58494
|
if (!prev) {
|
|
@@ -58616,14 +58644,15 @@ async function listLargeMailbox(client, exists, page) {
|
|
|
58616
58644
|
totalExact: true
|
|
58617
58645
|
};
|
|
58618
58646
|
}
|
|
58619
|
-
async function searchLargeMailbox(client, path3, criteria, exists, page) {
|
|
58647
|
+
async function searchLargeMailbox(client, path3, criteria, exists, page, colorIndex) {
|
|
58620
58648
|
const walk = await walkWindows(exists, page, FIRST_SEARCH_WINDOW, async (lo, hi, width) => {
|
|
58621
|
-
|
|
58649
|
+
let found = await searchUids(client, path3, { ...criteria, seq: `${lo}:${hi}` }, exists);
|
|
58622
58650
|
if (found.length > width) {
|
|
58623
58651
|
throw new Error(
|
|
58624
58652
|
`IMAP SEARCH on "${path3}" reported ${found.length} matches in a ${width}-message window \u2014 discarding as corrupted rather than trusting it (see #246).`
|
|
58625
58653
|
);
|
|
58626
58654
|
}
|
|
58655
|
+
if (colorIndex !== void 0) found = await filterUidsByColor(client, found, colorIndex);
|
|
58627
58656
|
return found;
|
|
58628
58657
|
});
|
|
58629
58658
|
return {
|
|
@@ -58632,14 +58661,14 @@ async function searchLargeMailbox(client, path3, criteria, exists, page) {
|
|
|
58632
58661
|
totalExact: walk.exhausted
|
|
58633
58662
|
};
|
|
58634
58663
|
}
|
|
58635
|
-
async function fetchMailboxMatches(client, path3, criteria, page) {
|
|
58664
|
+
async function fetchMailboxMatches(client, path3, criteria, page, colorIndex) {
|
|
58636
58665
|
const lock = await client.getMailboxLock(path3);
|
|
58637
58666
|
try {
|
|
58638
58667
|
const exists = await statusMessageCount(client, path3);
|
|
58639
58668
|
if (exists === 0) return { messages: [], omitted: [], total: 0, totalExact: true };
|
|
58640
58669
|
if (exists !== void 0 && exists > LARGE_MAILBOX_MESSAGES) {
|
|
58641
58670
|
try {
|
|
58642
|
-
return isUnfiltered(criteria) ? await listLargeMailbox(client, exists, page) : await searchLargeMailbox(client, path3, criteria, exists, page);
|
|
58671
|
+
return isUnfiltered(criteria) ? await listLargeMailbox(client, exists, page) : await searchLargeMailbox(client, path3, criteria, exists, page, colorIndex);
|
|
58643
58672
|
} catch (error2) {
|
|
58644
58673
|
const detail = errText(error2);
|
|
58645
58674
|
throw new Error(
|
|
@@ -58647,16 +58676,20 @@ async function fetchMailboxMatches(client, path3, criteria, page) {
|
|
|
58647
58676
|
);
|
|
58648
58677
|
}
|
|
58649
58678
|
}
|
|
58650
|
-
const
|
|
58651
|
-
if (
|
|
58652
|
-
return { messages: [], omitted: [], total:
|
|
58679
|
+
const rawUids = await searchUids(client, path3, criteria, exists);
|
|
58680
|
+
if (rawUids.length === 0) {
|
|
58681
|
+
return { messages: [], omitted: [], total: 0, totalExact: true };
|
|
58653
58682
|
}
|
|
58654
58683
|
const messages = exists ?? (await client.status(path3, { messages: true })).messages ?? void 0;
|
|
58655
|
-
if (typeof messages === "number" &&
|
|
58684
|
+
if (typeof messages === "number" && rawUids.length > messages) {
|
|
58656
58685
|
throw new Error(
|
|
58657
|
-
`IMAP SEARCH on "${path3}" reported ${
|
|
58686
|
+
`IMAP SEARCH on "${path3}" reported ${rawUids.length} matches, more than the mailbox's own ${messages} messages \u2014 discarding as corrupted rather than trusting it (see #246).`
|
|
58658
58687
|
);
|
|
58659
58688
|
}
|
|
58689
|
+
const uids = colorIndex !== void 0 ? await filterUidsByColor(client, rawUids, colorIndex) : rawUids;
|
|
58690
|
+
if (uids.length === 0 || page.take === 0) {
|
|
58691
|
+
return { messages: [], omitted: [], total: uids.length, totalExact: true };
|
|
58692
|
+
}
|
|
58660
58693
|
const newest = uids.slice().reverse().slice(page.skip, page.skip + page.take);
|
|
58661
58694
|
return { ...pageRows(await fetchRows(client, newest)), total: uids.length, totalExact: true };
|
|
58662
58695
|
} finally {
|
|
@@ -58706,7 +58739,7 @@ async function run(args, listMode, deps) {
|
|
|
58706
58739
|
}
|
|
58707
58740
|
try {
|
|
58708
58741
|
const outcome = await raceStop(
|
|
58709
|
-
fetchMailboxMatches(client, path3, criteria, page),
|
|
58742
|
+
fetchMailboxMatches(client, path3, criteria, page, args.flagColorIndex),
|
|
58710
58743
|
deps.signal
|
|
58711
58744
|
);
|
|
58712
58745
|
if (outcome === STOPPED) {
|
|
@@ -59427,6 +59460,9 @@ function mailFlagColorIndex(flags) {
|
|
|
59427
59460
|
}
|
|
59428
59461
|
return any ? idx : void 0;
|
|
59429
59462
|
}
|
|
59463
|
+
function flagColorName(index) {
|
|
59464
|
+
return index === void 0 ? void 0 : FLAG_COLOR_NAMES[index];
|
|
59465
|
+
}
|
|
59430
59466
|
function flagOp(id, flag, add, deps) {
|
|
59431
59467
|
const ref = decodeImapId(id);
|
|
59432
59468
|
if (!ref) return Promise.resolve({ success: false, error: `Not an IMAP message id: "${id}".` });
|
|
@@ -59940,7 +59976,7 @@ async function imapThread(id, deps = {}, limit = 50) {
|
|
|
59940
59976
|
true
|
|
59941
59977
|
);
|
|
59942
59978
|
}
|
|
59943
|
-
var import_imapflow, IMAP_ENV, defaultConnect, SPECIAL_USE_ALIASES, NOT_DELETED, LARGE_MAILBOX_MESSAGES, FIRST_SEARCH_WINDOW, MAX_WINDOW, MAX_UNSCOPED_SEARCH_DEPTH, ROW_QUERY, ROW_QUERY_NO_STRUCTURE, ROW_QUERY_HEADERS_ONLY, DELETED_MARGIN, poolConnect, pools, connecting, MAX_COMPOSE_SOURCE_BYTES, MAX_RFC822_INLINE_BYTES, MAX_RFC822_FILE_BYTES, HEADER_WINDOW_BYTES, MAIL_FLAG_BITS, imapMarkRead, imapMarkUnread, FALLBACK_TRASH_PATH, BASE64_TAIL_WINDOW, QP_EXACT_MAX_BYTES, imapBatchMarkRead, imapBatchMarkUnread, imapBatchFlag, imapBatchUnflag, imapBatchDelete;
|
|
59979
|
+
var import_imapflow, IMAP_ENV, defaultConnect, SPECIAL_USE_ALIASES, NOT_DELETED, LARGE_MAILBOX_MESSAGES, FIRST_SEARCH_WINDOW, MAX_WINDOW, MAX_UNSCOPED_SEARCH_DEPTH, ROW_QUERY, ROW_QUERY_NO_STRUCTURE, ROW_QUERY_HEADERS_ONLY, DELETED_MARGIN, poolConnect, pools, connecting, MAX_COMPOSE_SOURCE_BYTES, MAX_RFC822_INLINE_BYTES, MAX_RFC822_FILE_BYTES, HEADER_WINDOW_BYTES, MAIL_FLAG_BITS, FLAG_COLOR_NAMES, imapMarkRead, imapMarkUnread, FALLBACK_TRASH_PATH, BASE64_TAIL_WINDOW, QP_EXACT_MAX_BYTES, imapBatchMarkRead, imapBatchMarkUnread, imapBatchFlag, imapBatchUnflag, imapBatchDelete;
|
|
59944
59980
|
var init_imapClient = __esm({
|
|
59945
59981
|
"src/services/imapClient.ts"() {
|
|
59946
59982
|
"use strict";
|
|
@@ -60043,6 +60079,15 @@ var init_imapClient = __esm({
|
|
|
60043
60079
|
MAX_RFC822_FILE_BYTES = MAX_COMPOSE_SOURCE_BYTES;
|
|
60044
60080
|
HEADER_WINDOW_BYTES = 64 * 1024;
|
|
60045
60081
|
MAIL_FLAG_BITS = ["$MailFlagBit0", "$MailFlagBit1", "$MailFlagBit2"];
|
|
60082
|
+
FLAG_COLOR_NAMES = [
|
|
60083
|
+
"red",
|
|
60084
|
+
"orange",
|
|
60085
|
+
"yellow",
|
|
60086
|
+
"green",
|
|
60087
|
+
"blue",
|
|
60088
|
+
"purple",
|
|
60089
|
+
"gray"
|
|
60090
|
+
];
|
|
60046
60091
|
imapMarkRead = (id, deps = {}) => flagOp(id, "\\Seen", true, deps);
|
|
60047
60092
|
imapMarkUnread = (id, deps = {}) => flagOp(id, "\\Seen", false, deps);
|
|
60048
60093
|
FALLBACK_TRASH_PATH = "Trash";
|