apple-mail-mcp 2.19.13 → 2.19.15
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 +9 -0
- package/build/cli.js +92 -21
- package/build/index.js +128 -39
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1077,6 +1077,15 @@ 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
|
+
|
|
1080
1089
|
**Mail's local "On My Mac" mailboxes** are not children of any account — they
|
|
1081
1090
|
hang off the application — so they are reported under the synthetic account label
|
|
1082
1091
|
**`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,16 +58304,21 @@ 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
|
}
|
|
@@ -58391,6 +58410,13 @@ function structuredRow(m, account, path) {
|
|
|
58391
58410
|
...env.messageId ? { messageId: env.messageId } : {}
|
|
58392
58411
|
};
|
|
58393
58412
|
}
|
|
58413
|
+
function describeMailboxFailure(error) {
|
|
58414
|
+
const raw = errText(error);
|
|
58415
|
+
const oneLine = raw.split("\n")[0].trim();
|
|
58416
|
+
const looksSensitive = /pass(word)?\s*[:=]|authorization:\s*\S|bearer\s+\S{10,}/i.test(oneLine);
|
|
58417
|
+
const safe = looksSensitive ? "IMAP error (detail redacted \u2014 response text looked like it might contain a credential)" : oneLine || "unknown error";
|
|
58418
|
+
return safe.length > 300 ? `${safe.slice(0, 300)}\u2026` : safe;
|
|
58419
|
+
}
|
|
58394
58420
|
function hasMailboxFlag(mailbox, wanted) {
|
|
58395
58421
|
const normalized = wanted.toLowerCase();
|
|
58396
58422
|
return [...mailbox.flags ?? []].some((flag) => flag.toLowerCase() === normalized);
|
|
@@ -58472,6 +58498,7 @@ async function run(args, listMode, deps) {
|
|
|
58472
58498
|
const newestPerMailbox = offset + limit;
|
|
58473
58499
|
const fetched = [];
|
|
58474
58500
|
const failedMailboxes = [];
|
|
58501
|
+
const failedMailboxReasons = {};
|
|
58475
58502
|
let totalMatched = 0;
|
|
58476
58503
|
for (const path of paths) {
|
|
58477
58504
|
try {
|
|
@@ -58480,14 +58507,16 @@ async function run(args, listMode, deps) {
|
|
|
58480
58507
|
fetched.push(...result.messages.map((message) => ({ message, path })));
|
|
58481
58508
|
} catch (error) {
|
|
58482
58509
|
failedMailboxes.push(path);
|
|
58510
|
+
failedMailboxReasons[path] = describeMailboxFailure(error);
|
|
58483
58511
|
console.error(
|
|
58484
58512
|
`IMAP ${listMode ? "list" : "search"} failed for account "${cfg.accountLabel}", mailbox "${path}": ${String(error)}`
|
|
58485
58513
|
);
|
|
58486
58514
|
}
|
|
58487
58515
|
}
|
|
58488
58516
|
if (failedMailboxes.length === paths.length) {
|
|
58517
|
+
const detail = failedMailboxes.map((path) => `${path} (${failedMailboxReasons[path]})`).join(", ");
|
|
58489
58518
|
throw new Error(
|
|
58490
|
-
`IMAP ${listMode ? "list" : "search"} failed in every requested mailbox for account ${cfg.accountLabel}: ${
|
|
58519
|
+
`IMAP ${listMode ? "list" : "search"} failed in every requested mailbox for account ${cfg.accountLabel}: ${detail}.`
|
|
58491
58520
|
);
|
|
58492
58521
|
}
|
|
58493
58522
|
let ordered = fetched;
|
|
@@ -58509,7 +58538,7 @@ async function run(args, listMode, deps) {
|
|
|
58509
58538
|
const partial = failedMailboxes.length > 0;
|
|
58510
58539
|
const failureNote = partial ? `
|
|
58511
58540
|
|
|
58512
|
-
Partial result. Could not search mailbox(es): ${failedMailboxes.map((path) => `"${path}"`).join(", ")}.` : "";
|
|
58541
|
+
Partial result. Could not search mailbox(es): ${failedMailboxes.map((path) => `"${path}" (${failedMailboxReasons[path]})`).join(", ")}.` : "";
|
|
58513
58542
|
const verb = listMode ? "listed" : "matched";
|
|
58514
58543
|
const scope = unscopedSearch ? allMailboxCount === 1 ? `mailbox "${paths[0]}"` : `${allMailboxCount} selectable mailboxes` : `mailbox "${paths[0]}"`;
|
|
58515
58544
|
if (messages.length === 0) {
|
|
@@ -58518,14 +58547,22 @@ Partial result. Could not search mailbox(es): ${failedMailboxes.map((path) => `"
|
|
|
58518
58547
|
messages,
|
|
58519
58548
|
count: 0,
|
|
58520
58549
|
partial,
|
|
58521
|
-
failedMailboxes
|
|
58550
|
+
failedMailboxes,
|
|
58551
|
+
failedMailboxReasons
|
|
58522
58552
|
};
|
|
58523
58553
|
}
|
|
58524
58554
|
const text = `Found ${rows.length} message(s) via IMAP (server-side, account ${cfg.accountLabel}, ${scope}; ${totalMatched} total ${verb}):
|
|
58525
58555
|
` + rows.join("\n") + `
|
|
58526
58556
|
|
|
58527
58557
|
Note: these IMAP IDs (imap:\u2026) work with get-message and the message mutations (mark/flag/move/delete-message), which route back to IMAP.` + failureNote;
|
|
58528
|
-
return {
|
|
58558
|
+
return {
|
|
58559
|
+
text,
|
|
58560
|
+
messages,
|
|
58561
|
+
count: messages.length,
|
|
58562
|
+
partial,
|
|
58563
|
+
failedMailboxes,
|
|
58564
|
+
failedMailboxReasons
|
|
58565
|
+
};
|
|
58529
58566
|
},
|
|
58530
58567
|
true
|
|
58531
58568
|
);
|
|
@@ -58612,7 +58649,13 @@ function imapMailStats(deps = {}) {
|
|
|
58612
58649
|
);
|
|
58613
58650
|
}
|
|
58614
58651
|
function errText(e) {
|
|
58615
|
-
|
|
58652
|
+
if (!(e instanceof Error)) return String(e);
|
|
58653
|
+
const x = e;
|
|
58654
|
+
if (typeof x.responseStatus !== "string" || !x.responseStatus) return e.message;
|
|
58655
|
+
const code = typeof x.serverResponseCode === "string" && x.serverResponseCode;
|
|
58656
|
+
const text = typeof x.responseText === "string" ? x.responseText.trim() : "";
|
|
58657
|
+
const server = [x.responseStatus, code ? `[${code}]` : "", text].filter(Boolean).join(" ");
|
|
58658
|
+
return e.message && e.message !== "Command failed" ? `${e.message}: ${server}` : server;
|
|
58616
58659
|
}
|
|
58617
58660
|
function assertMutated(result, what) {
|
|
58618
58661
|
if (!result) throw new Error(`${what}: server rejected the command (IMAP NO/BAD)`);
|
|
@@ -58764,20 +58807,30 @@ function withClient(deps, fn) {
|
|
|
58764
58807
|
return useClient(deps, fn);
|
|
58765
58808
|
}
|
|
58766
58809
|
async function resolveMailbox(client, name) {
|
|
58767
|
-
const wanted = name.trim().toLowerCase();
|
|
58768
58810
|
const boxes = await client.list();
|
|
58769
|
-
|
|
58770
|
-
|
|
58771
|
-
|
|
58772
|
-
|
|
58773
|
-
if (
|
|
58774
|
-
|
|
58811
|
+
return matchMailbox(boxes, name);
|
|
58812
|
+
}
|
|
58813
|
+
function matchMailbox(boxes, name) {
|
|
58814
|
+
const exact = boxes.find((b) => b.path === name || b.path === name.trim());
|
|
58815
|
+
if (exact) return { kind: "found", path: exact.path };
|
|
58816
|
+
const wanted = mailboxNameKey(name);
|
|
58817
|
+
const tiers = [
|
|
58818
|
+
boxes.filter((b) => mailboxNameKey(b.path) === wanted),
|
|
58819
|
+
boxes.filter((b) => mailboxNameKey(b.name) === wanted)
|
|
58820
|
+
];
|
|
58821
|
+
for (const hits of tiers) {
|
|
58822
|
+
const paths = [...new Set(hits.map((b) => b.path))];
|
|
58823
|
+
if (paths.length === 1) return { kind: "found", path: paths[0] };
|
|
58824
|
+
if (paths.length > 1) return { kind: "ambiguous", candidates: paths.sort() };
|
|
58775
58825
|
}
|
|
58776
58826
|
return { kind: "none" };
|
|
58777
58827
|
}
|
|
58778
58828
|
function ambiguousMailboxError(name, candidates, accountLabel) {
|
|
58779
58829
|
const where = accountLabel ? ` on IMAP account ${accountLabel}` : "";
|
|
58780
|
-
|
|
58830
|
+
const listed = candidates.map((c) => `"${c}"`).join(" and ");
|
|
58831
|
+
const indistinguishable = new Set(candidates.map(mailboxNameKey)).size === 1;
|
|
58832
|
+
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.";
|
|
58833
|
+
return `Mailbox "${name}" is ambiguous${where} \u2014 it matches ${listed}.${hint}`;
|
|
58781
58834
|
}
|
|
58782
58835
|
async function findMailboxPathOrThrow(client, name) {
|
|
58783
58836
|
const res = await resolveMailbox(client, name);
|
|
@@ -58786,6 +58839,15 @@ async function findMailboxPathOrThrow(client, name) {
|
|
|
58786
58839
|
}
|
|
58787
58840
|
function imapCreateMailbox(name, deps = {}) {
|
|
58788
58841
|
return withClient(deps, async (client) => {
|
|
58842
|
+
let twin;
|
|
58843
|
+
try {
|
|
58844
|
+
const wanted = nfc(name);
|
|
58845
|
+
twin = (await client.list()).find((b) => nfc(b.path) === wanted)?.path;
|
|
58846
|
+
} catch {
|
|
58847
|
+
}
|
|
58848
|
+
if (twin !== void 0) {
|
|
58849
|
+
return { success: true, info: `Mailbox "${twin}" already existed.` };
|
|
58850
|
+
}
|
|
58789
58851
|
try {
|
|
58790
58852
|
const res = await client.mailboxCreate(name);
|
|
58791
58853
|
return res.created ? { success: true, info: `Created mailbox "${res.path}".` } : { success: true, info: `Mailbox "${res.path}" already existed.` };
|
|
@@ -58837,6 +58899,14 @@ function imapRenameMailbox(oldName, newName, deps = {}) {
|
|
|
58837
58899
|
};
|
|
58838
58900
|
}
|
|
58839
58901
|
const path = found.path;
|
|
58902
|
+
const wantedNew = nfc(newName);
|
|
58903
|
+
const clash = (await client.list()).find((b) => b.path !== path && nfc(b.path) === wantedNew);
|
|
58904
|
+
if (clash) {
|
|
58905
|
+
return {
|
|
58906
|
+
success: false,
|
|
58907
|
+
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).`
|
|
58908
|
+
};
|
|
58909
|
+
}
|
|
58840
58910
|
try {
|
|
58841
58911
|
const res = await client.mailboxRename(path, newName);
|
|
58842
58912
|
return { success: true, info: `Renamed "${res.path}" to "${res.newPath}" via IMAP.` };
|
|
@@ -59491,6 +59561,7 @@ var init_imapClient = __esm({
|
|
|
59491
59561
|
import_imapflow = __toESM(require_imap_flow(), 1);
|
|
59492
59562
|
init_smtpMailer();
|
|
59493
59563
|
init_docsUrls();
|
|
59564
|
+
init_mailboxName();
|
|
59494
59565
|
init_mimeParse();
|
|
59495
59566
|
init_headers();
|
|
59496
59567
|
init_auditLog();
|
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,16 +65633,21 @@ 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
|
}
|
|
@@ -65720,6 +65739,13 @@ function structuredRow(m, account, path) {
|
|
|
65720
65739
|
...env.messageId ? { messageId: env.messageId } : {}
|
|
65721
65740
|
};
|
|
65722
65741
|
}
|
|
65742
|
+
function describeMailboxFailure(error2) {
|
|
65743
|
+
const raw = errText(error2);
|
|
65744
|
+
const oneLine = raw.split("\n")[0].trim();
|
|
65745
|
+
const looksSensitive = /pass(word)?\s*[:=]|authorization:\s*\S|bearer\s+\S{10,}/i.test(oneLine);
|
|
65746
|
+
const safe = looksSensitive ? "IMAP error (detail redacted \u2014 response text looked like it might contain a credential)" : oneLine || "unknown error";
|
|
65747
|
+
return safe.length > 300 ? `${safe.slice(0, 300)}\u2026` : safe;
|
|
65748
|
+
}
|
|
65723
65749
|
function hasMailboxFlag(mailbox, wanted) {
|
|
65724
65750
|
const normalized = wanted.toLowerCase();
|
|
65725
65751
|
return [...mailbox.flags ?? []].some((flag) => flag.toLowerCase() === normalized);
|
|
@@ -65801,6 +65827,7 @@ async function run(args, listMode, deps) {
|
|
|
65801
65827
|
const newestPerMailbox = offset + limit;
|
|
65802
65828
|
const fetched = [];
|
|
65803
65829
|
const failedMailboxes = [];
|
|
65830
|
+
const failedMailboxReasons = {};
|
|
65804
65831
|
let totalMatched = 0;
|
|
65805
65832
|
for (const path of paths) {
|
|
65806
65833
|
try {
|
|
@@ -65809,14 +65836,16 @@ async function run(args, listMode, deps) {
|
|
|
65809
65836
|
fetched.push(...result.messages.map((message) => ({ message, path })));
|
|
65810
65837
|
} catch (error2) {
|
|
65811
65838
|
failedMailboxes.push(path);
|
|
65839
|
+
failedMailboxReasons[path] = describeMailboxFailure(error2);
|
|
65812
65840
|
console.error(
|
|
65813
65841
|
`IMAP ${listMode ? "list" : "search"} failed for account "${cfg.accountLabel}", mailbox "${path}": ${String(error2)}`
|
|
65814
65842
|
);
|
|
65815
65843
|
}
|
|
65816
65844
|
}
|
|
65817
65845
|
if (failedMailboxes.length === paths.length) {
|
|
65846
|
+
const detail = failedMailboxes.map((path) => `${path} (${failedMailboxReasons[path]})`).join(", ");
|
|
65818
65847
|
throw new Error(
|
|
65819
|
-
`IMAP ${listMode ? "list" : "search"} failed in every requested mailbox for account ${cfg.accountLabel}: ${
|
|
65848
|
+
`IMAP ${listMode ? "list" : "search"} failed in every requested mailbox for account ${cfg.accountLabel}: ${detail}.`
|
|
65820
65849
|
);
|
|
65821
65850
|
}
|
|
65822
65851
|
let ordered = fetched;
|
|
@@ -65838,7 +65867,7 @@ async function run(args, listMode, deps) {
|
|
|
65838
65867
|
const partial2 = failedMailboxes.length > 0;
|
|
65839
65868
|
const failureNote = partial2 ? `
|
|
65840
65869
|
|
|
65841
|
-
Partial result. Could not search mailbox(es): ${failedMailboxes.map((path) => `"${path}"`).join(", ")}.` : "";
|
|
65870
|
+
Partial result. Could not search mailbox(es): ${failedMailboxes.map((path) => `"${path}" (${failedMailboxReasons[path]})`).join(", ")}.` : "";
|
|
65842
65871
|
const verb = listMode ? "listed" : "matched";
|
|
65843
65872
|
const scope = unscopedSearch ? allMailboxCount === 1 ? `mailbox "${paths[0]}"` : `${allMailboxCount} selectable mailboxes` : `mailbox "${paths[0]}"`;
|
|
65844
65873
|
if (messages.length === 0) {
|
|
@@ -65847,14 +65876,22 @@ Partial result. Could not search mailbox(es): ${failedMailboxes.map((path) => `"
|
|
|
65847
65876
|
messages,
|
|
65848
65877
|
count: 0,
|
|
65849
65878
|
partial: partial2,
|
|
65850
|
-
failedMailboxes
|
|
65879
|
+
failedMailboxes,
|
|
65880
|
+
failedMailboxReasons
|
|
65851
65881
|
};
|
|
65852
65882
|
}
|
|
65853
65883
|
const text = `Found ${rows.length} message(s) via IMAP (server-side, account ${cfg.accountLabel}, ${scope}; ${totalMatched} total ${verb}):
|
|
65854
65884
|
` + rows.join("\n") + `
|
|
65855
65885
|
|
|
65856
65886
|
Note: these IMAP IDs (imap:\u2026) work with get-message and the message mutations (mark/flag/move/delete-message), which route back to IMAP.` + failureNote;
|
|
65857
|
-
return {
|
|
65887
|
+
return {
|
|
65888
|
+
text,
|
|
65889
|
+
messages,
|
|
65890
|
+
count: messages.length,
|
|
65891
|
+
partial: partial2,
|
|
65892
|
+
failedMailboxes,
|
|
65893
|
+
failedMailboxReasons
|
|
65894
|
+
};
|
|
65858
65895
|
},
|
|
65859
65896
|
true
|
|
65860
65897
|
);
|
|
@@ -65941,7 +65978,13 @@ function imapMailStats(deps = {}) {
|
|
|
65941
65978
|
);
|
|
65942
65979
|
}
|
|
65943
65980
|
function errText(e) {
|
|
65944
|
-
|
|
65981
|
+
if (!(e instanceof Error)) return String(e);
|
|
65982
|
+
const x = e;
|
|
65983
|
+
if (typeof x.responseStatus !== "string" || !x.responseStatus) return e.message;
|
|
65984
|
+
const code = typeof x.serverResponseCode === "string" && x.serverResponseCode;
|
|
65985
|
+
const text = typeof x.responseText === "string" ? x.responseText.trim() : "";
|
|
65986
|
+
const server2 = [x.responseStatus, code ? `[${code}]` : "", text].filter(Boolean).join(" ");
|
|
65987
|
+
return e.message && e.message !== "Command failed" ? `${e.message}: ${server2}` : server2;
|
|
65945
65988
|
}
|
|
65946
65989
|
function assertMutated(result, what) {
|
|
65947
65990
|
if (!result) throw new Error(`${what}: server rejected the command (IMAP NO/BAD)`);
|
|
@@ -66093,20 +66136,30 @@ function withClient(deps, fn) {
|
|
|
66093
66136
|
return useClient(deps, fn);
|
|
66094
66137
|
}
|
|
66095
66138
|
async function resolveMailbox(client, name) {
|
|
66096
|
-
const wanted = name.trim().toLowerCase();
|
|
66097
66139
|
const boxes = await client.list();
|
|
66098
|
-
|
|
66099
|
-
|
|
66100
|
-
|
|
66101
|
-
|
|
66102
|
-
if (
|
|
66103
|
-
|
|
66140
|
+
return matchMailbox(boxes, name);
|
|
66141
|
+
}
|
|
66142
|
+
function matchMailbox(boxes, name) {
|
|
66143
|
+
const exact = boxes.find((b) => b.path === name || b.path === name.trim());
|
|
66144
|
+
if (exact) return { kind: "found", path: exact.path };
|
|
66145
|
+
const wanted = mailboxNameKey(name);
|
|
66146
|
+
const tiers = [
|
|
66147
|
+
boxes.filter((b) => mailboxNameKey(b.path) === wanted),
|
|
66148
|
+
boxes.filter((b) => mailboxNameKey(b.name) === wanted)
|
|
66149
|
+
];
|
|
66150
|
+
for (const hits of tiers) {
|
|
66151
|
+
const paths = [...new Set(hits.map((b) => b.path))];
|
|
66152
|
+
if (paths.length === 1) return { kind: "found", path: paths[0] };
|
|
66153
|
+
if (paths.length > 1) return { kind: "ambiguous", candidates: paths.sort() };
|
|
66104
66154
|
}
|
|
66105
66155
|
return { kind: "none" };
|
|
66106
66156
|
}
|
|
66107
66157
|
function ambiguousMailboxError(name, candidates, accountLabel) {
|
|
66108
66158
|
const where = accountLabel ? ` on IMAP account ${accountLabel}` : "";
|
|
66109
|
-
|
|
66159
|
+
const listed = candidates.map((c) => `"${c}"`).join(" and ");
|
|
66160
|
+
const indistinguishable = new Set(candidates.map(mailboxNameKey)).size === 1;
|
|
66161
|
+
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.";
|
|
66162
|
+
return `Mailbox "${name}" is ambiguous${where} \u2014 it matches ${listed}.${hint}`;
|
|
66110
66163
|
}
|
|
66111
66164
|
async function findMailboxPathOrThrow(client, name) {
|
|
66112
66165
|
const res = await resolveMailbox(client, name);
|
|
@@ -66115,6 +66168,15 @@ async function findMailboxPathOrThrow(client, name) {
|
|
|
66115
66168
|
}
|
|
66116
66169
|
function imapCreateMailbox(name, deps = {}) {
|
|
66117
66170
|
return withClient(deps, async (client) => {
|
|
66171
|
+
let twin;
|
|
66172
|
+
try {
|
|
66173
|
+
const wanted = nfc(name);
|
|
66174
|
+
twin = (await client.list()).find((b) => nfc(b.path) === wanted)?.path;
|
|
66175
|
+
} catch {
|
|
66176
|
+
}
|
|
66177
|
+
if (twin !== void 0) {
|
|
66178
|
+
return { success: true, info: `Mailbox "${twin}" already existed.` };
|
|
66179
|
+
}
|
|
66118
66180
|
try {
|
|
66119
66181
|
const res = await client.mailboxCreate(name);
|
|
66120
66182
|
return res.created ? { success: true, info: `Created mailbox "${res.path}".` } : { success: true, info: `Mailbox "${res.path}" already existed.` };
|
|
@@ -66166,6 +66228,14 @@ function imapRenameMailbox(oldName, newName, deps = {}) {
|
|
|
66166
66228
|
};
|
|
66167
66229
|
}
|
|
66168
66230
|
const path = found.path;
|
|
66231
|
+
const wantedNew = nfc(newName);
|
|
66232
|
+
const clash = (await client.list()).find((b) => b.path !== path && nfc(b.path) === wantedNew);
|
|
66233
|
+
if (clash) {
|
|
66234
|
+
return {
|
|
66235
|
+
success: false,
|
|
66236
|
+
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).`
|
|
66237
|
+
};
|
|
66238
|
+
}
|
|
66169
66239
|
try {
|
|
66170
66240
|
const res = await client.mailboxRename(path, newName);
|
|
66171
66241
|
return { success: true, info: `Renamed "${res.path}" to "${res.newPath}" via IMAP.` };
|
|
@@ -66820,6 +66890,7 @@ var init_imapClient = __esm({
|
|
|
66820
66890
|
import_imapflow = __toESM(require_imap_flow(), 1);
|
|
66821
66891
|
init_smtpMailer();
|
|
66822
66892
|
init_docsUrls();
|
|
66893
|
+
init_mailboxName();
|
|
66823
66894
|
init_mimeParse();
|
|
66824
66895
|
init_headers();
|
|
66825
66896
|
init_auditLog();
|
|
@@ -81913,6 +81984,7 @@ function executeAppleScript(script, options = {}) {
|
|
|
81913
81984
|
init_docsUrls();
|
|
81914
81985
|
init_mimeParse();
|
|
81915
81986
|
init_headers();
|
|
81987
|
+
init_mailboxName();
|
|
81916
81988
|
|
|
81917
81989
|
// src/services/templateStore.ts
|
|
81918
81990
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
@@ -82548,23 +82620,28 @@ function mailboxLeaf(path) {
|
|
|
82548
82620
|
function resolveAppleMailboxPath(mailbox, actualPaths) {
|
|
82549
82621
|
if (actualPaths.length === 0) return mailbox;
|
|
82550
82622
|
const candidates = [mailbox, ...MAILBOX_ALIASES[mailbox.toLowerCase()] ?? []];
|
|
82623
|
+
const ambiguous = (matches) => {
|
|
82624
|
+
const sorted = [...new Set(matches)].sort();
|
|
82625
|
+
const paths = sorted.map((path) => `"${path}"`).join(" and ");
|
|
82626
|
+
const twins = new Set(sorted.map(mailboxNameKey)).size === 1;
|
|
82627
|
+
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.";
|
|
82628
|
+
return new Error(`Mailbox "${mailbox}" is ambiguous \u2014 it matches ${paths}.${hint}`);
|
|
82629
|
+
};
|
|
82551
82630
|
for (const candidate of candidates) {
|
|
82552
82631
|
const exact = actualPaths.find((path) => path === candidate);
|
|
82553
82632
|
if (exact) return exact;
|
|
82554
|
-
const
|
|
82555
|
-
|
|
82633
|
+
const key = mailboxNameKey(candidate);
|
|
82634
|
+
const folded = [...new Set(actualPaths.filter((path) => mailboxNameKey(path) === key))];
|
|
82635
|
+
if (folded.length === 1) return folded[0];
|
|
82636
|
+
if (folded.length > 1) throw ambiguous(folded);
|
|
82556
82637
|
}
|
|
82557
82638
|
for (const candidate of candidates) {
|
|
82558
|
-
const
|
|
82559
|
-
|
|
82560
|
-
|
|
82639
|
+
const key = mailboxNameKey(candidate);
|
|
82640
|
+
const leafMatches = [
|
|
82641
|
+
...new Set(actualPaths.filter((path) => mailboxNameKey(mailboxLeaf(path)) === key))
|
|
82642
|
+
];
|
|
82561
82643
|
if (leafMatches.length === 1) return leafMatches[0];
|
|
82562
|
-
if (leafMatches.length > 1)
|
|
82563
|
-
const paths = [...leafMatches].sort().map((path) => `"${path}"`).join(" and ");
|
|
82564
|
-
throw new Error(
|
|
82565
|
-
`Mailbox "${mailbox}" is ambiguous \u2014 it matches ${paths}. Pass the full path.`
|
|
82566
|
-
);
|
|
82567
|
-
}
|
|
82644
|
+
if (leafMatches.length > 1) throw ambiguous(leafMatches);
|
|
82568
82645
|
}
|
|
82569
82646
|
return mailbox;
|
|
82570
82647
|
}
|
|
@@ -87359,6 +87436,7 @@ async function fanOutImapMessages(args, kind, deps = {}, configs = resolveImapCo
|
|
|
87359
87436
|
const accountsQueried = [];
|
|
87360
87437
|
const accountsFailed = [];
|
|
87361
87438
|
const failedMailboxes = [];
|
|
87439
|
+
const failedMailboxReasons = {};
|
|
87362
87440
|
for (const config2 of configs) {
|
|
87363
87441
|
const perAccountArgs = { ...args, account: void 0 };
|
|
87364
87442
|
try {
|
|
@@ -87368,12 +87446,15 @@ async function fanOutImapMessages(args, kind, deps = {}, configs = resolveImapCo
|
|
|
87368
87446
|
failedMailboxes.push(
|
|
87369
87447
|
...res.failedMailboxes.map((mailbox) => `${config2.accountLabel} / ${mailbox}`)
|
|
87370
87448
|
);
|
|
87449
|
+
for (const [mailbox, reason] of Object.entries(res.failedMailboxReasons)) {
|
|
87450
|
+
failedMailboxReasons[`${config2.accountLabel} / ${mailbox}`] = reason;
|
|
87451
|
+
}
|
|
87371
87452
|
} catch (e) {
|
|
87372
87453
|
accountsFailed.push(config2.accountLabel);
|
|
87373
87454
|
console.error(`IMAP fan-out failed for account "${config2.accountLabel}": ${String(e)}`);
|
|
87374
87455
|
}
|
|
87375
87456
|
}
|
|
87376
|
-
return { rows, accountsQueried, accountsFailed, failedMailboxes };
|
|
87457
|
+
return { rows, accountsQueried, accountsFailed, failedMailboxes, failedMailboxReasons };
|
|
87377
87458
|
}
|
|
87378
87459
|
function configMatchesAccount(config2, account) {
|
|
87379
87460
|
const name = account.name.trim().toLowerCase();
|
|
@@ -87973,7 +88054,10 @@ var LIST_OUTPUT_SCHEMA = {
|
|
|
87973
88054
|
skippedLargeMailboxes: external_exports.array(external_exports.string()).optional(),
|
|
87974
88055
|
notSearchedMailboxes: external_exports.array(external_exports.string()).optional(),
|
|
87975
88056
|
timedOutAccounts: external_exports.array(external_exports.string()).optional(),
|
|
87976
|
-
failedMailboxes: external_exports.array(external_exports.string()).optional()
|
|
88057
|
+
failedMailboxes: external_exports.array(external_exports.string()).optional(),
|
|
88058
|
+
// Underlying error text per entry in `failedMailboxes`, same keys (#246
|
|
88059
|
+
// follow-up) — declared so a client can rely on it rather than parse text.
|
|
88060
|
+
failedMailboxReasons: external_exports.record(external_exports.string(), external_exports.string()).optional()
|
|
87977
88061
|
};
|
|
87978
88062
|
var BATCH_COUNT_OUTPUT_SCHEMA = {
|
|
87979
88063
|
ok: external_exports.boolean().optional(),
|
|
@@ -88063,7 +88147,8 @@ function mergedMessageResponse(fan, apple, limit, verb) {
|
|
|
88063
88147
|
skippedLargeMailboxes: diagnostics.skippedLargeMailboxes,
|
|
88064
88148
|
notSearchedMailboxes: diagnostics.notSearchedMailboxes,
|
|
88065
88149
|
timedOutAccounts: diagnostics.timedOutAccounts,
|
|
88066
|
-
failedMailboxes: fan.failedMailboxes
|
|
88150
|
+
failedMailboxes: fan.failedMailboxes,
|
|
88151
|
+
failedMailboxReasons: fan.failedMailboxReasons
|
|
88067
88152
|
};
|
|
88068
88153
|
const coverageBlock = partialCoverageBlock(diagnostics);
|
|
88069
88154
|
if (merged.length === 0) {
|
|
@@ -88165,7 +88250,8 @@ registerTool(
|
|
|
88165
88250
|
messages: r.messages,
|
|
88166
88251
|
count: r.count,
|
|
88167
88252
|
partial: r.partial,
|
|
88168
|
-
failedMailboxes: r.failedMailboxes
|
|
88253
|
+
failedMailboxes: r.failedMailboxes,
|
|
88254
|
+
failedMailboxReasons: r.failedMailboxReasons
|
|
88169
88255
|
});
|
|
88170
88256
|
}
|
|
88171
88257
|
const fan = await fanOutImapMessages(imapArgs, "search");
|
|
@@ -88548,7 +88634,8 @@ ${r.text}`, {
|
|
|
88548
88634
|
messages: r.messages,
|
|
88549
88635
|
count: r.count,
|
|
88550
88636
|
partial: r.partial,
|
|
88551
|
-
failedMailboxes: r.failedMailboxes
|
|
88637
|
+
failedMailboxes: r.failedMailboxes,
|
|
88638
|
+
failedMailboxReasons: r.failedMailboxReasons
|
|
88552
88639
|
});
|
|
88553
88640
|
}
|
|
88554
88641
|
const fan = await fanOutImapMessages({ subject: base, mailbox, limit }, "search");
|
|
@@ -88585,7 +88672,8 @@ ${r.text}`, {
|
|
|
88585
88672
|
messages: orderedRows,
|
|
88586
88673
|
count: orderedRows.length,
|
|
88587
88674
|
partial: partial2,
|
|
88588
|
-
failedMailboxes: fan.failedMailboxes
|
|
88675
|
+
failedMailboxes: fan.failedMailboxes,
|
|
88676
|
+
failedMailboxReasons: fan.failedMailboxReasons
|
|
88589
88677
|
};
|
|
88590
88678
|
if (orderedRows.length === 0) {
|
|
88591
88679
|
return successResponse(`No messages found in thread "${base}".${coverage}`, structured2);
|
|
@@ -88649,7 +88737,8 @@ registerTool(
|
|
|
88649
88737
|
messages: r.messages,
|
|
88650
88738
|
count: r.count,
|
|
88651
88739
|
partial: r.partial,
|
|
88652
|
-
failedMailboxes: r.failedMailboxes
|
|
88740
|
+
failedMailboxes: r.failedMailboxes,
|
|
88741
|
+
failedMailboxReasons: r.failedMailboxReasons
|
|
88653
88742
|
});
|
|
88654
88743
|
}
|
|
88655
88744
|
const fan = await fanOutImapMessages({ mailbox, limit, offset, from, unreadOnly }, "list");
|
package/package.json
CHANGED