apple-mail-mcp 2.19.14 → 2.19.16
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 +15 -0
- package/build/cli.js +93 -25
- package/build/index.js +110 -36
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1077,6 +1077,21 @@ refused rather than guessed; pass the full path to disambiguate. This also
|
|
|
1077
1077
|
means Gmail's nested special mailboxes now report their real path, e.g.
|
|
1078
1078
|
`[Gmail]/All Mail` rather than `All Mail` — a visible change from before 2.17.0.
|
|
1079
1079
|
|
|
1080
|
+
Matching ignores letter case **and Unicode normalization form**: a typed
|
|
1081
|
+
precomposed `México` (NFC) finds a mailbox the server stores decomposed (NFD —
|
|
1082
|
+
common for folders created on a Mac, and what iCloud keeps), and the server's
|
|
1083
|
+
own stored spelling is what gets selected. Two mailboxes whose names differ
|
|
1084
|
+
*only* in case or normalization look identical in any listing, so a name that
|
|
1085
|
+
matches both is refused with an error saying so — rename one of them. For the
|
|
1086
|
+
same reason `create-mailbox` treats a normalization-equivalent existing name as
|
|
1087
|
+
already existing, and `rename-mailbox` refuses to create such a twin.
|
|
1088
|
+
|
|
1089
|
+
**Messages awaiting expunge are not listed.** On IMAP accounts, search,
|
|
1090
|
+
list, thread and mail-stats queries only match messages *not* flagged
|
|
1091
|
+
`\Deleted` (IMAP `UNDELETED`). iCloud keeps flagged-but-never-expunged
|
|
1092
|
+
messages out of its message counts yet still returns them from `UID SEARCH`,
|
|
1093
|
+
which made a filter-less `list-messages` fail on such a mailbox before 2.19.16.
|
|
1094
|
+
|
|
1080
1095
|
**Mail's local "On My Mac" mailboxes** are not children of any account — they
|
|
1081
1096
|
hang off the application — so they are reported under the synthetic account label
|
|
1082
1097
|
**`On My Mac`**. An unscoped call includes them (listed last); `account="On My
|
package/build/cli.js
CHANGED
|
@@ -57647,6 +57647,19 @@ var require_imap_flow = __commonJS({
|
|
|
57647
57647
|
}
|
|
57648
57648
|
});
|
|
57649
57649
|
|
|
57650
|
+
// src/utils/mailboxName.ts
|
|
57651
|
+
function mailboxNameKey(name) {
|
|
57652
|
+
return name.trim().normalize("NFC").toLowerCase();
|
|
57653
|
+
}
|
|
57654
|
+
function nfc(name) {
|
|
57655
|
+
return name.trim().normalize("NFC");
|
|
57656
|
+
}
|
|
57657
|
+
var init_mailboxName = __esm({
|
|
57658
|
+
"src/utils/mailboxName.ts"() {
|
|
57659
|
+
"use strict";
|
|
57660
|
+
}
|
|
57661
|
+
});
|
|
57662
|
+
|
|
57650
57663
|
// src/utils/mimeParse.ts
|
|
57651
57664
|
function extractBoundary(source) {
|
|
57652
57665
|
const match = source.match(/boundary="?([^";\s\r\n]+)"?/i);
|
|
@@ -58076,6 +58089,7 @@ __export(imapClient_exports, {
|
|
|
58076
58089
|
listImapAccountLabels: () => listImapAccountLabels,
|
|
58077
58090
|
mailFlagBitsFor: () => mailFlagBitsFor,
|
|
58078
58091
|
mailFlagColorIndex: () => mailFlagColorIndex,
|
|
58092
|
+
matchMailbox: () => matchMailbox,
|
|
58079
58093
|
normalizeMessageId: () => normalizeMessageId,
|
|
58080
58094
|
resolveImapConfig: () => resolveImapConfig,
|
|
58081
58095
|
resolveImapConfigs: () => resolveImapConfigs,
|
|
@@ -58290,21 +58304,26 @@ function staticMailboxAlias(mailbox) {
|
|
|
58290
58304
|
}
|
|
58291
58305
|
async function resolveMailboxPath(client, mailbox, _mode) {
|
|
58292
58306
|
if (!mailbox) return "INBOX";
|
|
58307
|
+
let boxes;
|
|
58293
58308
|
try {
|
|
58294
|
-
|
|
58295
|
-
if (resolved.kind === "found") return resolved.path;
|
|
58296
|
-
const flag = SPECIAL_USE_ALIASES[mailbox.trim().toLowerCase()];
|
|
58297
|
-
if (flag) {
|
|
58298
|
-
const boxes = await client.list();
|
|
58299
|
-
const special = boxes.find((b) => b.specialUse?.toLowerCase() === flag);
|
|
58300
|
-
if (special) return special.path;
|
|
58301
|
-
}
|
|
58309
|
+
boxes = await client.list();
|
|
58302
58310
|
} catch {
|
|
58311
|
+
return staticMailboxAlias(mailbox);
|
|
58312
|
+
}
|
|
58313
|
+
const resolved = matchMailbox(boxes, mailbox);
|
|
58314
|
+
if (resolved.kind === "found") return resolved.path;
|
|
58315
|
+
const flag = SPECIAL_USE_ALIASES[mailbox.trim().toLowerCase()];
|
|
58316
|
+
if (flag) {
|
|
58317
|
+
const special = boxes.find((b) => b.specialUse?.toLowerCase() === flag);
|
|
58318
|
+
if (special) return special.path;
|
|
58319
|
+
}
|
|
58320
|
+
if (resolved.kind === "ambiguous") {
|
|
58321
|
+
throw new Error(ambiguousMailboxError(mailbox, resolved.candidates));
|
|
58303
58322
|
}
|
|
58304
58323
|
return staticMailboxAlias(mailbox);
|
|
58305
58324
|
}
|
|
58306
58325
|
function buildCriteria(a, listMode) {
|
|
58307
|
-
const c = {};
|
|
58326
|
+
const c = { ...NOT_DELETED };
|
|
58308
58327
|
if (a.query) c.or = [{ subject: a.query }, { from: a.query }];
|
|
58309
58328
|
if (a.body) c.body = a.body;
|
|
58310
58329
|
if (a.from) c.from = a.from;
|
|
@@ -58316,7 +58335,6 @@ function buildCriteria(a, listMode) {
|
|
|
58316
58335
|
if (a.isFlagged === false) c.unflagged = true;
|
|
58317
58336
|
if (a.dateFrom) c.since = new Date(a.dateFrom);
|
|
58318
58337
|
if (a.dateTo) c.before = new Date(a.dateTo);
|
|
58319
|
-
if (Object.keys(c).length === 0) c.all = true;
|
|
58320
58338
|
return c;
|
|
58321
58339
|
}
|
|
58322
58340
|
function validDate(d) {
|
|
@@ -58392,7 +58410,7 @@ function structuredRow(m, account, path) {
|
|
|
58392
58410
|
};
|
|
58393
58411
|
}
|
|
58394
58412
|
function describeMailboxFailure(error) {
|
|
58395
|
-
const raw =
|
|
58413
|
+
const raw = errText(error);
|
|
58396
58414
|
const oneLine = raw.split("\n")[0].trim();
|
|
58397
58415
|
const looksSensitive = /pass(word)?\s*[:=]|authorization:\s*\S|bearer\s+\S{10,}/i.test(oneLine);
|
|
58398
58416
|
const safe = looksSensitive ? "IMAP error (detail redacted \u2014 response text looked like it might contain a credential)" : oneLine || "unknown error";
|
|
@@ -58610,7 +58628,10 @@ function imapMailStats(deps = {}) {
|
|
|
58610
58628
|
try {
|
|
58611
58629
|
const lock = await client.getMailboxLock("INBOX");
|
|
58612
58630
|
try {
|
|
58613
|
-
const found = await client.search(
|
|
58631
|
+
const found = await client.search(
|
|
58632
|
+
{ since: since(days), ...NOT_DELETED },
|
|
58633
|
+
{ uid: true }
|
|
58634
|
+
);
|
|
58614
58635
|
return Array.isArray(found) ? found.length : 0;
|
|
58615
58636
|
} finally {
|
|
58616
58637
|
lock.release();
|
|
@@ -58630,7 +58651,13 @@ function imapMailStats(deps = {}) {
|
|
|
58630
58651
|
);
|
|
58631
58652
|
}
|
|
58632
58653
|
function errText(e) {
|
|
58633
|
-
|
|
58654
|
+
if (!(e instanceof Error)) return String(e);
|
|
58655
|
+
const x = e;
|
|
58656
|
+
if (typeof x.responseStatus !== "string" || !x.responseStatus) return e.message;
|
|
58657
|
+
const code = typeof x.serverResponseCode === "string" && x.serverResponseCode;
|
|
58658
|
+
const text = typeof x.responseText === "string" ? x.responseText.trim() : "";
|
|
58659
|
+
const server = [x.responseStatus, code ? `[${code}]` : "", text].filter(Boolean).join(" ");
|
|
58660
|
+
return e.message && e.message !== "Command failed" ? `${e.message}: ${server}` : server;
|
|
58634
58661
|
}
|
|
58635
58662
|
function assertMutated(result, what) {
|
|
58636
58663
|
if (!result) throw new Error(`${what}: server rejected the command (IMAP NO/BAD)`);
|
|
@@ -58782,20 +58809,30 @@ function withClient(deps, fn) {
|
|
|
58782
58809
|
return useClient(deps, fn);
|
|
58783
58810
|
}
|
|
58784
58811
|
async function resolveMailbox(client, name) {
|
|
58785
|
-
const wanted = name.trim().toLowerCase();
|
|
58786
58812
|
const boxes = await client.list();
|
|
58787
|
-
|
|
58788
|
-
|
|
58789
|
-
|
|
58790
|
-
|
|
58791
|
-
if (
|
|
58792
|
-
|
|
58813
|
+
return matchMailbox(boxes, name);
|
|
58814
|
+
}
|
|
58815
|
+
function matchMailbox(boxes, name) {
|
|
58816
|
+
const exact = boxes.find((b) => b.path === name || b.path === name.trim());
|
|
58817
|
+
if (exact) return { kind: "found", path: exact.path };
|
|
58818
|
+
const wanted = mailboxNameKey(name);
|
|
58819
|
+
const tiers = [
|
|
58820
|
+
boxes.filter((b) => mailboxNameKey(b.path) === wanted),
|
|
58821
|
+
boxes.filter((b) => mailboxNameKey(b.name) === wanted)
|
|
58822
|
+
];
|
|
58823
|
+
for (const hits of tiers) {
|
|
58824
|
+
const paths = [...new Set(hits.map((b) => b.path))];
|
|
58825
|
+
if (paths.length === 1) return { kind: "found", path: paths[0] };
|
|
58826
|
+
if (paths.length > 1) return { kind: "ambiguous", candidates: paths.sort() };
|
|
58793
58827
|
}
|
|
58794
58828
|
return { kind: "none" };
|
|
58795
58829
|
}
|
|
58796
58830
|
function ambiguousMailboxError(name, candidates, accountLabel) {
|
|
58797
58831
|
const where = accountLabel ? ` on IMAP account ${accountLabel}` : "";
|
|
58798
|
-
|
|
58832
|
+
const listed = candidates.map((c) => `"${c}"`).join(" and ");
|
|
58833
|
+
const indistinguishable = new Set(candidates.map(mailboxNameKey)).size === 1;
|
|
58834
|
+
const hint = indistinguishable ? " They differ only in letter case or Unicode normalization (precomposed vs decomposed accents), so no typed name can tell them apart. Rename one of them." : " Pass the full path.";
|
|
58835
|
+
return `Mailbox "${name}" is ambiguous${where} \u2014 it matches ${listed}.${hint}`;
|
|
58799
58836
|
}
|
|
58800
58837
|
async function findMailboxPathOrThrow(client, name) {
|
|
58801
58838
|
const res = await resolveMailbox(client, name);
|
|
@@ -58804,6 +58841,15 @@ async function findMailboxPathOrThrow(client, name) {
|
|
|
58804
58841
|
}
|
|
58805
58842
|
function imapCreateMailbox(name, deps = {}) {
|
|
58806
58843
|
return withClient(deps, async (client) => {
|
|
58844
|
+
let twin;
|
|
58845
|
+
try {
|
|
58846
|
+
const wanted = nfc(name);
|
|
58847
|
+
twin = (await client.list()).find((b) => nfc(b.path) === wanted)?.path;
|
|
58848
|
+
} catch {
|
|
58849
|
+
}
|
|
58850
|
+
if (twin !== void 0) {
|
|
58851
|
+
return { success: true, info: `Mailbox "${twin}" already existed.` };
|
|
58852
|
+
}
|
|
58807
58853
|
try {
|
|
58808
58854
|
const res = await client.mailboxCreate(name);
|
|
58809
58855
|
return res.created ? { success: true, info: `Created mailbox "${res.path}".` } : { success: true, info: `Mailbox "${res.path}" already existed.` };
|
|
@@ -58855,6 +58901,14 @@ function imapRenameMailbox(oldName, newName, deps = {}) {
|
|
|
58855
58901
|
};
|
|
58856
58902
|
}
|
|
58857
58903
|
const path = found.path;
|
|
58904
|
+
const wantedNew = nfc(newName);
|
|
58905
|
+
const clash = (await client.list()).find((b) => b.path !== path && nfc(b.path) === wantedNew);
|
|
58906
|
+
if (clash) {
|
|
58907
|
+
return {
|
|
58908
|
+
success: false,
|
|
58909
|
+
error: `Cannot rename "${path}" to "${newName}": mailbox "${clash.path}" already exists on IMAP account ${cfg.accountLabel} (the same name, differing only in how its accents are encoded).`
|
|
58910
|
+
};
|
|
58911
|
+
}
|
|
58858
58912
|
try {
|
|
58859
58913
|
const res = await client.mailboxRename(path, newName);
|
|
58860
58914
|
return { success: true, info: `Renamed "${res.path}" to "${res.newPath}" via IMAP.` };
|
|
@@ -59446,11 +59500,23 @@ async function imapThread(id, deps = {}, limit = 50) {
|
|
|
59446
59500
|
if (Array.isArray(found)) found.forEach((u) => uidSet.add(u));
|
|
59447
59501
|
};
|
|
59448
59502
|
if (seedMsgId) {
|
|
59449
|
-
addFound(
|
|
59450
|
-
|
|
59503
|
+
addFound(
|
|
59504
|
+
await client.search(
|
|
59505
|
+
{ header: { references: seedMsgId }, ...NOT_DELETED },
|
|
59506
|
+
{ uid: true }
|
|
59507
|
+
)
|
|
59508
|
+
);
|
|
59509
|
+
addFound(
|
|
59510
|
+
await client.search(
|
|
59511
|
+
{ header: { "in-reply-to": seedMsgId }, ...NOT_DELETED },
|
|
59512
|
+
{ uid: true }
|
|
59513
|
+
)
|
|
59514
|
+
);
|
|
59451
59515
|
}
|
|
59452
59516
|
for (const mid of [...refIds].slice(0, 20)) {
|
|
59453
|
-
addFound(
|
|
59517
|
+
addFound(
|
|
59518
|
+
await client.search({ header: { "message-id": mid }, ...NOT_DELETED }, { uid: true })
|
|
59519
|
+
);
|
|
59454
59520
|
}
|
|
59455
59521
|
if (uidSet.size <= 1) return null;
|
|
59456
59522
|
const uids = [...uidSet].slice(0, limit);
|
|
@@ -59502,13 +59568,14 @@ async function imapThread(id, deps = {}, limit = 50) {
|
|
|
59502
59568
|
true
|
|
59503
59569
|
);
|
|
59504
59570
|
}
|
|
59505
|
-
var import_imapflow, IMAP_ENV, defaultConnect, SPECIAL_USE_ALIASES, 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, imapBatchMarkRead, imapBatchMarkUnread, imapBatchFlag, imapBatchUnflag, imapBatchDelete;
|
|
59571
|
+
var import_imapflow, IMAP_ENV, defaultConnect, SPECIAL_USE_ALIASES, NOT_DELETED, 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, imapBatchMarkRead, imapBatchMarkUnread, imapBatchFlag, imapBatchUnflag, imapBatchDelete;
|
|
59506
59572
|
var init_imapClient = __esm({
|
|
59507
59573
|
"src/services/imapClient.ts"() {
|
|
59508
59574
|
"use strict";
|
|
59509
59575
|
import_imapflow = __toESM(require_imap_flow(), 1);
|
|
59510
59576
|
init_smtpMailer();
|
|
59511
59577
|
init_docsUrls();
|
|
59578
|
+
init_mailboxName();
|
|
59512
59579
|
init_mimeParse();
|
|
59513
59580
|
init_headers();
|
|
59514
59581
|
init_auditLog();
|
|
@@ -59554,6 +59621,7 @@ var init_imapClient = __esm({
|
|
|
59554
59621
|
junk: "\\junk",
|
|
59555
59622
|
starred: "\\flagged"
|
|
59556
59623
|
};
|
|
59624
|
+
NOT_DELETED = { deleted: false };
|
|
59557
59625
|
poolConnect = defaultConnect;
|
|
59558
59626
|
pools = /* @__PURE__ */ new Map();
|
|
59559
59627
|
connecting = /* @__PURE__ */ new Map();
|
package/build/index.js
CHANGED
|
@@ -7681,6 +7681,19 @@ var init_headers = __esm({
|
|
|
7681
7681
|
}
|
|
7682
7682
|
});
|
|
7683
7683
|
|
|
7684
|
+
// src/utils/mailboxName.ts
|
|
7685
|
+
function mailboxNameKey(name) {
|
|
7686
|
+
return name.trim().normalize("NFC").toLowerCase();
|
|
7687
|
+
}
|
|
7688
|
+
function nfc(name) {
|
|
7689
|
+
return name.trim().normalize("NFC");
|
|
7690
|
+
}
|
|
7691
|
+
var init_mailboxName = __esm({
|
|
7692
|
+
"src/utils/mailboxName.ts"() {
|
|
7693
|
+
"use strict";
|
|
7694
|
+
}
|
|
7695
|
+
});
|
|
7696
|
+
|
|
7684
7697
|
// src/utils/attachmentLimits.ts
|
|
7685
7698
|
function isInlineAttachmentBase64WithinLimit(contentBase64) {
|
|
7686
7699
|
if (contentBase64.length > MAX_INLINE_ATTACHMENT_BASE64_INPUT_CHARS) return false;
|
|
@@ -65405,6 +65418,7 @@ __export(imapClient_exports, {
|
|
|
65405
65418
|
listImapAccountLabels: () => listImapAccountLabels,
|
|
65406
65419
|
mailFlagBitsFor: () => mailFlagBitsFor,
|
|
65407
65420
|
mailFlagColorIndex: () => mailFlagColorIndex,
|
|
65421
|
+
matchMailbox: () => matchMailbox,
|
|
65408
65422
|
normalizeMessageId: () => normalizeMessageId,
|
|
65409
65423
|
resolveImapConfig: () => resolveImapConfig,
|
|
65410
65424
|
resolveImapConfigs: () => resolveImapConfigs,
|
|
@@ -65619,21 +65633,26 @@ function staticMailboxAlias(mailbox) {
|
|
|
65619
65633
|
}
|
|
65620
65634
|
async function resolveMailboxPath(client, mailbox, _mode) {
|
|
65621
65635
|
if (!mailbox) return "INBOX";
|
|
65636
|
+
let boxes;
|
|
65622
65637
|
try {
|
|
65623
|
-
|
|
65624
|
-
if (resolved.kind === "found") return resolved.path;
|
|
65625
|
-
const flag = SPECIAL_USE_ALIASES[mailbox.trim().toLowerCase()];
|
|
65626
|
-
if (flag) {
|
|
65627
|
-
const boxes = await client.list();
|
|
65628
|
-
const special = boxes.find((b) => b.specialUse?.toLowerCase() === flag);
|
|
65629
|
-
if (special) return special.path;
|
|
65630
|
-
}
|
|
65638
|
+
boxes = await client.list();
|
|
65631
65639
|
} catch {
|
|
65640
|
+
return staticMailboxAlias(mailbox);
|
|
65641
|
+
}
|
|
65642
|
+
const resolved = matchMailbox(boxes, mailbox);
|
|
65643
|
+
if (resolved.kind === "found") return resolved.path;
|
|
65644
|
+
const flag = SPECIAL_USE_ALIASES[mailbox.trim().toLowerCase()];
|
|
65645
|
+
if (flag) {
|
|
65646
|
+
const special = boxes.find((b) => b.specialUse?.toLowerCase() === flag);
|
|
65647
|
+
if (special) return special.path;
|
|
65648
|
+
}
|
|
65649
|
+
if (resolved.kind === "ambiguous") {
|
|
65650
|
+
throw new Error(ambiguousMailboxError(mailbox, resolved.candidates));
|
|
65632
65651
|
}
|
|
65633
65652
|
return staticMailboxAlias(mailbox);
|
|
65634
65653
|
}
|
|
65635
65654
|
function buildCriteria(a, listMode) {
|
|
65636
|
-
const c = {};
|
|
65655
|
+
const c = { ...NOT_DELETED };
|
|
65637
65656
|
if (a.query) c.or = [{ subject: a.query }, { from: a.query }];
|
|
65638
65657
|
if (a.body) c.body = a.body;
|
|
65639
65658
|
if (a.from) c.from = a.from;
|
|
@@ -65645,7 +65664,6 @@ function buildCriteria(a, listMode) {
|
|
|
65645
65664
|
if (a.isFlagged === false) c.unflagged = true;
|
|
65646
65665
|
if (a.dateFrom) c.since = new Date(a.dateFrom);
|
|
65647
65666
|
if (a.dateTo) c.before = new Date(a.dateTo);
|
|
65648
|
-
if (Object.keys(c).length === 0) c.all = true;
|
|
65649
65667
|
return c;
|
|
65650
65668
|
}
|
|
65651
65669
|
function validDate(d) {
|
|
@@ -65721,7 +65739,7 @@ function structuredRow(m, account, path) {
|
|
|
65721
65739
|
};
|
|
65722
65740
|
}
|
|
65723
65741
|
function describeMailboxFailure(error2) {
|
|
65724
|
-
const raw =
|
|
65742
|
+
const raw = errText(error2);
|
|
65725
65743
|
const oneLine = raw.split("\n")[0].trim();
|
|
65726
65744
|
const looksSensitive = /pass(word)?\s*[:=]|authorization:\s*\S|bearer\s+\S{10,}/i.test(oneLine);
|
|
65727
65745
|
const safe = looksSensitive ? "IMAP error (detail redacted \u2014 response text looked like it might contain a credential)" : oneLine || "unknown error";
|
|
@@ -65939,7 +65957,10 @@ function imapMailStats(deps = {}) {
|
|
|
65939
65957
|
try {
|
|
65940
65958
|
const lock = await client.getMailboxLock("INBOX");
|
|
65941
65959
|
try {
|
|
65942
|
-
const found = await client.search(
|
|
65960
|
+
const found = await client.search(
|
|
65961
|
+
{ since: since(days), ...NOT_DELETED },
|
|
65962
|
+
{ uid: true }
|
|
65963
|
+
);
|
|
65943
65964
|
return Array.isArray(found) ? found.length : 0;
|
|
65944
65965
|
} finally {
|
|
65945
65966
|
lock.release();
|
|
@@ -65959,7 +65980,13 @@ function imapMailStats(deps = {}) {
|
|
|
65959
65980
|
);
|
|
65960
65981
|
}
|
|
65961
65982
|
function errText(e) {
|
|
65962
|
-
|
|
65983
|
+
if (!(e instanceof Error)) return String(e);
|
|
65984
|
+
const x = e;
|
|
65985
|
+
if (typeof x.responseStatus !== "string" || !x.responseStatus) return e.message;
|
|
65986
|
+
const code = typeof x.serverResponseCode === "string" && x.serverResponseCode;
|
|
65987
|
+
const text = typeof x.responseText === "string" ? x.responseText.trim() : "";
|
|
65988
|
+
const server2 = [x.responseStatus, code ? `[${code}]` : "", text].filter(Boolean).join(" ");
|
|
65989
|
+
return e.message && e.message !== "Command failed" ? `${e.message}: ${server2}` : server2;
|
|
65963
65990
|
}
|
|
65964
65991
|
function assertMutated(result, what) {
|
|
65965
65992
|
if (!result) throw new Error(`${what}: server rejected the command (IMAP NO/BAD)`);
|
|
@@ -66111,20 +66138,30 @@ function withClient(deps, fn) {
|
|
|
66111
66138
|
return useClient(deps, fn);
|
|
66112
66139
|
}
|
|
66113
66140
|
async function resolveMailbox(client, name) {
|
|
66114
|
-
const wanted = name.trim().toLowerCase();
|
|
66115
66141
|
const boxes = await client.list();
|
|
66116
|
-
|
|
66117
|
-
|
|
66118
|
-
|
|
66119
|
-
|
|
66120
|
-
if (
|
|
66121
|
-
|
|
66142
|
+
return matchMailbox(boxes, name);
|
|
66143
|
+
}
|
|
66144
|
+
function matchMailbox(boxes, name) {
|
|
66145
|
+
const exact = boxes.find((b) => b.path === name || b.path === name.trim());
|
|
66146
|
+
if (exact) return { kind: "found", path: exact.path };
|
|
66147
|
+
const wanted = mailboxNameKey(name);
|
|
66148
|
+
const tiers = [
|
|
66149
|
+
boxes.filter((b) => mailboxNameKey(b.path) === wanted),
|
|
66150
|
+
boxes.filter((b) => mailboxNameKey(b.name) === wanted)
|
|
66151
|
+
];
|
|
66152
|
+
for (const hits of tiers) {
|
|
66153
|
+
const paths = [...new Set(hits.map((b) => b.path))];
|
|
66154
|
+
if (paths.length === 1) return { kind: "found", path: paths[0] };
|
|
66155
|
+
if (paths.length > 1) return { kind: "ambiguous", candidates: paths.sort() };
|
|
66122
66156
|
}
|
|
66123
66157
|
return { kind: "none" };
|
|
66124
66158
|
}
|
|
66125
66159
|
function ambiguousMailboxError(name, candidates, accountLabel) {
|
|
66126
66160
|
const where = accountLabel ? ` on IMAP account ${accountLabel}` : "";
|
|
66127
|
-
|
|
66161
|
+
const listed = candidates.map((c) => `"${c}"`).join(" and ");
|
|
66162
|
+
const indistinguishable = new Set(candidates.map(mailboxNameKey)).size === 1;
|
|
66163
|
+
const hint = indistinguishable ? " They differ only in letter case or Unicode normalization (precomposed vs decomposed accents), so no typed name can tell them apart. Rename one of them." : " Pass the full path.";
|
|
66164
|
+
return `Mailbox "${name}" is ambiguous${where} \u2014 it matches ${listed}.${hint}`;
|
|
66128
66165
|
}
|
|
66129
66166
|
async function findMailboxPathOrThrow(client, name) {
|
|
66130
66167
|
const res = await resolveMailbox(client, name);
|
|
@@ -66133,6 +66170,15 @@ async function findMailboxPathOrThrow(client, name) {
|
|
|
66133
66170
|
}
|
|
66134
66171
|
function imapCreateMailbox(name, deps = {}) {
|
|
66135
66172
|
return withClient(deps, async (client) => {
|
|
66173
|
+
let twin;
|
|
66174
|
+
try {
|
|
66175
|
+
const wanted = nfc(name);
|
|
66176
|
+
twin = (await client.list()).find((b) => nfc(b.path) === wanted)?.path;
|
|
66177
|
+
} catch {
|
|
66178
|
+
}
|
|
66179
|
+
if (twin !== void 0) {
|
|
66180
|
+
return { success: true, info: `Mailbox "${twin}" already existed.` };
|
|
66181
|
+
}
|
|
66136
66182
|
try {
|
|
66137
66183
|
const res = await client.mailboxCreate(name);
|
|
66138
66184
|
return res.created ? { success: true, info: `Created mailbox "${res.path}".` } : { success: true, info: `Mailbox "${res.path}" already existed.` };
|
|
@@ -66184,6 +66230,14 @@ function imapRenameMailbox(oldName, newName, deps = {}) {
|
|
|
66184
66230
|
};
|
|
66185
66231
|
}
|
|
66186
66232
|
const path = found.path;
|
|
66233
|
+
const wantedNew = nfc(newName);
|
|
66234
|
+
const clash = (await client.list()).find((b) => b.path !== path && nfc(b.path) === wantedNew);
|
|
66235
|
+
if (clash) {
|
|
66236
|
+
return {
|
|
66237
|
+
success: false,
|
|
66238
|
+
error: `Cannot rename "${path}" to "${newName}": mailbox "${clash.path}" already exists on IMAP account ${cfg.accountLabel} (the same name, differing only in how its accents are encoded).`
|
|
66239
|
+
};
|
|
66240
|
+
}
|
|
66187
66241
|
try {
|
|
66188
66242
|
const res = await client.mailboxRename(path, newName);
|
|
66189
66243
|
return { success: true, info: `Renamed "${res.path}" to "${res.newPath}" via IMAP.` };
|
|
@@ -66775,11 +66829,23 @@ async function imapThread(id, deps = {}, limit = 50) {
|
|
|
66775
66829
|
if (Array.isArray(found)) found.forEach((u) => uidSet.add(u));
|
|
66776
66830
|
};
|
|
66777
66831
|
if (seedMsgId) {
|
|
66778
|
-
addFound(
|
|
66779
|
-
|
|
66832
|
+
addFound(
|
|
66833
|
+
await client.search(
|
|
66834
|
+
{ header: { references: seedMsgId }, ...NOT_DELETED },
|
|
66835
|
+
{ uid: true }
|
|
66836
|
+
)
|
|
66837
|
+
);
|
|
66838
|
+
addFound(
|
|
66839
|
+
await client.search(
|
|
66840
|
+
{ header: { "in-reply-to": seedMsgId }, ...NOT_DELETED },
|
|
66841
|
+
{ uid: true }
|
|
66842
|
+
)
|
|
66843
|
+
);
|
|
66780
66844
|
}
|
|
66781
66845
|
for (const mid of [...refIds].slice(0, 20)) {
|
|
66782
|
-
addFound(
|
|
66846
|
+
addFound(
|
|
66847
|
+
await client.search({ header: { "message-id": mid }, ...NOT_DELETED }, { uid: true })
|
|
66848
|
+
);
|
|
66783
66849
|
}
|
|
66784
66850
|
if (uidSet.size <= 1) return null;
|
|
66785
66851
|
const uids = [...uidSet].slice(0, limit);
|
|
@@ -66831,13 +66897,14 @@ async function imapThread(id, deps = {}, limit = 50) {
|
|
|
66831
66897
|
true
|
|
66832
66898
|
);
|
|
66833
66899
|
}
|
|
66834
|
-
var import_imapflow, IMAP_ENV, defaultConnect, SPECIAL_USE_ALIASES, 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, imapBatchMarkRead, imapBatchMarkUnread, imapBatchFlag, imapBatchUnflag, imapBatchDelete;
|
|
66900
|
+
var import_imapflow, IMAP_ENV, defaultConnect, SPECIAL_USE_ALIASES, NOT_DELETED, 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, imapBatchMarkRead, imapBatchMarkUnread, imapBatchFlag, imapBatchUnflag, imapBatchDelete;
|
|
66835
66901
|
var init_imapClient = __esm({
|
|
66836
66902
|
"src/services/imapClient.ts"() {
|
|
66837
66903
|
"use strict";
|
|
66838
66904
|
import_imapflow = __toESM(require_imap_flow(), 1);
|
|
66839
66905
|
init_smtpMailer();
|
|
66840
66906
|
init_docsUrls();
|
|
66907
|
+
init_mailboxName();
|
|
66841
66908
|
init_mimeParse();
|
|
66842
66909
|
init_headers();
|
|
66843
66910
|
init_auditLog();
|
|
@@ -66883,6 +66950,7 @@ var init_imapClient = __esm({
|
|
|
66883
66950
|
junk: "\\junk",
|
|
66884
66951
|
starred: "\\flagged"
|
|
66885
66952
|
};
|
|
66953
|
+
NOT_DELETED = { deleted: false };
|
|
66886
66954
|
poolConnect = defaultConnect;
|
|
66887
66955
|
pools = /* @__PURE__ */ new Map();
|
|
66888
66956
|
connecting = /* @__PURE__ */ new Map();
|
|
@@ -81931,6 +81999,7 @@ function executeAppleScript(script, options = {}) {
|
|
|
81931
81999
|
init_docsUrls();
|
|
81932
82000
|
init_mimeParse();
|
|
81933
82001
|
init_headers();
|
|
82002
|
+
init_mailboxName();
|
|
81934
82003
|
|
|
81935
82004
|
// src/services/templateStore.ts
|
|
81936
82005
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
@@ -82566,23 +82635,28 @@ function mailboxLeaf(path) {
|
|
|
82566
82635
|
function resolveAppleMailboxPath(mailbox, actualPaths) {
|
|
82567
82636
|
if (actualPaths.length === 0) return mailbox;
|
|
82568
82637
|
const candidates = [mailbox, ...MAILBOX_ALIASES[mailbox.toLowerCase()] ?? []];
|
|
82638
|
+
const ambiguous = (matches) => {
|
|
82639
|
+
const sorted = [...new Set(matches)].sort();
|
|
82640
|
+
const paths = sorted.map((path) => `"${path}"`).join(" and ");
|
|
82641
|
+
const twins = new Set(sorted.map(mailboxNameKey)).size === 1;
|
|
82642
|
+
const hint = twins ? " They differ only in letter case or Unicode normalization (precomposed vs decomposed accents), so no typed name can tell them apart. Rename one of them." : " Pass the full path.";
|
|
82643
|
+
return new Error(`Mailbox "${mailbox}" is ambiguous \u2014 it matches ${paths}.${hint}`);
|
|
82644
|
+
};
|
|
82569
82645
|
for (const candidate of candidates) {
|
|
82570
82646
|
const exact = actualPaths.find((path) => path === candidate);
|
|
82571
82647
|
if (exact) return exact;
|
|
82572
|
-
const
|
|
82573
|
-
|
|
82648
|
+
const key = mailboxNameKey(candidate);
|
|
82649
|
+
const folded = [...new Set(actualPaths.filter((path) => mailboxNameKey(path) === key))];
|
|
82650
|
+
if (folded.length === 1) return folded[0];
|
|
82651
|
+
if (folded.length > 1) throw ambiguous(folded);
|
|
82574
82652
|
}
|
|
82575
82653
|
for (const candidate of candidates) {
|
|
82576
|
-
const
|
|
82577
|
-
|
|
82578
|
-
|
|
82654
|
+
const key = mailboxNameKey(candidate);
|
|
82655
|
+
const leafMatches = [
|
|
82656
|
+
...new Set(actualPaths.filter((path) => mailboxNameKey(mailboxLeaf(path)) === key))
|
|
82657
|
+
];
|
|
82579
82658
|
if (leafMatches.length === 1) return leafMatches[0];
|
|
82580
|
-
if (leafMatches.length > 1)
|
|
82581
|
-
const paths = [...leafMatches].sort().map((path) => `"${path}"`).join(" and ");
|
|
82582
|
-
throw new Error(
|
|
82583
|
-
`Mailbox "${mailbox}" is ambiguous \u2014 it matches ${paths}. Pass the full path.`
|
|
82584
|
-
);
|
|
82585
|
-
}
|
|
82659
|
+
if (leafMatches.length > 1) throw ambiguous(leafMatches);
|
|
82586
82660
|
}
|
|
82587
82661
|
return mailbox;
|
|
82588
82662
|
}
|
package/package.json
CHANGED