apple-mail-mcp 2.10.14 → 2.10.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 +39 -0
- package/build/index.js +426 -186
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -829,11 +829,35 @@ Save a message attachment to disk.
|
|
|
829
829
|
|
|
830
830
|
All batch operations accept an array of message IDs (max 100 per batch) and return per-item success/failure results.
|
|
831
831
|
|
|
832
|
+
**Numeric IDs are scoped to the mailbox you listed them from.** Mail.app numbers messages per
|
|
833
|
+
mailbox, so on a label store (Gmail, iCloud) one message answers to the same id in `INBOX`,
|
|
834
|
+
`Important` and `All Mail` at once — and deleting the `All Mail` copy is not the same operation as
|
|
835
|
+
deleting the `INBOX` copy. Each id is therefore bound to the mailbox it was listed/searched from and
|
|
836
|
+
the operation is applied only there, so **list or search the mailbox immediately before acting on
|
|
837
|
+
it**. An id the server hasn't seen listed is accepted only when exactly one mailbox holds it;
|
|
838
|
+
if several do, that id fails with the candidate mailboxes named instead of being applied to an
|
|
839
|
+
arbitrary copy. `imap:…` ids carry their own account + mailbox + UID and are never ambiguous.
|
|
840
|
+
|
|
841
|
+
**Say which mailbox with `sourceMailbox` / `sourceAccount`.** The binding above is remembered
|
|
842
|
+
per running server, so a client that reconnects, restarts, or replays a saved list of ids has
|
|
843
|
+
nothing recorded and every id takes the slower whole-tree path — where, on a label store, it is
|
|
844
|
+
likely to be refused as ambiguous. Passing the source mailbox explicitly is the reliable way to
|
|
845
|
+
stay scoped, and it overrides the remembered location. These parameters name where the ids **came
|
|
846
|
+
from**; for `batch-move-messages` that is distinct from `mailbox`, the destination.
|
|
847
|
+
|
|
848
|
+
`sourceMailbox` is the one that scopes, and it works on its own: omitting `sourceAccount` means
|
|
849
|
+
that mailbox **in the default account**, exactly as an omitted `account` does elsewhere. If the
|
|
850
|
+
default account cannot be determined, the ids fail with an error asking for an explicit
|
|
851
|
+
`sourceAccount` — a scope the server can't honor is never quietly downgraded to the guess-the-copy
|
|
852
|
+
walk. `sourceAccount` by itself pins nothing, since the mailbox is what an id is scoped to.
|
|
853
|
+
|
|
832
854
|
#### `batch-delete-messages`
|
|
833
855
|
|
|
834
856
|
| Parameter | Type | Required | Description |
|
|
835
857
|
|-----------|------|----------|-------------|
|
|
836
858
|
| `ids` | string[] | Yes | Message IDs to delete (max 100) |
|
|
859
|
+
| `sourceMailbox` | string | No | Mailbox the **numeric** ids were listed from — pins them to it. Ignored for `imap:` ids. |
|
|
860
|
+
| `sourceAccount` | string | No | Account the numeric ids were listed from. Defaults to the default account; on its own it pins nothing — pair it with `sourceMailbox`. |
|
|
837
861
|
|
|
838
862
|
**⚠️ Safety:** Destructive. Requires explicit user confirmation; search/list first to confirm the message ids.
|
|
839
863
|
|
|
@@ -844,12 +868,16 @@ All batch operations accept an array of message IDs (max 100 per batch) and retu
|
|
|
844
868
|
| `ids` | string[] | Yes | Message IDs to move (max 100) |
|
|
845
869
|
| `mailbox` | string | Yes | Destination mailbox |
|
|
846
870
|
| `account` | string | No | Account containing mailbox |
|
|
871
|
+
| `sourceMailbox` | string | No | Mailbox the **numeric** ids were listed from — pins them to it. Ignored for `imap:` ids. |
|
|
872
|
+
| `sourceAccount` | string | No | Account the numeric ids were listed from. Defaults to the default account; on its own it pins nothing — pair it with `sourceMailbox`. |
|
|
847
873
|
|
|
848
874
|
#### `batch-mark-as-read` / `batch-mark-as-unread`
|
|
849
875
|
|
|
850
876
|
| Parameter | Type | Required | Description |
|
|
851
877
|
|-----------|------|----------|-------------|
|
|
852
878
|
| `ids` | string[] | Yes | Message IDs (max 100) |
|
|
879
|
+
| `sourceMailbox` | string | No | Mailbox the **numeric** ids were listed from — pins them to it. Ignored for `imap:` ids. |
|
|
880
|
+
| `sourceAccount` | string | No | Account the numeric ids were listed from. Defaults to the default account; on its own it pins nothing — pair it with `sourceMailbox`. |
|
|
853
881
|
|
|
854
882
|
#### `batch-flag-messages` / `batch-unflag-messages`
|
|
855
883
|
|
|
@@ -857,6 +885,8 @@ All batch operations accept an array of message IDs (max 100 per batch) and retu
|
|
|
857
885
|
|-----------|------|----------|-------------|
|
|
858
886
|
| `ids` | string[] | Yes | Message IDs (max 100) |
|
|
859
887
|
| `color` | string | No | (`batch-flag-messages` only) Flag color — see [`flag-message`](#flag-message--unflag-message). Applied on both routes, so a mixed batch of numeric and `imap:` ids all end up colored. |
|
|
888
|
+
| `sourceMailbox` | string | No | Mailbox the **numeric** ids were listed from — pins them to it. Ignored for `imap:` ids. |
|
|
889
|
+
| `sourceAccount` | string | No | Account the numeric ids were listed from. Defaults to the default account; on its own it pins nothing — pair it with `sourceMailbox`. |
|
|
860
890
|
|
|
861
891
|
---
|
|
862
892
|
|
|
@@ -1438,6 +1468,15 @@ In a JSON string literal, `\\` — two characters — denotes **one** literal ba
|
|
|
1438
1468
|
- Message IDs change if the message is moved between mailboxes
|
|
1439
1469
|
- Use `search-messages` to find the current message ID
|
|
1440
1470
|
|
|
1471
|
+
### "... is present in more than one mailbox"
|
|
1472
|
+
- A bare numeric ID identifies a message only *within a mailbox*, and a label store (Gmail, iCloud)
|
|
1473
|
+
reports the same message under the same ID in `INBOX`, `Important` and `All Mail` at once. The
|
|
1474
|
+
server refuses rather than guessing which copy you meant.
|
|
1475
|
+
- Fix it by running `list-messages`/`search-messages` on the mailbox you actually want to act on,
|
|
1476
|
+
then using the IDs from that result — the operation is then scoped to that mailbox.
|
|
1477
|
+
- It only affects IDs the server hasn't seen listed (carried over from an earlier session, or typed
|
|
1478
|
+
by hand). `imap:…` IDs encode their own mailbox and never hit this.
|
|
1479
|
+
|
|
1441
1480
|
### `search-messages` says "Partial results" or skips a mailbox
|
|
1442
1481
|
- This is expected for very large IMAP/Gmail mailboxes (e.g. Gmail's `All Mail`, `Important`): Apple Mail can't scan them via AppleScript before timing out, so they're skipped and named in the result rather than silently returning empty.
|
|
1443
1482
|
- To search inside one, scope the call with `mailbox` **and** a `dateFrom`/`dateTo` window.
|
package/build/index.js
CHANGED
|
@@ -78237,6 +78237,8 @@ var CONTENT_MARKER = "CONTENT";
|
|
|
78237
78237
|
var MSGID_MARKER = "MSGID";
|
|
78238
78238
|
var HTML_MARKER = "HTML";
|
|
78239
78239
|
var BATCH_FATAL = "FATAL";
|
|
78240
|
+
var AMBIGUOUS_ID_PREFIX = "Message id ";
|
|
78241
|
+
var AMBIGUOUS_ID_BATCH = "This message id is present in more than one mailbox ";
|
|
78240
78242
|
function normalizeRfcMessageId(mid) {
|
|
78241
78243
|
return (mid || "").trim().replace(/^<+/, "").replace(/>+$/, "").trim();
|
|
78242
78244
|
}
|
|
@@ -78575,6 +78577,44 @@ var AppleMailManager = class {
|
|
|
78575
78577
|
if (oldest !== void 0) this.idLocationIndex.delete(oldest);
|
|
78576
78578
|
}
|
|
78577
78579
|
}
|
|
78580
|
+
/** Where a message id was last listed/searched from, if we've seen it. */
|
|
78581
|
+
locationFor(id) {
|
|
78582
|
+
return this.idLocationIndex.get(String(id));
|
|
78583
|
+
}
|
|
78584
|
+
/**
|
|
78585
|
+
* Publicly record where a message id lives.
|
|
78586
|
+
*
|
|
78587
|
+
* The index fills itself from list/search results, but that is per-process
|
|
78588
|
+
* state: a caller that carried ids across a process boundary (a stored triage
|
|
78589
|
+
* list, a scheduled job resuming) starts with an empty index, so every id is
|
|
78590
|
+
* "unlocated" and a label-store id gets refused as ambiguous. Registering the
|
|
78591
|
+
* known location restores scoped resolution.
|
|
78592
|
+
*/
|
|
78593
|
+
noteMessageLocation(id, account, mailbox) {
|
|
78594
|
+
this.rememberLocation(id, account, mailbox);
|
|
78595
|
+
}
|
|
78596
|
+
/**
|
|
78597
|
+
* AppleScript fragment resolving `account` + `mailbox` into `_tmb`, leaving
|
|
78598
|
+
* `_tmb` as `missing value` when it can't be pinned down. Exact-name match
|
|
78599
|
+
* only, and a name matching more than one mailbox resolves to nothing rather
|
|
78600
|
+
* than guessing — the same rule the move destination already applies.
|
|
78601
|
+
*/
|
|
78602
|
+
resolveMailboxFragment(account, mailbox) {
|
|
78603
|
+
const resolved = this.resolveMailbox(mailbox, account);
|
|
78604
|
+
return `
|
|
78605
|
+
set _tmb to missing value
|
|
78606
|
+
set _acctM to {}
|
|
78607
|
+
repeat with _a in accounts
|
|
78608
|
+
if (name of _a) is "${escapeForAppleScript(account)}" then set end of _acctM to _a
|
|
78609
|
+
end repeat
|
|
78610
|
+
if (count of _acctM) is 1 then
|
|
78611
|
+
set _mbM to {}
|
|
78612
|
+
repeat with _m in (mailboxes of (item 1 of _acctM))
|
|
78613
|
+
if (name of _m) is "${escapeForAppleScript(resolved)}" then set end of _mbM to _m
|
|
78614
|
+
end repeat
|
|
78615
|
+
if (count of _mbM) is 1 then set _tmb to item 1 of _mbM
|
|
78616
|
+
end if`;
|
|
78617
|
+
}
|
|
78578
78618
|
/**
|
|
78579
78619
|
* Returns cached accounts or fetches fresh data if cache is expired/empty.
|
|
78580
78620
|
*/
|
|
@@ -79781,24 +79821,63 @@ var AppleMailManager = class {
|
|
|
79781
79821
|
return true;
|
|
79782
79822
|
}
|
|
79783
79823
|
/**
|
|
79784
|
-
* Helper to find and operate on a message by ID
|
|
79824
|
+
* Helper to find and operate on a message by ID, scoped to the mailbox the id
|
|
79825
|
+
* was listed from.
|
|
79826
|
+
*
|
|
79827
|
+
* Mail.app numeric ids are per-mailbox, and on a label store (Gmail, iCloud)
|
|
79828
|
+
* ONE message is present in several mailboxes under the SAME id — INBOX,
|
|
79829
|
+
* "Important" and "All Mail" all report id 75816 for the same mail. This used
|
|
79830
|
+
* to walk every account's every mailbox and mutate the FIRST hit, so whichever
|
|
79831
|
+
* copy `mailboxes of <account>` happened to reach first won and the mailbox the
|
|
79832
|
+
* id was listed from lost whenever an alias came earlier in that (store-
|
|
79833
|
+
* dependent) order — the op reported success while the copy the caller meant
|
|
79834
|
+
* stayed put and a different one was moved/deleted (#152). See
|
|
79835
|
+
* runBatchOperation for the observed ordering on the reporting account.
|
|
79836
|
+
*
|
|
79837
|
+
* Which mailbox a mutation lands in is semantic — deleting the INBOX copy and
|
|
79838
|
+
* deleting the "All Mail" copy are different operations — so scope to the
|
|
79839
|
+
* mailbox the id actually came from (`idLocationIndex`, populated by every
|
|
79840
|
+
* list/search) and never guess.
|
|
79785
79841
|
*/
|
|
79786
79842
|
findMessageScript(id, operation) {
|
|
79843
|
+
const loc = this.locationFor(id);
|
|
79844
|
+
if (loc) {
|
|
79845
|
+
return buildAppLevelScript(`
|
|
79846
|
+
try
|
|
79847
|
+
${this.resolveMailboxFragment(loc.account, loc.mailbox)}
|
|
79848
|
+
if _tmb is missing value then return "error:Message not found"
|
|
79849
|
+
set matchingMsgs to (messages of _tmb whose id is ${Number(id)})
|
|
79850
|
+
if (count of matchingMsgs) > 0 then
|
|
79851
|
+
set msg to item 1 of matchingMsgs
|
|
79852
|
+
${operation}
|
|
79853
|
+
return "ok"
|
|
79854
|
+
end if
|
|
79855
|
+
return "error:Message not found"
|
|
79856
|
+
on error errMsg
|
|
79857
|
+
return "error:" & errMsg
|
|
79858
|
+
end try
|
|
79859
|
+
`);
|
|
79860
|
+
}
|
|
79787
79861
|
return buildAppLevelScript(`
|
|
79788
79862
|
try
|
|
79863
|
+
set _hits to {}
|
|
79864
|
+
set _names to ""
|
|
79789
79865
|
repeat with acct in accounts
|
|
79790
79866
|
repeat with mb in mailboxes of acct
|
|
79791
79867
|
try
|
|
79792
79868
|
set matchingMsgs to (messages of mb whose id is ${Number(id)})
|
|
79793
79869
|
if (count of matchingMsgs) > 0 then
|
|
79794
|
-
set
|
|
79795
|
-
|
|
79796
|
-
return "ok"
|
|
79870
|
+
set end of _hits to (item 1 of matchingMsgs)
|
|
79871
|
+
set _names to _names & (name of acct) & "/" & (name of mb) & ", "
|
|
79797
79872
|
end if
|
|
79798
79873
|
end try
|
|
79799
79874
|
end repeat
|
|
79800
79875
|
end repeat
|
|
79801
|
-
return "error:Message not found"
|
|
79876
|
+
if (count of _hits) is 0 then return "error:Message not found"
|
|
79877
|
+
if (count of _hits) > 1 then return "error:${AMBIGUOUS_ID_PREFIX}${Number(id)} is present in more than one mailbox (" & _names & "); list or search that mailbox first so the operation targets the right copy"
|
|
79878
|
+
set msg to item 1 of _hits
|
|
79879
|
+
${operation}
|
|
79880
|
+
return "ok"
|
|
79802
79881
|
on error errMsg
|
|
79803
79882
|
return "error:" & errMsg
|
|
79804
79883
|
end try
|
|
@@ -79983,6 +80062,31 @@ var AppleMailManager = class {
|
|
|
79983
80062
|
const targetMailbox = this.resolveMailbox(mailbox, targetAccount);
|
|
79984
80063
|
const safeMailbox = escapeForAppleScript(targetMailbox);
|
|
79985
80064
|
const safeAccount = escapeForAppleScript(targetAccount);
|
|
80065
|
+
const loc = this.locationFor(id);
|
|
80066
|
+
const findAndMove = loc ? `
|
|
80067
|
+
${this.resolveMailboxFragment(loc.account, loc.mailbox)}
|
|
80068
|
+
if _tmb is missing value then return "error:Message not found"
|
|
80069
|
+
set matchingMsgs to (messages of _tmb whose id is ${Number(id)})
|
|
80070
|
+
if (count of matchingMsgs) is 0 then return "error:Message not found"
|
|
80071
|
+
move (item 1 of matchingMsgs) to destMailbox
|
|
80072
|
+
return "ok"` : `
|
|
80073
|
+
set _hits to {}
|
|
80074
|
+
set _names to ""
|
|
80075
|
+
repeat with acct in accounts
|
|
80076
|
+
repeat with mb in (mailboxes of acct)
|
|
80077
|
+
try
|
|
80078
|
+
set matchingMsgs to (messages of mb whose id is ${Number(id)})
|
|
80079
|
+
if (count of matchingMsgs) > 0 then
|
|
80080
|
+
set end of _hits to (item 1 of matchingMsgs)
|
|
80081
|
+
set _names to _names & (name of acct) & "/" & (name of mb) & ", "
|
|
80082
|
+
end if
|
|
80083
|
+
end try
|
|
80084
|
+
end repeat
|
|
80085
|
+
end repeat
|
|
80086
|
+
if (count of _hits) is 0 then return "error:Message not found"
|
|
80087
|
+
if (count of _hits) > 1 then return "error:${AMBIGUOUS_ID_PREFIX}${Number(id)} is present in more than one mailbox (" & _names & "); list or search that mailbox first so the move targets the right copy"
|
|
80088
|
+
move (item 1 of _hits) to destMailbox
|
|
80089
|
+
return "ok"`;
|
|
79986
80090
|
const script = buildAppLevelScript(`
|
|
79987
80091
|
try
|
|
79988
80092
|
-- \`mailboxes of account\` is already flat: it includes nested mailboxes
|
|
@@ -79998,21 +80102,7 @@ var AppleMailManager = class {
|
|
|
79998
80102
|
if (count of destMatches) is 0 then return "error:Destination mailbox \\"" & destName & "\\" not found in account \\"${safeAccount}\\""
|
|
79999
80103
|
if (count of destMatches) > 1 then return "error:Destination mailbox \\"" & destName & "\\" is ambiguous (" & (count of destMatches) & " matches) in account \\"${safeAccount}\\"; disambiguate or move by full path"
|
|
80000
80104
|
set destMailbox to item 1 of destMatches
|
|
80001
|
-
|
|
80002
|
-
-- Find the message by id. The flat mailbox list already covers nested
|
|
80003
|
-
-- mailboxes, so this reaches messages in subfolders without recursing.
|
|
80004
|
-
repeat with acct in accounts
|
|
80005
|
-
repeat with mb in (mailboxes of acct)
|
|
80006
|
-
try
|
|
80007
|
-
set matchingMsgs to (messages of mb whose id is ${Number(id)})
|
|
80008
|
-
if (count of matchingMsgs) > 0 then
|
|
80009
|
-
move (item 1 of matchingMsgs) to destMailbox
|
|
80010
|
-
return "ok"
|
|
80011
|
-
end if
|
|
80012
|
-
end try
|
|
80013
|
-
end repeat
|
|
80014
|
-
end repeat
|
|
80015
|
-
return "error:Message not found"
|
|
80105
|
+
${findAndMove}
|
|
80016
80106
|
on error errMsg
|
|
80017
80107
|
return "error:" & errMsg
|
|
80018
80108
|
end try
|
|
@@ -80040,23 +80130,76 @@ var AppleMailManager = class {
|
|
|
80040
80130
|
// ===========================================================================
|
|
80041
80131
|
// Batch Operations
|
|
80042
80132
|
// ===========================================================================
|
|
80133
|
+
/**
|
|
80134
|
+
* Turn a caller-supplied batch source scope into an account+mailbox pair.
|
|
80135
|
+
*
|
|
80136
|
+
* `mailbox` is what does the scoping, and it works ON ITS OWN: an omitted
|
|
80137
|
+
* `account` means "the default account" here exactly as it does for every
|
|
80138
|
+
* other tool in this server (see resolveAccount), never "ignore the argument
|
|
80139
|
+
* you were given". Requiring both used to make a lone `sourceMailbox` a silent
|
|
80140
|
+
* no-op — the ids fell back to the whole-tree path and were then refused as
|
|
80141
|
+
* ambiguous, which is the very failure the parameter exists to prevent.
|
|
80142
|
+
*
|
|
80143
|
+
* The safety property is absolute: when the account cannot be determined there
|
|
80144
|
+
* is NO fallback to the scan-and-guess walk. The caller gets an error naming
|
|
80145
|
+
* the mailbox it asked for, so it can retry with an explicit `sourceAccount`.
|
|
80146
|
+
* (An `account` with no `mailbox` cannot pin anything, so it scopes nothing —
|
|
80147
|
+
* those ids still go through the index / ambiguity-checked path.)
|
|
80148
|
+
*/
|
|
80149
|
+
resolveBatchScope(scope) {
|
|
80150
|
+
const mailbox = scope?.mailbox?.trim();
|
|
80151
|
+
if (!mailbox) return { kind: "none" };
|
|
80152
|
+
const account = scope?.account?.trim();
|
|
80153
|
+
if (account) return { kind: "scoped", account, mailbox };
|
|
80154
|
+
const known = this.getCachedAccounts();
|
|
80155
|
+
if (known.length === 0) {
|
|
80156
|
+
return {
|
|
80157
|
+
kind: "unresolvable",
|
|
80158
|
+
error: `Cannot scope to source mailbox "${mailbox}": no sourceAccount was given and no Mail account could be read (Mail returned none, or the AppleScript transport failed). Retry with an explicit sourceAccount.`
|
|
80159
|
+
};
|
|
80160
|
+
}
|
|
80161
|
+
const chosen = this.resolveAccount();
|
|
80162
|
+
if (!known.some((a) => a.name === chosen)) {
|
|
80163
|
+
return {
|
|
80164
|
+
kind: "unresolvable",
|
|
80165
|
+
error: `Cannot scope to source mailbox "${mailbox}": no sourceAccount was given and the default account could not be determined. Retry with an explicit sourceAccount (available: ${known.map((a) => a.name).join(", ")}).`
|
|
80166
|
+
};
|
|
80167
|
+
}
|
|
80168
|
+
return { kind: "scoped", account: chosen, mailbox };
|
|
80169
|
+
}
|
|
80043
80170
|
/**
|
|
80044
80171
|
* Run one operation over many message IDs in a SINGLE osascript invocation.
|
|
80045
80172
|
*
|
|
80046
80173
|
* Previously each batch method looped and called the per-id method, so a
|
|
80047
80174
|
* 100-id batch spawned 100 osascript processes — each one re-resolving
|
|
80048
80175
|
* accounts and walking the whole account→mailbox tree — all serialized
|
|
80049
|
-
* through the gate (issue #31).
|
|
80050
|
-
*
|
|
80051
|
-
*
|
|
80052
|
-
*
|
|
80053
|
-
*
|
|
80054
|
-
* `notfound`, or `error:<msg>`), and results are returned in input order.
|
|
80176
|
+
* through the gate (issue #31). Still one osascript invocation, but the ids
|
|
80177
|
+
* are now grouped by the mailbox they were listed from and each group opens
|
|
80178
|
+
* exactly that one mailbox. Per-id outcomes come back as control-char
|
|
80179
|
+
* delimited `position<FS>status` records (status: `ok`, `notfound`, or
|
|
80180
|
+
* `error:<msg>`), and results are returned in input order.
|
|
80055
80181
|
*
|
|
80056
|
-
*
|
|
80057
|
-
*
|
|
80182
|
+
* Scoping is a CORRECTNESS requirement, not an optimization (#152). A Mail.app
|
|
80183
|
+
* numeric id is unique only within a mailbox, and a label store (Gmail,
|
|
80184
|
+
* iCloud) exposes one message in several mailboxes under the same id — INBOX,
|
|
80185
|
+
* "Important" and "All Mail" all report id 75816 for the same mail. The old
|
|
80186
|
+
* tree walk applied `operation` to the FIRST mailbox that matched while
|
|
80187
|
+
* iterating `mailboxes of <account>`, so whichever copy that iteration reached
|
|
80188
|
+
* first won — and the ids' real source mailbox lost whenever an alias came
|
|
80189
|
+
* earlier. Observed on the reporting account (`list-mailboxes`, 2026-08-13):
|
|
80190
|
+
* INBOX 1, "[Gmail]/All Mail" 5, "[Gmail]/Important" 9, "Sales Spam" 12 — so a
|
|
80191
|
+
* batch listed from "Sales Spam" was applied to the All Mail copies while the
|
|
80192
|
+
* Sales Spam messages stayed put, and every id still reported `ok`. That order
|
|
80193
|
+
* is a property of the store, not a guarantee: do not rely on it in either
|
|
80194
|
+
* direction — any mailbox the walk reaches late loses the same way. Grouping by
|
|
80195
|
+
* recorded source mailbox makes the op land on the copy the caller actually
|
|
80196
|
+
* listed; ids with no recorded mailbox are refused when ambiguous rather than
|
|
80197
|
+
* applied to an arbitrary copy.
|
|
80198
|
+
*
|
|
80199
|
+
* `setup` runs once up front (used by move to resolve the destination); it may
|
|
80200
|
+
* bail the whole batch by returning a `BATCH_FATAL`-prefixed string.
|
|
80058
80201
|
*/
|
|
80059
|
-
runBatchOperation(ids, operation, setup = "") {
|
|
80202
|
+
runBatchOperation(ids, operation, setup = "", scope) {
|
|
80060
80203
|
const valid = [];
|
|
80061
80204
|
for (const id of ids) {
|
|
80062
80205
|
const num = Number(id);
|
|
@@ -80065,39 +80208,101 @@ var AppleMailManager = class {
|
|
|
80065
80208
|
if (valid.length === 0) {
|
|
80066
80209
|
return ids.map((id) => ({ id, success: false, error: "Invalid message ID" }));
|
|
80067
80210
|
}
|
|
80068
|
-
const
|
|
80069
|
-
|
|
80070
|
-
|
|
80071
|
-
|
|
80072
|
-
|
|
80073
|
-
|
|
80074
|
-
|
|
80211
|
+
const resolved = this.resolveBatchScope(scope);
|
|
80212
|
+
if (resolved.kind === "unresolvable") {
|
|
80213
|
+
return ids.map((id) => ({ id, success: false, error: resolved.error }));
|
|
80214
|
+
}
|
|
80215
|
+
const callerScope = resolved.kind === "scoped" ? resolved : void 0;
|
|
80216
|
+
const groups = /* @__PURE__ */ new Map();
|
|
80217
|
+
const unlocated = [];
|
|
80218
|
+
valid.forEach((v, i) => {
|
|
80219
|
+
const pos = i + 1;
|
|
80220
|
+
const loc = callerScope ?? this.locationFor(v.id);
|
|
80221
|
+
if (!loc) {
|
|
80222
|
+
unlocated.push({ num: v.num, pos });
|
|
80223
|
+
return;
|
|
80224
|
+
}
|
|
80225
|
+
const key = `${loc.account}\0${loc.mailbox}`;
|
|
80226
|
+
const g = groups.get(key) ?? { account: loc.account, mailbox: loc.mailbox, items: [] };
|
|
80227
|
+
g.items.push({ num: v.num, pos });
|
|
80228
|
+
groups.set(key, g);
|
|
80229
|
+
});
|
|
80230
|
+
const asList = (nums) => `{${nums.join(", ")}}`;
|
|
80231
|
+
const scopedBlocks = [...groups.values()].map(
|
|
80232
|
+
(g) => `
|
|
80233
|
+
${this.resolveMailboxFragment(g.account, g.mailbox)}
|
|
80234
|
+
set _gids to ${asList(g.items.map((it) => it.num))}
|
|
80235
|
+
set _gpos to ${asList(g.items.map((it) => it.pos))}
|
|
80236
|
+
if _tmb is missing value then
|
|
80237
|
+
repeat with _k from 1 to (count of _gpos)
|
|
80238
|
+
set _out to _out & ((item _k of _gpos) as string) & "${FIELD_SEP}error:source mailbox \\"${escapeForAppleScript(g.mailbox)}\\" not found in account \\"${escapeForAppleScript(g.account)}\\"${RECORD_SEP}"
|
|
80239
|
+
end repeat
|
|
80240
|
+
else
|
|
80241
|
+
repeat with _k from 1 to (count of _gids)
|
|
80242
|
+
set _idx to item _k of _gpos
|
|
80243
|
+
try
|
|
80244
|
+
set _m to (messages of _tmb whose id is (item _k of _gids))
|
|
80245
|
+
if (count of _m) > 0 then
|
|
80246
|
+
set _msg to item 1 of _m
|
|
80247
|
+
${operation}
|
|
80248
|
+
set _out to _out & (_idx as string) & "${FIELD_SEP}ok${RECORD_SEP}"
|
|
80249
|
+
else
|
|
80250
|
+
set _out to _out & (_idx as string) & "${FIELD_SEP}notfound${RECORD_SEP}"
|
|
80251
|
+
end if
|
|
80252
|
+
on error _e
|
|
80253
|
+
set _out to _out & (_idx as string) & "${FIELD_SEP}error:" & _e & "${RECORD_SEP}"
|
|
80254
|
+
end try
|
|
80255
|
+
end repeat
|
|
80256
|
+
end if`
|
|
80257
|
+
).join("\n");
|
|
80258
|
+
const unlocatedBlock = unlocated.length ? `
|
|
80259
|
+
set _uids to ${asList(unlocated.map((it) => it.num))}
|
|
80260
|
+
set _upos to ${asList(unlocated.map((it) => it.pos))}
|
|
80261
|
+
set _ucount to count of _uids
|
|
80262
|
+
set _uhit to {}
|
|
80263
|
+
set _umsg to {}
|
|
80264
|
+
set _unames to {}
|
|
80265
|
+
repeat with _k from 1 to _ucount
|
|
80266
|
+
set end of _uhit to 0
|
|
80267
|
+
set end of _umsg to missing value
|
|
80268
|
+
set end of _unames to ""
|
|
80269
|
+
end repeat
|
|
80075
80270
|
repeat with acct in accounts
|
|
80076
|
-
if (count of _done) is _total then exit repeat
|
|
80077
80271
|
repeat with mb in (mailboxes of acct)
|
|
80078
|
-
|
|
80079
|
-
|
|
80080
|
-
|
|
80081
|
-
|
|
80082
|
-
|
|
80083
|
-
set
|
|
80084
|
-
|
|
80085
|
-
|
|
80086
|
-
|
|
80087
|
-
set end of _done to _idx
|
|
80088
|
-
set _out to _out & (_idx as string) & "${FIELD_SEP}ok${RECORD_SEP}"
|
|
80089
|
-
end if
|
|
80090
|
-
on error _e
|
|
80091
|
-
set end of _done to _idx
|
|
80092
|
-
set _out to _out & (_idx as string) & "${FIELD_SEP}error:" & _e & "${RECORD_SEP}"
|
|
80093
|
-
end try
|
|
80094
|
-
end if
|
|
80272
|
+
repeat with _k from 1 to _ucount
|
|
80273
|
+
try
|
|
80274
|
+
set _m to (messages of mb whose id is (item _k of _uids))
|
|
80275
|
+
if (count of _m) > 0 then
|
|
80276
|
+
set item _k of _uhit to ((item _k of _uhit) + 1)
|
|
80277
|
+
if (item _k of _uhit) is 1 then set item _k of _umsg to (item 1 of _m)
|
|
80278
|
+
set item _k of _unames to ((item _k of _unames) & (name of acct) & "/" & (name of mb) & ", ")
|
|
80279
|
+
end if
|
|
80280
|
+
end try
|
|
80095
80281
|
end repeat
|
|
80096
80282
|
end repeat
|
|
80097
80283
|
end repeat
|
|
80098
|
-
repeat with
|
|
80099
|
-
|
|
80100
|
-
|
|
80284
|
+
repeat with _k from 1 to _ucount
|
|
80285
|
+
set _idx to item _k of _upos
|
|
80286
|
+
if (item _k of _uhit) is 0 then
|
|
80287
|
+
set _out to _out & (_idx as string) & "${FIELD_SEP}notfound${RECORD_SEP}"
|
|
80288
|
+
else if (item _k of _uhit) > 1 then
|
|
80289
|
+
set _out to _out & (_idx as string) & "${FIELD_SEP}error:${AMBIGUOUS_ID_BATCH}(" & (item _k of _unames) & "); list or search that mailbox first so the operation targets the right copy${RECORD_SEP}"
|
|
80290
|
+
else
|
|
80291
|
+
try
|
|
80292
|
+
set _msg to item _k of _umsg
|
|
80293
|
+
${operation}
|
|
80294
|
+
set _out to _out & (_idx as string) & "${FIELD_SEP}ok${RECORD_SEP}"
|
|
80295
|
+
on error _e
|
|
80296
|
+
set _out to _out & (_idx as string) & "${FIELD_SEP}error:" & _e & "${RECORD_SEP}"
|
|
80297
|
+
end try
|
|
80298
|
+
end if
|
|
80299
|
+
end repeat` : "";
|
|
80300
|
+
const script = buildAppLevelScript(`
|
|
80301
|
+
try
|
|
80302
|
+
${setup}
|
|
80303
|
+
set _out to ""
|
|
80304
|
+
${scopedBlocks}
|
|
80305
|
+
${unlocatedBlock}
|
|
80101
80306
|
return _out
|
|
80102
80307
|
on error errMsg
|
|
80103
80308
|
return "${BATCH_FATAL}" & errMsg
|
|
@@ -80140,8 +80345,8 @@ var AppleMailManager = class {
|
|
|
80140
80345
|
/**
|
|
80141
80346
|
* Delete multiple messages at once (single tree walk — see runBatchOperation).
|
|
80142
80347
|
*/
|
|
80143
|
-
batchDeleteMessages(ids) {
|
|
80144
|
-
return this.runBatchOperation(ids, "delete _msg");
|
|
80348
|
+
batchDeleteMessages(ids, scope) {
|
|
80349
|
+
return this.runBatchOperation(ids, "delete _msg", "", scope);
|
|
80145
80350
|
}
|
|
80146
80351
|
/**
|
|
80147
80352
|
* Move multiple messages to a mailbox at once (single tree walk).
|
|
@@ -80150,7 +80355,7 @@ var AppleMailManager = class {
|
|
|
80150
80355
|
* matching more than one mailbox fails the whole batch rather than guessing),
|
|
80151
80356
|
* then every matched message is moved in the same walk.
|
|
80152
80357
|
*/
|
|
80153
|
-
batchMoveMessages(ids, mailbox, account) {
|
|
80358
|
+
batchMoveMessages(ids, mailbox, account, scope) {
|
|
80154
80359
|
const targetAccount = this.resolveAccount(account);
|
|
80155
80360
|
const targetMailbox = this.resolveMailbox(mailbox, targetAccount);
|
|
80156
80361
|
const safeMailbox = escapeForAppleScript(targetMailbox);
|
|
@@ -80164,31 +80369,31 @@ var AppleMailManager = class {
|
|
|
80164
80369
|
if (count of destMatches) is 0 then return "${BATCH_FATAL}Destination mailbox \\"" & destName & "\\" not found in account \\"${safeAccount}\\""
|
|
80165
80370
|
if (count of destMatches) > 1 then return "${BATCH_FATAL}Destination mailbox \\"" & destName & "\\" is ambiguous (" & (count of destMatches) & " matches) in account \\"${safeAccount}\\"; move by full path"
|
|
80166
80371
|
set destMailbox to item 1 of destMatches`;
|
|
80167
|
-
return this.runBatchOperation(ids, "move _msg to destMailbox", setup);
|
|
80372
|
+
return this.runBatchOperation(ids, "move _msg to destMailbox", setup, scope);
|
|
80168
80373
|
}
|
|
80169
80374
|
/**
|
|
80170
80375
|
* Mark multiple messages as read at once (single tree walk).
|
|
80171
80376
|
*/
|
|
80172
|
-
batchMarkAsRead(ids) {
|
|
80173
|
-
return this.runBatchOperation(ids, "set read status of _msg to true");
|
|
80377
|
+
batchMarkAsRead(ids, scope) {
|
|
80378
|
+
return this.runBatchOperation(ids, "set read status of _msg to true", "", scope);
|
|
80174
80379
|
}
|
|
80175
80380
|
/**
|
|
80176
80381
|
* Mark multiple messages as unread at once (single tree walk).
|
|
80177
80382
|
*/
|
|
80178
|
-
batchMarkAsUnread(ids) {
|
|
80179
|
-
return this.runBatchOperation(ids, "set read status of _msg to false");
|
|
80383
|
+
batchMarkAsUnread(ids, scope) {
|
|
80384
|
+
return this.runBatchOperation(ids, "set read status of _msg to false", "", scope);
|
|
80180
80385
|
}
|
|
80181
80386
|
/**
|
|
80182
80387
|
* Flag multiple messages at once (single tree walk).
|
|
80183
80388
|
*/
|
|
80184
|
-
batchFlagMessages(ids, colorIndex) {
|
|
80185
|
-
return this.runBatchOperation(ids, this.flagOperation("_msg", colorIndex));
|
|
80389
|
+
batchFlagMessages(ids, colorIndex, scope) {
|
|
80390
|
+
return this.runBatchOperation(ids, this.flagOperation("_msg", colorIndex), "", scope);
|
|
80186
80391
|
}
|
|
80187
80392
|
/**
|
|
80188
80393
|
* Unflag multiple messages at once (single tree walk).
|
|
80189
80394
|
*/
|
|
80190
|
-
batchUnflagMessages(ids) {
|
|
80191
|
-
return this.runBatchOperation(ids, "set flagged status of _msg to false");
|
|
80395
|
+
batchUnflagMessages(ids, scope) {
|
|
80396
|
+
return this.runBatchOperation(ids, "set flagged status of _msg to false", "", scope);
|
|
80192
80397
|
}
|
|
80193
80398
|
/**
|
|
80194
80399
|
* List attachments for a message.
|
|
@@ -82853,6 +83058,57 @@ function withErrorHandling(handler, errorPrefix) {
|
|
|
82853
83058
|
};
|
|
82854
83059
|
}
|
|
82855
83060
|
|
|
83061
|
+
// src/tools/batchResults.ts
|
|
83062
|
+
async function hybridBatchCounts(ids, appleFn, imapFn) {
|
|
83063
|
+
const imapIds = ids.filter((i) => i.startsWith("imap:"));
|
|
83064
|
+
const numericIds = ids.filter((i) => !i.startsWith("imap:"));
|
|
83065
|
+
let success = 0;
|
|
83066
|
+
let fail = 0;
|
|
83067
|
+
const errors = [];
|
|
83068
|
+
if (numericIds.length > 0) {
|
|
83069
|
+
const res = appleFn(numericIds);
|
|
83070
|
+
const s = res.filter((r) => r.success).length;
|
|
83071
|
+
success += s;
|
|
83072
|
+
fail += res.length - s;
|
|
83073
|
+
errors.push(...res.filter((r) => !r.success && r.error).map((r) => r.error));
|
|
83074
|
+
}
|
|
83075
|
+
if (imapIds.length > 0) {
|
|
83076
|
+
const r = await imapFn(imapIds);
|
|
83077
|
+
success += r.success;
|
|
83078
|
+
fail += r.failed;
|
|
83079
|
+
errors.push(...r.errors);
|
|
83080
|
+
}
|
|
83081
|
+
return { success, fail, errors };
|
|
83082
|
+
}
|
|
83083
|
+
function distinctErrors(errors) {
|
|
83084
|
+
return [...new Set(errors.filter(Boolean))];
|
|
83085
|
+
}
|
|
83086
|
+
function formatBatchErrors(errors, max = 5) {
|
|
83087
|
+
const distinct = distinctErrors(errors);
|
|
83088
|
+
if (distinct.length === 0) return "";
|
|
83089
|
+
const shown = distinct.slice(0, max);
|
|
83090
|
+
const more = distinct.length - shown.length;
|
|
83091
|
+
return `: ${shown.join("; ")}${more > 0 ? ` (+${more} more)` : ""}`;
|
|
83092
|
+
}
|
|
83093
|
+
var MAX_STRUCTURED_BATCH_ERRORS = 20;
|
|
83094
|
+
function batchResponse(counts, messages, extra = {}) {
|
|
83095
|
+
const { success, fail, errors } = counts;
|
|
83096
|
+
const distinct = distinctErrors(errors);
|
|
83097
|
+
const reported = distinct.slice(0, MAX_STRUCTURED_BATCH_ERRORS);
|
|
83098
|
+
const structured = {
|
|
83099
|
+
ok: fail === 0,
|
|
83100
|
+
success,
|
|
83101
|
+
failed: fail,
|
|
83102
|
+
...extra,
|
|
83103
|
+
...reported.length > 0 ? { errors: reported } : {},
|
|
83104
|
+
...distinct.length > reported.length ? { errorsTruncated: true } : {}
|
|
83105
|
+
};
|
|
83106
|
+
const suffix = formatBatchErrors(distinct);
|
|
83107
|
+
if (fail === 0) return successResponse(messages.allSucceeded(success), structured);
|
|
83108
|
+
if (success === 0) return errorResponse(`${messages.allFailed(fail)}${suffix}`, structured);
|
|
83109
|
+
return successResponse(`${messages.partial(success, fail)}${suffix}`, structured);
|
|
83110
|
+
}
|
|
83111
|
+
|
|
82856
83112
|
// src/services/imapMultiAccount.ts
|
|
82857
83113
|
function normalizeMessageId2(row) {
|
|
82858
83114
|
const raw = typeof row.messageId === "string" ? row.messageId.trim() : "";
|
|
@@ -83444,6 +83700,12 @@ function withJsonSchema2020_12(transport2) {
|
|
|
83444
83700
|
loadFileConfig();
|
|
83445
83701
|
var MESSAGE_ID_SCHEMA = external_exports.string().regex(/^(\d+|imap:[A-Za-z0-9_-]+)$/, "Message ID must be numeric or an IMAP id (imap:\u2026)");
|
|
83446
83702
|
var BATCH_IDS_SCHEMA = external_exports.array(MESSAGE_ID_SCHEMA).min(1, "At least one message ID is required").max(100, "Cannot process more than 100 messages in a single batch");
|
|
83703
|
+
var BATCH_SOURCE_MAILBOX_SCHEMA = external_exports.string().optional().describe(
|
|
83704
|
+
"Mailbox the numeric ids were listed from (e.g. 'INBOX'). Pins each id to that mailbox \u2014 strongly recommended, since one numeric id can match in several mailboxes. Works on its own: without sourceAccount it means that mailbox in the default account. Ignored for imap: ids."
|
|
83705
|
+
);
|
|
83706
|
+
var BATCH_SOURCE_ACCOUNT_SCHEMA = external_exports.string().optional().describe(
|
|
83707
|
+
"Account the numeric ids were listed from. Defaults to the default account. On its own it pins nothing \u2014 pair it with sourceMailbox."
|
|
83708
|
+
);
|
|
83447
83709
|
var FLAG_COLOR_INDEX = {
|
|
83448
83710
|
red: 0,
|
|
83449
83711
|
orange: 1,
|
|
@@ -83493,7 +83755,19 @@ var BATCH_COUNT_OUTPUT_SCHEMA = {
|
|
|
83493
83755
|
ok: external_exports.boolean().optional(),
|
|
83494
83756
|
success: external_exports.number().optional(),
|
|
83495
83757
|
failed: external_exports.number().optional(),
|
|
83496
|
-
mailbox: external_exports.string().optional()
|
|
83758
|
+
mailbox: external_exports.string().optional(),
|
|
83759
|
+
// Declared so the failure channel is part of the tool's advertised CONTRACT:
|
|
83760
|
+
// a client can rely on `errors` being string[] and code against it, and it
|
|
83761
|
+
// shows up in generated types and docs. Declaring is not what makes it
|
|
83762
|
+
// deliverable — registerTool() wraps every outputSchema in
|
|
83763
|
+
// `z.object(shape).passthrough()`, so these tools advertise
|
|
83764
|
+
// `additionalProperties: true` (verified against the built server) and an
|
|
83765
|
+
// undeclared key would be carried through, not rejected. Enumerating it is a
|
|
83766
|
+
// promise to callers, not a workaround for a validator.
|
|
83767
|
+
errors: external_exports.array(external_exports.string()).optional(),
|
|
83768
|
+
// Set when `errors` was capped (MAX_STRUCTURED_BATCH_ERRORS distinct reasons),
|
|
83769
|
+
// so a short list is never mistaken for the complete one.
|
|
83770
|
+
errorsTruncated: external_exports.boolean().optional()
|
|
83497
83771
|
};
|
|
83498
83772
|
var CHECK_ITEM_SCHEMA = external_exports.object({}).passthrough();
|
|
83499
83773
|
var require2 = createRequire(import.meta.url);
|
|
@@ -83574,26 +83848,6 @@ function registerTool(name, config2, cb) {
|
|
|
83574
83848
|
}
|
|
83575
83849
|
var mailManager = new AppleMailManager();
|
|
83576
83850
|
registerResourcesAndPrompts(server, mailManager);
|
|
83577
|
-
async function hybridBatchCounts(ids, appleFn, imapFn) {
|
|
83578
|
-
const imapIds = ids.filter((i) => i.startsWith("imap:"));
|
|
83579
|
-
const numericIds = ids.filter((i) => !i.startsWith("imap:"));
|
|
83580
|
-
let success = 0;
|
|
83581
|
-
let fail = 0;
|
|
83582
|
-
const errors = [];
|
|
83583
|
-
if (numericIds.length > 0) {
|
|
83584
|
-
const res = appleFn(numericIds);
|
|
83585
|
-
const s = res.filter((r) => r.success).length;
|
|
83586
|
-
success += s;
|
|
83587
|
-
fail += res.length - s;
|
|
83588
|
-
}
|
|
83589
|
-
if (imapIds.length > 0) {
|
|
83590
|
-
const r = await imapFn(imapIds);
|
|
83591
|
-
success += r.success;
|
|
83592
|
-
fail += r.failed;
|
|
83593
|
-
errors.push(...r.errors);
|
|
83594
|
-
}
|
|
83595
|
-
return { success, fail, errors };
|
|
83596
|
-
}
|
|
83597
83851
|
registerTool(
|
|
83598
83852
|
"search-messages",
|
|
83599
83853
|
{
|
|
@@ -84371,173 +84625,159 @@ registerTool(
|
|
|
84371
84625
|
registerTool(
|
|
84372
84626
|
"batch-delete-messages",
|
|
84373
84627
|
{
|
|
84374
|
-
description: "Use when: deleting multiple messages in one call (1\u2013100 ids; moves them to Trash).\nReturns: counts of how many were deleted and how many failed.\nDo not use when: deleting just one (use delete-message) or filing messages away (use batch-move-messages).\nSafety: destructive and applies to many messages at once \u2014 require explicit user confirmation, and search-messages/list-messages first to confirm every id is correct before deleting.",
|
|
84628
|
+
description: "Use when: deleting multiple messages in one call (1\u2013100 ids; moves them to Trash).\nReturns: counts of how many were deleted and how many failed, plus the distinct reasons for any failures.\nDo not use when: deleting just one (use delete-message) or filing messages away (use batch-move-messages).\nSafety: destructive and applies to many messages at once \u2014 require explicit user confirmation, and search-messages/list-messages first to confirm every id is correct before deleting. Pass sourceMailbox/sourceAccount (the mailbox you listed the ids from) so each numeric id is pinned to that mailbox; an id that matches in several mailboxes is refused, not guessed.",
|
|
84375
84629
|
inputSchema: {
|
|
84376
|
-
ids: BATCH_IDS_SCHEMA
|
|
84630
|
+
ids: BATCH_IDS_SCHEMA,
|
|
84631
|
+
sourceMailbox: BATCH_SOURCE_MAILBOX_SCHEMA,
|
|
84632
|
+
sourceAccount: BATCH_SOURCE_ACCOUNT_SCHEMA
|
|
84377
84633
|
},
|
|
84378
84634
|
outputSchema: BATCH_COUNT_OUTPUT_SCHEMA
|
|
84379
84635
|
},
|
|
84380
|
-
withErrorHandling(async ({ ids }) => {
|
|
84381
|
-
const
|
|
84636
|
+
withErrorHandling(async ({ ids, sourceMailbox, sourceAccount }) => {
|
|
84637
|
+
const counts = await hybridBatchCounts(
|
|
84382
84638
|
ids,
|
|
84383
|
-
(n) => mailManager.batchDeleteMessages(n),
|
|
84639
|
+
(n) => mailManager.batchDeleteMessages(n, { account: sourceAccount, mailbox: sourceMailbox }),
|
|
84384
84640
|
(im) => imapBatchDelete(im)
|
|
84385
84641
|
);
|
|
84386
|
-
|
|
84387
|
-
|
|
84388
|
-
|
|
84389
|
-
|
|
84390
|
-
|
|
84391
|
-
} else {
|
|
84392
|
-
return successResponse(`Deleted ${successCount} message(s), ${failCount} failed`, structured);
|
|
84393
|
-
}
|
|
84642
|
+
return batchResponse(counts, {
|
|
84643
|
+
allSucceeded: (n) => `Successfully deleted ${n} message(s)`,
|
|
84644
|
+
allFailed: (n) => `Failed to delete all ${n} message(s)`,
|
|
84645
|
+
partial: (ok, failed) => `Deleted ${ok} message(s), ${failed} failed`
|
|
84646
|
+
});
|
|
84394
84647
|
}, "Error batch deleting messages")
|
|
84395
84648
|
);
|
|
84396
84649
|
registerTool(
|
|
84397
84650
|
"batch-move-messages",
|
|
84398
84651
|
{
|
|
84399
|
-
description: "Use when: moving multiple messages (1\u2013100 ids) into the same destination mailbox/folder in one call, e.g. bulk archiving.\nReturns: counts of how many were moved and how many failed.\nDo not use when: moving just one (use move-message) or deleting (use batch-delete-messages). Use list-mailboxes to confirm the destination name exists.\nSafety: moves many real messages at once \u2014 confirm the destination mailbox, and search-messages/list-messages first to confirm the ids.",
|
|
84652
|
+
description: "Use when: moving multiple messages (1\u2013100 ids) into the same destination mailbox/folder in one call, e.g. bulk archiving.\nReturns: counts of how many were moved and how many failed, plus the distinct reasons for any failures.\nDo not use when: moving just one (use move-message) or deleting (use batch-delete-messages). Use list-mailboxes to confirm the destination name exists.\nSafety: moves many real messages at once \u2014 confirm the destination mailbox, and search-messages/list-messages first to confirm the ids. Pass sourceMailbox/sourceAccount (the mailbox you listed the ids from \u2014 not the destination) so each numeric id is pinned to that mailbox; an id that matches in several mailboxes is refused, not guessed.",
|
|
84400
84653
|
inputSchema: {
|
|
84401
84654
|
ids: BATCH_IDS_SCHEMA,
|
|
84402
84655
|
mailbox: external_exports.string().min(1, "Destination mailbox is required"),
|
|
84403
|
-
account: external_exports.string().optional().describe("Account containing the destination mailbox")
|
|
84656
|
+
account: external_exports.string().optional().describe("Account containing the destination mailbox"),
|
|
84657
|
+
sourceMailbox: BATCH_SOURCE_MAILBOX_SCHEMA,
|
|
84658
|
+
sourceAccount: BATCH_SOURCE_ACCOUNT_SCHEMA
|
|
84404
84659
|
},
|
|
84405
84660
|
outputSchema: BATCH_COUNT_OUTPUT_SCHEMA
|
|
84406
84661
|
},
|
|
84407
|
-
withErrorHandling(async ({ ids, mailbox, account }) => {
|
|
84408
|
-
const
|
|
84662
|
+
withErrorHandling(async ({ ids, mailbox, account, sourceMailbox, sourceAccount }) => {
|
|
84663
|
+
const counts = await hybridBatchCounts(
|
|
84409
84664
|
ids,
|
|
84410
|
-
(n) => mailManager.batchMoveMessages(n, mailbox, account
|
|
84665
|
+
(n) => mailManager.batchMoveMessages(n, mailbox, account, {
|
|
84666
|
+
account: sourceAccount,
|
|
84667
|
+
mailbox: sourceMailbox
|
|
84668
|
+
}),
|
|
84411
84669
|
(im) => imapBatchMove(im, mailbox, { account })
|
|
84412
84670
|
);
|
|
84413
|
-
|
|
84414
|
-
|
|
84415
|
-
|
|
84416
|
-
`Successfully moved ${
|
|
84417
|
-
|
|
84418
|
-
|
|
84419
|
-
|
|
84420
|
-
|
|
84421
|
-
|
|
84422
|
-
return successResponse(
|
|
84423
|
-
`Moved ${successCount} message(s) to "${mailbox}", ${failCount} failed`,
|
|
84424
|
-
structured
|
|
84425
|
-
);
|
|
84426
|
-
}
|
|
84671
|
+
return batchResponse(
|
|
84672
|
+
counts,
|
|
84673
|
+
{
|
|
84674
|
+
allSucceeded: (n) => `Successfully moved ${n} message(s) to "${mailbox}"`,
|
|
84675
|
+
allFailed: (n) => `Failed to move all ${n} message(s)`,
|
|
84676
|
+
partial: (ok, failed) => `Moved ${ok} message(s) to "${mailbox}", ${failed} failed`
|
|
84677
|
+
},
|
|
84678
|
+
{ mailbox }
|
|
84679
|
+
);
|
|
84427
84680
|
}, "Error batch moving messages")
|
|
84428
84681
|
);
|
|
84429
84682
|
registerTool(
|
|
84430
84683
|
"batch-mark-as-read",
|
|
84431
84684
|
{
|
|
84432
|
-
description: "Use when: marking multiple messages (1\u2013100 ids) as read in one call.\nReturns: counts of how many were marked read and how many failed.\nDo not use when: marking just one (use mark-as-read) or marking unread (use batch-mark-as-unread). Get the ids from search-messages or list-messages first.",
|
|
84685
|
+
description: "Use when: marking multiple messages (1\u2013100 ids) as read in one call.\nReturns: counts of how many were marked read and how many failed.\nDo not use when: marking just one (use mark-as-read) or marking unread (use batch-mark-as-unread). Get the ids from search-messages or list-messages first. Pass sourceMailbox/sourceAccount (the mailbox you listed the ids from) so each numeric id is pinned to that mailbox; an id that matches in several mailboxes is refused, not guessed.",
|
|
84433
84686
|
inputSchema: {
|
|
84434
|
-
ids: BATCH_IDS_SCHEMA
|
|
84687
|
+
ids: BATCH_IDS_SCHEMA,
|
|
84688
|
+
sourceMailbox: BATCH_SOURCE_MAILBOX_SCHEMA,
|
|
84689
|
+
sourceAccount: BATCH_SOURCE_ACCOUNT_SCHEMA
|
|
84435
84690
|
},
|
|
84436
84691
|
outputSchema: BATCH_COUNT_OUTPUT_SCHEMA
|
|
84437
84692
|
},
|
|
84438
|
-
withErrorHandling(async ({ ids }) => {
|
|
84439
|
-
const
|
|
84693
|
+
withErrorHandling(async ({ ids, sourceMailbox, sourceAccount }) => {
|
|
84694
|
+
const counts = await hybridBatchCounts(
|
|
84440
84695
|
ids,
|
|
84441
|
-
(n) => mailManager.batchMarkAsRead(n),
|
|
84696
|
+
(n) => mailManager.batchMarkAsRead(n, { account: sourceAccount, mailbox: sourceMailbox }),
|
|
84442
84697
|
(im) => imapBatchMarkRead(im)
|
|
84443
84698
|
);
|
|
84444
|
-
|
|
84445
|
-
|
|
84446
|
-
|
|
84447
|
-
|
|
84448
|
-
|
|
84449
|
-
} else {
|
|
84450
|
-
return successResponse(
|
|
84451
|
-
`Marked ${successCount} message(s) as read, ${failCount} failed`,
|
|
84452
|
-
structured
|
|
84453
|
-
);
|
|
84454
|
-
}
|
|
84699
|
+
return batchResponse(counts, {
|
|
84700
|
+
allSucceeded: (n) => `Successfully marked ${n} message(s) as read`,
|
|
84701
|
+
allFailed: (n) => `Failed to mark all ${n} message(s) as read`,
|
|
84702
|
+
partial: (ok, failed) => `Marked ${ok} message(s) as read, ${failed} failed`
|
|
84703
|
+
});
|
|
84455
84704
|
}, "Error batch marking messages as read")
|
|
84456
84705
|
);
|
|
84457
84706
|
registerTool(
|
|
84458
84707
|
"batch-mark-as-unread",
|
|
84459
84708
|
{
|
|
84460
|
-
description: "Use when: marking multiple messages (1\u2013100 ids) as unread in one call.\nReturns: counts of how many were marked unread and how many failed.\nDo not use when: marking just one (use mark-as-unread) or marking read (use batch-mark-as-read). Get the ids from search-messages or list-messages first.",
|
|
84709
|
+
description: "Use when: marking multiple messages (1\u2013100 ids) as unread in one call.\nReturns: counts of how many were marked unread and how many failed.\nDo not use when: marking just one (use mark-as-unread) or marking read (use batch-mark-as-read). Get the ids from search-messages or list-messages first. Pass sourceMailbox/sourceAccount (the mailbox you listed the ids from) so each numeric id is pinned to that mailbox; an id that matches in several mailboxes is refused, not guessed.",
|
|
84461
84710
|
inputSchema: {
|
|
84462
|
-
ids: BATCH_IDS_SCHEMA
|
|
84711
|
+
ids: BATCH_IDS_SCHEMA,
|
|
84712
|
+
sourceMailbox: BATCH_SOURCE_MAILBOX_SCHEMA,
|
|
84713
|
+
sourceAccount: BATCH_SOURCE_ACCOUNT_SCHEMA
|
|
84463
84714
|
},
|
|
84464
84715
|
outputSchema: BATCH_COUNT_OUTPUT_SCHEMA
|
|
84465
84716
|
},
|
|
84466
|
-
withErrorHandling(async ({ ids }) => {
|
|
84467
|
-
const
|
|
84717
|
+
withErrorHandling(async ({ ids, sourceMailbox, sourceAccount }) => {
|
|
84718
|
+
const counts = await hybridBatchCounts(
|
|
84468
84719
|
ids,
|
|
84469
|
-
(n) => mailManager.batchMarkAsUnread(n),
|
|
84720
|
+
(n) => mailManager.batchMarkAsUnread(n, { account: sourceAccount, mailbox: sourceMailbox }),
|
|
84470
84721
|
(im) => imapBatchMarkUnread(im)
|
|
84471
84722
|
);
|
|
84472
|
-
|
|
84473
|
-
|
|
84474
|
-
|
|
84475
|
-
|
|
84476
|
-
|
|
84477
|
-
);
|
|
84478
|
-
} else if (successCount === 0) {
|
|
84479
|
-
return errorResponse(`Failed to mark all ${failCount} message(s) as unread`);
|
|
84480
|
-
} else {
|
|
84481
|
-
return successResponse(
|
|
84482
|
-
`Marked ${successCount} message(s) as unread, ${failCount} failed`,
|
|
84483
|
-
structured
|
|
84484
|
-
);
|
|
84485
|
-
}
|
|
84723
|
+
return batchResponse(counts, {
|
|
84724
|
+
allSucceeded: (n) => `Successfully marked ${n} message(s) as unread`,
|
|
84725
|
+
allFailed: (n) => `Failed to mark all ${n} message(s) as unread`,
|
|
84726
|
+
partial: (ok, failed) => `Marked ${ok} message(s) as unread, ${failed} failed`
|
|
84727
|
+
});
|
|
84486
84728
|
}, "Error batch marking messages as unread")
|
|
84487
84729
|
);
|
|
84488
84730
|
registerTool(
|
|
84489
84731
|
"batch-flag-messages",
|
|
84490
84732
|
{
|
|
84491
|
-
description: "Use when: flagging multiple messages (1\u2013100 ids) in one call, optionally with a color (red/orange/yellow/green/blue/purple/gray).\nReturns: counts of how many were flagged and how many failed.\nDo not use when: flagging just one (use flag-message) or removing flags (use batch-unflag-messages). Get the ids from search-messages or list-messages first.\nNote: the color is applied on both routes \u2014 AppleScript sets the flag index, IMAP writes the equivalent $MailFlagBit0/1/2 keywords Mail.app reads \u2014 so a mixed batch of numeric and `imap:` ids all end up colored.",
|
|
84733
|
+
description: "Use when: flagging multiple messages (1\u2013100 ids) in one call, optionally with a color (red/orange/yellow/green/blue/purple/gray).\nReturns: counts of how many were flagged and how many failed.\nDo not use when: flagging just one (use flag-message) or removing flags (use batch-unflag-messages). Get the ids from search-messages or list-messages first. Pass sourceMailbox/sourceAccount (the mailbox you listed the ids from) so each numeric id is pinned to that mailbox; an id that matches in several mailboxes is refused, not guessed.\nNote: the color is applied on both routes \u2014 AppleScript sets the flag index, IMAP writes the equivalent $MailFlagBit0/1/2 keywords Mail.app reads \u2014 so a mixed batch of numeric and `imap:` ids all end up colored.",
|
|
84492
84734
|
inputSchema: {
|
|
84493
84735
|
ids: BATCH_IDS_SCHEMA,
|
|
84494
|
-
color: FLAG_COLOR_SCHEMA
|
|
84736
|
+
color: FLAG_COLOR_SCHEMA,
|
|
84737
|
+
sourceMailbox: BATCH_SOURCE_MAILBOX_SCHEMA,
|
|
84738
|
+
sourceAccount: BATCH_SOURCE_ACCOUNT_SCHEMA
|
|
84495
84739
|
},
|
|
84496
84740
|
outputSchema: BATCH_COUNT_OUTPUT_SCHEMA
|
|
84497
84741
|
},
|
|
84498
|
-
withErrorHandling(async ({ ids, color }) => {
|
|
84742
|
+
withErrorHandling(async ({ ids, color, sourceMailbox, sourceAccount }) => {
|
|
84499
84743
|
const colorIndex = color ? FLAG_COLOR_INDEX[color] : void 0;
|
|
84500
|
-
const
|
|
84744
|
+
const counts = await hybridBatchCounts(
|
|
84501
84745
|
ids,
|
|
84502
|
-
(n) => mailManager.batchFlagMessages(n, colorIndex
|
|
84746
|
+
(n) => mailManager.batchFlagMessages(n, colorIndex, {
|
|
84747
|
+
account: sourceAccount,
|
|
84748
|
+
mailbox: sourceMailbox
|
|
84749
|
+
}),
|
|
84503
84750
|
(im) => imapBatchFlag(im, colorIndex)
|
|
84504
84751
|
);
|
|
84505
|
-
|
|
84506
|
-
|
|
84507
|
-
|
|
84508
|
-
|
|
84509
|
-
|
|
84510
|
-
} else {
|
|
84511
|
-
return successResponse(`Flagged ${successCount} message(s), ${failCount} failed`, structured);
|
|
84512
|
-
}
|
|
84752
|
+
return batchResponse(counts, {
|
|
84753
|
+
allSucceeded: (n) => `Successfully flagged ${n} message(s)`,
|
|
84754
|
+
allFailed: (n) => `Failed to flag all ${n} message(s)`,
|
|
84755
|
+
partial: (ok, failed) => `Flagged ${ok} message(s), ${failed} failed`
|
|
84756
|
+
});
|
|
84513
84757
|
}, "Error batch flagging messages")
|
|
84514
84758
|
);
|
|
84515
84759
|
registerTool(
|
|
84516
84760
|
"batch-unflag-messages",
|
|
84517
84761
|
{
|
|
84518
|
-
description: "Use when: removing flags from multiple messages (1\u2013100 ids) in one call.\nReturns: counts of how many were unflagged and how many failed.\nDo not use when: unflagging just one (use unflag-message) or adding flags (use batch-flag-messages). Get the ids from search-messages or list-messages first.",
|
|
84762
|
+
description: "Use when: removing flags from multiple messages (1\u2013100 ids) in one call.\nReturns: counts of how many were unflagged and how many failed.\nDo not use when: unflagging just one (use unflag-message) or adding flags (use batch-flag-messages). Get the ids from search-messages or list-messages first. Pass sourceMailbox/sourceAccount (the mailbox you listed the ids from) so each numeric id is pinned to that mailbox; an id that matches in several mailboxes is refused, not guessed.",
|
|
84519
84763
|
inputSchema: {
|
|
84520
|
-
ids: BATCH_IDS_SCHEMA
|
|
84764
|
+
ids: BATCH_IDS_SCHEMA,
|
|
84765
|
+
sourceMailbox: BATCH_SOURCE_MAILBOX_SCHEMA,
|
|
84766
|
+
sourceAccount: BATCH_SOURCE_ACCOUNT_SCHEMA
|
|
84521
84767
|
},
|
|
84522
84768
|
outputSchema: BATCH_COUNT_OUTPUT_SCHEMA
|
|
84523
84769
|
},
|
|
84524
|
-
withErrorHandling(async ({ ids }) => {
|
|
84525
|
-
const
|
|
84770
|
+
withErrorHandling(async ({ ids, sourceMailbox, sourceAccount }) => {
|
|
84771
|
+
const counts = await hybridBatchCounts(
|
|
84526
84772
|
ids,
|
|
84527
|
-
(n) => mailManager.batchUnflagMessages(n),
|
|
84773
|
+
(n) => mailManager.batchUnflagMessages(n, { account: sourceAccount, mailbox: sourceMailbox }),
|
|
84528
84774
|
(im) => imapBatchUnflag(im)
|
|
84529
84775
|
);
|
|
84530
|
-
|
|
84531
|
-
|
|
84532
|
-
|
|
84533
|
-
|
|
84534
|
-
|
|
84535
|
-
} else {
|
|
84536
|
-
return successResponse(
|
|
84537
|
-
`Unflagged ${successCount} message(s), ${failCount} failed`,
|
|
84538
|
-
structured
|
|
84539
|
-
);
|
|
84540
|
-
}
|
|
84776
|
+
return batchResponse(counts, {
|
|
84777
|
+
allSucceeded: (n) => `Successfully unflagged ${n} message(s)`,
|
|
84778
|
+
allFailed: (n) => `Failed to unflag all ${n} message(s)`,
|
|
84779
|
+
partial: (ok, failed) => `Unflagged ${ok} message(s), ${failed} failed`
|
|
84780
|
+
});
|
|
84541
84781
|
}, "Error batch unflagging messages")
|
|
84542
84782
|
);
|
|
84543
84783
|
registerTool(
|
package/package.json
CHANGED