@marlinjai/email-mcp 1.4.4 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -2
- package/dist/index.js +300 -118
- package/dist/index.js.map +3 -3
- package/dist/setup/wizard.js +228 -91
- package/dist/setup/wizard.js.map +2 -2
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -35,18 +35,62 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
35
35
|
|
|
36
36
|
// src/auth/credential-store.ts
|
|
37
37
|
import crypto from "node:crypto";
|
|
38
|
+
import { execFileSync } from "node:child_process";
|
|
38
39
|
import fs from "node:fs";
|
|
39
40
|
import path from "node:path";
|
|
40
41
|
import os from "node:os";
|
|
41
|
-
function
|
|
42
|
-
|
|
42
|
+
function getStableMachineId() {
|
|
43
|
+
if (cachedMachineId !== void 0) return cachedMachineId;
|
|
44
|
+
cachedMachineId = computeStableMachineId();
|
|
45
|
+
return cachedMachineId;
|
|
43
46
|
}
|
|
44
|
-
function
|
|
45
|
-
|
|
47
|
+
function computeStableMachineId() {
|
|
48
|
+
try {
|
|
49
|
+
if (process.platform === "darwin") {
|
|
50
|
+
const out = execFileSync("ioreg", ["-rd1", "-c", "IOPlatformExpertDevice"], {
|
|
51
|
+
encoding: "utf-8"
|
|
52
|
+
});
|
|
53
|
+
const match = out.match(/"IOPlatformUUID"\s*=\s*"([^"]+)"/);
|
|
54
|
+
return match ? match[1] : null;
|
|
55
|
+
}
|
|
56
|
+
if (process.platform === "linux") {
|
|
57
|
+
for (const p of ["/etc/machine-id", "/var/lib/dbus/machine-id"]) {
|
|
58
|
+
if (fs.existsSync(p)) {
|
|
59
|
+
const id = fs.readFileSync(p, "utf-8").trim();
|
|
60
|
+
if (id) return id;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
if (process.platform === "win32") {
|
|
66
|
+
const out = execFileSync(
|
|
67
|
+
"reg",
|
|
68
|
+
["query", "HKLM\\SOFTWARE\\Microsoft\\Cryptography", "/v", "MachineGuid"],
|
|
69
|
+
{ encoding: "utf-8" }
|
|
70
|
+
);
|
|
71
|
+
const match = out.match(/MachineGuid\s+REG_SZ\s+([A-Za-z0-9-]+)/);
|
|
72
|
+
return match ? match[1] : null;
|
|
73
|
+
}
|
|
74
|
+
} catch {
|
|
75
|
+
}
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
function getSeedCandidates() {
|
|
79
|
+
const seeds = [];
|
|
80
|
+
const username = os.userInfo().username;
|
|
81
|
+
const envKey = process.env.EMAIL_MCP_KEY;
|
|
82
|
+
if (envKey) seeds.push(`email-mcp:env:${envKey}`);
|
|
83
|
+
const machineId = getStableMachineId();
|
|
84
|
+
if (machineId) seeds.push(`email-mcp:machine:${machineId}:${username}`);
|
|
85
|
+
seeds.push(`email-mcp:${os.hostname()}:${username}`);
|
|
86
|
+
return seeds;
|
|
87
|
+
}
|
|
88
|
+
function deriveKey(seed, salt) {
|
|
89
|
+
return crypto.pbkdf2Sync(seed, salt, PBKDF2_ITERATIONS, KEY_LENGTH, "sha512");
|
|
46
90
|
}
|
|
47
|
-
function encrypt(plaintext) {
|
|
91
|
+
function encrypt(plaintext, seed) {
|
|
48
92
|
const salt = crypto.randomBytes(SALT_LENGTH);
|
|
49
|
-
const key = deriveKey(salt);
|
|
93
|
+
const key = deriveKey(seed, salt);
|
|
50
94
|
const iv = crypto.randomBytes(IV_LENGTH);
|
|
51
95
|
const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
|
|
52
96
|
let encrypted = cipher.update(plaintext, "utf-8", "hex");
|
|
@@ -59,16 +103,27 @@ function encrypt(plaintext) {
|
|
|
59
103
|
data: encrypted
|
|
60
104
|
});
|
|
61
105
|
}
|
|
62
|
-
function decrypt(ciphertext) {
|
|
106
|
+
function decrypt(ciphertext, seeds) {
|
|
63
107
|
const { salt, iv, authTag, data } = JSON.parse(ciphertext);
|
|
64
|
-
const
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
let
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
108
|
+
const saltBuf = Buffer.from(salt, "hex");
|
|
109
|
+
const ivBuf = Buffer.from(iv, "hex");
|
|
110
|
+
const authTagBuf = Buffer.from(authTag, "hex");
|
|
111
|
+
let lastError;
|
|
112
|
+
for (const seed of seeds) {
|
|
113
|
+
try {
|
|
114
|
+
const key = deriveKey(seed, saltBuf);
|
|
115
|
+
const decipher = crypto.createDecipheriv(ALGORITHM, key, ivBuf);
|
|
116
|
+
decipher.setAuthTag(authTagBuf);
|
|
117
|
+
let decrypted = decipher.update(data, "hex", "utf-8");
|
|
118
|
+
decrypted += decipher.final("utf-8");
|
|
119
|
+
return { plaintext: decrypted, seedUsed: seed };
|
|
120
|
+
} catch (err) {
|
|
121
|
+
lastError = err;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
throw lastError ?? new Error("Unable to decrypt credentials");
|
|
125
|
+
}
|
|
126
|
+
var ALGORITHM, KEY_LENGTH, IV_LENGTH, SALT_LENGTH, PBKDF2_ITERATIONS, cachedMachineId, CredentialStore;
|
|
72
127
|
var init_credential_store = __esm({
|
|
73
128
|
"src/auth/credential-store.ts"() {
|
|
74
129
|
"use strict";
|
|
@@ -91,12 +146,20 @@ var init_credential_store = __esm({
|
|
|
91
146
|
return { accounts: {} };
|
|
92
147
|
}
|
|
93
148
|
const raw = fs.readFileSync(this.filePath, "utf-8");
|
|
94
|
-
const
|
|
95
|
-
|
|
149
|
+
const seeds = getSeedCandidates();
|
|
150
|
+
const { plaintext, seedUsed } = decrypt(raw, seeds);
|
|
151
|
+
const data = JSON.parse(plaintext);
|
|
152
|
+
if (seedUsed !== seeds[0]) {
|
|
153
|
+
try {
|
|
154
|
+
this.write(data);
|
|
155
|
+
} catch {
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return data;
|
|
96
159
|
}
|
|
97
160
|
write(data) {
|
|
98
161
|
const json2 = JSON.stringify(data);
|
|
99
|
-
const encrypted = encrypt(json2);
|
|
162
|
+
const encrypted = encrypt(json2, getSeedCandidates()[0]);
|
|
100
163
|
fs.writeFileSync(this.filePath, encrypted, { mode: 384 });
|
|
101
164
|
}
|
|
102
165
|
async save(creds) {
|
|
@@ -3314,8 +3377,8 @@ var require_utils = __commonJS({
|
|
|
3314
3377
|
}
|
|
3315
3378
|
return ind;
|
|
3316
3379
|
}
|
|
3317
|
-
function removeDotSegments(
|
|
3318
|
-
let input2 =
|
|
3380
|
+
function removeDotSegments(path4) {
|
|
3381
|
+
let input2 = path4;
|
|
3319
3382
|
const output2 = [];
|
|
3320
3383
|
let nextSlash = -1;
|
|
3321
3384
|
let len = 0;
|
|
@@ -3514,8 +3577,8 @@ var require_schemes = __commonJS({
|
|
|
3514
3577
|
wsComponent.secure = void 0;
|
|
3515
3578
|
}
|
|
3516
3579
|
if (wsComponent.resourceName) {
|
|
3517
|
-
const [
|
|
3518
|
-
wsComponent.path =
|
|
3580
|
+
const [path4, query] = wsComponent.resourceName.split("?");
|
|
3581
|
+
wsComponent.path = path4 && path4 !== "/" ? path4 : void 0;
|
|
3519
3582
|
wsComponent.query = query;
|
|
3520
3583
|
wsComponent.resourceName = void 0;
|
|
3521
3584
|
}
|
|
@@ -6877,12 +6940,12 @@ var require_dist = __commonJS({
|
|
|
6877
6940
|
throw new Error(`Unknown format "${name}"`);
|
|
6878
6941
|
return f;
|
|
6879
6942
|
};
|
|
6880
|
-
function addFormats(ajv, list,
|
|
6943
|
+
function addFormats(ajv, list, fs4, exportName) {
|
|
6881
6944
|
var _a2;
|
|
6882
6945
|
var _b;
|
|
6883
6946
|
(_a2 = (_b = ajv.opts.code).formats) !== null && _a2 !== void 0 ? _a2 : _b.formats = (0, codegen_1._)`require("ajv-formats/dist/formats").${exportName}`;
|
|
6884
6947
|
for (const f of list)
|
|
6885
|
-
ajv.addFormat(f,
|
|
6948
|
+
ajv.addFormat(f, fs4[f]);
|
|
6886
6949
|
}
|
|
6887
6950
|
module.exports = exports = formatsPlugin;
|
|
6888
6951
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -7805,8 +7868,8 @@ var init_adapter2 = __esm({
|
|
|
7805
7868
|
}
|
|
7806
7869
|
});
|
|
7807
7870
|
const originalApi = client.api.bind(client);
|
|
7808
|
-
client.api = (
|
|
7809
|
-
return originalApi(
|
|
7871
|
+
client.api = (path4) => {
|
|
7872
|
+
return originalApi(path4).header("Prefer", 'IdType="ImmutableId"');
|
|
7810
7873
|
};
|
|
7811
7874
|
this.client = client;
|
|
7812
7875
|
}
|
|
@@ -8499,6 +8562,7 @@ var init_adapter3 = __esm({
|
|
|
8499
8562
|
accountId = "";
|
|
8500
8563
|
email = "";
|
|
8501
8564
|
passwordCreds = null;
|
|
8565
|
+
connectingPromise = null;
|
|
8502
8566
|
async connect(credentials) {
|
|
8503
8567
|
if (!credentials.password) {
|
|
8504
8568
|
throw new Error("IMAP adapter requires password credentials");
|
|
@@ -8506,23 +8570,87 @@ var init_adapter3 = __esm({
|
|
|
8506
8570
|
this.accountId = credentials.id;
|
|
8507
8571
|
this.email = credentials.email;
|
|
8508
8572
|
this.passwordCreds = credentials.password;
|
|
8509
|
-
this.
|
|
8510
|
-
host: credentials.password.host,
|
|
8511
|
-
port: credentials.password.port,
|
|
8512
|
-
secure: credentials.password.tls,
|
|
8513
|
-
auth: {
|
|
8514
|
-
user: credentials.email,
|
|
8515
|
-
pass: credentials.password.password
|
|
8516
|
-
},
|
|
8517
|
-
logger: false
|
|
8518
|
-
});
|
|
8519
|
-
await this.client.connect();
|
|
8573
|
+
await this.ensureConnected();
|
|
8520
8574
|
}
|
|
8521
8575
|
async disconnect() {
|
|
8522
8576
|
if (this.client) {
|
|
8523
|
-
|
|
8577
|
+
try {
|
|
8578
|
+
await this.client.logout();
|
|
8579
|
+
} catch {
|
|
8580
|
+
}
|
|
8524
8581
|
this.client = null;
|
|
8525
8582
|
}
|
|
8583
|
+
this.passwordCreds = null;
|
|
8584
|
+
}
|
|
8585
|
+
/**
|
|
8586
|
+
* Builds a fresh ImapFlow client from stored credentials and wires up
|
|
8587
|
+
* close/error listeners so a dropped socket clears `this.client`, forcing
|
|
8588
|
+
* the next `ensureConnected()` call to reconnect.
|
|
8589
|
+
*/
|
|
8590
|
+
buildClient() {
|
|
8591
|
+
if (!this.passwordCreds) {
|
|
8592
|
+
throw new Error("Cannot build IMAP client without stored credentials");
|
|
8593
|
+
}
|
|
8594
|
+
const client = new ImapFlow({
|
|
8595
|
+
host: this.passwordCreds.host,
|
|
8596
|
+
port: this.passwordCreds.port,
|
|
8597
|
+
secure: this.passwordCreds.tls,
|
|
8598
|
+
auth: { user: this.email, pass: this.passwordCreds.password },
|
|
8599
|
+
logger: false
|
|
8600
|
+
});
|
|
8601
|
+
const drop = () => {
|
|
8602
|
+
if (this.client === client) this.client = null;
|
|
8603
|
+
};
|
|
8604
|
+
client.on("close", drop);
|
|
8605
|
+
client.on("error", drop);
|
|
8606
|
+
return client;
|
|
8607
|
+
}
|
|
8608
|
+
/**
|
|
8609
|
+
* Returns true while the underlying ImapFlow socket is healthy.
|
|
8610
|
+
* ImapFlow sets `usable=false` when the connection is closed, in the
|
|
8611
|
+
* middle of logging out, or otherwise unsafe to issue commands on.
|
|
8612
|
+
*/
|
|
8613
|
+
isClientUsable() {
|
|
8614
|
+
return !!this.client && this.client.usable === true;
|
|
8615
|
+
}
|
|
8616
|
+
/**
|
|
8617
|
+
* Guarantees a usable IMAP client and returns it. Reconnects transparently
|
|
8618
|
+
* if the previous client was dropped (idle timeout, server-side disconnect,
|
|
8619
|
+
* network flap). Concurrent callers share a single in-flight reconnect to
|
|
8620
|
+
* avoid duplicate sessions.
|
|
8621
|
+
*
|
|
8622
|
+
* Callers should bind the result to a local `const client` and use that
|
|
8623
|
+
* within the method instead of `this.client`, so a `'close'` event mid-call
|
|
8624
|
+
* does not race the active operation.
|
|
8625
|
+
*/
|
|
8626
|
+
async ensureConnected() {
|
|
8627
|
+
if (this.connectingPromise) {
|
|
8628
|
+
await this.connectingPromise;
|
|
8629
|
+
}
|
|
8630
|
+
if (!this.isClientUsable()) {
|
|
8631
|
+
if (!this.passwordCreds) {
|
|
8632
|
+
throw new Error("Not connected");
|
|
8633
|
+
}
|
|
8634
|
+
this.connectingPromise = (async () => {
|
|
8635
|
+
if (this.client) {
|
|
8636
|
+
try {
|
|
8637
|
+
await this.client.logout();
|
|
8638
|
+
} catch {
|
|
8639
|
+
}
|
|
8640
|
+
this.client = null;
|
|
8641
|
+
}
|
|
8642
|
+
const client = this.buildClient();
|
|
8643
|
+
await client.connect();
|
|
8644
|
+
this.client = client;
|
|
8645
|
+
})().finally(() => {
|
|
8646
|
+
this.connectingPromise = null;
|
|
8647
|
+
});
|
|
8648
|
+
await this.connectingPromise;
|
|
8649
|
+
}
|
|
8650
|
+
if (!this.client) {
|
|
8651
|
+
throw new Error("IMAP client unavailable after reconnect attempt");
|
|
8652
|
+
}
|
|
8653
|
+
return this.client;
|
|
8526
8654
|
}
|
|
8527
8655
|
async testConnection() {
|
|
8528
8656
|
try {
|
|
@@ -8533,14 +8661,14 @@ var init_adapter3 = __esm({
|
|
|
8533
8661
|
}
|
|
8534
8662
|
}
|
|
8535
8663
|
async listFolders() {
|
|
8536
|
-
|
|
8537
|
-
const imapFolders = await
|
|
8664
|
+
const client = await this.ensureConnected();
|
|
8665
|
+
const imapFolders = await client.list();
|
|
8538
8666
|
return imapFolders.map(mapImapFolder);
|
|
8539
8667
|
}
|
|
8540
8668
|
async createFolder(name, parentPath) {
|
|
8541
|
-
|
|
8669
|
+
const client = await this.ensureConnected();
|
|
8542
8670
|
const fullPath = parentPath ? `${parentPath}/${name}` : name;
|
|
8543
|
-
const result = await
|
|
8671
|
+
const result = await client.mailboxCreate(fullPath);
|
|
8544
8672
|
return {
|
|
8545
8673
|
id: result.path,
|
|
8546
8674
|
name: result.name || name,
|
|
@@ -8569,7 +8697,7 @@ var init_adapter3 = __esm({
|
|
|
8569
8697
|
* and matching by name or special-use type.
|
|
8570
8698
|
*/
|
|
8571
8699
|
async resolveFolder(folder) {
|
|
8572
|
-
|
|
8700
|
+
const client = await this.ensureConnected();
|
|
8573
8701
|
const FOLDER_ALIASES = {
|
|
8574
8702
|
junk: ["Junk", "Spam", "Bulk Mail", "Junk E-mail", "Junk Email"],
|
|
8575
8703
|
spam: ["Junk", "Spam", "Bulk Mail", "Junk E-mail", "Junk Email"],
|
|
@@ -8578,7 +8706,7 @@ var init_adapter3 = __esm({
|
|
|
8578
8706
|
drafts: ["Drafts", "Draft"],
|
|
8579
8707
|
archive: ["Archive", "All Mail"]
|
|
8580
8708
|
};
|
|
8581
|
-
const folders = await
|
|
8709
|
+
const folders = await client.list();
|
|
8582
8710
|
const lowerFolder = folder.toLowerCase();
|
|
8583
8711
|
const exactMatch = folders.find(
|
|
8584
8712
|
(f) => f.path === folder || f.path.toLowerCase() === lowerFolder
|
|
@@ -8616,7 +8744,7 @@ var init_adapter3 = __esm({
|
|
|
8616
8744
|
return folder;
|
|
8617
8745
|
}
|
|
8618
8746
|
async search(query) {
|
|
8619
|
-
|
|
8747
|
+
const client = await this.ensureConnected();
|
|
8620
8748
|
const requestedFolder = query.folder || "INBOX";
|
|
8621
8749
|
let folder;
|
|
8622
8750
|
try {
|
|
@@ -8625,17 +8753,25 @@ var init_adapter3 = __esm({
|
|
|
8625
8753
|
folder = requestedFolder;
|
|
8626
8754
|
}
|
|
8627
8755
|
try {
|
|
8628
|
-
await
|
|
8756
|
+
await client.noop();
|
|
8629
8757
|
} catch {
|
|
8630
8758
|
}
|
|
8631
8759
|
let lock;
|
|
8632
8760
|
try {
|
|
8633
|
-
lock = await
|
|
8761
|
+
lock = await client.getMailboxLock(folder);
|
|
8634
8762
|
} catch (error48) {
|
|
8635
8763
|
throw formatImapError(error48, `Failed to open folder "${folder}"`);
|
|
8636
8764
|
}
|
|
8637
8765
|
try {
|
|
8638
|
-
|
|
8766
|
+
let mailboxExists = client.mailbox?.exists ?? 0;
|
|
8767
|
+
if (mailboxExists === 0) {
|
|
8768
|
+
try {
|
|
8769
|
+
const status = await client.status(folder, { messages: true });
|
|
8770
|
+
mailboxExists = status?.messages ?? 0;
|
|
8771
|
+
} catch {
|
|
8772
|
+
mailboxExists = 0;
|
|
8773
|
+
}
|
|
8774
|
+
}
|
|
8639
8775
|
if (mailboxExists === 0) {
|
|
8640
8776
|
return [];
|
|
8641
8777
|
}
|
|
@@ -8643,14 +8779,14 @@ var init_adapter3 = __esm({
|
|
|
8643
8779
|
const hasCriteria = Object.keys(criteria).length > 0;
|
|
8644
8780
|
let allUids = [];
|
|
8645
8781
|
try {
|
|
8646
|
-
const searchResult = await
|
|
8782
|
+
const searchResult = await client.search(
|
|
8647
8783
|
hasCriteria ? criteria : { all: true },
|
|
8648
8784
|
{ uid: true }
|
|
8649
8785
|
);
|
|
8650
8786
|
allUids = Array.isArray(searchResult) ? searchResult : [];
|
|
8651
8787
|
} catch {
|
|
8652
8788
|
try {
|
|
8653
|
-
allUids = await this.collectUidsViaFetch(query);
|
|
8789
|
+
allUids = await this.collectUidsViaFetch(client, query);
|
|
8654
8790
|
} catch {
|
|
8655
8791
|
return [];
|
|
8656
8792
|
}
|
|
@@ -8658,7 +8794,7 @@ var init_adapter3 = __esm({
|
|
|
8658
8794
|
const offset = query.offset || 0;
|
|
8659
8795
|
const slicedUids = query.limit ? allUids.slice(offset, offset + query.limit) : allUids.slice(offset);
|
|
8660
8796
|
if (slicedUids.length === 0) return [];
|
|
8661
|
-
return await this.fetchEmails(slicedUids, folder, query.returnBody);
|
|
8797
|
+
return await this.fetchEmails(client, slicedUids, folder, query.returnBody);
|
|
8662
8798
|
} catch (error48) {
|
|
8663
8799
|
throw formatImapError(error48, `Search failed in folder "${folder}"`);
|
|
8664
8800
|
} finally {
|
|
@@ -8672,13 +8808,12 @@ var init_adapter3 = __esm({
|
|
|
8672
8808
|
* 2. FETCH 1:N using mailbox.exists as explicit range
|
|
8673
8809
|
* 3. Individual sequence number fetches (slowest but most resilient)
|
|
8674
8810
|
*/
|
|
8675
|
-
async collectUidsViaFetch(query) {
|
|
8676
|
-
if (!this.client) return [];
|
|
8811
|
+
async collectUidsViaFetch(client, query) {
|
|
8677
8812
|
const uids = [];
|
|
8678
|
-
const exists =
|
|
8813
|
+
const exists = client.mailbox?.exists ?? 0;
|
|
8679
8814
|
const safeFetch = async (range) => {
|
|
8680
8815
|
try {
|
|
8681
|
-
const messages = await
|
|
8816
|
+
const messages = await client.fetchAll(range, { uid: true, flags: true });
|
|
8682
8817
|
for (const msg of messages) {
|
|
8683
8818
|
if (query.unreadOnly && msg.flags?.has("\\Seen")) continue;
|
|
8684
8819
|
if (query.starredOnly && !msg.flags?.has("\\Flagged")) continue;
|
|
@@ -8698,7 +8833,7 @@ var init_adapter3 = __esm({
|
|
|
8698
8833
|
const count = exists > 0 ? exists : 50;
|
|
8699
8834
|
for (let seq = 1; seq <= count; seq++) {
|
|
8700
8835
|
try {
|
|
8701
|
-
const msgs = await
|
|
8836
|
+
const msgs = await client.fetchAll(String(seq), { uid: true, flags: true });
|
|
8702
8837
|
for (const msg of msgs) {
|
|
8703
8838
|
if (query.unreadOnly && msg.flags?.has("\\Seen")) continue;
|
|
8704
8839
|
if (query.starredOnly && !msg.flags?.has("\\Flagged")) continue;
|
|
@@ -8712,19 +8847,18 @@ var init_adapter3 = __esm({
|
|
|
8712
8847
|
/**
|
|
8713
8848
|
* Fetches email data for a set of UIDs, either with full body or lightweight headers.
|
|
8714
8849
|
*/
|
|
8715
|
-
async fetchEmails(uids, folder, returnBody) {
|
|
8716
|
-
if (!this.client) return [];
|
|
8850
|
+
async fetchEmails(client, uids, folder, returnBody) {
|
|
8717
8851
|
const emails = [];
|
|
8718
8852
|
const fetchOpts = returnBody ? { source: true, uid: true, flags: true } : { envelope: true, uid: true, flags: true, bodyStructure: true };
|
|
8719
8853
|
const uidRange = uids.join(",");
|
|
8720
8854
|
let messages;
|
|
8721
8855
|
try {
|
|
8722
|
-
messages = await
|
|
8856
|
+
messages = await client.fetchAll(uidRange, fetchOpts, { uid: true });
|
|
8723
8857
|
} catch {
|
|
8724
8858
|
messages = [];
|
|
8725
8859
|
for (const uid of uids) {
|
|
8726
8860
|
try {
|
|
8727
|
-
const batch = await
|
|
8861
|
+
const batch = await client.fetchAll(String(uid), fetchOpts, { uid: true });
|
|
8728
8862
|
messages.push(...batch);
|
|
8729
8863
|
} catch {
|
|
8730
8864
|
}
|
|
@@ -8763,17 +8897,17 @@ var init_adapter3 = __esm({
|
|
|
8763
8897
|
return emails;
|
|
8764
8898
|
}
|
|
8765
8899
|
async getEmail(id, folder) {
|
|
8766
|
-
|
|
8900
|
+
const client = await this.ensureConnected();
|
|
8767
8901
|
const targetFolder = folder || "INBOX";
|
|
8768
8902
|
let lock;
|
|
8769
8903
|
try {
|
|
8770
|
-
lock = await
|
|
8904
|
+
lock = await client.getMailboxLock(targetFolder);
|
|
8771
8905
|
} catch (error48) {
|
|
8772
8906
|
throw formatImapError(error48, `Failed to open folder "${targetFolder}"`);
|
|
8773
8907
|
}
|
|
8774
8908
|
try {
|
|
8775
8909
|
const uid = parseInt(id, 10);
|
|
8776
|
-
const msg = await
|
|
8910
|
+
const msg = await client.fetchOne(String(uid), { source: true, uid: true, flags: true }, { uid: true });
|
|
8777
8911
|
if (!msg) throw new Error(`Email ${id} not found`);
|
|
8778
8912
|
const parsed = await simpleParser(msg.source);
|
|
8779
8913
|
parsed.flags = msg.flags;
|
|
@@ -8783,22 +8917,22 @@ var init_adapter3 = __esm({
|
|
|
8783
8917
|
}
|
|
8784
8918
|
}
|
|
8785
8919
|
async getThread(threadId) {
|
|
8786
|
-
|
|
8920
|
+
const client = await this.ensureConnected();
|
|
8787
8921
|
let lock;
|
|
8788
8922
|
try {
|
|
8789
|
-
lock = await
|
|
8923
|
+
lock = await client.getMailboxLock("INBOX");
|
|
8790
8924
|
} catch (error48) {
|
|
8791
8925
|
throw formatImapError(error48, 'Failed to open folder "INBOX"');
|
|
8792
8926
|
}
|
|
8793
8927
|
try {
|
|
8794
|
-
const searchResult = await
|
|
8928
|
+
const searchResult = await client.search(
|
|
8795
8929
|
{ or: [{ header: { "message-id": threadId } }, { header: { references: threadId } }, { header: { "in-reply-to": threadId } }] },
|
|
8796
8930
|
{ uid: true }
|
|
8797
8931
|
);
|
|
8798
8932
|
const uids = Array.isArray(searchResult) ? searchResult : [];
|
|
8799
8933
|
if (uids.length === 0) throw new Error(`Thread ${threadId} not found`);
|
|
8800
8934
|
const messages = [];
|
|
8801
|
-
for await (const msg of
|
|
8935
|
+
for await (const msg of client.fetch(uids, { source: true, uid: true, flags: true })) {
|
|
8802
8936
|
const parsed = await simpleParser(msg.source);
|
|
8803
8937
|
parsed.flags = msg.flags;
|
|
8804
8938
|
messages.push(mapParsedEmail(parsed, "INBOX", this.accountId, msg.uid));
|
|
@@ -8824,15 +8958,15 @@ var init_adapter3 = __esm({
|
|
|
8824
8958
|
}
|
|
8825
8959
|
}
|
|
8826
8960
|
async getAttachment(emailId, attachmentId) {
|
|
8827
|
-
|
|
8961
|
+
const client = await this.ensureConnected();
|
|
8828
8962
|
let lock;
|
|
8829
8963
|
try {
|
|
8830
|
-
lock = await
|
|
8964
|
+
lock = await client.getMailboxLock("INBOX");
|
|
8831
8965
|
} catch (error48) {
|
|
8832
8966
|
throw formatImapError(error48, 'Failed to open folder "INBOX"');
|
|
8833
8967
|
}
|
|
8834
8968
|
try {
|
|
8835
|
-
const msg = await
|
|
8969
|
+
const msg = await client.fetchOne(String(emailId), { source: true, uid: true }, { uid: true });
|
|
8836
8970
|
if (!msg) throw new Error(`Email ${emailId} not found`);
|
|
8837
8971
|
const parsed = await simpleParser(msg.source);
|
|
8838
8972
|
const attachment = (parsed.attachments || []).find(
|
|
@@ -8859,7 +8993,7 @@ var init_adapter3 = __esm({
|
|
|
8859
8993
|
return { id: messageId };
|
|
8860
8994
|
}
|
|
8861
8995
|
async createDraft(params) {
|
|
8862
|
-
|
|
8996
|
+
const client = await this.ensureConnected();
|
|
8863
8997
|
const lines = [];
|
|
8864
8998
|
lines.push(`From: ${this.email}`);
|
|
8865
8999
|
lines.push(`To: ${params.to.map((c) => c.name ? `"${c.name}" <${c.email}>` : c.email).join(", ")}`);
|
|
@@ -8875,7 +9009,9 @@ var init_adapter3 = __esm({
|
|
|
8875
9009
|
lines.push("");
|
|
8876
9010
|
lines.push(params.body.text || "");
|
|
8877
9011
|
const rawMessage = lines.join("\r\n");
|
|
8878
|
-
const
|
|
9012
|
+
const draftsFolder = await this.resolveFolder("Drafts");
|
|
9013
|
+
const result = await client.append(draftsFolder, rawMessage, ["\\Draft", "\\Seen"]);
|
|
9014
|
+
if (!result) throw new Error("Failed to append draft");
|
|
8879
9015
|
return { id: String(result.uid || result) };
|
|
8880
9016
|
}
|
|
8881
9017
|
async listDrafts(limit, offset) {
|
|
@@ -8886,16 +9022,16 @@ var init_adapter3 = __esm({
|
|
|
8886
9022
|
});
|
|
8887
9023
|
}
|
|
8888
9024
|
async moveEmail(emailId, targetFolder, sourceFolder) {
|
|
8889
|
-
|
|
9025
|
+
const client = await this.ensureConnected();
|
|
8890
9026
|
const folder = sourceFolder || "INBOX";
|
|
8891
9027
|
let lock;
|
|
8892
9028
|
try {
|
|
8893
|
-
lock = await
|
|
9029
|
+
lock = await client.getMailboxLock(folder);
|
|
8894
9030
|
} catch (error48) {
|
|
8895
9031
|
throw formatImapError(error48, `Failed to open folder "${folder}"`);
|
|
8896
9032
|
}
|
|
8897
9033
|
try {
|
|
8898
|
-
await
|
|
9034
|
+
await client.messageMove(emailId, targetFolder, { uid: true });
|
|
8899
9035
|
} finally {
|
|
8900
9036
|
lock.release();
|
|
8901
9037
|
}
|
|
@@ -8925,44 +9061,44 @@ var init_adapter3 = __esm({
|
|
|
8925
9061
|
}
|
|
8926
9062
|
}
|
|
8927
9063
|
async deleteEmail(emailId, permanent, sourceFolder) {
|
|
8928
|
-
|
|
9064
|
+
const client = await this.ensureConnected();
|
|
8929
9065
|
const folder = sourceFolder ? await this.resolveFolder(sourceFolder) : "INBOX";
|
|
8930
9066
|
const trashFolder = await this.resolveFolder("Trash");
|
|
8931
9067
|
let lock;
|
|
8932
9068
|
try {
|
|
8933
|
-
lock = await
|
|
9069
|
+
lock = await client.getMailboxLock(folder);
|
|
8934
9070
|
} catch (error48) {
|
|
8935
9071
|
throw formatImapError(error48, `Failed to open folder "${folder}"`);
|
|
8936
9072
|
}
|
|
8937
9073
|
try {
|
|
8938
9074
|
if (permanent || folder === trashFolder) {
|
|
8939
|
-
await
|
|
9075
|
+
await client.messageDelete(emailId, { uid: true });
|
|
8940
9076
|
} else {
|
|
8941
|
-
await
|
|
9077
|
+
await client.messageMove(emailId, trashFolder, { uid: true });
|
|
8942
9078
|
}
|
|
8943
9079
|
} finally {
|
|
8944
9080
|
lock.release();
|
|
8945
9081
|
}
|
|
8946
9082
|
}
|
|
8947
9083
|
async markEmail(emailId, flags, sourceFolder) {
|
|
8948
|
-
|
|
9084
|
+
const client = await this.ensureConnected();
|
|
8949
9085
|
const folder = sourceFolder || "INBOX";
|
|
8950
9086
|
let lock;
|
|
8951
9087
|
try {
|
|
8952
|
-
lock = await
|
|
9088
|
+
lock = await client.getMailboxLock(folder);
|
|
8953
9089
|
} catch (error48) {
|
|
8954
9090
|
throw formatImapError(error48, `Failed to open folder "${folder}"`);
|
|
8955
9091
|
}
|
|
8956
9092
|
try {
|
|
8957
9093
|
if (flags.read === true) {
|
|
8958
|
-
await
|
|
9094
|
+
await client.messageFlagsAdd(emailId, ["\\Seen"], { uid: true });
|
|
8959
9095
|
} else if (flags.read === false) {
|
|
8960
|
-
await
|
|
9096
|
+
await client.messageFlagsRemove(emailId, ["\\Seen"], { uid: true });
|
|
8961
9097
|
}
|
|
8962
9098
|
if (flags.starred === true || flags.flagged === true) {
|
|
8963
|
-
await
|
|
9099
|
+
await client.messageFlagsAdd(emailId, ["\\Flagged"], { uid: true });
|
|
8964
9100
|
} else if (flags.starred === false || flags.flagged === false) {
|
|
8965
|
-
await
|
|
9101
|
+
await client.messageFlagsRemove(emailId, ["\\Flagged"], { uid: true });
|
|
8966
9102
|
}
|
|
8967
9103
|
} finally {
|
|
8968
9104
|
lock.release();
|
|
@@ -8995,32 +9131,32 @@ var init_adapter3 = __esm({
|
|
|
8995
9131
|
return { id: String(result?.uid ?? "") };
|
|
8996
9132
|
}
|
|
8997
9133
|
async batchDelete(emailIds, permanent, sourceFolder) {
|
|
8998
|
-
|
|
9134
|
+
const client = await this.ensureConnected();
|
|
8999
9135
|
const result = { succeeded: [], failed: [] };
|
|
9000
9136
|
const folder = sourceFolder ? await this.resolveFolder(sourceFolder) : "INBOX";
|
|
9001
9137
|
const trashFolder = await this.resolveFolder("Trash");
|
|
9002
9138
|
const shouldPermanentDelete = permanent || folder === trashFolder;
|
|
9003
9139
|
let lock;
|
|
9004
9140
|
try {
|
|
9005
|
-
lock = await
|
|
9141
|
+
lock = await client.getMailboxLock(folder);
|
|
9006
9142
|
} catch (error48) {
|
|
9007
9143
|
throw formatImapError(error48, `Failed to open folder "${folder}"`);
|
|
9008
9144
|
}
|
|
9009
9145
|
try {
|
|
9010
9146
|
const uidRange = emailIds.join(",");
|
|
9011
9147
|
if (shouldPermanentDelete) {
|
|
9012
|
-
await
|
|
9148
|
+
await client.messageDelete(uidRange, { uid: true });
|
|
9013
9149
|
} else {
|
|
9014
|
-
await
|
|
9150
|
+
await client.messageMove(uidRange, trashFolder, { uid: true });
|
|
9015
9151
|
}
|
|
9016
9152
|
result.succeeded = [...emailIds];
|
|
9017
9153
|
} catch (error48) {
|
|
9018
9154
|
for (const id of emailIds) {
|
|
9019
9155
|
try {
|
|
9020
9156
|
if (shouldPermanentDelete) {
|
|
9021
|
-
await
|
|
9157
|
+
await client.messageDelete(id, { uid: true });
|
|
9022
9158
|
} else {
|
|
9023
|
-
await
|
|
9159
|
+
await client.messageMove(id, trashFolder, { uid: true });
|
|
9024
9160
|
}
|
|
9025
9161
|
result.succeeded.push(id);
|
|
9026
9162
|
} catch (e) {
|
|
@@ -9033,23 +9169,23 @@ var init_adapter3 = __esm({
|
|
|
9033
9169
|
return result;
|
|
9034
9170
|
}
|
|
9035
9171
|
async batchMove(emailIds, targetFolder, sourceFolder) {
|
|
9036
|
-
|
|
9172
|
+
const client = await this.ensureConnected();
|
|
9037
9173
|
const result = { succeeded: [], failed: [] };
|
|
9038
9174
|
const folder = sourceFolder || "INBOX";
|
|
9039
9175
|
let lock;
|
|
9040
9176
|
try {
|
|
9041
|
-
lock = await
|
|
9177
|
+
lock = await client.getMailboxLock(folder);
|
|
9042
9178
|
} catch (error48) {
|
|
9043
9179
|
throw formatImapError(error48, `Failed to open folder "${folder}"`);
|
|
9044
9180
|
}
|
|
9045
9181
|
try {
|
|
9046
9182
|
const uidRange = emailIds.join(",");
|
|
9047
|
-
await
|
|
9183
|
+
await client.messageMove(uidRange, targetFolder, { uid: true });
|
|
9048
9184
|
result.succeeded = [...emailIds];
|
|
9049
9185
|
} catch (error48) {
|
|
9050
9186
|
for (const id of emailIds) {
|
|
9051
9187
|
try {
|
|
9052
|
-
await
|
|
9188
|
+
await client.messageMove(id, targetFolder, { uid: true });
|
|
9053
9189
|
result.succeeded.push(id);
|
|
9054
9190
|
} catch (e) {
|
|
9055
9191
|
result.failed.push({ id, error: e.message });
|
|
@@ -9061,26 +9197,26 @@ var init_adapter3 = __esm({
|
|
|
9061
9197
|
return result;
|
|
9062
9198
|
}
|
|
9063
9199
|
async batchMark(emailIds, flags, sourceFolder) {
|
|
9064
|
-
|
|
9200
|
+
const client = await this.ensureConnected();
|
|
9065
9201
|
const result = { succeeded: [], failed: [] };
|
|
9066
9202
|
const folder = sourceFolder || "INBOX";
|
|
9067
9203
|
let lock;
|
|
9068
9204
|
try {
|
|
9069
|
-
lock = await
|
|
9205
|
+
lock = await client.getMailboxLock(folder);
|
|
9070
9206
|
} catch (error48) {
|
|
9071
9207
|
throw formatImapError(error48, `Failed to open folder "${folder}"`);
|
|
9072
9208
|
}
|
|
9073
9209
|
try {
|
|
9074
9210
|
const uidRange = emailIds.join(",");
|
|
9075
9211
|
if (flags.read === true) {
|
|
9076
|
-
await
|
|
9212
|
+
await client.messageFlagsAdd(uidRange, ["\\Seen"], { uid: true });
|
|
9077
9213
|
} else if (flags.read === false) {
|
|
9078
|
-
await
|
|
9214
|
+
await client.messageFlagsRemove(uidRange, ["\\Seen"], { uid: true });
|
|
9079
9215
|
}
|
|
9080
9216
|
if (flags.starred === true || flags.flagged === true) {
|
|
9081
|
-
await
|
|
9217
|
+
await client.messageFlagsAdd(uidRange, ["\\Flagged"], { uid: true });
|
|
9082
9218
|
} else if (flags.starred === false || flags.flagged === false) {
|
|
9083
|
-
await
|
|
9219
|
+
await client.messageFlagsRemove(uidRange, ["\\Flagged"], { uid: true });
|
|
9084
9220
|
}
|
|
9085
9221
|
result.succeeded = [...emailIds];
|
|
9086
9222
|
} catch (error48) {
|
|
@@ -10164,8 +10300,8 @@ function getErrorMap() {
|
|
|
10164
10300
|
|
|
10165
10301
|
// node_modules/zod/v3/helpers/parseUtil.js
|
|
10166
10302
|
var makeIssue = (params) => {
|
|
10167
|
-
const { data, path:
|
|
10168
|
-
const fullPath = [...
|
|
10303
|
+
const { data, path: path4, errorMaps, issueData } = params;
|
|
10304
|
+
const fullPath = [...path4, ...issueData.path || []];
|
|
10169
10305
|
const fullIssue = {
|
|
10170
10306
|
...issueData,
|
|
10171
10307
|
path: fullPath
|
|
@@ -10280,11 +10416,11 @@ var errorUtil;
|
|
|
10280
10416
|
|
|
10281
10417
|
// node_modules/zod/v3/types.js
|
|
10282
10418
|
var ParseInputLazyPath = class {
|
|
10283
|
-
constructor(parent, value,
|
|
10419
|
+
constructor(parent, value, path4, key) {
|
|
10284
10420
|
this._cachedPath = [];
|
|
10285
10421
|
this.parent = parent;
|
|
10286
10422
|
this.data = value;
|
|
10287
|
-
this._path =
|
|
10423
|
+
this._path = path4;
|
|
10288
10424
|
this._key = key;
|
|
10289
10425
|
}
|
|
10290
10426
|
get path() {
|
|
@@ -14207,10 +14343,10 @@ function mergeDefs(...defs) {
|
|
|
14207
14343
|
function cloneDef(schema) {
|
|
14208
14344
|
return mergeDefs(schema._zod.def);
|
|
14209
14345
|
}
|
|
14210
|
-
function getElementAtPath(obj,
|
|
14211
|
-
if (!
|
|
14346
|
+
function getElementAtPath(obj, path4) {
|
|
14347
|
+
if (!path4)
|
|
14212
14348
|
return obj;
|
|
14213
|
-
return
|
|
14349
|
+
return path4.reduce((acc, key) => acc?.[key], obj);
|
|
14214
14350
|
}
|
|
14215
14351
|
function promiseAllObject(promisesObj) {
|
|
14216
14352
|
const keys = Object.keys(promisesObj);
|
|
@@ -14593,11 +14729,11 @@ function aborted(x, startIndex = 0) {
|
|
|
14593
14729
|
}
|
|
14594
14730
|
return false;
|
|
14595
14731
|
}
|
|
14596
|
-
function prefixIssues(
|
|
14732
|
+
function prefixIssues(path4, issues) {
|
|
14597
14733
|
return issues.map((iss) => {
|
|
14598
14734
|
var _a2;
|
|
14599
14735
|
(_a2 = iss).path ?? (_a2.path = []);
|
|
14600
|
-
iss.path.unshift(
|
|
14736
|
+
iss.path.unshift(path4);
|
|
14601
14737
|
return iss;
|
|
14602
14738
|
});
|
|
14603
14739
|
}
|
|
@@ -14780,7 +14916,7 @@ function formatError(error48, mapper = (issue2) => issue2.message) {
|
|
|
14780
14916
|
}
|
|
14781
14917
|
function treeifyError(error48, mapper = (issue2) => issue2.message) {
|
|
14782
14918
|
const result = { errors: [] };
|
|
14783
|
-
const processError = (error49,
|
|
14919
|
+
const processError = (error49, path4 = []) => {
|
|
14784
14920
|
var _a2, _b;
|
|
14785
14921
|
for (const issue2 of error49.issues) {
|
|
14786
14922
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
@@ -14790,7 +14926,7 @@ function treeifyError(error48, mapper = (issue2) => issue2.message) {
|
|
|
14790
14926
|
} else if (issue2.code === "invalid_element") {
|
|
14791
14927
|
processError({ issues: issue2.issues }, issue2.path);
|
|
14792
14928
|
} else {
|
|
14793
|
-
const fullpath = [...
|
|
14929
|
+
const fullpath = [...path4, ...issue2.path];
|
|
14794
14930
|
if (fullpath.length === 0) {
|
|
14795
14931
|
result.errors.push(mapper(issue2));
|
|
14796
14932
|
continue;
|
|
@@ -14822,8 +14958,8 @@ function treeifyError(error48, mapper = (issue2) => issue2.message) {
|
|
|
14822
14958
|
}
|
|
14823
14959
|
function toDotPath(_path) {
|
|
14824
14960
|
const segs = [];
|
|
14825
|
-
const
|
|
14826
|
-
for (const seg of
|
|
14961
|
+
const path4 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
|
|
14962
|
+
for (const seg of path4) {
|
|
14827
14963
|
if (typeof seg === "number")
|
|
14828
14964
|
segs.push(`[${seg}]`);
|
|
14829
14965
|
else if (typeof seg === "symbol")
|
|
@@ -27229,13 +27365,13 @@ function resolveRef(ref, ctx) {
|
|
|
27229
27365
|
if (!ref.startsWith("#")) {
|
|
27230
27366
|
throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
|
|
27231
27367
|
}
|
|
27232
|
-
const
|
|
27233
|
-
if (
|
|
27368
|
+
const path4 = ref.slice(1).split("/").filter(Boolean);
|
|
27369
|
+
if (path4.length === 0) {
|
|
27234
27370
|
return ctx.rootSchema;
|
|
27235
27371
|
}
|
|
27236
27372
|
const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
|
|
27237
|
-
if (
|
|
27238
|
-
const key =
|
|
27373
|
+
if (path4[0] === defsKey) {
|
|
27374
|
+
const key = path4[1];
|
|
27239
27375
|
if (!key || !ctx.defs[key]) {
|
|
27240
27376
|
throw new Error(`Reference not found: ${ref}`);
|
|
27241
27377
|
}
|
|
@@ -33068,9 +33204,28 @@ function registerAccountTools(server, accountManager) {
|
|
|
33068
33204
|
}
|
|
33069
33205
|
|
|
33070
33206
|
// src/tools/reading.ts
|
|
33207
|
+
import fs3 from "node:fs";
|
|
33208
|
+
import os3 from "node:os";
|
|
33209
|
+
import path3 from "node:path";
|
|
33071
33210
|
function jsonResult2(data) {
|
|
33072
33211
|
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
33073
33212
|
}
|
|
33213
|
+
function getDownloadsBaseDir() {
|
|
33214
|
+
return path3.resolve(
|
|
33215
|
+
process.env.EMAIL_MCP_DOWNLOADS_DIR || path3.join(os3.homedir(), ".email-mcp", "downloads")
|
|
33216
|
+
);
|
|
33217
|
+
}
|
|
33218
|
+
function resolveDownloadPath(outputPath) {
|
|
33219
|
+
const baseDir = getDownloadsBaseDir();
|
|
33220
|
+
const resolved = path3.resolve(baseDir, outputPath);
|
|
33221
|
+
const relative = path3.relative(baseDir, resolved);
|
|
33222
|
+
if (relative.startsWith("..") || path3.isAbsolute(relative)) {
|
|
33223
|
+
throw new Error(
|
|
33224
|
+
`outputPath must resolve inside ${baseDir} (set EMAIL_MCP_DOWNLOADS_DIR to change the base directory)`
|
|
33225
|
+
);
|
|
33226
|
+
}
|
|
33227
|
+
return resolved;
|
|
33228
|
+
}
|
|
33074
33229
|
function stripBodies(emails) {
|
|
33075
33230
|
return emails.map((email3) => ({
|
|
33076
33231
|
...email3,
|
|
@@ -33195,6 +33350,33 @@ function registerReadingTools(server, accountManager) {
|
|
|
33195
33350
|
}
|
|
33196
33351
|
}
|
|
33197
33352
|
);
|
|
33353
|
+
server.tool(
|
|
33354
|
+
"email_save_attachment",
|
|
33355
|
+
`Download an email attachment and save it directly to disk, returning metadata only (no base64 in the response) \u2014 avoids paying the token cost of round-tripping large attachments through the calling model. outputPath is relative to a fixed downloads directory (${getDownloadsBaseDir()}, override with EMAIL_MCP_DOWNLOADS_DIR) and cannot escape it.`,
|
|
33356
|
+
{
|
|
33357
|
+
accountId: external_exports3.string(),
|
|
33358
|
+
emailId: external_exports3.string(),
|
|
33359
|
+
attachmentId: external_exports3.string(),
|
|
33360
|
+
outputPath: external_exports3.string().describe("Relative file path (within the downloads directory) to save the attachment as")
|
|
33361
|
+
},
|
|
33362
|
+
async (args) => {
|
|
33363
|
+
try {
|
|
33364
|
+
const absPath = resolveDownloadPath(args.outputPath);
|
|
33365
|
+
const provider = await accountManager.getProvider(args.accountId);
|
|
33366
|
+
const { data, meta: meta3 } = await provider.getAttachment(args.emailId, args.attachmentId);
|
|
33367
|
+
fs3.mkdirSync(path3.dirname(absPath), { recursive: true });
|
|
33368
|
+
fs3.writeFileSync(absPath, Buffer.from(data));
|
|
33369
|
+
return jsonResult2({
|
|
33370
|
+
path: absPath,
|
|
33371
|
+
bytes: data.length,
|
|
33372
|
+
filename: meta3.filename,
|
|
33373
|
+
mimeType: meta3.contentType
|
|
33374
|
+
});
|
|
33375
|
+
} catch (error48) {
|
|
33376
|
+
return jsonResult2({ error: error48.message });
|
|
33377
|
+
}
|
|
33378
|
+
}
|
|
33379
|
+
);
|
|
33198
33380
|
}
|
|
33199
33381
|
|
|
33200
33382
|
// src/tools/sending.ts
|