apple-mail-mcp 2.20.0 → 2.20.1
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 +16 -2
- package/build/cli.js +121 -10
- package/build/index.js +130 -17
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -813,7 +813,10 @@ Save an email to Drafts without sending.
|
|
|
813
813
|
so a draft with `inReplyTo`/`references` cannot go through AppleScript. Instead the
|
|
814
814
|
server composes it with the same MIME builder as the SMTP send (attachments included) and
|
|
815
815
|
files it over IMAP into the account's Drafts mailbox (SPECIAL-USE `\Drafts`), flagged
|
|
816
|
-
`\Draft`. The account is `account` when given (it must be a configured IMAP account
|
|
816
|
+
`\Draft`. The account is `account` when given (it must be a configured IMAP account —
|
|
817
|
+
pass the exact `accountLabel` or login from `list-accounts`/your `APPLE_MAIL_MCP_IMAP_*`
|
|
818
|
+
config, e.g. `"user@icloud.com"`; a generic provider name like `"iCloud"` is not a
|
|
819
|
+
recognized alias and is refused);
|
|
817
820
|
otherwise the IMAP account whose login is the SMTP identity, else the only configured
|
|
818
821
|
IMAP account. When none qualifies — IMAP unconfigured, an unknown `account`, or several
|
|
819
822
|
accounts and no way to choose — the call **fails and files nothing**; it never falls back
|
|
@@ -1024,7 +1027,18 @@ List attachments on a message.
|
|
|
1024
1027
|
| --------- | ------ | -------- | ----------- |
|
|
1025
1028
|
| `id` | string | Yes | Message ID |
|
|
1026
1029
|
|
|
1027
|
-
**Returns:** List of attachments with name, MIME type, and size
|
|
1030
|
+
**Returns:** List of attachments with name, MIME type, and `size` — the **decoded** byte count,
|
|
1031
|
+
i.e. what `fetch-attachment` returns.
|
|
1032
|
+
|
|
1033
|
+
**Sizes over IMAP.** `BODYSTRUCTURE` reports a part's size in its transfer encoding, so a
|
|
1034
|
+
base64 attachment's raw figure is ~4/3 of the file. `list-attachments` converts it: a
|
|
1035
|
+
`Content-Disposition` `size=` parameter wins when present; 7bit/8bit/binary parts are exact
|
|
1036
|
+
as reported; base64 parts are measured exactly from a short tail of each part (padding and
|
|
1037
|
+
line wrap), fetched in one batched `FETCH` for the whole message; quoted-printable parts up
|
|
1038
|
+
to 64 KiB are fetched and counted exactly. If a part can't be measured (the probe fails, the
|
|
1039
|
+
wrap is irregular, or a quoted-printable part is larger), the size is an estimate within a
|
|
1040
|
+
few bytes for base64 — an upper bound for quoted-printable — and the entry carries
|
|
1041
|
+
`sizeApproximate: true`. `fetch-attachment` always reports the exact `bytes`.
|
|
1028
1042
|
|
|
1029
1043
|
---
|
|
1030
1044
|
|
package/build/cli.js
CHANGED
|
@@ -57841,9 +57841,12 @@ __export(imapClient_exports, {
|
|
|
57841
57841
|
MAX_UNSCOPED_SEARCH_DEPTH: () => MAX_UNSCOPED_SEARCH_DEPTH,
|
|
57842
57842
|
__resetPool: () => __resetPool,
|
|
57843
57843
|
__setPoolConnect: () => __setPoolConnect,
|
|
57844
|
+
base64DecodedLength: () => base64DecodedLength,
|
|
57845
|
+
base64DecodedSizeFromTail: () => base64DecodedSizeFromTail,
|
|
57844
57846
|
bodyStructureHasAttachments: () => bodyStructureHasAttachments,
|
|
57845
57847
|
buildImapConnectionOptions: () => buildImapConnectionOptions,
|
|
57846
57848
|
decodeImapId: () => decodeImapId,
|
|
57849
|
+
decodedSizeWithoutBody: () => decodedSizeWithoutBody,
|
|
57847
57850
|
dropAllPools: () => dropAllPools,
|
|
57848
57851
|
encodeImapId: () => encodeImapId,
|
|
57849
57852
|
imapAppendDraft: () => imapAppendDraft,
|
|
@@ -57884,6 +57887,7 @@ __export(imapClient_exports, {
|
|
|
57884
57887
|
matchMailbox: () => matchMailbox,
|
|
57885
57888
|
normalizeMessageId: () => normalizeMessageId,
|
|
57886
57889
|
omittedNote: () => omittedNote,
|
|
57890
|
+
quotedPrintableDecodedLength: () => quotedPrintableDecodedLength,
|
|
57887
57891
|
resolveDraftImapAccount: () => resolveDraftImapAccount,
|
|
57888
57892
|
resolveImapConfig: () => resolveImapConfig,
|
|
57889
57893
|
resolveImapConfigs: () => resolveImapConfigs,
|
|
@@ -59344,6 +59348,56 @@ async function imapDeleteMessageById(id, deps = {}) {
|
|
|
59344
59348
|
}
|
|
59345
59349
|
});
|
|
59346
59350
|
}
|
|
59351
|
+
function dispositionSize(params) {
|
|
59352
|
+
const raw = params?.size?.trim();
|
|
59353
|
+
return raw && /^\d+$/.test(raw) ? Number(raw) : void 0;
|
|
59354
|
+
}
|
|
59355
|
+
function base64DecodedLength(body) {
|
|
59356
|
+
const s = typeof body === "string" ? body : body.toString("latin1");
|
|
59357
|
+
const n = s.replace(/[^A-Za-z0-9+/]/g, "").length;
|
|
59358
|
+
return Math.floor(n * 3 / 4);
|
|
59359
|
+
}
|
|
59360
|
+
function base64DecodedSizeFromTail(total, tail) {
|
|
59361
|
+
if (tail.length >= total) return base64DecodedLength(tail);
|
|
59362
|
+
const s = tail.toString("latin1");
|
|
59363
|
+
const term = s.includes("\r\n") ? "\r\n" : "\n";
|
|
59364
|
+
const segs = s.split(term);
|
|
59365
|
+
if (segs.length < 3) return void 0;
|
|
59366
|
+
let trailing = 0;
|
|
59367
|
+
while (segs.length > 1 && segs[segs.length - 1] === "") {
|
|
59368
|
+
segs.pop();
|
|
59369
|
+
trailing++;
|
|
59370
|
+
}
|
|
59371
|
+
const last = segs[segs.length - 1];
|
|
59372
|
+
const full = segs.slice(1, -1);
|
|
59373
|
+
if (full.length === 0 || !last) return void 0;
|
|
59374
|
+
const L = full[0].length;
|
|
59375
|
+
const alphabet = /^[A-Za-z0-9+/]+$/;
|
|
59376
|
+
if (L === 0 || L % 4 !== 0 || !full.every((l) => l.length === L && alphabet.test(l))) {
|
|
59377
|
+
return void 0;
|
|
59378
|
+
}
|
|
59379
|
+
const m = /^([A-Za-z0-9+/]*)(=*)$/.exec(last);
|
|
59380
|
+
if (!m || m[2].length > 2 || last.length > L) return void 0;
|
|
59381
|
+
const before = total - trailing * term.length - last.length;
|
|
59382
|
+
if (before < 0 || before % (L + term.length) !== 0) return void 0;
|
|
59383
|
+
const chars = before / (L + term.length) * L + last.length;
|
|
59384
|
+
if (chars % 4 !== 0) return void 0;
|
|
59385
|
+
return chars / 4 * 3 - m[2].length;
|
|
59386
|
+
}
|
|
59387
|
+
function quotedPrintableDecodedLength(body) {
|
|
59388
|
+
const s = (typeof body === "string" ? body : body.toString("latin1")).replace(/=\r?\n/g, "");
|
|
59389
|
+
const escapes = s.match(/=[0-9A-Fa-f]{2}/g)?.length ?? 0;
|
|
59390
|
+
return s.length - escapes * 2;
|
|
59391
|
+
}
|
|
59392
|
+
function decodedSizeWithoutBody(a) {
|
|
59393
|
+
if (a.declaredSize !== void 0) return { size: a.declaredSize, exact: true };
|
|
59394
|
+
if (a.encoding === "base64") {
|
|
59395
|
+
const chars = a.encodedSize - 2 * Math.floor(a.encodedSize / 78);
|
|
59396
|
+
return { size: Math.floor(chars * 3 / 4), exact: false };
|
|
59397
|
+
}
|
|
59398
|
+
if (a.encoding === "quoted-printable") return { size: a.encodedSize, exact: false };
|
|
59399
|
+
return { size: a.encodedSize, exact: true };
|
|
59400
|
+
}
|
|
59347
59401
|
function collectAttachments(node, out = []) {
|
|
59348
59402
|
if (!node) return out;
|
|
59349
59403
|
const filename = node.dispositionParameters?.filename || node.parameters?.name;
|
|
@@ -59355,7 +59409,9 @@ function collectAttachments(node, out = []) {
|
|
|
59355
59409
|
part: node.part,
|
|
59356
59410
|
filename: filename || `part-${node.part}`,
|
|
59357
59411
|
mimeType: node.type || "application/octet-stream",
|
|
59358
|
-
|
|
59412
|
+
encodedSize: node.size ?? 0,
|
|
59413
|
+
encoding: (node.encoding || "").toLowerCase().trim(),
|
|
59414
|
+
declaredSize: dispositionSize(node.dispositionParameters)
|
|
59359
59415
|
});
|
|
59360
59416
|
}
|
|
59361
59417
|
for (const child of node.childNodes ?? []) collectAttachments(child, out);
|
|
@@ -59376,6 +59432,52 @@ async function streamToBuffer(content, maxBytes) {
|
|
|
59376
59432
|
}
|
|
59377
59433
|
return Buffer.concat(chunks);
|
|
59378
59434
|
}
|
|
59435
|
+
async function resolveDecodedSizes(client, uid, atts) {
|
|
59436
|
+
const out = atts.map(decodedSizeWithoutBody);
|
|
59437
|
+
const probes = [];
|
|
59438
|
+
atts.forEach((a, i) => {
|
|
59439
|
+
if (a.declaredSize !== void 0 || a.encodedSize <= 0) return;
|
|
59440
|
+
if (a.encoding === "base64") {
|
|
59441
|
+
probes.push({ i, start: Math.max(0, a.encodedSize - BASE64_TAIL_WINDOW), kind: "b64" });
|
|
59442
|
+
} else if (a.encoding === "quoted-printable" && a.encodedSize <= QP_EXACT_MAX_BYTES) {
|
|
59443
|
+
probes.push({ i, start: 0, kind: "qp" });
|
|
59444
|
+
}
|
|
59445
|
+
});
|
|
59446
|
+
if (probes.length === 0) return out;
|
|
59447
|
+
let parts;
|
|
59448
|
+
try {
|
|
59449
|
+
const msg = await client.fetchOne(
|
|
59450
|
+
String(uid),
|
|
59451
|
+
{
|
|
59452
|
+
uid: true,
|
|
59453
|
+
bodyParts: probes.map((p) => ({
|
|
59454
|
+
key: atts[p.i].part,
|
|
59455
|
+
start: p.start,
|
|
59456
|
+
maxLength: atts[p.i].encodedSize - p.start
|
|
59457
|
+
}))
|
|
59458
|
+
},
|
|
59459
|
+
{ uid: true }
|
|
59460
|
+
);
|
|
59461
|
+
parts = msg ? msg.bodyParts : void 0;
|
|
59462
|
+
} catch {
|
|
59463
|
+
return out;
|
|
59464
|
+
}
|
|
59465
|
+
for (const p of probes) {
|
|
59466
|
+
const a = atts[p.i];
|
|
59467
|
+
const buf = parts?.get(a.part.toLowerCase());
|
|
59468
|
+
if (!buf) continue;
|
|
59469
|
+
let exact;
|
|
59470
|
+
if (p.kind === "qp") {
|
|
59471
|
+
exact = quotedPrintableDecodedLength(buf);
|
|
59472
|
+
} else {
|
|
59473
|
+
const requested = a.encodedSize - p.start;
|
|
59474
|
+
const total = buf.length < requested ? p.start + buf.length : a.encodedSize;
|
|
59475
|
+
exact = base64DecodedSizeFromTail(total, buf);
|
|
59476
|
+
}
|
|
59477
|
+
if (exact !== void 0) out[p.i] = { size: exact, exact: true };
|
|
59478
|
+
}
|
|
59479
|
+
return out;
|
|
59480
|
+
}
|
|
59379
59481
|
async function imapListAttachments(id, deps = {}) {
|
|
59380
59482
|
const ref = decodeImapId(id);
|
|
59381
59483
|
if (!ref) return { success: false, error: `Not an IMAP message id: "${id}".` };
|
|
@@ -59384,12 +59486,18 @@ async function imapListAttachments(id, deps = {}) {
|
|
|
59384
59486
|
if (!msg || !msg.bodyStructure) {
|
|
59385
59487
|
return { success: false, error: `IMAP message UID ${ref.uid} not found in "${ref.path}".` };
|
|
59386
59488
|
}
|
|
59387
|
-
const
|
|
59388
|
-
|
|
59389
|
-
|
|
59390
|
-
|
|
59391
|
-
|
|
59392
|
-
|
|
59489
|
+
const atts = collectAttachments(msg.bodyStructure);
|
|
59490
|
+
const sizes = await resolveDecodedSizes(client, ref.uid, atts);
|
|
59491
|
+
const attachments = atts.map((a, i) => {
|
|
59492
|
+
const info = {
|
|
59493
|
+
id: `${id}#${a.part}`,
|
|
59494
|
+
name: a.filename,
|
|
59495
|
+
mimeType: a.mimeType,
|
|
59496
|
+
size: sizes[i].size
|
|
59497
|
+
};
|
|
59498
|
+
if (!sizes[i].exact) info.sizeApproximate = true;
|
|
59499
|
+
return info;
|
|
59500
|
+
});
|
|
59393
59501
|
return { success: true, attachments };
|
|
59394
59502
|
});
|
|
59395
59503
|
}
|
|
@@ -59410,10 +59518,11 @@ async function imapFetchAttachment(id, attachmentName, deps = {}) {
|
|
|
59410
59518
|
error: `Attachment "${attachmentName}" not found on UID ${ref.uid}. Available: ${names}.`
|
|
59411
59519
|
};
|
|
59412
59520
|
}
|
|
59413
|
-
|
|
59521
|
+
const expected = decodedSizeWithoutBody(match).size;
|
|
59522
|
+
if (expected > MAX_IMAP_ATTACHMENT_BYTES) {
|
|
59414
59523
|
return {
|
|
59415
59524
|
success: false,
|
|
59416
|
-
error: `IMAP attachment "${attachmentName}" is ${
|
|
59525
|
+
error: `IMAP attachment "${attachmentName}" is ${expected} bytes; the maximum is ${MAX_IMAP_ATTACHMENT_BYTES} bytes (25 MiB).`
|
|
59417
59526
|
};
|
|
59418
59527
|
}
|
|
59419
59528
|
try {
|
|
@@ -59601,7 +59710,7 @@ async function imapThread(id, deps = {}, limit = 50) {
|
|
|
59601
59710
|
true
|
|
59602
59711
|
);
|
|
59603
59712
|
}
|
|
59604
|
-
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;
|
|
59713
|
+
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, BASE64_TAIL_WINDOW, QP_EXACT_MAX_BYTES, imapBatchMarkRead, imapBatchMarkUnread, imapBatchFlag, imapBatchUnflag, imapBatchDelete;
|
|
59605
59714
|
var init_imapClient = __esm({
|
|
59606
59715
|
"src/services/imapClient.ts"() {
|
|
59607
59716
|
"use strict";
|
|
@@ -59706,6 +59815,8 @@ var init_imapClient = __esm({
|
|
|
59706
59815
|
imapMarkRead = (id, deps = {}) => flagOp(id, "\\Seen", true, deps);
|
|
59707
59816
|
imapMarkUnread = (id, deps = {}) => flagOp(id, "\\Seen", false, deps);
|
|
59708
59817
|
FALLBACK_TRASH_PATH = "Trash";
|
|
59818
|
+
BASE64_TAIL_WINDOW = 1024;
|
|
59819
|
+
QP_EXACT_MAX_BYTES = 64 * 1024;
|
|
59709
59820
|
imapBatchMarkRead = (ids, deps = {}) => imapBatch(ids, deps, async (c, uids) => {
|
|
59710
59821
|
assertMutated(
|
|
59711
59822
|
await c.messageFlagsAdd(uids, ["\\Seen"], { uid: true }),
|
package/build/index.js
CHANGED
|
@@ -7256,7 +7256,7 @@ function isInlineDisposition(headers) {
|
|
|
7256
7256
|
function extractSize(headers) {
|
|
7257
7257
|
const dispHeader = getHeader(headers, "Content-Disposition");
|
|
7258
7258
|
if (dispHeader) {
|
|
7259
|
-
const sizeMatch = dispHeader.match(/size
|
|
7259
|
+
const sizeMatch = dispHeader.match(/(?:^|;)\s*size\s*=\s*"?(\d+)/i);
|
|
7260
7260
|
if (sizeMatch) return parseInt(sizeMatch[1], 10);
|
|
7261
7261
|
}
|
|
7262
7262
|
return 0;
|
|
@@ -7285,8 +7285,8 @@ function decodePartText(bytes, charset) {
|
|
|
7285
7285
|
return new TextDecoder("windows-1252").decode(bytes);
|
|
7286
7286
|
}
|
|
7287
7287
|
}
|
|
7288
|
-
function
|
|
7289
|
-
const cleaned = base64Body.replace(/[
|
|
7288
|
+
function base64DecodedSize(base64Body) {
|
|
7289
|
+
const cleaned = base64Body.replace(/[^A-Za-z0-9+/]/g, "");
|
|
7290
7290
|
return Math.floor(cleaned.length * 3 / 4);
|
|
7291
7291
|
}
|
|
7292
7292
|
function splitMimeParts(source, boundary) {
|
|
@@ -7349,7 +7349,8 @@ function decodeQuotedPrintable(body) {
|
|
|
7349
7349
|
}
|
|
7350
7350
|
function estimateSize(body, encoding) {
|
|
7351
7351
|
const enc = (encoding || "").toLowerCase().trim();
|
|
7352
|
-
if (enc === "base64") return
|
|
7352
|
+
if (enc === "base64") return base64DecodedSize(body);
|
|
7353
|
+
if (enc === "quoted-printable") return decodeQuotedPrintable(body).length;
|
|
7353
7354
|
return body.length;
|
|
7354
7355
|
}
|
|
7355
7356
|
function parseMimeAttachments(source) {
|
|
@@ -65182,9 +65183,12 @@ __export(imapClient_exports, {
|
|
|
65182
65183
|
MAX_UNSCOPED_SEARCH_DEPTH: () => MAX_UNSCOPED_SEARCH_DEPTH,
|
|
65183
65184
|
__resetPool: () => __resetPool,
|
|
65184
65185
|
__setPoolConnect: () => __setPoolConnect,
|
|
65186
|
+
base64DecodedLength: () => base64DecodedLength,
|
|
65187
|
+
base64DecodedSizeFromTail: () => base64DecodedSizeFromTail,
|
|
65185
65188
|
bodyStructureHasAttachments: () => bodyStructureHasAttachments,
|
|
65186
65189
|
buildImapConnectionOptions: () => buildImapConnectionOptions,
|
|
65187
65190
|
decodeImapId: () => decodeImapId,
|
|
65191
|
+
decodedSizeWithoutBody: () => decodedSizeWithoutBody,
|
|
65188
65192
|
dropAllPools: () => dropAllPools,
|
|
65189
65193
|
encodeImapId: () => encodeImapId,
|
|
65190
65194
|
imapAppendDraft: () => imapAppendDraft,
|
|
@@ -65225,6 +65229,7 @@ __export(imapClient_exports, {
|
|
|
65225
65229
|
matchMailbox: () => matchMailbox,
|
|
65226
65230
|
normalizeMessageId: () => normalizeMessageId,
|
|
65227
65231
|
omittedNote: () => omittedNote,
|
|
65232
|
+
quotedPrintableDecodedLength: () => quotedPrintableDecodedLength,
|
|
65228
65233
|
resolveDraftImapAccount: () => resolveDraftImapAccount,
|
|
65229
65234
|
resolveImapConfig: () => resolveImapConfig,
|
|
65230
65235
|
resolveImapConfigs: () => resolveImapConfigs,
|
|
@@ -66685,6 +66690,56 @@ async function imapDeleteMessageById(id, deps = {}) {
|
|
|
66685
66690
|
}
|
|
66686
66691
|
});
|
|
66687
66692
|
}
|
|
66693
|
+
function dispositionSize(params) {
|
|
66694
|
+
const raw = params?.size?.trim();
|
|
66695
|
+
return raw && /^\d+$/.test(raw) ? Number(raw) : void 0;
|
|
66696
|
+
}
|
|
66697
|
+
function base64DecodedLength(body) {
|
|
66698
|
+
const s = typeof body === "string" ? body : body.toString("latin1");
|
|
66699
|
+
const n = s.replace(/[^A-Za-z0-9+/]/g, "").length;
|
|
66700
|
+
return Math.floor(n * 3 / 4);
|
|
66701
|
+
}
|
|
66702
|
+
function base64DecodedSizeFromTail(total, tail) {
|
|
66703
|
+
if (tail.length >= total) return base64DecodedLength(tail);
|
|
66704
|
+
const s = tail.toString("latin1");
|
|
66705
|
+
const term = s.includes("\r\n") ? "\r\n" : "\n";
|
|
66706
|
+
const segs = s.split(term);
|
|
66707
|
+
if (segs.length < 3) return void 0;
|
|
66708
|
+
let trailing = 0;
|
|
66709
|
+
while (segs.length > 1 && segs[segs.length - 1] === "") {
|
|
66710
|
+
segs.pop();
|
|
66711
|
+
trailing++;
|
|
66712
|
+
}
|
|
66713
|
+
const last = segs[segs.length - 1];
|
|
66714
|
+
const full = segs.slice(1, -1);
|
|
66715
|
+
if (full.length === 0 || !last) return void 0;
|
|
66716
|
+
const L = full[0].length;
|
|
66717
|
+
const alphabet = /^[A-Za-z0-9+/]+$/;
|
|
66718
|
+
if (L === 0 || L % 4 !== 0 || !full.every((l) => l.length === L && alphabet.test(l))) {
|
|
66719
|
+
return void 0;
|
|
66720
|
+
}
|
|
66721
|
+
const m = /^([A-Za-z0-9+/]*)(=*)$/.exec(last);
|
|
66722
|
+
if (!m || m[2].length > 2 || last.length > L) return void 0;
|
|
66723
|
+
const before = total - trailing * term.length - last.length;
|
|
66724
|
+
if (before < 0 || before % (L + term.length) !== 0) return void 0;
|
|
66725
|
+
const chars = before / (L + term.length) * L + last.length;
|
|
66726
|
+
if (chars % 4 !== 0) return void 0;
|
|
66727
|
+
return chars / 4 * 3 - m[2].length;
|
|
66728
|
+
}
|
|
66729
|
+
function quotedPrintableDecodedLength(body) {
|
|
66730
|
+
const s = (typeof body === "string" ? body : body.toString("latin1")).replace(/=\r?\n/g, "");
|
|
66731
|
+
const escapes = s.match(/=[0-9A-Fa-f]{2}/g)?.length ?? 0;
|
|
66732
|
+
return s.length - escapes * 2;
|
|
66733
|
+
}
|
|
66734
|
+
function decodedSizeWithoutBody(a) {
|
|
66735
|
+
if (a.declaredSize !== void 0) return { size: a.declaredSize, exact: true };
|
|
66736
|
+
if (a.encoding === "base64") {
|
|
66737
|
+
const chars = a.encodedSize - 2 * Math.floor(a.encodedSize / 78);
|
|
66738
|
+
return { size: Math.floor(chars * 3 / 4), exact: false };
|
|
66739
|
+
}
|
|
66740
|
+
if (a.encoding === "quoted-printable") return { size: a.encodedSize, exact: false };
|
|
66741
|
+
return { size: a.encodedSize, exact: true };
|
|
66742
|
+
}
|
|
66688
66743
|
function collectAttachments(node, out = []) {
|
|
66689
66744
|
if (!node) return out;
|
|
66690
66745
|
const filename = node.dispositionParameters?.filename || node.parameters?.name;
|
|
@@ -66696,7 +66751,9 @@ function collectAttachments(node, out = []) {
|
|
|
66696
66751
|
part: node.part,
|
|
66697
66752
|
filename: filename || `part-${node.part}`,
|
|
66698
66753
|
mimeType: node.type || "application/octet-stream",
|
|
66699
|
-
|
|
66754
|
+
encodedSize: node.size ?? 0,
|
|
66755
|
+
encoding: (node.encoding || "").toLowerCase().trim(),
|
|
66756
|
+
declaredSize: dispositionSize(node.dispositionParameters)
|
|
66700
66757
|
});
|
|
66701
66758
|
}
|
|
66702
66759
|
for (const child of node.childNodes ?? []) collectAttachments(child, out);
|
|
@@ -66717,6 +66774,52 @@ async function streamToBuffer(content, maxBytes) {
|
|
|
66717
66774
|
}
|
|
66718
66775
|
return Buffer.concat(chunks);
|
|
66719
66776
|
}
|
|
66777
|
+
async function resolveDecodedSizes(client, uid, atts) {
|
|
66778
|
+
const out = atts.map(decodedSizeWithoutBody);
|
|
66779
|
+
const probes = [];
|
|
66780
|
+
atts.forEach((a, i) => {
|
|
66781
|
+
if (a.declaredSize !== void 0 || a.encodedSize <= 0) return;
|
|
66782
|
+
if (a.encoding === "base64") {
|
|
66783
|
+
probes.push({ i, start: Math.max(0, a.encodedSize - BASE64_TAIL_WINDOW), kind: "b64" });
|
|
66784
|
+
} else if (a.encoding === "quoted-printable" && a.encodedSize <= QP_EXACT_MAX_BYTES) {
|
|
66785
|
+
probes.push({ i, start: 0, kind: "qp" });
|
|
66786
|
+
}
|
|
66787
|
+
});
|
|
66788
|
+
if (probes.length === 0) return out;
|
|
66789
|
+
let parts;
|
|
66790
|
+
try {
|
|
66791
|
+
const msg = await client.fetchOne(
|
|
66792
|
+
String(uid),
|
|
66793
|
+
{
|
|
66794
|
+
uid: true,
|
|
66795
|
+
bodyParts: probes.map((p) => ({
|
|
66796
|
+
key: atts[p.i].part,
|
|
66797
|
+
start: p.start,
|
|
66798
|
+
maxLength: atts[p.i].encodedSize - p.start
|
|
66799
|
+
}))
|
|
66800
|
+
},
|
|
66801
|
+
{ uid: true }
|
|
66802
|
+
);
|
|
66803
|
+
parts = msg ? msg.bodyParts : void 0;
|
|
66804
|
+
} catch {
|
|
66805
|
+
return out;
|
|
66806
|
+
}
|
|
66807
|
+
for (const p of probes) {
|
|
66808
|
+
const a = atts[p.i];
|
|
66809
|
+
const buf = parts?.get(a.part.toLowerCase());
|
|
66810
|
+
if (!buf) continue;
|
|
66811
|
+
let exact;
|
|
66812
|
+
if (p.kind === "qp") {
|
|
66813
|
+
exact = quotedPrintableDecodedLength(buf);
|
|
66814
|
+
} else {
|
|
66815
|
+
const requested = a.encodedSize - p.start;
|
|
66816
|
+
const total = buf.length < requested ? p.start + buf.length : a.encodedSize;
|
|
66817
|
+
exact = base64DecodedSizeFromTail(total, buf);
|
|
66818
|
+
}
|
|
66819
|
+
if (exact !== void 0) out[p.i] = { size: exact, exact: true };
|
|
66820
|
+
}
|
|
66821
|
+
return out;
|
|
66822
|
+
}
|
|
66720
66823
|
async function imapListAttachments(id, deps = {}) {
|
|
66721
66824
|
const ref = decodeImapId(id);
|
|
66722
66825
|
if (!ref) return { success: false, error: `Not an IMAP message id: "${id}".` };
|
|
@@ -66725,12 +66828,18 @@ async function imapListAttachments(id, deps = {}) {
|
|
|
66725
66828
|
if (!msg || !msg.bodyStructure) {
|
|
66726
66829
|
return { success: false, error: `IMAP message UID ${ref.uid} not found in "${ref.path}".` };
|
|
66727
66830
|
}
|
|
66728
|
-
const
|
|
66729
|
-
|
|
66730
|
-
|
|
66731
|
-
|
|
66732
|
-
|
|
66733
|
-
|
|
66831
|
+
const atts = collectAttachments(msg.bodyStructure);
|
|
66832
|
+
const sizes = await resolveDecodedSizes(client, ref.uid, atts);
|
|
66833
|
+
const attachments = atts.map((a, i) => {
|
|
66834
|
+
const info = {
|
|
66835
|
+
id: `${id}#${a.part}`,
|
|
66836
|
+
name: a.filename,
|
|
66837
|
+
mimeType: a.mimeType,
|
|
66838
|
+
size: sizes[i].size
|
|
66839
|
+
};
|
|
66840
|
+
if (!sizes[i].exact) info.sizeApproximate = true;
|
|
66841
|
+
return info;
|
|
66842
|
+
});
|
|
66734
66843
|
return { success: true, attachments };
|
|
66735
66844
|
});
|
|
66736
66845
|
}
|
|
@@ -66751,10 +66860,11 @@ async function imapFetchAttachment(id, attachmentName, deps = {}) {
|
|
|
66751
66860
|
error: `Attachment "${attachmentName}" not found on UID ${ref.uid}. Available: ${names}.`
|
|
66752
66861
|
};
|
|
66753
66862
|
}
|
|
66754
|
-
|
|
66863
|
+
const expected = decodedSizeWithoutBody(match).size;
|
|
66864
|
+
if (expected > MAX_IMAP_ATTACHMENT_BYTES) {
|
|
66755
66865
|
return {
|
|
66756
66866
|
success: false,
|
|
66757
|
-
error: `IMAP attachment "${attachmentName}" is ${
|
|
66867
|
+
error: `IMAP attachment "${attachmentName}" is ${expected} bytes; the maximum is ${MAX_IMAP_ATTACHMENT_BYTES} bytes (25 MiB).`
|
|
66758
66868
|
};
|
|
66759
66869
|
}
|
|
66760
66870
|
try {
|
|
@@ -66942,7 +67052,7 @@ async function imapThread(id, deps = {}, limit = 50) {
|
|
|
66942
67052
|
true
|
|
66943
67053
|
);
|
|
66944
67054
|
}
|
|
66945
|
-
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;
|
|
67055
|
+
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, BASE64_TAIL_WINDOW, QP_EXACT_MAX_BYTES, imapBatchMarkRead, imapBatchMarkUnread, imapBatchFlag, imapBatchUnflag, imapBatchDelete;
|
|
66946
67056
|
var init_imapClient = __esm({
|
|
66947
67057
|
"src/services/imapClient.ts"() {
|
|
66948
67058
|
"use strict";
|
|
@@ -67047,6 +67157,8 @@ var init_imapClient = __esm({
|
|
|
67047
67157
|
imapMarkRead = (id, deps = {}) => flagOp(id, "\\Seen", true, deps);
|
|
67048
67158
|
imapMarkUnread = (id, deps = {}) => flagOp(id, "\\Seen", false, deps);
|
|
67049
67159
|
FALLBACK_TRASH_PATH = "Trash";
|
|
67160
|
+
BASE64_TAIL_WINDOW = 1024;
|
|
67161
|
+
QP_EXACT_MAX_BYTES = 64 * 1024;
|
|
67050
67162
|
imapBatchMarkRead = (ids, deps = {}) => imapBatch(ids, deps, async (c, uids) => {
|
|
67051
67163
|
assertMutated(
|
|
67052
67164
|
await c.messageFlagsAdd(uids, ["\\Seen"], { uid: true }),
|
|
@@ -89721,7 +89833,7 @@ registerTool(
|
|
|
89721
89833
|
registerTool(
|
|
89722
89834
|
"list-attachments",
|
|
89723
89835
|
{
|
|
89724
|
-
description: "Use when: enumerating a message's attachments (by id) to discover their names, MIME types, and sizes \u2014 typically before saving or fetching one.\nReturns: each attachment's name, MIME type, and size, plus a count.\nDo not use when: you want the bytes (use fetch-attachment for inline base64, or save-attachment to write to disk). Get the message id from search-messages or list-messages first.",
|
|
89836
|
+
description: "Use when: enumerating a message's attachments (by id) to discover their names, MIME types, and sizes \u2014 typically before saving or fetching one.\nReturns: each attachment's name, MIME type, and decoded size in bytes (what fetch-attachment returns), plus a count; sizeApproximate: true marks the rare IMAP size that is an estimate.\nDo not use when: you want the bytes (use fetch-attachment for inline base64, or save-attachment to write to disk). Get the message id from search-messages or list-messages first.",
|
|
89725
89837
|
inputSchema: {
|
|
89726
89838
|
id: MESSAGE_ID_SCHEMA
|
|
89727
89839
|
},
|
|
@@ -89741,8 +89853,9 @@ registerTool(
|
|
|
89741
89853
|
return successResponse("No attachments found", structured);
|
|
89742
89854
|
}
|
|
89743
89855
|
const attachmentList = attachments.map((a) => {
|
|
89744
|
-
const
|
|
89745
|
-
|
|
89856
|
+
const approx = "sizeApproximate" in a && a.sizeApproximate ? "~" : "";
|
|
89857
|
+
const size = a.size < 1024 ? `${a.size} bytes` : `${Math.round(a.size / 1024)} KB`;
|
|
89858
|
+
return ` - ${a.name} (${a.mimeType}, ${approx}${size})`;
|
|
89746
89859
|
}).join("\n");
|
|
89747
89860
|
return successResponse(
|
|
89748
89861
|
`Found ${attachments.length} attachment(s):
|
package/package.json
CHANGED