apple-mail-mcp 2.19.21 → 2.20.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 +94 -33
- package/build/cli.js +94 -39
- package/build/index.js +336 -90
- package/package.json +1 -1
package/build/index.js
CHANGED
|
@@ -52883,9 +52883,9 @@ var require_util3 = __commonJS({
|
|
|
52883
52883
|
}
|
|
52884
52884
|
});
|
|
52885
52885
|
|
|
52886
|
-
// node_modules/.pnpm/ip-address@10.7.
|
|
52886
|
+
// node_modules/.pnpm/ip-address@10.7.2/node_modules/ip-address/dist/address-error.js
|
|
52887
52887
|
var require_address_error = __commonJS({
|
|
52888
|
-
"node_modules/.pnpm/ip-address@10.7.
|
|
52888
|
+
"node_modules/.pnpm/ip-address@10.7.2/node_modules/ip-address/dist/address-error.js"(exports) {
|
|
52889
52889
|
"use strict";
|
|
52890
52890
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
52891
52891
|
exports.AddressError = void 0;
|
|
@@ -52900,9 +52900,9 @@ var require_address_error = __commonJS({
|
|
|
52900
52900
|
}
|
|
52901
52901
|
});
|
|
52902
52902
|
|
|
52903
|
-
// node_modules/.pnpm/ip-address@10.7.
|
|
52903
|
+
// node_modules/.pnpm/ip-address@10.7.2/node_modules/ip-address/dist/common.js
|
|
52904
52904
|
var require_common = __commonJS({
|
|
52905
|
-
"node_modules/.pnpm/ip-address@10.7.
|
|
52905
|
+
"node_modules/.pnpm/ip-address@10.7.2/node_modules/ip-address/dist/common.js"(exports) {
|
|
52906
52906
|
"use strict";
|
|
52907
52907
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
52908
52908
|
exports.isInSubnet = isInSubnet;
|
|
@@ -52923,6 +52923,9 @@ var require_common = __commonJS({
|
|
|
52923
52923
|
return isHostInSubnet.call(this, address);
|
|
52924
52924
|
}
|
|
52925
52925
|
function isHostInSubnet(address) {
|
|
52926
|
+
if (this.binaryZeroPad().length !== address.binaryZeroPad().length) {
|
|
52927
|
+
return false;
|
|
52928
|
+
}
|
|
52926
52929
|
return this.mask(address.subnetMask) === address.mask();
|
|
52927
52930
|
}
|
|
52928
52931
|
function isGloballyReachable(entries) {
|
|
@@ -53000,9 +53003,9 @@ var require_common = __commonJS({
|
|
|
53000
53003
|
}
|
|
53001
53004
|
});
|
|
53002
53005
|
|
|
53003
|
-
// node_modules/.pnpm/ip-address@10.7.
|
|
53006
|
+
// node_modules/.pnpm/ip-address@10.7.2/node_modules/ip-address/dist/v4/constants.js
|
|
53004
53007
|
var require_constants3 = __commonJS({
|
|
53005
|
-
"node_modules/.pnpm/ip-address@10.7.
|
|
53008
|
+
"node_modules/.pnpm/ip-address@10.7.2/node_modules/ip-address/dist/v4/constants.js"(exports) {
|
|
53006
53009
|
"use strict";
|
|
53007
53010
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
53008
53011
|
exports.SPECIAL_PURPOSE = exports.RE_SUBNET_STRING = exports.RE_ADDRESS = exports.GROUPS = exports.BITS = void 0;
|
|
@@ -53041,9 +53044,9 @@ var require_constants3 = __commonJS({
|
|
|
53041
53044
|
}
|
|
53042
53045
|
});
|
|
53043
53046
|
|
|
53044
|
-
// node_modules/.pnpm/ip-address@10.7.
|
|
53047
|
+
// node_modules/.pnpm/ip-address@10.7.2/node_modules/ip-address/dist/ipv4.js
|
|
53045
53048
|
var require_ipv4 = __commonJS({
|
|
53046
|
-
"node_modules/.pnpm/ip-address@10.7.
|
|
53049
|
+
"node_modules/.pnpm/ip-address@10.7.2/node_modules/ip-address/dist/ipv4.js"(exports) {
|
|
53047
53050
|
"use strict";
|
|
53048
53051
|
var __createBinding = exports && exports.__createBinding || (Object.create ? (function(o, m, k, k2) {
|
|
53049
53052
|
if (k2 === void 0) k2 = k;
|
|
@@ -53101,6 +53104,10 @@ var require_ipv4 = __commonJS({
|
|
|
53101
53104
|
}
|
|
53102
53105
|
address = address.replace(constants.RE_SUBNET_STRING, "");
|
|
53103
53106
|
}
|
|
53107
|
+
const longest = constants.GROUPS * 4 - 1;
|
|
53108
|
+
if (address.length > longest) {
|
|
53109
|
+
throw new address_error_1.AddressError(`IPv4 addresses are at most ${longest} characters.`);
|
|
53110
|
+
}
|
|
53104
53111
|
this.addressMinusSuffix = address;
|
|
53105
53112
|
this.parsedAddress = this.parse(address);
|
|
53106
53113
|
}
|
|
@@ -53237,15 +53244,18 @@ var require_ipv4 = __commonJS({
|
|
|
53237
53244
|
return _Address4.fromHex(integer2.toString(16).padStart(8, "0"));
|
|
53238
53245
|
}
|
|
53239
53246
|
/**
|
|
53240
|
-
* Return an address from in-addr.arpa form
|
|
53247
|
+
* Return an address from in-addr.arpa form: the four octets reversed, with
|
|
53248
|
+
* or without the `.in-addr.arpa` suffix and root dot, in any case. Throws
|
|
53249
|
+
* `AddressError` unless the reversed labels form a valid IPv4 address, so
|
|
53250
|
+
* `fromArpa(x.reverseForm())` round-trips {@link reverseForm}.
|
|
53241
53251
|
* @param {string} arpaFormAddress - an 'in-addr.arpa' form ipv4 address
|
|
53242
53252
|
* @returns {Adress4}
|
|
53243
53253
|
* @example
|
|
53244
|
-
* var address = Address4.fromArpa(42.2.0.192.in-addr.arpa.)
|
|
53254
|
+
* var address = Address4.fromArpa('42.2.0.192.in-addr.arpa.')
|
|
53245
53255
|
* address.correctForm(); // '192.0.2.42'
|
|
53246
53256
|
*/
|
|
53247
53257
|
static fromArpa(arpaFormAddress) {
|
|
53248
|
-
const leader = arpaFormAddress.replace(/(\.in-addr\.arpa)
|
|
53258
|
+
const leader = arpaFormAddress.replace(/(\.in-addr\.arpa)?\.?$/i, "");
|
|
53249
53259
|
const address = leader.split(".").reverse().join(".");
|
|
53250
53260
|
return new _Address4(address);
|
|
53251
53261
|
}
|
|
@@ -53599,9 +53609,9 @@ var require_ipv4 = __commonJS({
|
|
|
53599
53609
|
}
|
|
53600
53610
|
});
|
|
53601
53611
|
|
|
53602
|
-
// node_modules/.pnpm/ip-address@10.7.
|
|
53612
|
+
// node_modules/.pnpm/ip-address@10.7.2/node_modules/ip-address/dist/v6/constants.js
|
|
53603
53613
|
var require_constants4 = __commonJS({
|
|
53604
|
-
"node_modules/.pnpm/ip-address@10.7.
|
|
53614
|
+
"node_modules/.pnpm/ip-address@10.7.2/node_modules/ip-address/dist/v6/constants.js"(exports) {
|
|
53605
53615
|
"use strict";
|
|
53606
53616
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
53607
53617
|
exports.SPECIAL_PURPOSE = exports.RE_URL_WITH_PORT = exports.RE_URL = exports.RE_ZONE_STRING = exports.RE_SUBNET_STRING = exports.RE_BAD_ADDRESS = exports.RE_BAD_CHARACTERS = exports.TYPES = exports.SCOPES = exports.GROUPS = exports.BITS = void 0;
|
|
@@ -53689,9 +53699,9 @@ var require_constants4 = __commonJS({
|
|
|
53689
53699
|
}
|
|
53690
53700
|
});
|
|
53691
53701
|
|
|
53692
|
-
// node_modules/.pnpm/ip-address@10.7.
|
|
53702
|
+
// node_modules/.pnpm/ip-address@10.7.2/node_modules/ip-address/dist/v6/helpers.js
|
|
53693
53703
|
var require_helpers = __commonJS({
|
|
53694
|
-
"node_modules/.pnpm/ip-address@10.7.
|
|
53704
|
+
"node_modules/.pnpm/ip-address@10.7.2/node_modules/ip-address/dist/v6/helpers.js"(exports) {
|
|
53695
53705
|
"use strict";
|
|
53696
53706
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
53697
53707
|
exports.escapeHtml = escapeHtml;
|
|
@@ -53728,9 +53738,9 @@ var require_helpers = __commonJS({
|
|
|
53728
53738
|
}
|
|
53729
53739
|
});
|
|
53730
53740
|
|
|
53731
|
-
// node_modules/.pnpm/ip-address@10.7.
|
|
53741
|
+
// node_modules/.pnpm/ip-address@10.7.2/node_modules/ip-address/dist/v6/regular-expressions.js
|
|
53732
53742
|
var require_regular_expressions = __commonJS({
|
|
53733
|
-
"node_modules/.pnpm/ip-address@10.7.
|
|
53743
|
+
"node_modules/.pnpm/ip-address@10.7.2/node_modules/ip-address/dist/v6/regular-expressions.js"(exports) {
|
|
53734
53744
|
"use strict";
|
|
53735
53745
|
var __createBinding = exports && exports.__createBinding || (Object.create ? (function(o, m, k, k2) {
|
|
53736
53746
|
if (k2 === void 0) k2 = k;
|
|
@@ -53820,9 +53830,9 @@ var require_regular_expressions = __commonJS({
|
|
|
53820
53830
|
}
|
|
53821
53831
|
});
|
|
53822
53832
|
|
|
53823
|
-
// node_modules/.pnpm/ip-address@10.7.
|
|
53833
|
+
// node_modules/.pnpm/ip-address@10.7.2/node_modules/ip-address/dist/ipv6.js
|
|
53824
53834
|
var require_ipv6 = __commonJS({
|
|
53825
|
-
"node_modules/.pnpm/ip-address@10.7.
|
|
53835
|
+
"node_modules/.pnpm/ip-address@10.7.2/node_modules/ip-address/dist/ipv6.js"(exports) {
|
|
53826
53836
|
"use strict";
|
|
53827
53837
|
var __createBinding = exports && exports.__createBinding || (Object.create ? (function(o, m, k, k2) {
|
|
53828
53838
|
if (k2 === void 0) k2 = k;
|
|
@@ -53933,6 +53943,10 @@ var require_ipv6 = __commonJS({
|
|
|
53933
53943
|
this.zone = zone[0];
|
|
53934
53944
|
address = address.replace(constants6.RE_ZONE_STRING, "");
|
|
53935
53945
|
}
|
|
53946
|
+
const longest = this.groups * 5 + 5;
|
|
53947
|
+
if (address.length > longest) {
|
|
53948
|
+
throw new address_error_1.AddressError(`IPv6 addresses are at most ${longest} characters.`);
|
|
53949
|
+
}
|
|
53936
53950
|
this.addressMinusSuffix = address;
|
|
53937
53951
|
this.parsedAddress = this.parse(this.addressMinusSuffix);
|
|
53938
53952
|
}
|
|
@@ -54129,7 +54143,7 @@ var require_ipv6 = __commonJS({
|
|
|
54129
54143
|
* Address6.fromArpa('8.b.d.0.1.0.0.2.ip6.arpa.').networkForm(); // '2001:db8::/32'
|
|
54130
54144
|
*/
|
|
54131
54145
|
static fromArpa(arpaFormAddress) {
|
|
54132
|
-
const nibbles = arpaFormAddress.replace(/(\.ip6\.arpa)
|
|
54146
|
+
const nibbles = arpaFormAddress.replace(/(\.ip6\.arpa)?\.?$/i, "");
|
|
54133
54147
|
if (!/^[0-9a-f](\.[0-9a-f]){0,31}$/i.test(nibbles)) {
|
|
54134
54148
|
throw new address_error_1.AddressError("Invalid 'ip6.arpa' form.");
|
|
54135
54149
|
}
|
|
@@ -55149,9 +55163,9 @@ var require_ipv6 = __commonJS({
|
|
|
55149
55163
|
}
|
|
55150
55164
|
});
|
|
55151
55165
|
|
|
55152
|
-
// node_modules/.pnpm/ip-address@10.7.
|
|
55166
|
+
// node_modules/.pnpm/ip-address@10.7.2/node_modules/ip-address/dist/ip-address.js
|
|
55153
55167
|
var require_ip_address = __commonJS({
|
|
55154
|
-
"node_modules/.pnpm/ip-address@10.7.
|
|
55168
|
+
"node_modules/.pnpm/ip-address@10.7.2/node_modules/ip-address/dist/ip-address.js"(exports) {
|
|
55155
55169
|
"use strict";
|
|
55156
55170
|
var __createBinding = exports && exports.__createBinding || (Object.create ? (function(o, m, k, k2) {
|
|
55157
55171
|
if (k2 === void 0) k2 = k;
|
|
@@ -65173,6 +65187,7 @@ __export(imapClient_exports, {
|
|
|
65173
65187
|
decodeImapId: () => decodeImapId,
|
|
65174
65188
|
dropAllPools: () => dropAllPools,
|
|
65175
65189
|
encodeImapId: () => encodeImapId,
|
|
65190
|
+
imapAppendDraft: () => imapAppendDraft,
|
|
65176
65191
|
imapAppendSentCopy: () => imapAppendSentCopy,
|
|
65177
65192
|
imapBatchDelete: () => imapBatchDelete,
|
|
65178
65193
|
imapBatchFlag: () => imapBatchFlag,
|
|
@@ -65210,6 +65225,7 @@ __export(imapClient_exports, {
|
|
|
65210
65225
|
matchMailbox: () => matchMailbox,
|
|
65211
65226
|
normalizeMessageId: () => normalizeMessageId,
|
|
65212
65227
|
omittedNote: () => omittedNote,
|
|
65228
|
+
resolveDraftImapAccount: () => resolveDraftImapAccount,
|
|
65213
65229
|
resolveImapConfig: () => resolveImapConfig,
|
|
65214
65230
|
resolveImapConfigs: () => resolveImapConfigs,
|
|
65215
65231
|
resolveMailboxPath: () => resolveMailboxPath,
|
|
@@ -66267,6 +66283,40 @@ async function imapAppendSentCopy(smtpUser, raw, deps = {}) {
|
|
|
66267
66283
|
return { attempted: true, success: false, error: errText(e) };
|
|
66268
66284
|
}
|
|
66269
66285
|
}
|
|
66286
|
+
function resolveDraftImapAccount(account, smtpUser, env = process.env) {
|
|
66287
|
+
const specs = listImapAccountSpecs(env);
|
|
66288
|
+
const why = "Mail.app's AppleScript cannot set In-Reply-To/References on a draft, so a threaded draft is filed over IMAP into the account's Drafts mailbox";
|
|
66289
|
+
if (specs.length === 0) {
|
|
66290
|
+
throw new Error(
|
|
66291
|
+
`${why}, and IMAP is not configured. Configure IMAP, omit inReplyTo/references, or use reply-to-message with send=false. ${SETUP_HINT}`
|
|
66292
|
+
);
|
|
66293
|
+
}
|
|
66294
|
+
let spec;
|
|
66295
|
+
if (account) {
|
|
66296
|
+
spec = specs.find((s) => specMatchesSelector(s, account));
|
|
66297
|
+
if (!spec) {
|
|
66298
|
+
throw new Error(
|
|
66299
|
+
`${why}, and "${account}" is not a configured IMAP account. Configured: ${specs.map((s) => s.accountLabel).join(", ")}.`
|
|
66300
|
+
);
|
|
66301
|
+
}
|
|
66302
|
+
} else {
|
|
66303
|
+
spec = (smtpUser ? specs.find((s) => specMatchesSelector(s, smtpUser)) : void 0) ?? (specs.length === 1 ? specs[0] : void 0);
|
|
66304
|
+
if (!spec) {
|
|
66305
|
+
throw new Error(
|
|
66306
|
+
`${why}; several IMAP accounts are configured, so pass \`account\` to choose one: ${specs.map((s) => s.accountLabel).join(", ")}.`
|
|
66307
|
+
);
|
|
66308
|
+
}
|
|
66309
|
+
}
|
|
66310
|
+
return { label: spec.accountLabel, user: spec.user };
|
|
66311
|
+
}
|
|
66312
|
+
async function imapAppendDraft(account, raw, deps = {}) {
|
|
66313
|
+
return withClient({ ...deps, account: deps.account ?? account }, async (client, cfg) => {
|
|
66314
|
+
const path3 = await resolveMailboxPath(client, "drafts", "list");
|
|
66315
|
+
const res = await client.append(path3, raw, ["\\Draft", "\\Seen"]);
|
|
66316
|
+
if (!res) throw new Error(`server rejected the APPEND to "${path3}" (IMAP NO/BAD)`);
|
|
66317
|
+
return { mailbox: path3, account: cfg.accountLabel };
|
|
66318
|
+
});
|
|
66319
|
+
}
|
|
66270
66320
|
async function withMailbox(path3, deps, fn) {
|
|
66271
66321
|
return withClient(deps, async (client) => {
|
|
66272
66322
|
const lock = await client.getMailboxLock(path3);
|
|
@@ -67132,6 +67182,34 @@ function buildAttachments(attachments) {
|
|
|
67132
67182
|
return { filename: a.filename, content: decodeInlineAttachment(a.contentBase64) };
|
|
67133
67183
|
});
|
|
67134
67184
|
}
|
|
67185
|
+
function buildMailOptions(opts, from, attachments) {
|
|
67186
|
+
const html = opts.htmlBody?.trim() ? opts.htmlBody : void 0;
|
|
67187
|
+
return {
|
|
67188
|
+
from,
|
|
67189
|
+
to: opts.to,
|
|
67190
|
+
cc: opts.cc,
|
|
67191
|
+
bcc: opts.bcc,
|
|
67192
|
+
subject: opts.subject,
|
|
67193
|
+
text: opts.body,
|
|
67194
|
+
// When present, nodemailer emits multipart/alternative (text + html).
|
|
67195
|
+
html,
|
|
67196
|
+
attachments,
|
|
67197
|
+
// RFC 5322 threading for SMTP replies/forwards (2.5.0). nodemailer treats
|
|
67198
|
+
// In-Reply-To / References as structured msg-id headers and never
|
|
67199
|
+
// RFC 2047-encodes them, however long the id (pinned by a test, #267).
|
|
67200
|
+
inReplyTo: opts.inReplyTo?.trim() || void 0,
|
|
67201
|
+
references: opts.references?.length ? opts.references : void 0,
|
|
67202
|
+
// Reply-To header (2.18.0, issue #220): nodemailer already supports this.
|
|
67203
|
+
replyTo: opts.replyTo?.trim() || void 0
|
|
67204
|
+
};
|
|
67205
|
+
}
|
|
67206
|
+
function composeRawMime(opts, from) {
|
|
67207
|
+
return buildRawMime({
|
|
67208
|
+
...buildMailOptions(opts, from, buildAttachments(opts.attachments)),
|
|
67209
|
+
keepBcc: true,
|
|
67210
|
+
date: /* @__PURE__ */ new Date()
|
|
67211
|
+
});
|
|
67212
|
+
}
|
|
67135
67213
|
async function sendViaSmtp(opts, config2, createTransport2 = nodemailer_default.createTransport, appendSentCopy = defaultAppendSentCopy) {
|
|
67136
67214
|
let cfg;
|
|
67137
67215
|
try {
|
|
@@ -67170,23 +67248,7 @@ async function sendViaSmtp(opts, config2, createTransport2 = nodemailer_default.
|
|
|
67170
67248
|
requireTLS,
|
|
67171
67249
|
auth: { user: cfg.user, pass: cfg.pass }
|
|
67172
67250
|
});
|
|
67173
|
-
const
|
|
67174
|
-
const mailOptions = {
|
|
67175
|
-
from: requestedFrom || cfg.from,
|
|
67176
|
-
to: opts.to,
|
|
67177
|
-
cc: opts.cc,
|
|
67178
|
-
bcc: opts.bcc,
|
|
67179
|
-
subject: opts.subject,
|
|
67180
|
-
text: opts.body,
|
|
67181
|
-
// When present, nodemailer emits multipart/alternative (text + html).
|
|
67182
|
-
html,
|
|
67183
|
-
attachments,
|
|
67184
|
-
// RFC 5322 threading for SMTP replies/forwards (2.5.0).
|
|
67185
|
-
inReplyTo: opts.inReplyTo?.trim() || void 0,
|
|
67186
|
-
references: opts.references?.length ? opts.references : void 0,
|
|
67187
|
-
// Reply-To header (2.18.0, issue #220): nodemailer already supports this.
|
|
67188
|
-
replyTo: opts.replyTo?.trim() || void 0
|
|
67189
|
-
};
|
|
67251
|
+
const mailOptions = buildMailOptions(opts, requestedFrom || cfg.from, attachments);
|
|
67190
67252
|
try {
|
|
67191
67253
|
const info = await transporter.sendMail(mailOptions);
|
|
67192
67254
|
let copyFields = {};
|
|
@@ -82130,7 +82192,7 @@ function materializeAttachments(attachments) {
|
|
|
82130
82192
|
if (!dir) dir = mkdtempSync(join3(tmpdir2(), "amcp-att-"));
|
|
82131
82193
|
const safeName = a.filename.replace(/[/\\]/g, "_");
|
|
82132
82194
|
const p = join3(dir, safeName);
|
|
82133
|
-
writeFileSync2(p, decodeInlineAttachment(a.contentBase64));
|
|
82195
|
+
writeFileSync2(p, decodeInlineAttachment(a.contentBase64), { mode: 384 });
|
|
82134
82196
|
return p;
|
|
82135
82197
|
});
|
|
82136
82198
|
} catch (error3) {
|
|
@@ -82144,6 +82206,18 @@ function materializeAttachments(attachments) {
|
|
|
82144
82206
|
}
|
|
82145
82207
|
};
|
|
82146
82208
|
}
|
|
82209
|
+
function preflightAttachments(attachments) {
|
|
82210
|
+
for (const a of attachments ?? []) {
|
|
82211
|
+
if (typeof a === "string") {
|
|
82212
|
+
resolveAttachmentReadPath(a);
|
|
82213
|
+
continue;
|
|
82214
|
+
}
|
|
82215
|
+
if (!a.filename || !a.contentBase64) {
|
|
82216
|
+
throw new Error("Inline attachment requires both filename and contentBase64.");
|
|
82217
|
+
}
|
|
82218
|
+
decodeInlineAttachment(a.contentBase64);
|
|
82219
|
+
}
|
|
82220
|
+
}
|
|
82147
82221
|
|
|
82148
82222
|
// src/services/appleMailManager.ts
|
|
82149
82223
|
init_attachmentReadPolicy();
|
|
@@ -82407,6 +82481,11 @@ function buildAttachmentCommands(attachments) {
|
|
|
82407
82481
|
}
|
|
82408
82482
|
return commands;
|
|
82409
82483
|
}
|
|
82484
|
+
function composeAttachmentBlock(msgVar, paths) {
|
|
82485
|
+
const commands = buildAttachmentCommands(paths);
|
|
82486
|
+
return commands ? `tell ${msgVar}
|
|
82487
|
+
${commands}end tell` : "";
|
|
82488
|
+
}
|
|
82410
82489
|
function asDateToString(varName) {
|
|
82411
82490
|
return `((year of ${varName}) as string) & "-" & ((month of ${varName} as integer) as string) & "-" & ((day of ${varName}) as string) & "-" & ((hours of ${varName}) as string) & "-" & ((minutes of ${varName}) as string) & "-" & ((seconds of ${varName}) as string)`;
|
|
82412
82491
|
}
|
|
@@ -84656,20 +84735,28 @@ ${indent}end try${this.sanitizeFragment("_uacct", indent)}${this.sanitizeFragmen
|
|
|
84656
84735
|
* @param body - Reply body
|
|
84657
84736
|
* @param replyAll - If true, reply to all recipients
|
|
84658
84737
|
* @param send - If true, send immediately; if false, save as draft
|
|
84738
|
+
* @param attachments - Files to attach (#267): allowlisted paths and/or inline
|
|
84739
|
+
* base64 items (materialized to 0600 temp files, removed afterwards)
|
|
84659
84740
|
* @returns true if reply created/sent successfully
|
|
84660
84741
|
*/
|
|
84661
|
-
replyToMessage(id, body, replyAll = false, send = true) {
|
|
84742
|
+
replyToMessage(id, body, replyAll = false, send = true, attachments) {
|
|
84662
84743
|
const safeBody = escapeForAppleScriptBody(body);
|
|
84663
84744
|
const replyAllClause = replyAll ? " with reply to all" : "";
|
|
84664
84745
|
const finalAction = send ? "send theReply" : "save theReply\n close theReply saving yes";
|
|
84665
|
-
const
|
|
84666
|
-
|
|
84667
|
-
|
|
84746
|
+
const mat = materializeAttachments(attachments);
|
|
84747
|
+
try {
|
|
84748
|
+
const script = this.findMessageScript(
|
|
84749
|
+
id,
|
|
84750
|
+
`
|
|
84668
84751
|
set theReply to reply msg without opening window${replyAllClause}
|
|
84669
84752
|
set content of theReply to "${safeBody}"
|
|
84753
|
+
${composeAttachmentBlock("theReply", mat.paths)}
|
|
84670
84754
|
${finalAction}`
|
|
84671
|
-
|
|
84672
|
-
|
|
84755
|
+
);
|
|
84756
|
+
return this.runComposeScript(script, "reply to");
|
|
84757
|
+
} finally {
|
|
84758
|
+
mat.cleanup();
|
|
84759
|
+
}
|
|
84673
84760
|
}
|
|
84674
84761
|
/**
|
|
84675
84762
|
* Forward a message.
|
|
@@ -84678,9 +84765,10 @@ ${indent}end try${this.sanitizeFragment("_uacct", indent)}${this.sanitizeFragmen
|
|
|
84678
84765
|
* @param to - Recipients to forward to
|
|
84679
84766
|
* @param body - Optional body to prepend
|
|
84680
84767
|
* @param send - If true, send immediately; if false, save as draft
|
|
84768
|
+
* @param attachments - Additional files to attach (#267), as for replyToMessage
|
|
84681
84769
|
* @returns true if forward created/sent successfully
|
|
84682
84770
|
*/
|
|
84683
|
-
forwardMessage(id, to, body, send = true) {
|
|
84771
|
+
forwardMessage(id, to, body, send = true, attachments) {
|
|
84684
84772
|
const safeBody = body ? escapeForAppleScriptBody(body) : "";
|
|
84685
84773
|
const finalAction = send ? "send theForward" : "save theForward\n close theForward saving yes";
|
|
84686
84774
|
let recipientCommands = "";
|
|
@@ -84688,15 +84776,21 @@ ${indent}end try${this.sanitizeFragment("_uacct", indent)}${this.sanitizeFragmen
|
|
|
84688
84776
|
recipientCommands += `make new to recipient at end of to recipients of theForward with properties {address:"${escapeForAppleScript(addr)}"}
|
|
84689
84777
|
`;
|
|
84690
84778
|
}
|
|
84691
|
-
const
|
|
84692
|
-
|
|
84693
|
-
|
|
84779
|
+
const mat = materializeAttachments(attachments);
|
|
84780
|
+
try {
|
|
84781
|
+
const script = this.findMessageScript(
|
|
84782
|
+
id,
|
|
84783
|
+
`
|
|
84694
84784
|
set theForward to forward msg without opening window
|
|
84695
84785
|
${recipientCommands}
|
|
84696
84786
|
${safeBody ? `set content of theForward to "${safeBody}"` : ""}
|
|
84787
|
+
${composeAttachmentBlock("theForward", mat.paths)}
|
|
84697
84788
|
${finalAction}`
|
|
84698
|
-
|
|
84699
|
-
|
|
84789
|
+
);
|
|
84790
|
+
return this.runComposeScript(script, "forward");
|
|
84791
|
+
} finally {
|
|
84792
|
+
mat.cleanup();
|
|
84793
|
+
}
|
|
84700
84794
|
}
|
|
84701
84795
|
/**
|
|
84702
84796
|
* Helper to find and operate on a message by ID, scoped to the mailbox the id
|
|
@@ -84731,7 +84825,7 @@ ${indent}end try${this.sanitizeFragment("_uacct", indent)}${this.sanitizeFragmen
|
|
|
84731
84825
|
* behind one indistinguishable message.
|
|
84732
84826
|
*/
|
|
84733
84827
|
runComposeScript(script, verb) {
|
|
84734
|
-
const result = executeAppleScript(script, { timeoutMs: 6e4 });
|
|
84828
|
+
const result = executeAppleScript(script, { timeoutMs: 6e4, maxRetries: 1 });
|
|
84735
84829
|
if (!result.success || result.output.startsWith("error:")) {
|
|
84736
84830
|
const raw = result.error || result.output;
|
|
84737
84831
|
const error3 = raw.startsWith("error:") ? raw.slice("error:".length) : raw;
|
|
@@ -84921,13 +85015,13 @@ ${indent}end try${this.sanitizeFragment("_uacct", indent)}${this.sanitizeFragmen
|
|
|
84921
85015
|
try
|
|
84922
85016
|
set inMb to mailbox "INBOX" of acct
|
|
84923
85017
|
set inHits to ${matchClause("inMb")}
|
|
84924
|
-
if (count of inHits) > 0 then return (id of (item 1 of inHits)) as string
|
|
85018
|
+
if (count of inHits) > 0 then return ((id of (item 1 of inHits)) as string) & tab & (name of inMb) & tab & (name of acct)
|
|
84925
85019
|
end try
|
|
84926
85020
|
repeat with mb in mailboxes of acct
|
|
84927
85021
|
try
|
|
84928
85022
|
set matchingMsgs to ${matchClause("mb")}
|
|
84929
85023
|
if (count of matchingMsgs) > 0 then
|
|
84930
|
-
return (id of (item 1 of matchingMsgs)) as string
|
|
85024
|
+
return ((id of (item 1 of matchingMsgs)) as string) & tab & (name of mb) & tab & (name of acct)
|
|
84931
85025
|
end if
|
|
84932
85026
|
end try
|
|
84933
85027
|
end repeat
|
|
@@ -84939,8 +85033,10 @@ ${indent}end try${this.sanitizeFragment("_uacct", indent)}${this.sanitizeFragmen
|
|
|
84939
85033
|
`);
|
|
84940
85034
|
const result = executeAppleScript(script, { timeoutMs: 6e4 });
|
|
84941
85035
|
if (!result.success || result.output.startsWith("error:")) return null;
|
|
84942
|
-
const
|
|
84943
|
-
|
|
85036
|
+
const [numericId, mailbox, account] = result.output.trim().split(" ");
|
|
85037
|
+
if (!/^\d+$/.test(numericId ?? "")) return null;
|
|
85038
|
+
if (mailbox && account) this.rememberLocation(numericId, account, mailbox);
|
|
85039
|
+
return numericId;
|
|
84944
85040
|
}
|
|
84945
85041
|
/**
|
|
84946
85042
|
* Unflag a message.
|
|
@@ -87181,19 +87277,28 @@ async function replyComposeBody(deps, id, body) {
|
|
|
87181
87277
|
const source = await readOriginalForQuote(deps, id);
|
|
87182
87278
|
return source ? buildReplyBody(body, source.original, source.plainText) : body;
|
|
87183
87279
|
}
|
|
87184
|
-
async function forwardComposeBody(deps, id, body) {
|
|
87185
|
-
if (!body) return body;
|
|
87280
|
+
async function forwardComposeBody(deps, id, body, hasAttachments = false) {
|
|
87281
|
+
if (!body && !hasAttachments) return body;
|
|
87186
87282
|
const source = await readOriginalForQuote(deps, id);
|
|
87187
87283
|
return source ? buildForwardBody(source.original, source.plainText, body) : body;
|
|
87188
87284
|
}
|
|
87189
87285
|
async function runCompose(deps, args) {
|
|
87190
|
-
const { id, send, transport: transport2 } = args;
|
|
87286
|
+
const { id, send, transport: transport2, attachments } = args;
|
|
87191
87287
|
const verb = args.kind === "reply" ? "reply to" : "forward";
|
|
87192
87288
|
if (!send && transport2 === "smtp") {
|
|
87193
87289
|
return errorResponse(
|
|
87194
87290
|
"SMTP cannot save a Mail.app draft. Omit transport or use transport=applescript with send=false."
|
|
87195
87291
|
);
|
|
87196
87292
|
}
|
|
87293
|
+
try {
|
|
87294
|
+
preflightAttachments(attachments);
|
|
87295
|
+
} catch (error3) {
|
|
87296
|
+
return errorResponse(
|
|
87297
|
+
`Failed to ${verb} message "${id}": ${error3 instanceof Error ? error3.message : String(error3)}`
|
|
87298
|
+
);
|
|
87299
|
+
}
|
|
87300
|
+
const attachmentCount = attachments?.length ?? 0;
|
|
87301
|
+
const withFiles = attachmentCount ? ` with ${attachmentCount} attachment(s)` : "";
|
|
87197
87302
|
const smtp = send && transport2 !== "applescript" && (transport2 === "smtp" || deps.smtpConfigured());
|
|
87198
87303
|
if (smtp) {
|
|
87199
87304
|
try {
|
|
@@ -87225,19 +87330,21 @@ async function runCompose(deps, args) {
|
|
|
87225
87330
|
body: args.body,
|
|
87226
87331
|
from: cfg.from
|
|
87227
87332
|
});
|
|
87333
|
+
if (attachmentCount) opts.attachments = attachments;
|
|
87228
87334
|
const result = await deps.smtpSend(opts, cfg);
|
|
87229
87335
|
if (!result.success)
|
|
87230
87336
|
return errorResponse(
|
|
87231
87337
|
`Failed to ${verb} message "${id}" via SMTP: ${result.error ?? "unknown SMTP error"}`
|
|
87232
87338
|
);
|
|
87233
87339
|
return successResponse(
|
|
87234
|
-
args.kind === "reply" ?
|
|
87340
|
+
args.kind === "reply" ? `Reply sent via SMTP${withFiles}` : `Message forwarded via SMTP to ${args.to.join(", ")}${withFiles}`,
|
|
87235
87341
|
{
|
|
87236
87342
|
ok: true,
|
|
87237
87343
|
sent: true,
|
|
87238
87344
|
id,
|
|
87239
87345
|
transport: "smtp",
|
|
87240
87346
|
messageId: result.messageId,
|
|
87347
|
+
attachmentCount,
|
|
87241
87348
|
...args.kind === "forward" ? { recipients: args.to } : {},
|
|
87242
87349
|
// Best-effort Sent-folder copy (issue #220) — same field shape as
|
|
87243
87350
|
// send-email, since it's the same sendViaSmtp underneath.
|
|
@@ -87260,23 +87367,26 @@ async function runCompose(deps, args) {
|
|
|
87260
87367
|
resolved.numericId,
|
|
87261
87368
|
await replyComposeBody(deps, id, args.body),
|
|
87262
87369
|
args.replyAll,
|
|
87263
|
-
send
|
|
87370
|
+
send,
|
|
87371
|
+
attachmentCount ? attachments : void 0
|
|
87264
87372
|
) : deps.mail.forwardMessage(
|
|
87265
87373
|
resolved.numericId,
|
|
87266
87374
|
args.to,
|
|
87267
|
-
await forwardComposeBody(deps, id, args.body),
|
|
87268
|
-
send
|
|
87375
|
+
await forwardComposeBody(deps, id, args.body, attachmentCount > 0),
|
|
87376
|
+
send,
|
|
87377
|
+
attachmentCount ? attachments : void 0
|
|
87269
87378
|
);
|
|
87270
87379
|
if (!outcome.success)
|
|
87271
87380
|
return errorResponse(
|
|
87272
87381
|
`Failed to ${verb} message "${id}": ${outcome.error ?? "Mail.app compose failed"}`
|
|
87273
87382
|
);
|
|
87274
|
-
const text = args.kind === "reply" ? send ? "Reply sent via AppleScript" : "Reply saved as draft" : send ? `Message forwarded to ${args.to.join(", ")}` : "Forward saved as draft";
|
|
87383
|
+
const text = (args.kind === "reply" ? send ? "Reply sent via AppleScript" : "Reply saved as draft" : send ? `Message forwarded to ${args.to.join(", ")}` : "Forward saved as draft") + withFiles;
|
|
87275
87384
|
return successResponse(text, {
|
|
87276
87385
|
ok: true,
|
|
87277
87386
|
sent: send,
|
|
87278
87387
|
id,
|
|
87279
87388
|
transport: "applescript",
|
|
87389
|
+
attachmentCount,
|
|
87280
87390
|
...args.kind === "forward" ? { recipients: args.to } : {}
|
|
87281
87391
|
});
|
|
87282
87392
|
}
|
|
@@ -87287,6 +87397,70 @@ function runForward(deps, args) {
|
|
|
87287
87397
|
return runCompose(deps, { ...args, kind: "forward" });
|
|
87288
87398
|
}
|
|
87289
87399
|
|
|
87400
|
+
// src/tools/threading.ts
|
|
87401
|
+
function threadingHeaders(args) {
|
|
87402
|
+
const inReplyTo = args.inReplyTo?.trim() || void 0;
|
|
87403
|
+
const references = args.references?.length ? args.references : void 0;
|
|
87404
|
+
if (!inReplyTo && !references) return void 0;
|
|
87405
|
+
return { inReplyTo, references: references ?? (inReplyTo ? [inReplyTo] : void 0) };
|
|
87406
|
+
}
|
|
87407
|
+
var SEND_THREADING_NEEDS_SMTP = "inReplyTo/references require the SMTP transport: Mail.app's AppleScript cannot set In-Reply-To or References, so the message would go out unthreaded. Configure SMTP (and do not pass transport=applescript or a Mail.app account label), or use reply-to-message, which threads on both transports.";
|
|
87408
|
+
function sendEmailThreading(args, useSmtp) {
|
|
87409
|
+
const headers = threadingHeaders(args);
|
|
87410
|
+
if (headers && !useSmtp) return { ok: false, error: SEND_THREADING_NEEDS_SMTP };
|
|
87411
|
+
return { ok: true, headers };
|
|
87412
|
+
}
|
|
87413
|
+
function messageIdOf(raw) {
|
|
87414
|
+
const head = raw.toString("utf8").split(/\r?\n\r?\n/)[0] ?? "";
|
|
87415
|
+
return head.replace(/\r?\n[ \t]+/g, " ").match(/^Message-ID:\s*(<[^>\s]+>)/im)?.[1];
|
|
87416
|
+
}
|
|
87417
|
+
async function runThreadedDraft(deps, args) {
|
|
87418
|
+
const threading = threadingHeaders(args);
|
|
87419
|
+
if (!threading) throw new Error("runThreadedDraft called without threading headers");
|
|
87420
|
+
const fail = (e) => errorResponse(
|
|
87421
|
+
`Failed to create threaded draft: ${e instanceof Error ? e.message : String(e)} Nothing was filed; no Mail.app fallback was attempted (it would drop the threading headers).`
|
|
87422
|
+
);
|
|
87423
|
+
try {
|
|
87424
|
+
preflightAttachments(args.attachments);
|
|
87425
|
+
const smtp = deps.smtpIdentity();
|
|
87426
|
+
const acct = deps.resolveAccount(args.account, smtp.user);
|
|
87427
|
+
const from = smtp.user && smtp.user.toLowerCase() === acct.user.toLowerCase() && smtp.from ? smtp.from : acct.user;
|
|
87428
|
+
const raw = await deps.compose(
|
|
87429
|
+
{
|
|
87430
|
+
to: args.to,
|
|
87431
|
+
cc: args.cc,
|
|
87432
|
+
bcc: args.bcc,
|
|
87433
|
+
subject: args.subject,
|
|
87434
|
+
body: args.body,
|
|
87435
|
+
attachments: args.attachments,
|
|
87436
|
+
inReplyTo: threading.inReplyTo,
|
|
87437
|
+
references: threading.references
|
|
87438
|
+
},
|
|
87439
|
+
from
|
|
87440
|
+
);
|
|
87441
|
+
const filed = await deps.append(acct.label, raw);
|
|
87442
|
+
const attachmentCount = args.attachments?.length ?? 0;
|
|
87443
|
+
const attachInfo = attachmentCount ? ` with ${attachmentCount} attachment(s)` : "";
|
|
87444
|
+
const messageId = messageIdOf(raw);
|
|
87445
|
+
return successResponse(
|
|
87446
|
+
`Threaded draft for ${args.to.join(", ")}${attachInfo} filed over IMAP in "${filed.mailbox}" (account ${filed.account}). Review and send it from Mail.app.`,
|
|
87447
|
+
{
|
|
87448
|
+
ok: true,
|
|
87449
|
+
recipients: args.to,
|
|
87450
|
+
attachmentCount,
|
|
87451
|
+
transport: "imap",
|
|
87452
|
+
account: filed.account,
|
|
87453
|
+
mailbox: filed.mailbox,
|
|
87454
|
+
...messageId ? { messageId } : {},
|
|
87455
|
+
...threading.inReplyTo ? { inReplyTo: threading.inReplyTo } : {},
|
|
87456
|
+
references: threading.references
|
|
87457
|
+
}
|
|
87458
|
+
);
|
|
87459
|
+
} catch (e) {
|
|
87460
|
+
return fail(e);
|
|
87461
|
+
}
|
|
87462
|
+
}
|
|
87463
|
+
|
|
87290
87464
|
// src/index.ts
|
|
87291
87465
|
init_imapClient();
|
|
87292
87466
|
|
|
@@ -87798,6 +87972,17 @@ var ATTACHMENTS_SCHEMA = external_exports.array(
|
|
|
87798
87972
|
).max(20, "Cannot attach more than 20 files").optional().describe(
|
|
87799
87973
|
"Files to attach: absolute paths in the configured attachment read roots (e.g. '/Users/me/Documents/report.pdf') and/or inline {filename, contentBase64} objects up to 25 MiB decoded each."
|
|
87800
87974
|
);
|
|
87975
|
+
var RFC_MESSAGE_ID_TOKEN = /^<[^<>\s@]+@[^<>\s@]+>$/;
|
|
87976
|
+
var MESSAGE_ID_TOKEN_SCHEMA = external_exports.string().max(998, "Message-ID is longer than an RFC 5322 header line (998 chars)").regex(
|
|
87977
|
+
RFC_MESSAGE_ID_TOKEN,
|
|
87978
|
+
"Must be an RFC 5322 Message-ID token including angle brackets, e.g. <abc123@mail.example.com>"
|
|
87979
|
+
);
|
|
87980
|
+
var IN_REPLY_TO_SCHEMA = MESSAGE_ID_TOKEN_SCHEMA.optional().describe(
|
|
87981
|
+
"Thread this message as a reply: the parent's Message-ID token, angle brackets included (e.g. '<abc123@mail.example.com>'). Emitted verbatim as In-Reply-To, never RFC 2047-encoded. When `references` is omitted, References defaults to [inReplyTo]. Prefer reply-to-message, which derives recipients, threading and the quote from the original for you."
|
|
87982
|
+
);
|
|
87983
|
+
var REFERENCES_SCHEMA = external_exports.array(MESSAGE_ID_TOKEN_SCHEMA).min(1, "references must contain at least one Message-ID").max(100, "Cannot list more than 100 Message-IDs in references").optional().describe(
|
|
87984
|
+
"Thread chain, oldest first: the parent's own References followed by the parent's Message-ID (each an angle-bracketed token). Emitted verbatim, never RFC 2047-encoded."
|
|
87985
|
+
);
|
|
87801
87986
|
|
|
87802
87987
|
// src/tools/thread.ts
|
|
87803
87988
|
function normalizeSubject(subject) {
|
|
@@ -88882,7 +89067,7 @@ var SENT_COPY_ERROR_SCHEMA = external_exports.string().optional().describe("Pres
|
|
|
88882
89067
|
registerTool(
|
|
88883
89068
|
"send-email",
|
|
88884
89069
|
{
|
|
88885
|
-
description: "Use when: the user has explicitly confirmed they want to send a single email now to the given recipients (to/cc/bcc are arrays), optionally with attachments
|
|
89070
|
+
description: "Use when: the user has explicitly confirmed they want to send a single email now to the given recipients (to/cc/bcc are arrays), optionally with attachments, a chosen transport, and caller-built threading headers (inReplyTo/references, SMTP only).\nReturns: a confirmation naming the recipients and attachment count; over SMTP, also whether a Sent-folder copy was filed (sentCopy).\nDo not use when: the user wants to review first (use create-draft), is replying to or forwarding an existing message (use reply-to-message / forward-message \u2014 they derive recipients, threading and the quote, and also take attachments), or wants per-recipient personalized copies (use send-serial-email).\nSafety: this SENDS real email immediately and it cannot be unsent \u2014 require explicit user confirmation of the exact recipients, subject, and body before calling. Prefer create-draft when there is any doubt.",
|
|
88886
89071
|
inputSchema: {
|
|
88887
89072
|
to: external_exports.array(external_exports.string()).min(1, "At least one recipient is required"),
|
|
88888
89073
|
subject: external_exports.string().min(1, "Subject is required"),
|
|
@@ -88891,6 +89076,8 @@ registerTool(
|
|
|
88891
89076
|
bcc: external_exports.array(external_exports.string()).optional().describe("BCC recipients"),
|
|
88892
89077
|
account: external_exports.string().optional().describe("Account to send from"),
|
|
88893
89078
|
attachments: ATTACHMENTS_SCHEMA,
|
|
89079
|
+
inReplyTo: IN_REPLY_TO_SCHEMA,
|
|
89080
|
+
references: REFERENCES_SCHEMA,
|
|
88894
89081
|
replyTo: external_exports.string().optional().describe(
|
|
88895
89082
|
"SMTP only (issue #220): sets the Reply-To header when replies should go somewhere other than the From/login address, e.g. a domain-alias setup where APPLE_MAIL_MCP_SMTP_FROM differs from APPLE_MAIL_MCP_SMTP_USER. Ignored on the AppleScript transport."
|
|
88896
89083
|
),
|
|
@@ -88903,15 +89090,31 @@ registerTool(
|
|
|
88903
89090
|
recipients: external_exports.array(external_exports.string()).optional(),
|
|
88904
89091
|
attachmentCount: external_exports.number().optional(),
|
|
88905
89092
|
transport: external_exports.string().optional(),
|
|
89093
|
+
messageId: external_exports.string().optional(),
|
|
88906
89094
|
sentCopy: SENT_COPY_SCHEMA,
|
|
88907
89095
|
sentCopyError: SENT_COPY_ERROR_SCHEMA
|
|
88908
89096
|
}
|
|
88909
89097
|
},
|
|
88910
89098
|
withErrorHandling(
|
|
88911
|
-
async ({
|
|
89099
|
+
async ({
|
|
89100
|
+
to,
|
|
89101
|
+
subject,
|
|
89102
|
+
body,
|
|
89103
|
+
cc,
|
|
89104
|
+
bcc,
|
|
89105
|
+
account,
|
|
89106
|
+
attachments,
|
|
89107
|
+
transport: transport2,
|
|
89108
|
+
replyTo,
|
|
89109
|
+
inReplyTo,
|
|
89110
|
+
references
|
|
89111
|
+
}) => {
|
|
88912
89112
|
const attachInfo = attachments?.length ? ` with ${attachments.length} attachment(s)` : "";
|
|
88913
89113
|
const attachmentCount = attachments?.length ?? 0;
|
|
88914
|
-
|
|
89114
|
+
const useSmtp = shouldUseSmtp(transport2, account);
|
|
89115
|
+
const threading = sendEmailThreading({ inReplyTo, references }, useSmtp);
|
|
89116
|
+
if (!threading.ok) return errorResponse(threading.error);
|
|
89117
|
+
if (useSmtp) {
|
|
88915
89118
|
const smtpFrom = account?.includes("@") ? account : void 0;
|
|
88916
89119
|
const result = await sendViaSmtp({
|
|
88917
89120
|
to,
|
|
@@ -88921,7 +89124,8 @@ registerTool(
|
|
|
88921
89124
|
bcc,
|
|
88922
89125
|
from: smtpFrom,
|
|
88923
89126
|
attachments,
|
|
88924
|
-
replyTo
|
|
89127
|
+
replyTo,
|
|
89128
|
+
...threading.headers
|
|
88925
89129
|
});
|
|
88926
89130
|
if (!result.success) {
|
|
88927
89131
|
return errorResponse(result.error ?? "Failed to send email via SMTP.");
|
|
@@ -88932,6 +89136,7 @@ registerTool(
|
|
|
88932
89136
|
recipients: to,
|
|
88933
89137
|
attachmentCount,
|
|
88934
89138
|
transport: "smtp",
|
|
89139
|
+
...result.messageId ? { messageId: result.messageId } : {},
|
|
88935
89140
|
...result.sentCopy !== void 0 ? { sentCopy: result.sentCopy } : {},
|
|
88936
89141
|
...result.sentCopyError !== void 0 ? { sentCopyError: result.sentCopyError } : {}
|
|
88937
89142
|
});
|
|
@@ -89009,36 +89214,73 @@ ${details}`,
|
|
|
89009
89214
|
registerTool(
|
|
89010
89215
|
"create-draft",
|
|
89011
89216
|
{
|
|
89012
|
-
description: "Use when: composing an email the user should review in Mail.app before sending \u2014 the safe default for any new message (to/cc/bcc are arrays, optional attachments).\nReturns: a confirmation that the draft was created, with recipients and attachment count.\nDo not use when: the user has already confirmed they want it sent now (use send-email).\nSafety: low risk \u2014 creates a draft only and sends nothing; the user must open Mail.app and send it themselves.",
|
|
89217
|
+
description: "Use when: composing an email the user should review in Mail.app before sending \u2014 the safe default for any new message (to/cc/bcc are arrays, optional attachments). With inReplyTo/references the draft is threaded: it is composed by this server and filed over IMAP into the account's Drafts mailbox (Mail.app's AppleScript cannot set those headers), so an IMAP account is required.\nReturns: a confirmation that the draft was created, with recipients and attachment count; for a threaded draft also its Drafts mailbox, account and Message-ID (transport: imap).\nDo not use when: the user has already confirmed they want it sent now (use send-email), or is replying to an existing message (use reply-to-message with send=false \u2014 it derives recipients, threading and the quote and takes attachments).\nSafety: low risk \u2014 creates a draft only and sends nothing; the user must open Mail.app and send it themselves.",
|
|
89013
89218
|
inputSchema: {
|
|
89014
89219
|
to: external_exports.array(external_exports.string()).min(1, "At least one recipient is required"),
|
|
89015
89220
|
subject: external_exports.string().min(1, "Subject is required"),
|
|
89016
89221
|
body: external_exports.string().min(1, "Body is required"),
|
|
89017
89222
|
cc: external_exports.array(external_exports.string()).optional().describe("CC recipients"),
|
|
89018
89223
|
bcc: external_exports.array(external_exports.string()).optional().describe("BCC recipients"),
|
|
89019
|
-
account: external_exports.string().optional().describe(
|
|
89020
|
-
|
|
89224
|
+
account: external_exports.string().optional().describe(
|
|
89225
|
+
"Account to create draft in. With inReplyTo/references this must name a configured IMAP account (label or login); when omitted, the IMAP account matching the SMTP identity, else the only IMAP account, is used."
|
|
89226
|
+
),
|
|
89227
|
+
attachments: ATTACHMENTS_SCHEMA,
|
|
89228
|
+
inReplyTo: IN_REPLY_TO_SCHEMA,
|
|
89229
|
+
references: REFERENCES_SCHEMA
|
|
89021
89230
|
},
|
|
89022
89231
|
outputSchema: {
|
|
89023
89232
|
ok: external_exports.boolean().optional(),
|
|
89024
89233
|
recipients: external_exports.array(external_exports.string()).optional(),
|
|
89025
|
-
attachmentCount: external_exports.number().optional()
|
|
89234
|
+
attachmentCount: external_exports.number().optional(),
|
|
89235
|
+
transport: external_exports.enum(["applescript", "imap"]).optional(),
|
|
89236
|
+
account: external_exports.string().optional().describe("Threaded draft only: IMAP account it was filed in"),
|
|
89237
|
+
mailbox: external_exports.string().optional().describe("Threaded draft only: Drafts mailbox path"),
|
|
89238
|
+
messageId: external_exports.string().optional().describe("Threaded draft only: the draft's Message-ID"),
|
|
89239
|
+
inReplyTo: external_exports.string().optional(),
|
|
89240
|
+
references: external_exports.array(external_exports.string()).optional()
|
|
89026
89241
|
}
|
|
89027
89242
|
},
|
|
89028
|
-
withErrorHandling(
|
|
89029
|
-
|
|
89030
|
-
|
|
89031
|
-
|
|
89032
|
-
|
|
89033
|
-
|
|
89034
|
-
|
|
89035
|
-
|
|
89036
|
-
|
|
89037
|
-
|
|
89038
|
-
|
|
89039
|
-
|
|
89040
|
-
|
|
89243
|
+
withErrorHandling(
|
|
89244
|
+
async ({ to, subject, body, cc, bcc, account, attachments, inReplyTo, references }) => {
|
|
89245
|
+
if (threadingHeaders({ inReplyTo, references })) {
|
|
89246
|
+
return runThreadedDraft(threadedDraftDeps, {
|
|
89247
|
+
to,
|
|
89248
|
+
subject,
|
|
89249
|
+
body,
|
|
89250
|
+
cc,
|
|
89251
|
+
bcc,
|
|
89252
|
+
account,
|
|
89253
|
+
attachments,
|
|
89254
|
+
inReplyTo,
|
|
89255
|
+
references
|
|
89256
|
+
});
|
|
89257
|
+
}
|
|
89258
|
+
const success = mailManager.createDraft(to, subject, body, cc, bcc, account, attachments);
|
|
89259
|
+
if (!success) {
|
|
89260
|
+
return errorResponse("Failed to create draft. Check Mail.app configuration.");
|
|
89261
|
+
}
|
|
89262
|
+
const attachmentCount = attachments?.length ?? 0;
|
|
89263
|
+
const attachInfo = attachmentCount ? ` with ${attachmentCount} attachment(s)` : "";
|
|
89264
|
+
return successResponse(`Draft created for ${to.join(", ")}${attachInfo}`, {
|
|
89265
|
+
ok: true,
|
|
89266
|
+
recipients: to,
|
|
89267
|
+
attachmentCount,
|
|
89268
|
+
transport: "applescript"
|
|
89269
|
+
});
|
|
89270
|
+
},
|
|
89271
|
+
"Error creating draft"
|
|
89272
|
+
)
|
|
89041
89273
|
);
|
|
89274
|
+
var threadedDraftDeps = {
|
|
89275
|
+
resolveAccount: (account, smtpUser) => resolveDraftImapAccount(account, smtpUser),
|
|
89276
|
+
smtpIdentity: () => {
|
|
89277
|
+
if (!isSmtpConfigured()) return {};
|
|
89278
|
+
const user = process.env[SMTP_ENV.user]?.trim();
|
|
89279
|
+
return { user, from: process.env[SMTP_ENV.from]?.trim() || user };
|
|
89280
|
+
},
|
|
89281
|
+
compose: composeRawMime,
|
|
89282
|
+
append: (account, raw) => imapAppendDraft(account, raw)
|
|
89283
|
+
};
|
|
89042
89284
|
function resolveSmtpOrFallback() {
|
|
89043
89285
|
try {
|
|
89044
89286
|
return resolveSmtpConfig();
|
|
@@ -89077,13 +89319,14 @@ var COMPOSE_TRANSPORT_SCHEMA = external_exports.enum(["applescript", "smtp"]).op
|
|
|
89077
89319
|
registerTool(
|
|
89078
89320
|
"reply-to-message",
|
|
89079
89321
|
{
|
|
89080
|
-
description: "Use when: replying to an existing message by id, preserving its threading headers. Set replyAll for all recipients; set send=false to save as a draft instead of sending.\nReturns: a confirmation that the reply was sent or saved as a draft; over SMTP, also whether a Sent-folder copy was filed (sentCopy).\nDo not use when: composing a brand-new message (use send-email / create-draft) or forwarding to new recipients (use forward-message).\nSafety: with the default send=true this SENDS real email immediately and cannot be unsent \u2014 require explicit user confirmation of the recipients and body, or pass send=false to let the user review.",
|
|
89322
|
+
description: "Use when: replying to an existing message by id, preserving its threading headers and quoting the original, optionally with attachments (same rules as send-email). Set replyAll for all recipients; set send=false to save as a draft instead of sending.\nReturns: a confirmation that the reply was sent or saved as a draft, with the attachment count; over SMTP, also whether a Sent-folder copy was filed (sentCopy).\nDo not use when: composing a brand-new message (use send-email / create-draft) or forwarding to new recipients (use forward-message).\nSafety: with the default send=true this SENDS real email immediately and cannot be unsent \u2014 require explicit user confirmation of the recipients and body, or pass send=false to let the user review.",
|
|
89081
89323
|
inputSchema: {
|
|
89082
89324
|
id: MESSAGE_ID_SCHEMA,
|
|
89083
89325
|
transport: COMPOSE_TRANSPORT_SCHEMA,
|
|
89084
89326
|
body: external_exports.string().min(1, "Reply body is required").describe("Reply body (plain text; HTML tags such as <br> are not rendered)"),
|
|
89085
89327
|
replyAll: external_exports.boolean().optional().default(false).describe("Reply to all recipients"),
|
|
89086
|
-
send: external_exports.boolean().optional().default(true).describe("Send immediately (false = save as draft)")
|
|
89328
|
+
send: external_exports.boolean().optional().default(true).describe("Send immediately (false = save as draft)"),
|
|
89329
|
+
attachments: ATTACHMENTS_SCHEMA
|
|
89087
89330
|
},
|
|
89088
89331
|
outputSchema: {
|
|
89089
89332
|
transport: external_exports.enum(["smtp", "applescript"]).optional(),
|
|
@@ -89091,6 +89334,7 @@ registerTool(
|
|
|
89091
89334
|
ok: external_exports.boolean().optional(),
|
|
89092
89335
|
sent: external_exports.boolean().optional(),
|
|
89093
89336
|
id: external_exports.string().optional(),
|
|
89337
|
+
attachmentCount: external_exports.number().optional(),
|
|
89094
89338
|
sentCopy: SENT_COPY_SCHEMA,
|
|
89095
89339
|
sentCopyError: SENT_COPY_ERROR_SCHEMA
|
|
89096
89340
|
}
|
|
@@ -89100,13 +89344,14 @@ registerTool(
|
|
|
89100
89344
|
registerTool(
|
|
89101
89345
|
"forward-message",
|
|
89102
89346
|
{
|
|
89103
|
-
description: "Use when: forwarding an existing message (by id) to new recipients (to is an array), with an optional body to prepend. Set send=false to save as a draft.\nReturns: a confirmation that the message was forwarded or saved as a draft; over SMTP, also whether a Sent-folder copy was filed (sentCopy).\nDo not use when: replying to the sender/recipients (use reply-to-message) or composing a new message (use send-email / create-draft).\nSafety: with the default send=true this SENDS real email immediately and cannot be unsent \u2014 require explicit user confirmation of the recipients and any prepended body, or pass send=false to let the user review.",
|
|
89347
|
+
description: "Use when: forwarding an existing message (by id) to new recipients (to is an array), with an optional body to prepend and optional extra attachments (same rules as send-email). Set send=false to save as a draft.\nReturns: a confirmation that the message was forwarded or saved as a draft, with the attachment count; over SMTP, also whether a Sent-folder copy was filed (sentCopy).\nDo not use when: replying to the sender/recipients (use reply-to-message) or composing a new message (use send-email / create-draft).\nSafety: with the default send=true this SENDS real email immediately and cannot be unsent \u2014 require explicit user confirmation of the recipients and any prepended body, or pass send=false to let the user review.",
|
|
89104
89348
|
inputSchema: {
|
|
89105
89349
|
id: MESSAGE_ID_SCHEMA,
|
|
89106
89350
|
transport: COMPOSE_TRANSPORT_SCHEMA,
|
|
89107
89351
|
to: external_exports.array(external_exports.string()).min(1, "At least one recipient is required"),
|
|
89108
89352
|
body: external_exports.string().optional().describe("Optional message to prepend (plain text)"),
|
|
89109
|
-
send: external_exports.boolean().optional().default(true).describe("Send immediately (false = save as draft)")
|
|
89353
|
+
send: external_exports.boolean().optional().default(true).describe("Send immediately (false = save as draft)"),
|
|
89354
|
+
attachments: ATTACHMENTS_SCHEMA
|
|
89110
89355
|
},
|
|
89111
89356
|
outputSchema: {
|
|
89112
89357
|
transport: external_exports.enum(["smtp", "applescript"]).optional(),
|
|
@@ -89115,6 +89360,7 @@ registerTool(
|
|
|
89115
89360
|
sent: external_exports.boolean().optional(),
|
|
89116
89361
|
recipients: external_exports.array(external_exports.string()).optional(),
|
|
89117
89362
|
id: external_exports.string().optional(),
|
|
89363
|
+
attachmentCount: external_exports.number().optional(),
|
|
89118
89364
|
sentCopy: SENT_COPY_SCHEMA,
|
|
89119
89365
|
sentCopyError: SENT_COPY_ERROR_SCHEMA
|
|
89120
89366
|
}
|