apple-mail-mcp 2.14.1 → 2.15.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 +17 -6
- package/build/index.js +78 -34
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1444,12 +1444,23 @@ Three more honesty rules:
|
|
|
1444
1444
|
than list positions — and `expected` stays comparable with the mailbox instead
|
|
1445
1445
|
of double-counting a duplicate into a false `over`.
|
|
1446
1446
|
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1447
|
+
**`imap:` ids are reconciled too, as of 2.15.0.** `batch-delete-messages` and
|
|
1448
|
+
`batch-move-messages` return the same `countDelta` structure on the IMAP path, so
|
|
1449
|
+
one shape covers both backends and a mixed batch reports an entry per source
|
|
1450
|
+
mailbox from whichever backend handled it. The entries are **concatenated, never
|
|
1451
|
+
summed** — Mail's own count can lag (#155) while the server's `STATUS` cannot, and
|
|
1452
|
+
averaging the two would hide which reading you were looking at.
|
|
1453
|
+
|
|
1454
|
+
Only the operations that actually remove messages from their source reconcile.
|
|
1455
|
+
`batch-mark-as-read` and the flag tools change no count, so emitting
|
|
1456
|
+
`expected: N, observed: 0` for them would manufacture an alarm; they report no
|
|
1457
|
+
`countDelta` at all.
|
|
1458
|
+
|
|
1459
|
+
Note the mis-targeting class `countDelta` was originally built for cannot occur
|
|
1460
|
+
on the IMAP path — a UID names exactly one message in exactly one mailbox — so
|
|
1461
|
+
there the value is effect confirmation rather than target confirmation.
|
|
1462
|
+
|
|
1463
|
+
Single-message tools carry a post-condition check instead. `delete-message` and
|
|
1453
1464
|
`move-message` on an `imap:` id return a **`verification`** object in
|
|
1454
1465
|
`structuredContent`:
|
|
1455
1466
|
|
package/build/index.js
CHANGED
|
@@ -78828,6 +78828,14 @@ function auditSnapshotChunk() {
|
|
|
78828
78828
|
if (!Number.isFinite(n) || n < 1) return DEFAULT_SNAPSHOT_CHUNK;
|
|
78829
78829
|
return Math.floor(n);
|
|
78830
78830
|
}
|
|
78831
|
+
function classifyCountStatus(readable, expected, observed) {
|
|
78832
|
+
if (!readable) return { status: "unknown", unknownReason: "count-unreadable" };
|
|
78833
|
+
if (expected === null) return { status: "unknown", unknownReason: "no-expectation" };
|
|
78834
|
+
if (observed === expected) return { status: "match" };
|
|
78835
|
+
if ((observed ?? 0) > expected) return { status: "over" };
|
|
78836
|
+
if (observed === 0) return { status: "unknown", unknownReason: "count-did-not-move" };
|
|
78837
|
+
return { status: "unknown", unknownReason: "count-partial" };
|
|
78838
|
+
}
|
|
78831
78839
|
function writeAuditRecord(record2) {
|
|
78832
78840
|
const path = auditLogPath();
|
|
78833
78841
|
if (!path) return;
|
|
@@ -79771,25 +79779,7 @@ ${indent}end try${this.sanitizeFragment("_uacct", indent)}${this.sanitizeFragmen
|
|
|
79771
79779
|
const readable = m.before >= 0 && m.after >= 0;
|
|
79772
79780
|
const observed = readable ? m.before - m.after : null;
|
|
79773
79781
|
const note = noteFor(m.account, m.mailbox);
|
|
79774
|
-
|
|
79775
|
-
let unknownReason;
|
|
79776
|
-
if (!readable) {
|
|
79777
|
-
status = "unknown";
|
|
79778
|
-
unknownReason = "count-unreadable";
|
|
79779
|
-
} else if (m.expected === null) {
|
|
79780
|
-
status = "unknown";
|
|
79781
|
-
unknownReason = "no-expectation";
|
|
79782
|
-
} else if (observed === m.expected) {
|
|
79783
|
-
status = "match";
|
|
79784
|
-
} else if ((observed ?? 0) > m.expected) {
|
|
79785
|
-
status = "over";
|
|
79786
|
-
} else if (observed === 0) {
|
|
79787
|
-
status = "unknown";
|
|
79788
|
-
unknownReason = "count-did-not-move";
|
|
79789
|
-
} else {
|
|
79790
|
-
status = "unknown";
|
|
79791
|
-
unknownReason = "count-partial";
|
|
79792
|
-
}
|
|
79782
|
+
const { status, unknownReason } = classifyCountStatus(readable, m.expected, observed);
|
|
79793
79783
|
return {
|
|
79794
79784
|
account: m.account,
|
|
79795
79785
|
mailbox: m.mailbox,
|
|
@@ -84475,7 +84465,15 @@ async function imapFetchAttachment(id, attachmentName, deps = {}) {
|
|
|
84475
84465
|
}
|
|
84476
84466
|
});
|
|
84477
84467
|
}
|
|
84478
|
-
async function
|
|
84468
|
+
async function mailboxCount(client, path) {
|
|
84469
|
+
try {
|
|
84470
|
+
const st = await client.status(path, { messages: true });
|
|
84471
|
+
return typeof st.messages === "number" ? st.messages : null;
|
|
84472
|
+
} catch {
|
|
84473
|
+
return null;
|
|
84474
|
+
}
|
|
84475
|
+
}
|
|
84476
|
+
async function imapBatch(ids, deps, op, opts = {}) {
|
|
84479
84477
|
const groups = /* @__PURE__ */ new Map();
|
|
84480
84478
|
const errors = [];
|
|
84481
84479
|
let failed = 0;
|
|
@@ -84492,15 +84490,39 @@ async function imapBatch(ids, deps, op) {
|
|
|
84492
84490
|
groups.set(key, g);
|
|
84493
84491
|
}
|
|
84494
84492
|
let success = 0;
|
|
84493
|
+
const countDelta = [];
|
|
84495
84494
|
for (const g of groups.values()) {
|
|
84496
84495
|
try {
|
|
84497
84496
|
await useClient(depsForAccount(g.account, deps), async (client) => {
|
|
84497
|
+
const before = opts.reconcile ? await mailboxCount(client, g.path) : null;
|
|
84498
84498
|
const lock = await client.getMailboxLock(g.path);
|
|
84499
84499
|
try {
|
|
84500
84500
|
await op(client, g.uids, g.path);
|
|
84501
84501
|
} finally {
|
|
84502
84502
|
lock.release();
|
|
84503
84503
|
}
|
|
84504
|
+
if (!opts.reconcile) return;
|
|
84505
|
+
const after = await mailboxCount(client, g.path);
|
|
84506
|
+
const readable = before !== null && after !== null;
|
|
84507
|
+
const observed = readable ? before - after : null;
|
|
84508
|
+
const { status, unknownReason } = classifyCountStatus(readable, g.uids.length, observed);
|
|
84509
|
+
countDelta.push({
|
|
84510
|
+
account: g.account,
|
|
84511
|
+
mailbox: g.path,
|
|
84512
|
+
before,
|
|
84513
|
+
after,
|
|
84514
|
+
expected: g.uids.length,
|
|
84515
|
+
observed,
|
|
84516
|
+
status,
|
|
84517
|
+
...unknownReason ? { unknownReason } : {},
|
|
84518
|
+
...unknownReason === "count-unreadable" ? { note: "The server did not answer STATUS for this mailbox" } : {},
|
|
84519
|
+
...unknownReason === "count-did-not-move" ? {
|
|
84520
|
+
note: `The mailbox count did not move. On a label store (Gmail) a message can stay visible in an all-mail view after being moved out of a label, so this is not by itself evidence the operation failed \u2014 check the destination.`
|
|
84521
|
+
} : {},
|
|
84522
|
+
...unknownReason === "count-partial" ? {
|
|
84523
|
+
note: `Fewer messages left than were operated on. \`observed\` is a LOWER BOUND on what left, not a count of what left \u2014 a concurrent delivery to this mailbox masks departures one-for-one.`
|
|
84524
|
+
} : {}
|
|
84525
|
+
});
|
|
84504
84526
|
});
|
|
84505
84527
|
success += g.uids.length;
|
|
84506
84528
|
} catch (e) {
|
|
@@ -84508,7 +84530,7 @@ async function imapBatch(ids, deps, op) {
|
|
|
84508
84530
|
errors.push(`${g.path}: ${errText(e)}`);
|
|
84509
84531
|
}
|
|
84510
84532
|
}
|
|
84511
|
-
return { success, failed, errors };
|
|
84533
|
+
return { success, failed, errors, ...countDelta.length ? { countDelta } : {} };
|
|
84512
84534
|
}
|
|
84513
84535
|
var imapBatchMarkRead = (ids, deps = {}) => imapBatch(ids, deps, async (c, uids) => {
|
|
84514
84536
|
assertMutated(
|
|
@@ -84543,17 +84565,27 @@ var imapBatchUnflag = (ids, deps = {}) => imapBatch(ids, deps, async (c, uids) =
|
|
|
84543
84565
|
`IMAP unflag of ${uids.length} message(s)`
|
|
84544
84566
|
);
|
|
84545
84567
|
});
|
|
84546
|
-
var imapBatchDelete = (ids, deps = {}) => imapBatch(
|
|
84547
|
-
|
|
84548
|
-
|
|
84568
|
+
var imapBatchDelete = (ids, deps = {}) => imapBatch(
|
|
84569
|
+
ids,
|
|
84570
|
+
deps,
|
|
84571
|
+
async (c, uids, path) => {
|
|
84572
|
+
await trashUids(c, uids, path);
|
|
84573
|
+
},
|
|
84574
|
+
{ reconcile: true }
|
|
84575
|
+
);
|
|
84549
84576
|
function imapBatchMove(ids, destMailbox, deps = {}) {
|
|
84550
|
-
return imapBatch(
|
|
84551
|
-
|
|
84552
|
-
|
|
84553
|
-
|
|
84554
|
-
|
|
84555
|
-
|
|
84556
|
-
|
|
84577
|
+
return imapBatch(
|
|
84578
|
+
ids,
|
|
84579
|
+
deps,
|
|
84580
|
+
async (c, uids) => {
|
|
84581
|
+
const dest = await findMailboxPathOrThrow(c, destMailbox) ?? resolveMailboxPath(destMailbox, "list");
|
|
84582
|
+
assertMutated(
|
|
84583
|
+
await c.messageMove(uids, dest, { uid: true }),
|
|
84584
|
+
`IMAP move of ${uids.length} message(s) to "${dest}"`
|
|
84585
|
+
);
|
|
84586
|
+
},
|
|
84587
|
+
{ reconcile: true }
|
|
84588
|
+
);
|
|
84557
84589
|
}
|
|
84558
84590
|
function senderName(from) {
|
|
84559
84591
|
const a = from?.[0];
|
|
@@ -84752,13 +84784,15 @@ async function hybridBatchCounts(ids, appleFn, imapFn) {
|
|
|
84752
84784
|
fail += res.length - s;
|
|
84753
84785
|
errors.push(...res.filter((r) => !r.success && r.error).map((r) => r.error));
|
|
84754
84786
|
}
|
|
84787
|
+
let countDelta;
|
|
84755
84788
|
if (imapIds.length > 0) {
|
|
84756
84789
|
const r = await imapFn(imapIds);
|
|
84757
84790
|
success += r.success;
|
|
84758
84791
|
fail += r.failed;
|
|
84759
84792
|
errors.push(...r.errors);
|
|
84793
|
+
if (r.countDelta?.length) countDelta = r.countDelta;
|
|
84760
84794
|
}
|
|
84761
|
-
return { success, fail, errors };
|
|
84795
|
+
return { success, fail, errors, ...countDelta ? { countDelta } : {} };
|
|
84762
84796
|
}
|
|
84763
84797
|
function distinctErrors(errors) {
|
|
84764
84798
|
return [...new Set(errors.filter(Boolean))];
|
|
@@ -84797,6 +84831,10 @@ ${warnings.join("\n")}` : "";
|
|
|
84797
84831
|
function toManagerScope(args) {
|
|
84798
84832
|
return { account: args.sourceAccount, mailbox: args.sourceMailbox };
|
|
84799
84833
|
}
|
|
84834
|
+
function mergeCountDeltas(apple, imap) {
|
|
84835
|
+
const all = [...apple ?? [], ...imap ?? []];
|
|
84836
|
+
return all.length ? { countDelta: all } : {};
|
|
84837
|
+
}
|
|
84800
84838
|
async function runBatchDelete(deps, args) {
|
|
84801
84839
|
const { ids, sourceMailbox, sourceAccount } = args;
|
|
84802
84840
|
let forensics = { warnings: [] };
|
|
@@ -84820,7 +84858,10 @@ async function runBatchDelete(deps, args) {
|
|
|
84820
84858
|
allFailed: (n) => `Failed to delete all ${n} message(s)`,
|
|
84821
84859
|
partial: (ok, failed) => `Deleted ${ok} message(s), ${failed} failed`
|
|
84822
84860
|
},
|
|
84823
|
-
|
|
84861
|
+
// #181: merge both backends' reconciliation. An AppleScript-only batch is
|
|
84862
|
+
// unchanged; an IMAP-only one now reports a delta where it previously
|
|
84863
|
+
// reported nothing; a mixed batch reports both, per source mailbox.
|
|
84864
|
+
mergeCountDeltas(forensics.countDelta, counts.countDelta),
|
|
84824
84865
|
forensics.warnings
|
|
84825
84866
|
);
|
|
84826
84867
|
}
|
|
@@ -84854,7 +84895,10 @@ async function runBatchMove(deps, args) {
|
|
|
84854
84895
|
allFailed: (n) => `Failed to move all ${n} message(s)`,
|
|
84855
84896
|
partial: (ok, failed) => `Moved ${ok} message(s) to "${mailbox}", ${failed} failed`
|
|
84856
84897
|
},
|
|
84857
|
-
{
|
|
84898
|
+
{
|
|
84899
|
+
mailbox,
|
|
84900
|
+
...mergeCountDeltas(forensics.countDelta, counts.countDelta)
|
|
84901
|
+
},
|
|
84858
84902
|
forensics.warnings
|
|
84859
84903
|
);
|
|
84860
84904
|
}
|
package/package.json
CHANGED