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/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.0/node_modules/ip-address/dist/address-error.js
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.0/node_modules/ip-address/dist/address-error.js"(exports) {
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.0/node_modules/ip-address/dist/common.js
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.0/node_modules/ip-address/dist/common.js"(exports) {
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.0/node_modules/ip-address/dist/v4/constants.js
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.0/node_modules/ip-address/dist/v4/constants.js"(exports) {
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.0/node_modules/ip-address/dist/ipv4.js
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.0/node_modules/ip-address/dist/ipv4.js"(exports) {
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.0/node_modules/ip-address/dist/v6/constants.js
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.0/node_modules/ip-address/dist/v6/constants.js"(exports) {
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.0/node_modules/ip-address/dist/v6/helpers.js
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.0/node_modules/ip-address/dist/v6/helpers.js"(exports) {
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.0/node_modules/ip-address/dist/v6/regular-expressions.js
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.0/node_modules/ip-address/dist/v6/regular-expressions.js"(exports) {
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.0/node_modules/ip-address/dist/ipv6.js
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.0/node_modules/ip-address/dist/ipv6.js"(exports) {
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.0/node_modules/ip-address/dist/ip-address.js
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.0/node_modules/ip-address/dist/ip-address.js"(exports) {
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 html = opts.htmlBody?.trim() ? opts.htmlBody : void 0;
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 script = this.findMessageScript(
84666
- id,
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
- return this.runComposeScript(script, "reply to");
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 script = this.findMessageScript(
84692
- id,
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
- return this.runComposeScript(script, "forward");
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 out = result.output.trim();
84943
- return /^\d+$/.test(out) ? out : null;
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" ? "Reply sent via SMTP" : `Message forwarded via SMTP to ${args.to.join(", ")}`,
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 and a chosen transport.\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), 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.",
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 ({ to, subject, body, cc, bcc, account, attachments, transport: transport2, replyTo }) => {
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
- if (shouldUseSmtp(transport2, account)) {
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("Account to create draft in"),
89020
- attachments: ATTACHMENTS_SCHEMA
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(({ to, subject, body, cc, bcc, account, attachments }) => {
89029
- const success = mailManager.createDraft(to, subject, body, cc, bcc, account, attachments);
89030
- if (!success) {
89031
- return errorResponse("Failed to create draft. Check Mail.app configuration.");
89032
- }
89033
- const attachmentCount = attachments?.length ?? 0;
89034
- const attachInfo = attachmentCount ? ` with ${attachmentCount} attachment(s)` : "";
89035
- return successResponse(`Draft created for ${to.join(", ")}${attachInfo}`, {
89036
- ok: true,
89037
- recipients: to,
89038
- attachmentCount
89039
- });
89040
- }, "Error creating draft")
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
  }