apple-mail-mcp 2.19.18 → 2.19.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -5
- package/build/cli.js +33 -10
- package/build/index.js +84 -30
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -240,6 +240,7 @@ Search for messages matching criteria. Searches all accounts by default.
|
|
|
240
240
|
| `dateFrom` | string | No | Start date filter (e.g., "January 1, 2026") |
|
|
241
241
|
| `dateTo` | string | No | End date filter (e.g., "March 1, 2026") |
|
|
242
242
|
| `limit` | number | No | Max results, 1–500 (default: 50) |
|
|
243
|
+
| `offset` | number | No | Newest matches to skip, ≥ 0 (for pagination). IMAP accounts only; without a `mailbox`, `offset` + `limit` may not exceed 5,000 |
|
|
243
244
|
|
|
244
245
|
**Returns:** List of matching messages with ID, date, subject, sender, and read state.
|
|
245
246
|
|
|
@@ -1103,14 +1104,23 @@ which made a filter-less `list-messages` fail on such a mailbox before 2.19.16.
|
|
|
1103
1104
|
**Very large IMAP mailboxes are read newest-first in bounded windows**
|
|
1104
1105
|
([#256](https://github.com/sweetrb/apple-mail-mcp/issues/256)). Above 10,000
|
|
1105
1106
|
messages, a filter-less `list-messages`/`search-messages` pages by message
|
|
1106
|
-
sequence number
|
|
1107
|
-
|
|
1108
|
-
`\Deleted` ones
|
|
1109
|
-
|
|
1107
|
+
sequence number: page `offset` of `limit` is sequence numbers
|
|
1108
|
+
`N-offset-limit+1 .. N-offset`, so it fetches UIDs and flags for that range
|
|
1109
|
+
alone (plus a small margin; `\Deleted` ones are skipped and the page topped up
|
|
1110
|
+
from below) and full rows for the page. The cost is the page, not the offset:
|
|
1111
|
+
`limit: 1` at offset 790,000 of a 793,614-message mailbox is one ~33-message
|
|
1112
|
+
`FETCH`, not a whole-mailbox `SEARCH` or a walk over every skipped message.
|
|
1113
|
+
Offsets count sequence positions. iCloud keeps messages awaiting expunge out of
|
|
1114
|
+
the sequence space, so there they are exact; on a server that keeps them in it,
|
|
1115
|
+
a page shifts by the number of such messages above it (they are never
|
|
1116
|
+
returned).
|
|
1110
1117
|
A filtered search there runs the same criteria over newest-first sequence
|
|
1111
1118
|
windows (5,000 messages, growing to 50,000) and stops once the page is full;
|
|
1112
1119
|
the reported total then reads `at least N` unless the walk reached the bottom
|
|
1113
|
-
of the mailbox. A
|
|
1120
|
+
of the mailbox. A filtered search's deep `offset` has no such shortcut — which
|
|
1121
|
+
messages match is only known by searching the skipped windows — so its cost
|
|
1122
|
+
still grows with the offset (in 50,000-message `SEARCH` windows); narrow it
|
|
1123
|
+
with `dateTo` instead. A `SEARCH` or `FETCH` that fails names the mailbox, its size and
|
|
1114
1124
|
the server's own reason (or a likely timeout) instead of reporting "no
|
|
1115
1125
|
messages".
|
|
1116
1126
|
|
package/build/cli.js
CHANGED
|
@@ -58049,6 +58049,7 @@ __export(imapClient_exports, {
|
|
|
58049
58049
|
MAX_COMPOSE_SOURCE_BYTES: () => MAX_COMPOSE_SOURCE_BYTES,
|
|
58050
58050
|
MAX_RFC822_FILE_BYTES: () => MAX_RFC822_FILE_BYTES,
|
|
58051
58051
|
MAX_RFC822_INLINE_BYTES: () => MAX_RFC822_INLINE_BYTES,
|
|
58052
|
+
MAX_UNSCOPED_SEARCH_DEPTH: () => MAX_UNSCOPED_SEARCH_DEPTH,
|
|
58052
58053
|
__resetPool: () => __resetPool,
|
|
58053
58054
|
__setPoolConnect: () => __setPoolConnect,
|
|
58054
58055
|
bodyStructureHasAttachments: () => bodyStructureHasAttachments,
|
|
@@ -58096,7 +58097,8 @@ __export(imapClient_exports, {
|
|
|
58096
58097
|
resolveImapConfig: () => resolveImapConfig,
|
|
58097
58098
|
resolveImapConfigs: () => resolveImapConfigs,
|
|
58098
58099
|
resolveMailboxPath: () => resolveMailboxPath,
|
|
58099
|
-
shouldUseImap: () => shouldUseImap
|
|
58100
|
+
shouldUseImap: () => shouldUseImap,
|
|
58101
|
+
unscopedSearchOffsetError: () => unscopedSearchOffsetError
|
|
58100
58102
|
});
|
|
58101
58103
|
import { createHash } from "node:crypto";
|
|
58102
58104
|
function encodeImapId(account, path, uid) {
|
|
@@ -58442,6 +58444,10 @@ function messageIdentity(entry) {
|
|
|
58442
58444
|
const messageId = raw.replace(/^<+|>+$/g, "").trim().toLowerCase();
|
|
58443
58445
|
return messageId ? `mid:${messageId}` : `${entry.path}\0${entry.message.uid}`;
|
|
58444
58446
|
}
|
|
58447
|
+
function unscopedSearchOffsetError(offset, limit) {
|
|
58448
|
+
if (offset <= 0 || offset + limit <= MAX_UNSCOPED_SEARCH_DEPTH) return void 0;
|
|
58449
|
+
return `search-messages without a mailbox merges every mailbox's newest offset+limit matches, so it pages at most ${MAX_UNSCOPED_SEARCH_DEPTH.toLocaleString("en-US")} deep (offset ${offset} + limit ${limit} asked). Name a mailbox (e.g. the account's All Mail or Archive) to page deeper, or narrow the search with dateTo.`;
|
|
58450
|
+
}
|
|
58445
58451
|
function isUnfiltered(criteria) {
|
|
58446
58452
|
const keys = Object.keys(criteria);
|
|
58447
58453
|
return keys.length === 1 && criteria.deleted === false;
|
|
@@ -58565,16 +58571,16 @@ function pageRows(fetch) {
|
|
|
58565
58571
|
function omissionReason(fetch) {
|
|
58566
58572
|
return "the server's FETCH response for this message could not be read, even without BODYSTRUCTURE or ENVELOPE" + (fetch.cause ? ` (${fetch.cause})` : "");
|
|
58567
58573
|
}
|
|
58568
|
-
async function walkWindows(
|
|
58574
|
+
async function walkWindows(top, page, firstWindow, readWindow, openTop = true) {
|
|
58569
58575
|
const wanted = page.skip + page.take;
|
|
58570
58576
|
const uids = [];
|
|
58571
58577
|
let seen = 0;
|
|
58572
58578
|
let matched = 0;
|
|
58573
|
-
let hi =
|
|
58579
|
+
let hi = top;
|
|
58574
58580
|
let width = Math.max(1, Math.min(firstWindow, MAX_WINDOW));
|
|
58575
58581
|
while (hi >= 1 && seen < wanted) {
|
|
58576
58582
|
const lo = Math.max(1, hi - width + 1);
|
|
58577
|
-
const live = (await readWindow(lo, hi ===
|
|
58583
|
+
const live = (await readWindow(lo, hi === top && openTop ? "*" : hi, hi - lo + 1)).slice().sort((a, b) => b - a);
|
|
58578
58584
|
matched += live.length;
|
|
58579
58585
|
for (const uid of live) {
|
|
58580
58586
|
if (seen >= wanted) break;
|
|
@@ -58586,13 +58592,21 @@ async function walkWindows(exists, page, firstWindow, readWindow) {
|
|
|
58586
58592
|
}
|
|
58587
58593
|
return { uids, matched, exhausted: hi < 1 };
|
|
58588
58594
|
}
|
|
58595
|
+
function sequenceTop(client, statusCount) {
|
|
58596
|
+
const mb = client.mailbox;
|
|
58597
|
+
const exists = mb && typeof mb.exists === "number" && mb.exists >= 0 ? mb.exists : void 0;
|
|
58598
|
+
return exists ?? statusCount;
|
|
58599
|
+
}
|
|
58589
58600
|
async function listLargeMailbox(client, exists, page) {
|
|
58601
|
+
const top = sequenceTop(client, exists) - page.skip;
|
|
58602
|
+
if (top < 1 || page.take === 0) {
|
|
58603
|
+
return { messages: [], omitted: [], total: exists, totalExact: true };
|
|
58604
|
+
}
|
|
58590
58605
|
let deletedSeen = 0;
|
|
58591
58606
|
const walk = await walkWindows(
|
|
58592
|
-
|
|
58593
|
-
page,
|
|
58594
|
-
|
|
58595
|
-
page.skip + page.take + 64,
|
|
58607
|
+
top,
|
|
58608
|
+
{ skip: 0, take: page.take },
|
|
58609
|
+
page.take + DELETED_MARGIN,
|
|
58596
58610
|
async (lo, hi) => {
|
|
58597
58611
|
const live = [];
|
|
58598
58612
|
for await (const msg of client.fetch(`${lo}:${hi}`, { uid: true, flags: true })) {
|
|
@@ -58600,7 +58614,10 @@ async function listLargeMailbox(client, exists, page) {
|
|
|
58600
58614
|
else live.push(msg.uid);
|
|
58601
58615
|
}
|
|
58602
58616
|
return live;
|
|
58603
|
-
}
|
|
58617
|
+
},
|
|
58618
|
+
// From the very top the first window is `lo:*`, as before; below it the
|
|
58619
|
+
// range is exact.
|
|
58620
|
+
page.skip === 0
|
|
58604
58621
|
);
|
|
58605
58622
|
return {
|
|
58606
58623
|
...pageRows(await fetchRows(client, walk.uids)),
|
|
@@ -58678,6 +58695,10 @@ async function run(args, listMode, deps) {
|
|
|
58678
58695
|
}
|
|
58679
58696
|
const limit = args.limit ?? 50;
|
|
58680
58697
|
const offset = args.offset ?? 0;
|
|
58698
|
+
if (unscopedSearch) {
|
|
58699
|
+
const tooDeep = unscopedSearchOffsetError(offset, limit);
|
|
58700
|
+
if (tooDeep) throw new Error(tooDeep);
|
|
58701
|
+
}
|
|
58681
58702
|
const criteria = buildCriteria(args, listMode);
|
|
58682
58703
|
const page = unscopedSearch ? { skip: 0, take: offset + limit } : { skip: offset, take: limit };
|
|
58683
58704
|
const fetched = [];
|
|
@@ -59755,7 +59776,7 @@ async function imapThread(id, deps = {}, limit = 50) {
|
|
|
59755
59776
|
true
|
|
59756
59777
|
);
|
|
59757
59778
|
}
|
|
59758
|
-
var import_imapflow, IMAP_ENV, defaultConnect, SPECIAL_USE_ALIASES, NOT_DELETED, LARGE_MAILBOX_MESSAGES, FIRST_SEARCH_WINDOW, MAX_WINDOW, ROW_QUERY, ROW_QUERY_NO_STRUCTURE, ROW_QUERY_HEADERS_ONLY, poolConnect, pools, connecting, MAX_COMPOSE_SOURCE_BYTES, MAX_RFC822_INLINE_BYTES, MAX_RFC822_FILE_BYTES, HEADER_WINDOW_BYTES, MAIL_FLAG_BITS, imapMarkRead, imapMarkUnread, FALLBACK_TRASH_PATH, imapBatchMarkRead, imapBatchMarkUnread, imapBatchFlag, imapBatchUnflag, imapBatchDelete;
|
|
59779
|
+
var import_imapflow, IMAP_ENV, defaultConnect, SPECIAL_USE_ALIASES, NOT_DELETED, LARGE_MAILBOX_MESSAGES, FIRST_SEARCH_WINDOW, MAX_WINDOW, MAX_UNSCOPED_SEARCH_DEPTH, ROW_QUERY, ROW_QUERY_NO_STRUCTURE, ROW_QUERY_HEADERS_ONLY, DELETED_MARGIN, poolConnect, pools, connecting, MAX_COMPOSE_SOURCE_BYTES, MAX_RFC822_INLINE_BYTES, MAX_RFC822_FILE_BYTES, HEADER_WINDOW_BYTES, MAIL_FLAG_BITS, imapMarkRead, imapMarkUnread, FALLBACK_TRASH_PATH, imapBatchMarkRead, imapBatchMarkUnread, imapBatchFlag, imapBatchUnflag, imapBatchDelete;
|
|
59759
59780
|
var init_imapClient = __esm({
|
|
59760
59781
|
"src/services/imapClient.ts"() {
|
|
59761
59782
|
"use strict";
|
|
@@ -59829,6 +59850,7 @@ var init_imapClient = __esm({
|
|
|
59829
59850
|
LARGE_MAILBOX_MESSAGES = 1e4;
|
|
59830
59851
|
FIRST_SEARCH_WINDOW = 5e3;
|
|
59831
59852
|
MAX_WINDOW = 5e4;
|
|
59853
|
+
MAX_UNSCOPED_SEARCH_DEPTH = 5e3;
|
|
59832
59854
|
ROW_QUERY = {
|
|
59833
59855
|
envelope: true,
|
|
59834
59856
|
flags: true,
|
|
@@ -59847,6 +59869,7 @@ var init_imapClient = __esm({
|
|
|
59847
59869
|
internalDate: true,
|
|
59848
59870
|
headers: ["date", "from", "subject", "message-id", "in-reply-to", "content-type"]
|
|
59849
59871
|
};
|
|
59872
|
+
DELETED_MARGIN = 32;
|
|
59850
59873
|
poolConnect = defaultConnect;
|
|
59851
59874
|
pools = /* @__PURE__ */ new Map();
|
|
59852
59875
|
connecting = /* @__PURE__ */ new Map();
|
package/build/index.js
CHANGED
|
@@ -3118,12 +3118,13 @@ var require_data = __commonJS({
|
|
|
3118
3118
|
}
|
|
3119
3119
|
});
|
|
3120
3120
|
|
|
3121
|
-
// node_modules/.pnpm/fast-uri@3.1.
|
|
3121
|
+
// node_modules/.pnpm/fast-uri@3.1.8/node_modules/fast-uri/lib/utils.js
|
|
3122
3122
|
var require_utils = __commonJS({
|
|
3123
|
-
"node_modules/.pnpm/fast-uri@3.1.
|
|
3123
|
+
"node_modules/.pnpm/fast-uri@3.1.8/node_modules/fast-uri/lib/utils.js"(exports, module) {
|
|
3124
3124
|
"use strict";
|
|
3125
3125
|
var isUUID = RegExp.prototype.test.bind(/^[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}$/iu);
|
|
3126
3126
|
var isIPv4 = RegExp.prototype.test.bind(/^(?:(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)$/u);
|
|
3127
|
+
var isPort = RegExp.prototype.test.bind(/^\d*$/u);
|
|
3127
3128
|
var isHexPair = RegExp.prototype.test.bind(/^[\da-f]{2}$/iu);
|
|
3128
3129
|
var isUnreserved = RegExp.prototype.test.bind(/^[\da-z\-._~]$/iu);
|
|
3129
3130
|
var isPathCharacter = RegExp.prototype.test.bind(/^[A-Za-z0-9\-._~!$&'()*+,;=:@/]$/u);
|
|
@@ -3589,8 +3590,12 @@ var require_utils = __commonJS({
|
|
|
3589
3590
|
uriTokens.push(host);
|
|
3590
3591
|
}
|
|
3591
3592
|
if (typeof component.port === "number" || typeof component.port === "string") {
|
|
3593
|
+
const port = String(component.port);
|
|
3594
|
+
if (!isPort(port)) {
|
|
3595
|
+
throw new TypeError("URI port is malformed.");
|
|
3596
|
+
}
|
|
3592
3597
|
uriTokens.push(":");
|
|
3593
|
-
uriTokens.push(
|
|
3598
|
+
uriTokens.push(port);
|
|
3594
3599
|
}
|
|
3595
3600
|
return uriTokens.length ? uriTokens.join("") : void 0;
|
|
3596
3601
|
}
|
|
@@ -3615,9 +3620,9 @@ var require_utils = __commonJS({
|
|
|
3615
3620
|
}
|
|
3616
3621
|
});
|
|
3617
3622
|
|
|
3618
|
-
// node_modules/.pnpm/fast-uri@3.1.
|
|
3623
|
+
// node_modules/.pnpm/fast-uri@3.1.8/node_modules/fast-uri/lib/schemes.js
|
|
3619
3624
|
var require_schemes = __commonJS({
|
|
3620
|
-
"node_modules/.pnpm/fast-uri@3.1.
|
|
3625
|
+
"node_modules/.pnpm/fast-uri@3.1.8/node_modules/fast-uri/lib/schemes.js"(exports, module) {
|
|
3621
3626
|
"use strict";
|
|
3622
3627
|
var { isUUID } = require_utils();
|
|
3623
3628
|
var URN_REG = /^([\da-z][\d\-a-z]{0,31}):((?:[\w!$'()*+,\-./:;=@]|%[\da-f]{2})+)$/iu;
|
|
@@ -3826,9 +3831,9 @@ var require_schemes = __commonJS({
|
|
|
3826
3831
|
}
|
|
3827
3832
|
});
|
|
3828
3833
|
|
|
3829
|
-
// node_modules/.pnpm/fast-uri@3.1.
|
|
3834
|
+
// node_modules/.pnpm/fast-uri@3.1.8/node_modules/fast-uri/index.js
|
|
3830
3835
|
var require_fast_uri = __commonJS({
|
|
3831
|
-
"node_modules/.pnpm/fast-uri@3.1.
|
|
3836
|
+
"node_modules/.pnpm/fast-uri@3.1.8/node_modules/fast-uri/index.js"(exports, module) {
|
|
3832
3837
|
"use strict";
|
|
3833
3838
|
var { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizePercentEncoding, normalizePathEncoding, serializePathEncoding, normalizeQueryFragmentEncoding, encodeQuery, encodeFragment, reescapeHostDelimiters, isIPv4, nonSimpleDomain } = require_utils();
|
|
3834
3839
|
var { SCHEMES, getSchemeHandler } = require_schemes();
|
|
@@ -4033,12 +4038,15 @@ var require_fast_uri = __commonJS({
|
|
|
4033
4038
|
}
|
|
4034
4039
|
return false;
|
|
4035
4040
|
}
|
|
4041
|
+
function isIPLiteral(host) {
|
|
4042
|
+
return host[0] === "[" && host[host.length - 1] === "]";
|
|
4043
|
+
}
|
|
4036
4044
|
function hasMalformedComponentPercentEncoding(matches) {
|
|
4037
4045
|
const host = matches[4];
|
|
4038
|
-
return hasMalformedPercentEncoding(matches[3]) || host !== void 0 && !(host
|
|
4046
|
+
return hasMalformedPercentEncoding(matches[3]) || host !== void 0 && !isIPLiteral(host) && hasMalformedPercentEncoding(host) || hasMalformedPercentEncoding(matches[6]) || hasMalformedPercentEncoding(matches[7]) || hasMalformedPercentEncoding(matches[8]);
|
|
4039
4047
|
}
|
|
4040
4048
|
function canonicalizeHost(parsed, options, schemeHandler, isIP) {
|
|
4041
|
-
if (!options.unicodeSupport && (!schemeHandler || !schemeHandler.unicodeSupport) && parsed.host && parsed.host
|
|
4049
|
+
if (!options.unicodeSupport && (!schemeHandler || !schemeHandler.unicodeSupport) && parsed.host && !isIPLiteral(parsed.host) && (options.domainHost || schemeHandler && schemeHandler.domainHost) && isIP === false && nonSimpleDomain(parsed.host)) {
|
|
4042
4050
|
try {
|
|
4043
4051
|
parsed.host = new URL("http://" + parsed.host).hostname;
|
|
4044
4052
|
} catch (e) {
|
|
@@ -4125,10 +4133,11 @@ var require_fast_uri = __commonJS({
|
|
|
4125
4133
|
if (parsed.host) {
|
|
4126
4134
|
const ipv4result = isIPv4(parsed.host);
|
|
4127
4135
|
if (ipv4result === false) {
|
|
4128
|
-
const bracketedIPLiteral = parsed.host
|
|
4136
|
+
const bracketedIPLiteral = isIPLiteral(parsed.host);
|
|
4137
|
+
const hasIPLiteralBracket = parsed.host.indexOf("[") !== -1 || parsed.host.indexOf("]") !== -1;
|
|
4129
4138
|
const ipv6result = normalizeIPv6(parsed.host);
|
|
4130
4139
|
isIP = ipv6result.isIPV6 || ipv6result.isIPVFuture === true;
|
|
4131
|
-
malformedIPLiteral =
|
|
4140
|
+
malformedIPLiteral = hasIPLiteralBracket && (!bracketedIPLiteral || ipv6result.error === true);
|
|
4132
4141
|
parsed.host = isIP ? ipv6result.host : ipv6result.host.toLowerCase();
|
|
4133
4142
|
if (malformedIPLiteral) {
|
|
4134
4143
|
parsed.error = parsed.error || "URI host is malformed.";
|
|
@@ -4151,14 +4160,17 @@ var require_fast_uri = __commonJS({
|
|
|
4151
4160
|
parsed.error = parsed.error || "URI is not a " + options.reference + " reference.";
|
|
4152
4161
|
}
|
|
4153
4162
|
const schemeHandler = getSchemeHandler(options.scheme || parsed.scheme);
|
|
4154
|
-
|
|
4155
|
-
|
|
4156
|
-
|
|
4157
|
-
|
|
4158
|
-
|
|
4159
|
-
|
|
4160
|
-
|
|
4163
|
+
if (!malformedIPLiteral) {
|
|
4164
|
+
malformedHost = canonicalizeHost(parsed, options, schemeHandler, isIP);
|
|
4165
|
+
}
|
|
4166
|
+
if (uri.indexOf("%") !== -1 && parsed.host !== void 0 && !malformedIPLiteral) {
|
|
4167
|
+
let host = isIP ? parsed.host : normalizePercentEncoding(parsed.host, true);
|
|
4168
|
+
if (!isIP) {
|
|
4169
|
+
host = normalizePercentEncoding(host.toLowerCase());
|
|
4161
4170
|
}
|
|
4171
|
+
parsed.host = reescapeHostDelimiters(host, isIP);
|
|
4172
|
+
}
|
|
4173
|
+
if (!schemeHandler || schemeHandler && !schemeHandler.skipNormalize) {
|
|
4162
4174
|
if (parsed.path) {
|
|
4163
4175
|
parsed.path = normalizePathEncoding(parsed.path);
|
|
4164
4176
|
}
|
|
@@ -65378,6 +65390,7 @@ __export(imapClient_exports, {
|
|
|
65378
65390
|
MAX_COMPOSE_SOURCE_BYTES: () => MAX_COMPOSE_SOURCE_BYTES,
|
|
65379
65391
|
MAX_RFC822_FILE_BYTES: () => MAX_RFC822_FILE_BYTES,
|
|
65380
65392
|
MAX_RFC822_INLINE_BYTES: () => MAX_RFC822_INLINE_BYTES,
|
|
65393
|
+
MAX_UNSCOPED_SEARCH_DEPTH: () => MAX_UNSCOPED_SEARCH_DEPTH,
|
|
65381
65394
|
__resetPool: () => __resetPool,
|
|
65382
65395
|
__setPoolConnect: () => __setPoolConnect,
|
|
65383
65396
|
bodyStructureHasAttachments: () => bodyStructureHasAttachments,
|
|
@@ -65425,7 +65438,8 @@ __export(imapClient_exports, {
|
|
|
65425
65438
|
resolveImapConfig: () => resolveImapConfig,
|
|
65426
65439
|
resolveImapConfigs: () => resolveImapConfigs,
|
|
65427
65440
|
resolveMailboxPath: () => resolveMailboxPath,
|
|
65428
|
-
shouldUseImap: () => shouldUseImap
|
|
65441
|
+
shouldUseImap: () => shouldUseImap,
|
|
65442
|
+
unscopedSearchOffsetError: () => unscopedSearchOffsetError
|
|
65429
65443
|
});
|
|
65430
65444
|
import { createHash } from "node:crypto";
|
|
65431
65445
|
function encodeImapId(account, path, uid) {
|
|
@@ -65771,6 +65785,10 @@ function messageIdentity(entry) {
|
|
|
65771
65785
|
const messageId = raw.replace(/^<+|>+$/g, "").trim().toLowerCase();
|
|
65772
65786
|
return messageId ? `mid:${messageId}` : `${entry.path}\0${entry.message.uid}`;
|
|
65773
65787
|
}
|
|
65788
|
+
function unscopedSearchOffsetError(offset, limit) {
|
|
65789
|
+
if (offset <= 0 || offset + limit <= MAX_UNSCOPED_SEARCH_DEPTH) return void 0;
|
|
65790
|
+
return `search-messages without a mailbox merges every mailbox's newest offset+limit matches, so it pages at most ${MAX_UNSCOPED_SEARCH_DEPTH.toLocaleString("en-US")} deep (offset ${offset} + limit ${limit} asked). Name a mailbox (e.g. the account's All Mail or Archive) to page deeper, or narrow the search with dateTo.`;
|
|
65791
|
+
}
|
|
65774
65792
|
function isUnfiltered(criteria) {
|
|
65775
65793
|
const keys = Object.keys(criteria);
|
|
65776
65794
|
return keys.length === 1 && criteria.deleted === false;
|
|
@@ -65894,16 +65912,16 @@ function pageRows(fetch) {
|
|
|
65894
65912
|
function omissionReason(fetch) {
|
|
65895
65913
|
return "the server's FETCH response for this message could not be read, even without BODYSTRUCTURE or ENVELOPE" + (fetch.cause ? ` (${fetch.cause})` : "");
|
|
65896
65914
|
}
|
|
65897
|
-
async function walkWindows(
|
|
65915
|
+
async function walkWindows(top, page, firstWindow, readWindow, openTop = true) {
|
|
65898
65916
|
const wanted = page.skip + page.take;
|
|
65899
65917
|
const uids = [];
|
|
65900
65918
|
let seen = 0;
|
|
65901
65919
|
let matched = 0;
|
|
65902
|
-
let hi =
|
|
65920
|
+
let hi = top;
|
|
65903
65921
|
let width = Math.max(1, Math.min(firstWindow, MAX_WINDOW));
|
|
65904
65922
|
while (hi >= 1 && seen < wanted) {
|
|
65905
65923
|
const lo = Math.max(1, hi - width + 1);
|
|
65906
|
-
const live = (await readWindow(lo, hi ===
|
|
65924
|
+
const live = (await readWindow(lo, hi === top && openTop ? "*" : hi, hi - lo + 1)).slice().sort((a, b) => b - a);
|
|
65907
65925
|
matched += live.length;
|
|
65908
65926
|
for (const uid of live) {
|
|
65909
65927
|
if (seen >= wanted) break;
|
|
@@ -65915,13 +65933,21 @@ async function walkWindows(exists, page, firstWindow, readWindow) {
|
|
|
65915
65933
|
}
|
|
65916
65934
|
return { uids, matched, exhausted: hi < 1 };
|
|
65917
65935
|
}
|
|
65936
|
+
function sequenceTop(client, statusCount) {
|
|
65937
|
+
const mb = client.mailbox;
|
|
65938
|
+
const exists = mb && typeof mb.exists === "number" && mb.exists >= 0 ? mb.exists : void 0;
|
|
65939
|
+
return exists ?? statusCount;
|
|
65940
|
+
}
|
|
65918
65941
|
async function listLargeMailbox(client, exists, page) {
|
|
65942
|
+
const top = sequenceTop(client, exists) - page.skip;
|
|
65943
|
+
if (top < 1 || page.take === 0) {
|
|
65944
|
+
return { messages: [], omitted: [], total: exists, totalExact: true };
|
|
65945
|
+
}
|
|
65919
65946
|
let deletedSeen = 0;
|
|
65920
65947
|
const walk = await walkWindows(
|
|
65921
|
-
|
|
65922
|
-
page,
|
|
65923
|
-
|
|
65924
|
-
page.skip + page.take + 64,
|
|
65948
|
+
top,
|
|
65949
|
+
{ skip: 0, take: page.take },
|
|
65950
|
+
page.take + DELETED_MARGIN,
|
|
65925
65951
|
async (lo, hi) => {
|
|
65926
65952
|
const live = [];
|
|
65927
65953
|
for await (const msg of client.fetch(`${lo}:${hi}`, { uid: true, flags: true })) {
|
|
@@ -65929,7 +65955,10 @@ async function listLargeMailbox(client, exists, page) {
|
|
|
65929
65955
|
else live.push(msg.uid);
|
|
65930
65956
|
}
|
|
65931
65957
|
return live;
|
|
65932
|
-
}
|
|
65958
|
+
},
|
|
65959
|
+
// From the very top the first window is `lo:*`, as before; below it the
|
|
65960
|
+
// range is exact.
|
|
65961
|
+
page.skip === 0
|
|
65933
65962
|
);
|
|
65934
65963
|
return {
|
|
65935
65964
|
...pageRows(await fetchRows(client, walk.uids)),
|
|
@@ -66007,6 +66036,10 @@ async function run(args, listMode, deps) {
|
|
|
66007
66036
|
}
|
|
66008
66037
|
const limit = args.limit ?? 50;
|
|
66009
66038
|
const offset = args.offset ?? 0;
|
|
66039
|
+
if (unscopedSearch) {
|
|
66040
|
+
const tooDeep = unscopedSearchOffsetError(offset, limit);
|
|
66041
|
+
if (tooDeep) throw new Error(tooDeep);
|
|
66042
|
+
}
|
|
66010
66043
|
const criteria = buildCriteria(args, listMode);
|
|
66011
66044
|
const page = unscopedSearch ? { skip: 0, take: offset + limit } : { skip: offset, take: limit };
|
|
66012
66045
|
const fetched = [];
|
|
@@ -67084,7 +67117,7 @@ async function imapThread(id, deps = {}, limit = 50) {
|
|
|
67084
67117
|
true
|
|
67085
67118
|
);
|
|
67086
67119
|
}
|
|
67087
|
-
var import_imapflow, IMAP_ENV, defaultConnect, SPECIAL_USE_ALIASES, NOT_DELETED, LARGE_MAILBOX_MESSAGES, FIRST_SEARCH_WINDOW, MAX_WINDOW, ROW_QUERY, ROW_QUERY_NO_STRUCTURE, ROW_QUERY_HEADERS_ONLY, poolConnect, pools, connecting, MAX_COMPOSE_SOURCE_BYTES, MAX_RFC822_INLINE_BYTES, MAX_RFC822_FILE_BYTES, HEADER_WINDOW_BYTES, MAIL_FLAG_BITS, imapMarkRead, imapMarkUnread, FALLBACK_TRASH_PATH, imapBatchMarkRead, imapBatchMarkUnread, imapBatchFlag, imapBatchUnflag, imapBatchDelete;
|
|
67120
|
+
var import_imapflow, IMAP_ENV, defaultConnect, SPECIAL_USE_ALIASES, NOT_DELETED, LARGE_MAILBOX_MESSAGES, FIRST_SEARCH_WINDOW, MAX_WINDOW, MAX_UNSCOPED_SEARCH_DEPTH, ROW_QUERY, ROW_QUERY_NO_STRUCTURE, ROW_QUERY_HEADERS_ONLY, DELETED_MARGIN, poolConnect, pools, connecting, MAX_COMPOSE_SOURCE_BYTES, MAX_RFC822_INLINE_BYTES, MAX_RFC822_FILE_BYTES, HEADER_WINDOW_BYTES, MAIL_FLAG_BITS, imapMarkRead, imapMarkUnread, FALLBACK_TRASH_PATH, imapBatchMarkRead, imapBatchMarkUnread, imapBatchFlag, imapBatchUnflag, imapBatchDelete;
|
|
67088
67121
|
var init_imapClient = __esm({
|
|
67089
67122
|
"src/services/imapClient.ts"() {
|
|
67090
67123
|
"use strict";
|
|
@@ -67158,6 +67191,7 @@ var init_imapClient = __esm({
|
|
|
67158
67191
|
LARGE_MAILBOX_MESSAGES = 1e4;
|
|
67159
67192
|
FIRST_SEARCH_WINDOW = 5e3;
|
|
67160
67193
|
MAX_WINDOW = 5e4;
|
|
67194
|
+
MAX_UNSCOPED_SEARCH_DEPTH = 5e3;
|
|
67161
67195
|
ROW_QUERY = {
|
|
67162
67196
|
envelope: true,
|
|
67163
67197
|
flags: true,
|
|
@@ -67176,6 +67210,7 @@ var init_imapClient = __esm({
|
|
|
67176
67210
|
internalDate: true,
|
|
67177
67211
|
headers: ["date", "from", "subject", "message-id", "in-reply-to", "content-type"]
|
|
67178
67212
|
};
|
|
67213
|
+
DELETED_MARGIN = 32;
|
|
67179
67214
|
poolConnect = defaultConnect;
|
|
67180
67215
|
pools = /* @__PURE__ */ new Map();
|
|
67181
67216
|
connecting = /* @__PURE__ */ new Map();
|
|
@@ -88471,7 +88506,10 @@ registerTool(
|
|
|
88471
88506
|
isFlagged: external_exports.boolean().optional().describe("Filter by flagged status"),
|
|
88472
88507
|
dateFrom: DATE_FILTER_SCHEMA.describe("Start date filter (e.g., 'January 1, 2026')"),
|
|
88473
88508
|
dateTo: DATE_FILTER_SCHEMA.describe("End date filter (e.g., 'March 1, 2026')"),
|
|
88474
|
-
limit: external_exports.number().int().min(1).max(500).optional().describe("Maximum number of results (default: 50, max: 500)")
|
|
88509
|
+
limit: external_exports.number().int().min(1).max(500).optional().describe("Maximum number of results (default: 50, max: 500)"),
|
|
88510
|
+
offset: external_exports.number().int().min(0).optional().describe(
|
|
88511
|
+
"Number of newest matches to skip (for pagination; IMAP accounts only). Without a mailbox, offset + limit may not exceed 5,000 \u2014 name a mailbox to page deeper."
|
|
88512
|
+
)
|
|
88475
88513
|
},
|
|
88476
88514
|
outputSchema: LIST_OUTPUT_SCHEMA
|
|
88477
88515
|
},
|
|
@@ -88482,6 +88520,7 @@ registerTool(
|
|
|
88482
88520
|
mailbox,
|
|
88483
88521
|
account,
|
|
88484
88522
|
limit = 50,
|
|
88523
|
+
offset = 0,
|
|
88485
88524
|
dateFrom,
|
|
88486
88525
|
dateTo,
|
|
88487
88526
|
from,
|
|
@@ -88489,12 +88528,17 @@ registerTool(
|
|
|
88489
88528
|
isRead,
|
|
88490
88529
|
isFlagged
|
|
88491
88530
|
}) => {
|
|
88531
|
+
if (offset > 0 && !mailbox) {
|
|
88532
|
+
const tooDeep = unscopedSearchOffsetError(offset, limit);
|
|
88533
|
+
if (tooDeep) return errorResponse(tooDeep);
|
|
88534
|
+
}
|
|
88492
88535
|
if (shouldUseImap(account)) {
|
|
88493
88536
|
const imapArgs = {
|
|
88494
88537
|
query,
|
|
88495
88538
|
body,
|
|
88496
88539
|
mailbox,
|
|
88497
88540
|
limit,
|
|
88541
|
+
offset,
|
|
88498
88542
|
dateFrom,
|
|
88499
88543
|
dateTo,
|
|
88500
88544
|
from,
|
|
@@ -88513,11 +88557,16 @@ registerTool(
|
|
|
88513
88557
|
omittedMessages: r.omittedMessages
|
|
88514
88558
|
});
|
|
88515
88559
|
}
|
|
88516
|
-
const fan = await fanOutImapMessages(imapArgs, "search");
|
|
88517
88560
|
const { appleScriptOnly } = partitionAccountsForCounts(
|
|
88518
88561
|
mailManager.listAccounts(),
|
|
88519
88562
|
resolveImapConfigs()
|
|
88520
88563
|
);
|
|
88564
|
+
if (offset > 0 && appleScriptOnly.length > 0 && !body) {
|
|
88565
|
+
return errorResponse(
|
|
88566
|
+
`offset is supported only on IMAP accounts, and ${appleScriptOnly.map((a) => `"${a.name}"`).join(", ")} ${appleScriptOnly.length === 1 ? "is" : "are"} AppleScript-only. Pass an IMAP account to page, or omit offset.`
|
|
88567
|
+
);
|
|
88568
|
+
}
|
|
88569
|
+
const fan = await fanOutImapMessages(imapArgs, "search");
|
|
88521
88570
|
if (body) {
|
|
88522
88571
|
const apple2 = {
|
|
88523
88572
|
rows: [],
|
|
@@ -88548,6 +88597,11 @@ registerTool(
|
|
|
88548
88597
|
);
|
|
88549
88598
|
return mergedMessageResponse(fan, apple, limit, "matched");
|
|
88550
88599
|
}
|
|
88600
|
+
if (offset > 0) {
|
|
88601
|
+
return errorResponse(
|
|
88602
|
+
`offset is supported only on IMAP accounts; IMAP is not configured for ${account ? `account "${account}"` : "any account"}. Omit offset, or narrow the search with dateFrom/dateTo instead.`
|
|
88603
|
+
);
|
|
88604
|
+
}
|
|
88551
88605
|
if (body) {
|
|
88552
88606
|
return errorResponse(
|
|
88553
88607
|
`Body search requires the IMAP backend, which is not configured for ${account ? `account "${account}"` : "any account"}. Configure IMAP (see the IMAP backend section of the README) or search by query/subject/from instead.`
|
package/package.json
CHANGED