@marlinjai/email-mcp 1.3.0 → 1.4.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 +13 -1
- package/dist/index.js +907 -573
- package/dist/index.js.map +4 -4
- package/dist/setup/wizard.js +315 -127
- package/dist/setup/wizard.js.map +3 -3
- package/package.json +2 -3
package/dist/setup/wizard.js
CHANGED
|
@@ -4,7 +4,101 @@
|
|
|
4
4
|
// src/setup/wizard.ts
|
|
5
5
|
import crypto3 from "node:crypto";
|
|
6
6
|
import { exec } from "node:child_process";
|
|
7
|
-
|
|
7
|
+
|
|
8
|
+
// src/setup/prompts.ts
|
|
9
|
+
import readline from "node:readline/promises";
|
|
10
|
+
import { stdin as input, stdout as output } from "node:process";
|
|
11
|
+
var CTRL_C = "";
|
|
12
|
+
var CTRL_D = "";
|
|
13
|
+
var BACKSPACE = "\x7F";
|
|
14
|
+
var sharedInterface = null;
|
|
15
|
+
function getInterface() {
|
|
16
|
+
if (!sharedInterface) {
|
|
17
|
+
sharedInterface = readline.createInterface({ input, output });
|
|
18
|
+
}
|
|
19
|
+
return sharedInterface;
|
|
20
|
+
}
|
|
21
|
+
function closePrompts() {
|
|
22
|
+
sharedInterface?.close();
|
|
23
|
+
sharedInterface = null;
|
|
24
|
+
}
|
|
25
|
+
async function askText(message, defaultValue) {
|
|
26
|
+
const rl = getInterface();
|
|
27
|
+
const suffix = defaultValue ? ` (${defaultValue})` : "";
|
|
28
|
+
const answer = await rl.question(`${message}${suffix} `);
|
|
29
|
+
const trimmed = answer.trim();
|
|
30
|
+
return trimmed.length > 0 ? trimmed : defaultValue ?? "";
|
|
31
|
+
}
|
|
32
|
+
async function askValidated(message, defaultValue, validate) {
|
|
33
|
+
for (; ; ) {
|
|
34
|
+
const value = await askText(message, defaultValue);
|
|
35
|
+
const result = validate(value);
|
|
36
|
+
if (result === true) return value;
|
|
37
|
+
console.log(` ${result}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
async function askConfirm(message, defaultValue = false) {
|
|
41
|
+
const suffix = defaultValue ? "Y/n" : "y/N";
|
|
42
|
+
const answer = await askText(`${message} (${suffix})`);
|
|
43
|
+
if (!answer) return defaultValue;
|
|
44
|
+
return /^y/i.test(answer);
|
|
45
|
+
}
|
|
46
|
+
async function askChoice(message, choices) {
|
|
47
|
+
console.log(message);
|
|
48
|
+
choices.forEach((c, i) => console.log(` ${i + 1}) ${c.name}`));
|
|
49
|
+
for (; ; ) {
|
|
50
|
+
const answer = await askText("Enter a number:");
|
|
51
|
+
const idx = parseInt(answer, 10) - 1;
|
|
52
|
+
if (idx >= 0 && idx < choices.length) return choices[idx].value;
|
|
53
|
+
console.log(` Please enter a number between 1 and ${choices.length}.`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function askPassword(message) {
|
|
57
|
+
return new Promise((resolve) => {
|
|
58
|
+
output.write(`${message} `);
|
|
59
|
+
if (!input.isTTY || typeof input.setRawMode !== "function") {
|
|
60
|
+
const rl = getInterface();
|
|
61
|
+
rl.question("").then(resolve);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
let value = "";
|
|
65
|
+
input.setRawMode(true);
|
|
66
|
+
input.resume();
|
|
67
|
+
input.setEncoding("utf8");
|
|
68
|
+
const cleanup = () => {
|
|
69
|
+
input.setRawMode(false);
|
|
70
|
+
input.pause();
|
|
71
|
+
input.removeListener("data", onData);
|
|
72
|
+
};
|
|
73
|
+
const onData = (char) => {
|
|
74
|
+
switch (char) {
|
|
75
|
+
case "\n":
|
|
76
|
+
case "\r":
|
|
77
|
+
case CTRL_D:
|
|
78
|
+
cleanup();
|
|
79
|
+
output.write("\n");
|
|
80
|
+
resolve(value);
|
|
81
|
+
break;
|
|
82
|
+
case CTRL_C:
|
|
83
|
+
cleanup();
|
|
84
|
+
output.write("\n");
|
|
85
|
+
process.exit(130);
|
|
86
|
+
break;
|
|
87
|
+
case BACKSPACE:
|
|
88
|
+
case "\b":
|
|
89
|
+
if (value.length > 0) {
|
|
90
|
+
value = value.slice(0, -1);
|
|
91
|
+
output.write("\b \b");
|
|
92
|
+
}
|
|
93
|
+
break;
|
|
94
|
+
default:
|
|
95
|
+
value += char;
|
|
96
|
+
output.write("*");
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
input.on("data", onData);
|
|
100
|
+
});
|
|
101
|
+
}
|
|
8
102
|
|
|
9
103
|
// src/auth/credential-store.ts
|
|
10
104
|
import crypto from "node:crypto";
|
|
@@ -264,7 +358,10 @@ function createFileCachePlugin(cacheFilePath) {
|
|
|
264
358
|
}
|
|
265
359
|
var OutlookAuth = class _OutlookAuth {
|
|
266
360
|
static AUTHORITY = "https://login.microsoftonline.com/consumers";
|
|
267
|
-
|
|
361
|
+
// MailboxSettings.ReadWrite is required for the messageRules API (block
|
|
362
|
+
// rules) — confirmed by Microsoft's own docs to be supported for personal
|
|
363
|
+
// Microsoft accounts (not just work/school), not just Mail.ReadWrite/Send.
|
|
364
|
+
static SCOPES = ["Mail.ReadWrite", "Mail.Send", "MailboxSettings.ReadWrite", "offline_access"];
|
|
268
365
|
pca;
|
|
269
366
|
cryptoProvider;
|
|
270
367
|
constructor(clientId, cacheFilePath) {
|
|
@@ -901,6 +998,74 @@ var GmailAdapter = class {
|
|
|
901
998
|
messageCount: label.messagesTotal ?? 0
|
|
902
999
|
}));
|
|
903
1000
|
}
|
|
1001
|
+
/**
|
|
1002
|
+
* Trains Gmail's own spam filter — the same signal the "Report spam" UI
|
|
1003
|
+
* button sends (add SPAM, drop INBOX). Not an abuse report to Google.
|
|
1004
|
+
*/
|
|
1005
|
+
async reportSpam(emailId, _sourceFolder) {
|
|
1006
|
+
const gmail = this.ensureConnected();
|
|
1007
|
+
await gmail.users.messages.modify({
|
|
1008
|
+
userId: "me",
|
|
1009
|
+
id: emailId,
|
|
1010
|
+
requestBody: { addLabelIds: ["SPAM"], removeLabelIds: ["INBOX"] }
|
|
1011
|
+
});
|
|
1012
|
+
}
|
|
1013
|
+
async createBlockRule(rule) {
|
|
1014
|
+
const gmail = this.ensureConnected();
|
|
1015
|
+
const criteria = {};
|
|
1016
|
+
switch (rule.matchType) {
|
|
1017
|
+
case "senderDomain":
|
|
1018
|
+
criteria.from = `@${rule.value}`;
|
|
1019
|
+
break;
|
|
1020
|
+
case "senderAddress":
|
|
1021
|
+
criteria.from = rule.value;
|
|
1022
|
+
break;
|
|
1023
|
+
case "subjectContains":
|
|
1024
|
+
criteria.subject = rule.value;
|
|
1025
|
+
break;
|
|
1026
|
+
case "headerContains":
|
|
1027
|
+
criteria.query = rule.value;
|
|
1028
|
+
break;
|
|
1029
|
+
}
|
|
1030
|
+
const action = rule.action === "delete" ? { addLabelIds: ["TRASH"] } : { addLabelIds: ["SPAM"], removeLabelIds: ["INBOX"] };
|
|
1031
|
+
const res = await gmail.users.settings.filters.create({
|
|
1032
|
+
userId: "me",
|
|
1033
|
+
requestBody: { criteria, action }
|
|
1034
|
+
});
|
|
1035
|
+
return { id: res.data.id || "" };
|
|
1036
|
+
}
|
|
1037
|
+
async listBlockRules() {
|
|
1038
|
+
const gmail = this.ensureConnected();
|
|
1039
|
+
const res = await gmail.users.settings.filters.list({ userId: "me" });
|
|
1040
|
+
return (res.data.filter || []).map((f) => {
|
|
1041
|
+
const criteria = f.criteria || {};
|
|
1042
|
+
const addLabelIds = f.action?.addLabelIds || [];
|
|
1043
|
+
let matchType = "headerContains";
|
|
1044
|
+
let value = criteria.query || "";
|
|
1045
|
+
if (typeof criteria.from === "string" && criteria.from.startsWith("@")) {
|
|
1046
|
+
matchType = "senderDomain";
|
|
1047
|
+
value = criteria.from.slice(1);
|
|
1048
|
+
} else if (criteria.from) {
|
|
1049
|
+
matchType = "senderAddress";
|
|
1050
|
+
value = criteria.from;
|
|
1051
|
+
} else if (criteria.subject) {
|
|
1052
|
+
matchType = "subjectContains";
|
|
1053
|
+
value = criteria.subject;
|
|
1054
|
+
}
|
|
1055
|
+
return {
|
|
1056
|
+
id: f.id || "",
|
|
1057
|
+
matchType,
|
|
1058
|
+
value,
|
|
1059
|
+
action: addLabelIds.includes("TRASH") ? "delete" : "moveToJunk",
|
|
1060
|
+
createdAt: ""
|
|
1061
|
+
// Gmail's filter API does not expose a creation timestamp
|
|
1062
|
+
};
|
|
1063
|
+
});
|
|
1064
|
+
}
|
|
1065
|
+
async deleteBlockRule(ruleId) {
|
|
1066
|
+
const gmail = this.ensureConnected();
|
|
1067
|
+
await gmail.users.settings.filters.delete({ userId: "me", id: ruleId });
|
|
1068
|
+
}
|
|
904
1069
|
buildRfc2822(params) {
|
|
905
1070
|
const lines = [];
|
|
906
1071
|
lines.push(`From: ${this.email}`);
|
|
@@ -1420,6 +1585,86 @@ var OutlookAdapter = class {
|
|
|
1420
1585
|
}
|
|
1421
1586
|
return result;
|
|
1422
1587
|
}
|
|
1588
|
+
/**
|
|
1589
|
+
* Trains Outlook's own spam filter — the same signal "Report Junk" sends
|
|
1590
|
+
* in the UI (move to the Junk Email well-known folder). Not an abuse
|
|
1591
|
+
* report to Microsoft's security team.
|
|
1592
|
+
*/
|
|
1593
|
+
async reportSpam(emailId, _sourceFolder) {
|
|
1594
|
+
const client = this.ensureClient();
|
|
1595
|
+
await client.api(`/me/messages/${encodeURIComponent(emailId)}/move`).post({
|
|
1596
|
+
destinationId: "junkemail"
|
|
1597
|
+
});
|
|
1598
|
+
}
|
|
1599
|
+
/**
|
|
1600
|
+
* Requires the `MailboxSettings.ReadWrite` delegated scope, confirmed by
|
|
1601
|
+
* Microsoft's own docs to be supported for personal Microsoft accounts
|
|
1602
|
+
* (outlook.com/hotmail), not just work/school. Accounts authenticated
|
|
1603
|
+
* before this scope was added need to re-run the setup wizard to
|
|
1604
|
+
* re-consent — old tokens will 403 on this call until then.
|
|
1605
|
+
*/
|
|
1606
|
+
async createBlockRule(rule) {
|
|
1607
|
+
const client = this.ensureClient();
|
|
1608
|
+
const conditions = {};
|
|
1609
|
+
switch (rule.matchType) {
|
|
1610
|
+
case "senderDomain":
|
|
1611
|
+
case "senderAddress":
|
|
1612
|
+
conditions.senderContains = [rule.value];
|
|
1613
|
+
break;
|
|
1614
|
+
case "subjectContains":
|
|
1615
|
+
conditions.subjectContains = [rule.value];
|
|
1616
|
+
break;
|
|
1617
|
+
case "headerContains":
|
|
1618
|
+
conditions.headerContains = [rule.value];
|
|
1619
|
+
break;
|
|
1620
|
+
}
|
|
1621
|
+
const actions = { stopProcessingRules: true };
|
|
1622
|
+
if (rule.action === "delete") {
|
|
1623
|
+
actions.delete = true;
|
|
1624
|
+
} else {
|
|
1625
|
+
actions.moveToFolder = await this.resolveFolder("junkemail");
|
|
1626
|
+
}
|
|
1627
|
+
const existing = await this.listBlockRules();
|
|
1628
|
+
const result = await client.api("/me/mailFolders/inbox/messageRules").post({
|
|
1629
|
+
displayName: `email-mcp: ${rule.matchType} "${rule.value}"`.slice(0, 255),
|
|
1630
|
+
sequence: existing.length + 1,
|
|
1631
|
+
isEnabled: true,
|
|
1632
|
+
conditions,
|
|
1633
|
+
actions
|
|
1634
|
+
});
|
|
1635
|
+
return { id: result.id };
|
|
1636
|
+
}
|
|
1637
|
+
async listBlockRules() {
|
|
1638
|
+
const client = this.ensureClient();
|
|
1639
|
+
const response = await client.api("/me/mailFolders/inbox/messageRules").get();
|
|
1640
|
+
return (response.value || []).map((r) => {
|
|
1641
|
+
const conditions = r.conditions || {};
|
|
1642
|
+
let matchType = "headerContains";
|
|
1643
|
+
let value = "";
|
|
1644
|
+
if (conditions.senderContains?.length) {
|
|
1645
|
+
value = conditions.senderContains[0];
|
|
1646
|
+
matchType = value.includes("@") ? "senderAddress" : "senderDomain";
|
|
1647
|
+
} else if (conditions.subjectContains?.length) {
|
|
1648
|
+
matchType = "subjectContains";
|
|
1649
|
+
value = conditions.subjectContains[0];
|
|
1650
|
+
} else if (conditions.headerContains?.length) {
|
|
1651
|
+
matchType = "headerContains";
|
|
1652
|
+
value = conditions.headerContains[0];
|
|
1653
|
+
}
|
|
1654
|
+
return {
|
|
1655
|
+
id: r.id,
|
|
1656
|
+
matchType,
|
|
1657
|
+
value,
|
|
1658
|
+
action: r.actions?.delete ? "delete" : "moveToJunk",
|
|
1659
|
+
createdAt: ""
|
|
1660
|
+
// Graph's messageRule resource has no creation timestamp field
|
|
1661
|
+
};
|
|
1662
|
+
});
|
|
1663
|
+
}
|
|
1664
|
+
async deleteBlockRule(ruleId) {
|
|
1665
|
+
const client = this.ensureClient();
|
|
1666
|
+
await client.api(`/me/mailFolders/inbox/messageRules/${encodeURIComponent(ruleId)}`).delete();
|
|
1667
|
+
}
|
|
1423
1668
|
async getCategories() {
|
|
1424
1669
|
const client = this.ensureClient();
|
|
1425
1670
|
const response = await client.api("/me/outlook/masterCategories").get();
|
|
@@ -1969,6 +2214,30 @@ var ImapAdapter = class {
|
|
|
1969
2214
|
lock.release();
|
|
1970
2215
|
}
|
|
1971
2216
|
}
|
|
2217
|
+
/**
|
|
2218
|
+
* Best-effort only: generic IMAP has no server-side ML classifier to
|
|
2219
|
+
* train, so this just moves the message to the account's Junk-typed
|
|
2220
|
+
* folder (via the same alias resolution `deleteEmail` uses for Trash).
|
|
2221
|
+
* No standing block-rule mechanism exists across IMAP servers (Sieve
|
|
2222
|
+
* exists on some but isn't universal), so `createBlockRule` is
|
|
2223
|
+
* intentionally left unimplemented for this provider.
|
|
2224
|
+
*/
|
|
2225
|
+
async reportSpam(emailId, sourceFolder) {
|
|
2226
|
+
if (!this.client) throw new Error("Not connected");
|
|
2227
|
+
const folder = sourceFolder || "INBOX";
|
|
2228
|
+
const junkFolder = await this.resolveFolder("junk");
|
|
2229
|
+
let lock;
|
|
2230
|
+
try {
|
|
2231
|
+
lock = await this.client.getMailboxLock(folder);
|
|
2232
|
+
} catch (error) {
|
|
2233
|
+
throw formatImapError(error, `Failed to open folder "${folder}"`);
|
|
2234
|
+
}
|
|
2235
|
+
try {
|
|
2236
|
+
await this.client.messageMove(emailId, junkFolder, { uid: true });
|
|
2237
|
+
} finally {
|
|
2238
|
+
lock.release();
|
|
2239
|
+
}
|
|
2240
|
+
}
|
|
1972
2241
|
async deleteEmail(emailId, permanent, sourceFolder) {
|
|
1973
2242
|
if (!this.client) throw new Error("Not connected");
|
|
1974
2243
|
const folder = sourceFolder ? await this.resolveFolder(sourceFolder) : "INBOX";
|
|
@@ -2352,15 +2621,7 @@ async function testAndSave(creds) {
|
|
|
2352
2621
|
}
|
|
2353
2622
|
}
|
|
2354
2623
|
async function promptAccountName(defaultName) {
|
|
2355
|
-
|
|
2356
|
-
{
|
|
2357
|
-
type: "input",
|
|
2358
|
-
name: "name",
|
|
2359
|
-
message: "Account name:",
|
|
2360
|
-
default: defaultName
|
|
2361
|
-
}
|
|
2362
|
-
]);
|
|
2363
|
-
return name;
|
|
2624
|
+
return askText("Account name:", defaultName);
|
|
2364
2625
|
}
|
|
2365
2626
|
async function setupGmail() {
|
|
2366
2627
|
console.log("\n--- Gmail Setup ---");
|
|
@@ -2452,30 +2713,25 @@ async function setupICloud() {
|
|
|
2452
2713
|
console.log(
|
|
2453
2714
|
"You need an app-specific password for iCloud Mail.\nGenerate one at https://appleid.apple.com/account/manage\n(Sign In & Security > App-Specific Passwords)\n"
|
|
2454
2715
|
);
|
|
2455
|
-
const
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
message: "App-specific password:",
|
|
2466
|
-
mask: "*",
|
|
2467
|
-
validate: (v) => v.trim().length > 0 || "Password is required"
|
|
2468
|
-
}
|
|
2469
|
-
]);
|
|
2716
|
+
const email = await askValidated(
|
|
2717
|
+
"iCloud email address:",
|
|
2718
|
+
void 0,
|
|
2719
|
+
(v) => v.includes("@") || "Enter a valid email address"
|
|
2720
|
+
);
|
|
2721
|
+
let password = await askPassword("App-specific password:");
|
|
2722
|
+
while (password.trim().length === 0) {
|
|
2723
|
+
console.log(" Password is required");
|
|
2724
|
+
password = await askPassword("App-specific password:");
|
|
2725
|
+
}
|
|
2470
2726
|
const name = await promptAccountName("iCloud");
|
|
2471
2727
|
const id = crypto3.randomUUID();
|
|
2472
2728
|
const creds = {
|
|
2473
2729
|
id,
|
|
2474
2730
|
name,
|
|
2475
2731
|
provider: ProviderType.ICloud,
|
|
2476
|
-
email:
|
|
2732
|
+
email: email.trim(),
|
|
2477
2733
|
password: {
|
|
2478
|
-
password
|
|
2734
|
+
password,
|
|
2479
2735
|
host: "imap.mail.me.com",
|
|
2480
2736
|
port: 993,
|
|
2481
2737
|
tls: true,
|
|
@@ -2487,72 +2743,26 @@ async function setupICloud() {
|
|
|
2487
2743
|
}
|
|
2488
2744
|
async function setupIMAP() {
|
|
2489
2745
|
console.log("\n--- Generic IMAP Setup ---\n");
|
|
2490
|
-
const
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
type: "input",
|
|
2505
|
-
name: "port",
|
|
2506
|
-
message: "IMAP port:",
|
|
2507
|
-
default: "993",
|
|
2508
|
-
validate: (v) => {
|
|
2509
|
-
const n = parseInt(v, 10);
|
|
2510
|
-
return n > 0 && n <= 65535 || "Enter a valid port number (1-65535)";
|
|
2511
|
-
}
|
|
2512
|
-
},
|
|
2513
|
-
{
|
|
2514
|
-
type: "confirm",
|
|
2515
|
-
name: "tls",
|
|
2516
|
-
message: "Use TLS?",
|
|
2517
|
-
default: true
|
|
2518
|
-
},
|
|
2519
|
-
{
|
|
2520
|
-
type: "password",
|
|
2521
|
-
name: "password",
|
|
2522
|
-
message: "Password:",
|
|
2523
|
-
mask: "*",
|
|
2524
|
-
validate: (v) => v.trim().length > 0 || "Password is required"
|
|
2525
|
-
},
|
|
2526
|
-
{
|
|
2527
|
-
type: "confirm",
|
|
2528
|
-
name: "configureSmtp",
|
|
2529
|
-
message: "Configure SMTP (for sending)?",
|
|
2530
|
-
default: false
|
|
2531
|
-
}
|
|
2532
|
-
]);
|
|
2746
|
+
const portValidator = (v) => {
|
|
2747
|
+
const n = parseInt(v, 10);
|
|
2748
|
+
return n > 0 && n <= 65535 || "Enter a valid port number (1-65535)";
|
|
2749
|
+
};
|
|
2750
|
+
const email = await askValidated("Email address:", void 0, (v) => v.includes("@") || "Enter a valid email address");
|
|
2751
|
+
const host = await askValidated("IMAP host:", void 0, (v) => v.trim().length > 0 || "Host is required");
|
|
2752
|
+
const portStr = await askValidated("IMAP port:", "993", portValidator);
|
|
2753
|
+
const tls = await askConfirm("Use TLS?", true);
|
|
2754
|
+
let password = await askPassword("Password:");
|
|
2755
|
+
while (password.trim().length === 0) {
|
|
2756
|
+
console.log(" Password is required");
|
|
2757
|
+
password = await askPassword("Password:");
|
|
2758
|
+
}
|
|
2759
|
+
const configureSmtp = await askConfirm("Configure SMTP (for sending)?", false);
|
|
2533
2760
|
let smtpHost;
|
|
2534
2761
|
let smtpPort;
|
|
2535
|
-
if (
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
name: "smtpHost",
|
|
2540
|
-
message: "SMTP host:",
|
|
2541
|
-
validate: (v) => v.trim().length > 0 || "SMTP host is required"
|
|
2542
|
-
},
|
|
2543
|
-
{
|
|
2544
|
-
type: "input",
|
|
2545
|
-
name: "smtpPort",
|
|
2546
|
-
message: "SMTP port:",
|
|
2547
|
-
default: "587",
|
|
2548
|
-
validate: (v) => {
|
|
2549
|
-
const n = parseInt(v, 10);
|
|
2550
|
-
return n > 0 && n <= 65535 || "Enter a valid port number (1-65535)";
|
|
2551
|
-
}
|
|
2552
|
-
}
|
|
2553
|
-
]);
|
|
2554
|
-
smtpHost = smtp.smtpHost.trim();
|
|
2555
|
-
smtpPort = parseInt(smtp.smtpPort, 10);
|
|
2762
|
+
if (configureSmtp) {
|
|
2763
|
+
smtpHost = await askValidated("SMTP host:", void 0, (v) => v.trim().length > 0 || "SMTP host is required");
|
|
2764
|
+
const smtpPortStr = await askValidated("SMTP port:", "587", portValidator);
|
|
2765
|
+
smtpPort = parseInt(smtpPortStr, 10);
|
|
2556
2766
|
}
|
|
2557
2767
|
const name = await promptAccountName("IMAP");
|
|
2558
2768
|
const id = crypto3.randomUUID();
|
|
@@ -2560,12 +2770,12 @@ async function setupIMAP() {
|
|
|
2560
2770
|
id,
|
|
2561
2771
|
name,
|
|
2562
2772
|
provider: ProviderType.IMAP,
|
|
2563
|
-
email:
|
|
2773
|
+
email: email.trim(),
|
|
2564
2774
|
password: {
|
|
2565
|
-
password
|
|
2566
|
-
host:
|
|
2567
|
-
port: parseInt(
|
|
2568
|
-
tls
|
|
2775
|
+
password,
|
|
2776
|
+
host: host.trim(),
|
|
2777
|
+
port: parseInt(portStr, 10),
|
|
2778
|
+
tls,
|
|
2569
2779
|
smtpHost,
|
|
2570
2780
|
smtpPort
|
|
2571
2781
|
}
|
|
@@ -2595,14 +2805,7 @@ async function removeAccount(id) {
|
|
|
2595
2805
|
console.error(`Account not found: ${id}`);
|
|
2596
2806
|
process.exit(1);
|
|
2597
2807
|
}
|
|
2598
|
-
const
|
|
2599
|
-
{
|
|
2600
|
-
type: "confirm",
|
|
2601
|
-
name: "confirm",
|
|
2602
|
-
message: `Remove account "${acct.name}" (${acct.email})?`,
|
|
2603
|
-
default: false
|
|
2604
|
-
}
|
|
2605
|
-
]);
|
|
2808
|
+
const confirm = await askConfirm(`Remove account "${acct.name}" (${acct.email})?`, false);
|
|
2606
2809
|
if (confirm) {
|
|
2607
2810
|
await store.remove(id);
|
|
2608
2811
|
console.log(`Account "${acct.name}" removed.`);
|
|
@@ -2629,18 +2832,11 @@ async function main() {
|
|
|
2629
2832
|
console.log("@marlinjai/email-mcp Account Setup\n");
|
|
2630
2833
|
let addMore = true;
|
|
2631
2834
|
while (addMore) {
|
|
2632
|
-
const
|
|
2633
|
-
{
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
choices: [
|
|
2638
|
-
{ name: "Gmail", value: "gmail" },
|
|
2639
|
-
{ name: "Outlook", value: "outlook" },
|
|
2640
|
-
{ name: "iCloud", value: "icloud" },
|
|
2641
|
-
{ name: "Other IMAP", value: "imap" }
|
|
2642
|
-
]
|
|
2643
|
-
}
|
|
2835
|
+
const provider = await askChoice("Select provider:", [
|
|
2836
|
+
{ name: "Gmail", value: "gmail" },
|
|
2837
|
+
{ name: "Outlook", value: "outlook" },
|
|
2838
|
+
{ name: "iCloud", value: "icloud" },
|
|
2839
|
+
{ name: "Other IMAP", value: "imap" }
|
|
2644
2840
|
]);
|
|
2645
2841
|
switch (provider) {
|
|
2646
2842
|
case ProviderType.Gmail:
|
|
@@ -2659,15 +2855,7 @@ async function main() {
|
|
|
2659
2855
|
console.error(`Unknown provider: ${provider}`);
|
|
2660
2856
|
process.exit(1);
|
|
2661
2857
|
}
|
|
2662
|
-
|
|
2663
|
-
{
|
|
2664
|
-
type: "confirm",
|
|
2665
|
-
name: "another",
|
|
2666
|
-
message: "Would you like to add another email account?",
|
|
2667
|
-
default: false
|
|
2668
|
-
}
|
|
2669
|
-
]);
|
|
2670
|
-
addMore = another;
|
|
2858
|
+
addMore = await askConfirm("Would you like to add another email account?", false);
|
|
2671
2859
|
}
|
|
2672
2860
|
console.log("\nSetup complete! You can now use the email tools in Claude Code.");
|
|
2673
2861
|
}
|
|
@@ -2675,5 +2863,5 @@ main().catch((err) => {
|
|
|
2675
2863
|
console.error(`
|
|
2676
2864
|
Setup failed: ${err.message}`);
|
|
2677
2865
|
process.exit(1);
|
|
2678
|
-
});
|
|
2866
|
+
}).finally(closePrompts);
|
|
2679
2867
|
//# sourceMappingURL=wizard.js.map
|