gogcli-mcp-gmail 2.26.0 → 2.27.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/dist/index.js +72 -70
- package/manifest.json +1 -1
- package/package.json +1 -1
- package/src/tools/gmail-extra.ts +5 -111
- package/tests/tools/gmail-extra.test.ts +0 -146
package/dist/index.js
CHANGED
|
@@ -31809,6 +31809,13 @@ function formatAuthHealth(raw, now) {
|
|
|
31809
31809
|
}
|
|
31810
31810
|
return accounts.map((a) => formatOneAccountHealth(a, now)).join("\n\n");
|
|
31811
31811
|
}
|
|
31812
|
+
function assertNotBoth(inlineParam, fileParam, inlineValue, fileValue) {
|
|
31813
|
+
if (inlineValue !== void 0 && fileValue !== void 0) {
|
|
31814
|
+
throw new Error(
|
|
31815
|
+
`${inlineParam} and ${fileParam} are mutually exclusive \u2014 gog accepts only one of them. Pass ${inlineParam} with the content itself (it is written to a temp file automatically when large), or ${fileParam} with a path that already exists on the gog server.`
|
|
31816
|
+
);
|
|
31817
|
+
}
|
|
31818
|
+
}
|
|
31812
31819
|
|
|
31813
31820
|
// ../gogcli-mcp/src/tools/auth.ts
|
|
31814
31821
|
function registerAuthToolsWith(server, defaultServices) {
|
|
@@ -32133,6 +32140,45 @@ function finish(base, itemsKey, merged, token) {
|
|
|
32133
32140
|
}
|
|
32134
32141
|
|
|
32135
32142
|
// ../gogcli-mcp/src/tools/gmail.ts
|
|
32143
|
+
var replySchema = {
|
|
32144
|
+
messageId: external_exports.string().describe("Gmail message ID to reply to \u2014 the short hex `id` from gog_gmail_get / _search (or gog_gmail_messages_search, gogcli-mcp-gmail only). NOT the threadId, NOT the RFC822 `<\u2026@host>` Message-Id header."),
|
|
32145
|
+
body: external_exports.string().optional().describe("Reply body (plain text; required unless bodyHtml or bodyHtmlFile is set). Any size \u2014 a large body is written to a temp file on the gog server rather than inlined into the command line. Note gog strips trailing newlines from a file-delivered body."),
|
|
32146
|
+
bodyHtml: external_exports.string().optional().describe("Reply body (HTML; optional). Pass the HTML itself at any size \u2014 a large body is written to a temp file on the gog server rather than inlined into the command line. Mutually exclusive with bodyHtmlFile."),
|
|
32147
|
+
bodyHtmlFile: external_exports.string().optional().describe(`Path to an HTML file that ALREADY EXISTS on the gog server for the reply body. gog also accepts "-" for stdin, but this server never writes to gog's stdin, so "-" would hang until the call times out. Mutually exclusive with bodyHtml \u2014 supplying both is rejected. You rarely need this: bodyHtml handles large bodies on its own.`),
|
|
32148
|
+
to: external_exports.array(external_exports.string()).optional().describe("Add or move recipients to To (repeatable). Added on top of the recipients inherited from the original message."),
|
|
32149
|
+
cc: external_exports.array(external_exports.string()).optional().describe("Add or move recipients to Cc (repeatable)"),
|
|
32150
|
+
bcc: external_exports.array(external_exports.string()).optional().describe("Add or move recipients to Bcc (repeatable)"),
|
|
32151
|
+
remove: external_exports.array(external_exports.string()).optional().describe("Remove these recipients from all fields (repeatable) \u2014 e.g. to drop someone from a reply-all."),
|
|
32152
|
+
subject: external_exports.string().optional().describe('Override reply subject (default: "Re: <original>"). A changed subject starts a NEW Gmail thread.'),
|
|
32153
|
+
noQuote: external_exports.boolean().optional().describe("Do not include the original message quoted below the reply (default: the original is quoted)"),
|
|
32154
|
+
attach: external_exports.array(external_exports.string()).optional().describe(`File paths to attach (repeatable), resolved ON THE GOG SERVER's filesystem \u2014 NOT this client's. Only usable when gog runs on the same machine you do (local stdio); on the hosted connector or any GOG_RUNNER_URL backend these paths do not exist and the call fails with "no such file or directory" \u2014 use attachInline there. Read on the server, base64-encoded with a MIME type inferred from the extension.`),
|
|
32155
|
+
attachInline: attachInlineParam,
|
|
32156
|
+
from: external_exports.string().optional().describe("Send from this email address (must be a verified send-as alias)"),
|
|
32157
|
+
autoFromAddressedAlias: external_exports.boolean().optional().describe("When from is omitted, send from the verified send-as alias the original message was addressed TO, instead of the account's primary address \u2014 so a reply to mail sent to an alias goes back out from that alias. Ignored when from is set."),
|
|
32158
|
+
signature: external_exports.boolean().optional().describe("Append the Gmail signature from the active send-as address"),
|
|
32159
|
+
signatureFrom: external_exports.string().optional().describe("Append the Gmail signature from this send-as email address"),
|
|
32160
|
+
signatureFile: external_exports.string().optional().describe("Append a local signature file (plain text or HTML), read on the gog server"),
|
|
32161
|
+
account: accountParam
|
|
32162
|
+
};
|
|
32163
|
+
function appendReplyFlags(args, f) {
|
|
32164
|
+
assertNotBoth("bodyHtml", "bodyHtmlFile", f.bodyHtml, f.bodyHtmlFile);
|
|
32165
|
+
if (f.body) args.push(payloadArg("body", "body-file", f.body));
|
|
32166
|
+
if (f.bodyHtml) args.push(payloadArg("body-html", "body-html-file", f.bodyHtml, "html"));
|
|
32167
|
+
else if (f.bodyHtmlFile) args.push(`--body-html-file=${f.bodyHtmlFile}`);
|
|
32168
|
+
if (f.to) for (const r of f.to) args.push(`--to=${r}`);
|
|
32169
|
+
if (f.cc) for (const r of f.cc) args.push(`--cc=${r}`);
|
|
32170
|
+
if (f.bcc) for (const r of f.bcc) args.push(`--bcc=${r}`);
|
|
32171
|
+
if (f.remove) for (const r of f.remove) args.push(`--remove=${r}`);
|
|
32172
|
+
if (f.subject) args.push(`--subject=${f.subject}`);
|
|
32173
|
+
if (f.noQuote) args.push("--no-quote");
|
|
32174
|
+
if (f.attach) for (const p of f.attach) args.push(`--attach=${p}`);
|
|
32175
|
+
args.push(...inlineAttachmentArgs("attach", f.attachInline, args));
|
|
32176
|
+
if (f.from) args.push(`--from=${f.from}`);
|
|
32177
|
+
if (f.signature) args.push("--signature");
|
|
32178
|
+
if (f.signatureFrom) args.push(`--signature-from=${f.signatureFrom}`);
|
|
32179
|
+
if (f.signatureFile) args.push(`--signature-file=${f.signatureFile}`);
|
|
32180
|
+
args.push(f.autoFromAddressedAlias ? "--auto-from-addressed-alias" : "--auto-from-addressed-alias=false");
|
|
32181
|
+
}
|
|
32136
32182
|
function registerGmailTools(server) {
|
|
32137
32183
|
server.registerTool("gog_gmail_search", {
|
|
32138
32184
|
description: 'Search Gmail threads using Gmail query syntax (e.g. "from:alice subject:invoice is:unread"). The query is passed verbatim to Gmail; a bare name token (from:alison) matches per Gmail\'s own heuristics, a full address (from:alison@example.com) is exact. To match a contact across several addresses, OR them: from:(a@x.com OR b@y.com). Results are ALWAYS newest-first by Gmail\'s internalDate \u2014 the wrapper sorts them, so the first result is the most recent match and a recent message can never be buried below older ones. IMPORTANT \u2014 a response carrying "truncated": true is an INCOMPLETE view of the matches: NEVER report that a message does not exist, or that there is no such mail, on the strength of one. Page through it (pass nextPageToken back as `pageToken`), set maxPages to walk several pages in one call, or narrow the query, and only then draw a conclusion. If you already know the thread, do not search for it at all \u2014 read it directly with gog_gmail_thread_get, which returns the whole thread and cannot be truncated or mis-ranked.',
|
|
@@ -32185,7 +32231,7 @@ function registerGmailTools(server) {
|
|
|
32185
32231
|
return runOrDiagnose(args, { account });
|
|
32186
32232
|
});
|
|
32187
32233
|
server.registerTool("gog_gmail_send", {
|
|
32188
|
-
description: 'Send an email. Two ways to attach a file: `attach` takes paths READ ON THE GOG SERVER, and `attachInline` takes the bytes themselves. Use attachInline unless you know the file exists on the same machine gog runs on \u2014 on the hosted connector and any remote deployment there is no shared filesystem, so no path you can name resolves there and `attach` will fail with "no such file or directory". When either is used, the JSON result echoes the attached filenames and byte sizes \u2014 check it to confirm the files were embedded.',
|
|
32234
|
+
description: 'Send an email. Two ways to attach a file: `attach` takes paths READ ON THE GOG SERVER, and `attachInline` takes the bytes themselves. Use attachInline unless you know the file exists on the same machine gog runs on \u2014 on the hosted connector and any remote deployment there is no shared filesystem, so no path you can name resolves there and `attach` will fail with "no such file or directory". When either is used, the JSON result echoes the attached filenames and byte sizes \u2014 check it to confirm the files were embedded. NOT the tool for answering a message: replyToMessageId only files this in the right thread \u2014 the subject, recipients and body are entirely yours, and the original is not quoted unless you set quote. Use gog_gmail_reply / gog_gmail_reply_all instead, which inherit all three.',
|
|
32189
32235
|
annotations: { destructiveHint: true },
|
|
32190
32236
|
inputSchema: {
|
|
32191
32237
|
to: external_exports.string().describe("Recipient(s), comma-separated"),
|
|
@@ -32193,23 +32239,43 @@ function registerGmailTools(server) {
|
|
|
32193
32239
|
body: external_exports.string().describe("Email body (plain text). Any size \u2014 a large body is written to a temp file on the gog server rather than inlined into the command line. Note gog strips trailing newlines from a file-delivered body."),
|
|
32194
32240
|
cc: external_exports.string().optional().describe("CC recipients, comma-separated"),
|
|
32195
32241
|
bcc: external_exports.string().optional().describe("BCC recipients, comma-separated"),
|
|
32196
|
-
replyToMessageId: external_exports.string().optional().describe(
|
|
32197
|
-
threadId: external_exports.string().optional().describe("Thread ID to
|
|
32242
|
+
replyToMessageId: external_exports.string().optional().describe('Message ID to thread this message against \u2014 sets In-Reply-To/References only. It does NOT quote the original (pass quote for that), inherit its recipients, or prefix the subject with "Re:". For an actual reply use gog_gmail_reply.'),
|
|
32243
|
+
threadId: external_exports.string().optional().describe("Thread ID to thread this message within. Same caveat as replyToMessageId: threading only, no quote and no inherited subject or recipients."),
|
|
32244
|
+
quote: external_exports.boolean().optional().describe("Include the original message quoted below the body. Requires replyToMessageId or threadId. gog quotes by DEFAULT on gmail reply but never on gmail send, so without this a threaded send arrives with the original nowhere in it."),
|
|
32198
32245
|
attach: external_exports.array(external_exports.string()).optional().describe(`File paths to attach (repeatable), resolved ON THE GOG SERVER's filesystem \u2014 NOT this client's. Only usable when gog runs on the same machine you do (local stdio); on the hosted connector or any GOG_RUNNER_URL backend these paths do not exist and the call fails with "no such file or directory" \u2014 use attachInline there. Each file is read on the server, base64-encoded with a MIME type inferred from its extension, and added as a multipart attachment.`),
|
|
32199
32246
|
attachInline: attachInlineParam,
|
|
32200
32247
|
account: accountParam
|
|
32201
32248
|
}
|
|
32202
|
-
}, async ({ to, subject, body, cc, bcc, replyToMessageId, threadId, attach, attachInline, account }) => {
|
|
32249
|
+
}, async ({ to, subject, body, cc, bcc, replyToMessageId, threadId, quote, attach, attachInline, account }) => {
|
|
32203
32250
|
const args = ["gmail", "send", `--to=${to}`, `--subject=${subject}`, payloadArg("body", "body-file", body)];
|
|
32204
32251
|
if (cc) args.push(`--cc=${cc}`);
|
|
32205
32252
|
if (bcc) args.push(`--bcc=${bcc}`);
|
|
32206
32253
|
if (replyToMessageId) args.push(`--reply-to-message-id=${replyToMessageId}`);
|
|
32207
32254
|
if (threadId) args.push(`--thread-id=${threadId}`);
|
|
32255
|
+
if (quote) args.push("--quote");
|
|
32208
32256
|
if (attach) for (const path of attach) args.push(`--attach=${path}`);
|
|
32209
32257
|
const inline = inlineAttachmentArgs("attach", attachInline, args);
|
|
32210
32258
|
args.push(...inline);
|
|
32211
32259
|
return runOrDiagnose(args, { account });
|
|
32212
32260
|
});
|
|
32261
|
+
server.registerTool("gog_gmail_reply", {
|
|
32262
|
+
description: 'Reply to a Gmail message (goes to the original sender only). USE THIS, not gog_gmail_send, whenever you are answering a message: it threads off the original AND inherits its "Re:" subject and quotes its body below yours, which gog_gmail_send does not \u2014 a send with replyToMessageId lands in the right thread but reads as a brand-new message, with the original nowhere in it. To answer every participant use gog_gmail_reply_all. The gogcli-mcp-gmail package adds two more routes with the same composition: gog_gmail_autoreply to reply across every message matching a query, and gog_gmail_drafts_reply to stage this exact reply as a draft instead of sending it.',
|
|
32263
|
+
annotations: { destructiveHint: true },
|
|
32264
|
+
inputSchema: replySchema
|
|
32265
|
+
}, async ({ messageId, account, ...flags }) => {
|
|
32266
|
+
const args = ["gmail", "reply", messageId];
|
|
32267
|
+
appendReplyFlags(args, flags);
|
|
32268
|
+
return runOrDiagnose(args, { account });
|
|
32269
|
+
});
|
|
32270
|
+
server.registerTool("gog_gmail_reply_all", {
|
|
32271
|
+
description: 'Reply to all participants of a Gmail message (the sender plus every To/Cc recipient). Same inherited "Re:" subject and quoted original as gog_gmail_reply. Use the remove flag to drop specific recipients from the reply-all. To stage it as a draft rather than send it, use gog_gmail_drafts_reply_all (gogcli-mcp-gmail only).',
|
|
32272
|
+
annotations: { destructiveHint: true },
|
|
32273
|
+
inputSchema: replySchema
|
|
32274
|
+
}, async ({ messageId, account, ...flags }) => {
|
|
32275
|
+
const args = ["gmail", "reply-all", messageId];
|
|
32276
|
+
appendReplyFlags(args, flags);
|
|
32277
|
+
return runOrDiagnose(args, { account });
|
|
32278
|
+
});
|
|
32213
32279
|
registerRunTool(server, { service: "gmail", examples: '"archive", "mark-read", "labels"' });
|
|
32214
32280
|
}
|
|
32215
32281
|
|
|
@@ -32223,7 +32289,7 @@ var failIfNotEmptyParam = external_exports.boolean().optional().describe(
|
|
|
32223
32289
|
);
|
|
32224
32290
|
|
|
32225
32291
|
// ../gogcli-mcp/src/server.ts
|
|
32226
|
-
var VERSION = true ? "2.
|
|
32292
|
+
var VERSION = true ? "2.27.0" : "0.0.0";
|
|
32227
32293
|
|
|
32228
32294
|
// ../gogcli-mcp/src/auth-log.ts
|
|
32229
32295
|
var FAILURES = /* @__PURE__ */ new Set([
|
|
@@ -32710,13 +32776,6 @@ function useRemoteGogRunner(env = process.env) {
|
|
|
32710
32776
|
}
|
|
32711
32777
|
|
|
32712
32778
|
// src/tools/gmail-extra.ts
|
|
32713
|
-
function assertNotBoth(inlineParam, fileParam, inlineValue, fileValue) {
|
|
32714
|
-
if (inlineValue !== void 0 && fileValue !== void 0) {
|
|
32715
|
-
throw new Error(
|
|
32716
|
-
`${inlineParam} and ${fileParam} are mutually exclusive \u2014 gog accepts only one of them. Pass ${inlineParam} with the content itself (it is written to a temp file automatically when large), or ${fileParam} with a path that already exists on the gog server.`
|
|
32717
|
-
);
|
|
32718
|
-
}
|
|
32719
|
-
}
|
|
32720
32779
|
function resultText(result) {
|
|
32721
32780
|
const first = result.content[0];
|
|
32722
32781
|
return first?.type === "text" ? first.text : void 0;
|
|
@@ -34192,7 +34251,7 @@ function registerExtraGmailTools(server) {
|
|
|
34192
34251
|
replyToMessageId: external_exports.string().optional().describe("Reply to a specific Gmail MESSAGE id \u2014 the short hex `id` field from gog_gmail_get / _search / _thread_get (e.g. 19e7593d77fd9636), NOT a thread id and NOT the RFC822 `<\u2026@host>` Message-Id header. Anchors In-Reply-To/References to that exact message. To reply to a thread when you don't know the latest message, use replyToThreadId instead. If both are given, replyToMessageId wins."),
|
|
34193
34252
|
replyToThreadId: external_exports.string().optional().describe(`Reply to a Gmail THREAD id \u2014 passed to gog as --thread-id, which threads the draft using the thread's latest-message headers (In-Reply-To/References). This is what "reply to this thread" almost always means. Mutually exclusive with replyToMessageId (which wins if both are set). Thread ids and message ids are both 16-hex strings and easy to confuse \u2014 use this param, not replyToMessageId, when the id came from a thread.`),
|
|
34194
34253
|
replyTo: external_exports.string().optional().describe("Reply-To header address"),
|
|
34195
|
-
quote: external_exports.boolean().optional().describe(
|
|
34254
|
+
quote: external_exports.boolean().optional().describe('Include the original message quoted below the body. Requires replyToMessageId or replyToThreadId. DEFAULTS OFF: a draft created with a reply target but without this threads correctly and still reads as a brand-new message, because gog only quotes by default on its reply subcommands. For a real reply draft prefer gog_gmail_drafts_reply / gog_gmail_drafts_reply_all, which also inherit the recipients and the "Re:" subject that this tool leaves to you.'),
|
|
34196
34255
|
replyAll: external_exports.boolean().optional().describe("Auto-populate recipients from the original message (reply-all), inferring To/Cc from it. Requires replyToMessageId or replyToThreadId. Explicit to/cc/bcc still apply on top; omitRecipients still suppresses them."),
|
|
34197
34256
|
attach: external_exports.array(external_exports.string()).optional().describe(`File paths to attach (repeatable), resolved ON THE GOG SERVER's filesystem \u2014 NOT this client's. Only usable when gog runs on the same machine you do (local stdio); on the hosted connector or any GOG_RUNNER_URL backend these paths do not exist and the call fails with "no such file or directory" \u2014 use attachInline there. Read on the server, base64-encoded with a MIME type inferred from the extension. The JSON result echoes attached filenames and byte sizes \u2014 check it to confirm the files were found and embedded. On gog_gmail_drafts_update, supplying attach REPLACES the draft's existing attachments; omitting it preserves them (use clearAttachments to remove all).`),
|
|
34198
34257
|
attachInline: attachInlineParam,
|
|
@@ -34380,63 +34439,6 @@ function registerExtraGmailTools(server) {
|
|
|
34380
34439
|
if (skipAttachments) args.push("--skip-attachments");
|
|
34381
34440
|
return runOrDiagnose(args, { account });
|
|
34382
34441
|
});
|
|
34383
|
-
const replySchema = {
|
|
34384
|
-
messageId: external_exports.string().describe("Gmail message ID to reply to \u2014 the short hex `id` from gog_gmail_get / _search / _messages_search (NOT the threadId, NOT the RFC822 `<\u2026@host>` Message-Id header)."),
|
|
34385
|
-
body: external_exports.string().optional().describe("Reply body (plain text; required unless bodyHtml or bodyHtmlFile is set). Any size \u2014 a large body is written to a temp file on the gog server rather than inlined into the command line. Note gog strips trailing newlines from a file-delivered body."),
|
|
34386
|
-
bodyHtml: external_exports.string().optional().describe("Reply body (HTML; optional). Pass the HTML itself at any size \u2014 a large body is written to a temp file on the gog server rather than inlined into the command line. Mutually exclusive with bodyHtmlFile."),
|
|
34387
|
-
bodyHtmlFile: external_exports.string().optional().describe(`Path to an HTML file that ALREADY EXISTS on the gog server for the reply body. gog also accepts "-" for stdin, but this server never writes to gog's stdin, so "-" would hang until the call times out. Mutually exclusive with bodyHtml \u2014 supplying both is rejected. You rarely need this: bodyHtml handles large bodies on its own.`),
|
|
34388
|
-
to: external_exports.array(external_exports.string()).optional().describe("Add or move recipients to To (repeatable). Added on top of the recipients inherited from the original message."),
|
|
34389
|
-
cc: external_exports.array(external_exports.string()).optional().describe("Add or move recipients to Cc (repeatable)"),
|
|
34390
|
-
bcc: external_exports.array(external_exports.string()).optional().describe("Add or move recipients to Bcc (repeatable)"),
|
|
34391
|
-
remove: external_exports.array(external_exports.string()).optional().describe("Remove these recipients from all fields (repeatable) \u2014 e.g. to drop someone from a reply-all."),
|
|
34392
|
-
subject: external_exports.string().optional().describe('Override reply subject (default: "Re: <original>"). A changed subject starts a NEW Gmail thread.'),
|
|
34393
|
-
noQuote: external_exports.boolean().optional().describe("Do not include the original message quoted below the reply (default: the original is quoted)"),
|
|
34394
|
-
attach: external_exports.array(external_exports.string()).optional().describe(`File paths to attach (repeatable), resolved ON THE GOG SERVER's filesystem \u2014 NOT this client's. Only usable when gog runs on the same machine you do (local stdio); on the hosted connector or any GOG_RUNNER_URL backend these paths do not exist and the call fails with "no such file or directory" \u2014 use attachInline there. Read on the server, base64-encoded with a MIME type inferred from the extension.`),
|
|
34395
|
-
attachInline: attachInlineParam,
|
|
34396
|
-
from: external_exports.string().optional().describe("Send from this email address (must be a verified send-as alias)"),
|
|
34397
|
-
autoFromAddressedAlias: external_exports.boolean().optional().describe("When from is omitted, send from the verified send-as alias the original message was addressed TO, instead of the account's primary address \u2014 so a reply to mail sent to an alias goes back out from that alias. Ignored when from is set."),
|
|
34398
|
-
signature: external_exports.boolean().optional().describe("Append the Gmail signature from the active send-as address"),
|
|
34399
|
-
signatureFrom: external_exports.string().optional().describe("Append the Gmail signature from this send-as email address"),
|
|
34400
|
-
signatureFile: external_exports.string().optional().describe("Append a local signature file (plain text or HTML), read on the gog server"),
|
|
34401
|
-
account: accountParam
|
|
34402
|
-
};
|
|
34403
|
-
function appendReplyFlags(args, f) {
|
|
34404
|
-
assertNotBoth("bodyHtml", "bodyHtmlFile", f.bodyHtml, f.bodyHtmlFile);
|
|
34405
|
-
if (f.body) args.push(payloadArg("body", "body-file", f.body));
|
|
34406
|
-
if (f.bodyHtml) args.push(payloadArg("body-html", "body-html-file", f.bodyHtml, "html"));
|
|
34407
|
-
else if (f.bodyHtmlFile) args.push(`--body-html-file=${f.bodyHtmlFile}`);
|
|
34408
|
-
if (f.to) for (const r of f.to) args.push(`--to=${r}`);
|
|
34409
|
-
if (f.cc) for (const r of f.cc) args.push(`--cc=${r}`);
|
|
34410
|
-
if (f.bcc) for (const r of f.bcc) args.push(`--bcc=${r}`);
|
|
34411
|
-
if (f.remove) for (const r of f.remove) args.push(`--remove=${r}`);
|
|
34412
|
-
if (f.subject) args.push(`--subject=${f.subject}`);
|
|
34413
|
-
if (f.noQuote) args.push("--no-quote");
|
|
34414
|
-
if (f.attach) for (const p of f.attach) args.push(`--attach=${p}`);
|
|
34415
|
-
args.push(...inlineAttachmentArgs("attach", f.attachInline, args));
|
|
34416
|
-
if (f.from) args.push(`--from=${f.from}`);
|
|
34417
|
-
if (f.signature) args.push("--signature");
|
|
34418
|
-
if (f.signatureFrom) args.push(`--signature-from=${f.signatureFrom}`);
|
|
34419
|
-
if (f.signatureFile) args.push(`--signature-file=${f.signatureFile}`);
|
|
34420
|
-
args.push(f.autoFromAddressedAlias ? "--auto-from-addressed-alias" : "--auto-from-addressed-alias=false");
|
|
34421
|
-
}
|
|
34422
|
-
server.registerTool("gog_gmail_reply", {
|
|
34423
|
-
description: 'Reply to a Gmail message (sends to the original sender only). Threads off the message and inherits a "Re:" subject and the quoted original by default. For replying to every participant use gog_gmail_reply_all; to reply across many messages matching a query use gog_gmail_autoreply; to stage this same reply without sending it use gog_gmail_drafts_reply, which composes exactly what this tool would send.',
|
|
34424
|
-
annotations: { destructiveHint: true },
|
|
34425
|
-
inputSchema: replySchema
|
|
34426
|
-
}, async ({ messageId, account, ...flags }) => {
|
|
34427
|
-
const args = ["gmail", "reply", messageId];
|
|
34428
|
-
appendReplyFlags(args, flags);
|
|
34429
|
-
return runOrDiagnose(args, { account });
|
|
34430
|
-
});
|
|
34431
|
-
server.registerTool("gog_gmail_reply_all", {
|
|
34432
|
-
description: 'Reply to all participants of a Gmail message (sender plus every To/Cc recipient). Same inherited "Re:" subject and quoting as gog_gmail_reply. Use the remove flag to drop specific recipients from the reply-all. To stage it without sending use gog_gmail_drafts_reply_all.',
|
|
34433
|
-
annotations: { destructiveHint: true },
|
|
34434
|
-
inputSchema: replySchema
|
|
34435
|
-
}, async ({ messageId, account, ...flags }) => {
|
|
34436
|
-
const args = ["gmail", "reply-all", messageId];
|
|
34437
|
-
appendReplyFlags(args, flags);
|
|
34438
|
-
return runOrDiagnose(args, { account });
|
|
34439
|
-
});
|
|
34440
34442
|
const draftReplyNote = ' Composes exactly what gog_gmail_reply%s would send \u2014 inherited recipients, "Re:" subject and quoted original \u2014 but SAVES IT AS A DRAFT instead of sending. Nothing leaves the mailbox; send it later with gog_gmail_drafts_send, or edit it first with gog_gmail_drafts_update (which overwrites the whole body, quote included \u2014 read the draft back before editing).';
|
|
34441
34443
|
server.registerTool("gog_gmail_drafts_reply", {
|
|
34442
34444
|
description: "Save a reply to a Gmail message as a draft (to the original sender only)." + draftReplyNote.replace("%s", "") + " Prefer this over gog_gmail_drafts_create + replyToMessageId when the draft is a real reply: that route threads the draft but leaves recipients and quoting for you to reconstruct.",
|
package/manifest.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"manifest_version": "0.3",
|
|
4
4
|
"name": "gogcli-mcp-gmail",
|
|
5
5
|
"display_name": "gogcli (Gmail)",
|
|
6
|
-
"version": "2.
|
|
6
|
+
"version": "2.27.0",
|
|
7
7
|
"description": "Extended Gmail for Claude via gogcli — auth + full Gmail support (threads, labels, drafts, attachments, forward, autoreply, bulk operations)",
|
|
8
8
|
"author": {
|
|
9
9
|
"name": "Chris Hall",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gogcli-mcp-gmail",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.27.0",
|
|
4
4
|
"mcpName": "io.github.chrischall/gogcli-mcp-gmail",
|
|
5
5
|
"description": "Extended Gmail MCP server via gogcli — auth + full Gmail support (threads, labels, drafts, attachments, forward, autoreply, bulk operations)",
|
|
6
6
|
"author": "Claude Code (AI) <https://www.anthropic.com/claude>",
|
package/src/tools/gmail-extra.ts
CHANGED
|
@@ -2,29 +2,9 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
|
|
4
4
|
import { rawTextResult, textResult, errorResult } from '@chrischall/mcp-utils';
|
|
5
|
-
import { accountParam, runOrDiagnose, run, diagnose, payloadArg, runExecutor, normalizeTimestamps, finalizeGmailSearch, fetchGmailPages, pageTokenParam, pageAliasParam, resolvePageToken, attachInlineParam, inlineAttachmentArgs} from '../../../gogcli-mcp/src/lib.js';
|
|
5
|
+
import { accountParam, runOrDiagnose, run, diagnose, payloadArg, runExecutor, normalizeTimestamps, finalizeGmailSearch, fetchGmailPages, pageTokenParam, pageAliasParam, resolvePageToken, attachInlineParam, inlineAttachmentArgs, assertNotBoth, replySchema, appendReplyFlags} from '../../../gogcli-mcp/src/lib.js';
|
|
6
6
|
import type { GogArg, InlineAttachmentInput } from '../../../gogcli-mcp/src/lib.js';
|
|
7
7
|
|
|
8
|
-
// gog rejects an inline flag together with its --*-file twin — `gmail drafts
|
|
9
|
-
// create` errors with "use only one of --body-html or --body-html-file", and
|
|
10
|
-
// `gmail forward` does the same for --note (misreporting it as --body). Catch
|
|
11
|
-
// the conflict here so the caller gets a message naming the TOOL params it
|
|
12
|
-
// actually passed, instead of a gog error naming flags it never saw.
|
|
13
|
-
function assertNotBoth(
|
|
14
|
-
inlineParam: string,
|
|
15
|
-
fileParam: string,
|
|
16
|
-
inlineValue: string | undefined,
|
|
17
|
-
fileValue: string | undefined,
|
|
18
|
-
): void {
|
|
19
|
-
if (inlineValue !== undefined && fileValue !== undefined) {
|
|
20
|
-
throw new Error(
|
|
21
|
-
`${inlineParam} and ${fileParam} are mutually exclusive — gog accepts only one of them. ` +
|
|
22
|
-
`Pass ${inlineParam} with the content itself (it is written to a temp file automatically when large), ` +
|
|
23
|
-
`or ${fileParam} with a path that already exists on the gog server.`,
|
|
24
|
-
);
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
8
|
// Pull the text out of a single-text-block tool result; undefined for any
|
|
29
9
|
// other shape (an error result is still a text block, so it parses below).
|
|
30
10
|
function resultText(result: CallToolResult): string | undefined {
|
|
@@ -2889,7 +2869,7 @@ export function registerExtraGmailTools(server: McpServer): void {
|
|
|
2889
2869
|
replyToMessageId: z.string().optional().describe('Reply to a specific Gmail MESSAGE id — the short hex `id` field from gog_gmail_get / _search / _thread_get (e.g. 19e7593d77fd9636), NOT a thread id and NOT the RFC822 `<…@host>` Message-Id header. Anchors In-Reply-To/References to that exact message. To reply to a thread when you don\'t know the latest message, use replyToThreadId instead. If both are given, replyToMessageId wins.'),
|
|
2890
2870
|
replyToThreadId: z.string().optional().describe('Reply to a Gmail THREAD id — passed to gog as --thread-id, which threads the draft using the thread\'s latest-message headers (In-Reply-To/References). This is what "reply to this thread" almost always means. Mutually exclusive with replyToMessageId (which wins if both are set). Thread ids and message ids are both 16-hex strings and easy to confuse — use this param, not replyToMessageId, when the id came from a thread.'),
|
|
2891
2871
|
replyTo: z.string().optional().describe('Reply-To header address'),
|
|
2892
|
-
quote: z.boolean().optional().describe('Include
|
|
2872
|
+
quote: z.boolean().optional().describe('Include the original message quoted below the body. Requires replyToMessageId or replyToThreadId. DEFAULTS OFF: a draft created with a reply target but without this threads correctly and still reads as a brand-new message, because gog only quotes by default on its reply subcommands. For a real reply draft prefer gog_gmail_drafts_reply / gog_gmail_drafts_reply_all, which also inherit the recipients and the "Re:" subject that this tool leaves to you.'),
|
|
2893
2873
|
replyAll: z.boolean().optional().describe('Auto-populate recipients from the original message (reply-all), inferring To/Cc from it. Requires replyToMessageId or replyToThreadId. Explicit to/cc/bcc still apply on top; omitRecipients still suppresses them.'),
|
|
2894
2874
|
attach: z.array(z.string()).optional().describe('File paths to attach (repeatable), resolved ON THE GOG SERVER\'s filesystem — NOT this client\'s. Only usable when gog runs on the same machine you do (local stdio); on the hosted connector or any GOG_RUNNER_URL backend these paths do not exist and the call fails with "no such file or directory" — use attachInline there. Read on the server, base64-encoded with a MIME type inferred from the extension. The JSON result echoes attached filenames and byte sizes — check it to confirm the files were found and embedded. On gog_gmail_drafts_update, supplying attach REPLACES the draft\'s existing attachments; omitting it preserves them (use clearAttachments to remove all).'),
|
|
2895
2875
|
attachInline: attachInlineParam,
|
|
@@ -3216,97 +3196,11 @@ export function registerExtraGmailTools(server: McpServer): void {
|
|
|
3216
3196
|
return runOrDiagnose(args, { account });
|
|
3217
3197
|
});
|
|
3218
3198
|
|
|
3219
|
-
// gmail reply / reply-all share an identical flag set (gog 0.27+); they differ
|
|
3220
|
-
// only in the subcommand and default recipient set (reply → sender; reply-all
|
|
3221
|
-
// → every participant). Recipient flags are repeatable on the CLI, so they are
|
|
3222
|
-
// arrays here. --to/--cc/--bcc ADD or MOVE recipients onto the inherited reply
|
|
3223
|
-
// set; --remove drops them. Body/HTML follow the same inline-or-file shape as
|
|
3224
|
-
// the draft tools.
|
|
3225
|
-
const replySchema = {
|
|
3226
|
-
messageId: z.string().describe('Gmail message ID to reply to — the short hex `id` from gog_gmail_get / _search / _messages_search (NOT the threadId, NOT the RFC822 `<…@host>` Message-Id header).'),
|
|
3227
|
-
body: z.string().optional().describe('Reply body (plain text; required unless bodyHtml or bodyHtmlFile is set). Any size — a large body is written to a temp file on the gog server rather than inlined into the command line. Note gog strips trailing newlines from a file-delivered body.'),
|
|
3228
|
-
bodyHtml: z.string().optional().describe('Reply body (HTML; optional). Pass the HTML itself at any size — a large body is written to a temp file on the gog server rather than inlined into the command line. Mutually exclusive with bodyHtmlFile.'),
|
|
3229
|
-
bodyHtmlFile: z.string().optional().describe('Path to an HTML file that ALREADY EXISTS on the gog server for the reply body. gog also accepts "-" for stdin, but this server never writes to gog\'s stdin, so "-" would hang until the call times out. Mutually exclusive with bodyHtml — supplying both is rejected. You rarely need this: bodyHtml handles large bodies on its own.'),
|
|
3230
|
-
to: z.array(z.string()).optional().describe('Add or move recipients to To (repeatable). Added on top of the recipients inherited from the original message.'),
|
|
3231
|
-
cc: z.array(z.string()).optional().describe('Add or move recipients to Cc (repeatable)'),
|
|
3232
|
-
bcc: z.array(z.string()).optional().describe('Add or move recipients to Bcc (repeatable)'),
|
|
3233
|
-
remove: z.array(z.string()).optional().describe('Remove these recipients from all fields (repeatable) — e.g. to drop someone from a reply-all.'),
|
|
3234
|
-
subject: z.string().optional().describe('Override reply subject (default: "Re: <original>"). A changed subject starts a NEW Gmail thread.'),
|
|
3235
|
-
noQuote: z.boolean().optional().describe('Do not include the original message quoted below the reply (default: the original is quoted)'),
|
|
3236
|
-
attach: z.array(z.string()).optional().describe('File paths to attach (repeatable), resolved ON THE GOG SERVER\'s filesystem — NOT this client\'s. Only usable when gog runs on the same machine you do (local stdio); on the hosted connector or any GOG_RUNNER_URL backend these paths do not exist and the call fails with "no such file or directory" — use attachInline there. Read on the server, base64-encoded with a MIME type inferred from the extension.'),
|
|
3237
|
-
attachInline: attachInlineParam,
|
|
3238
|
-
from: z.string().optional().describe('Send from this email address (must be a verified send-as alias)'),
|
|
3239
|
-
autoFromAddressedAlias: z.boolean().optional().describe('When from is omitted, send from the verified send-as alias the original message was addressed TO, instead of the account\'s primary address — so a reply to mail sent to an alias goes back out from that alias. Ignored when from is set.'),
|
|
3240
|
-
signature: z.boolean().optional().describe('Append the Gmail signature from the active send-as address'),
|
|
3241
|
-
signatureFrom: z.string().optional().describe('Append the Gmail signature from this send-as email address'),
|
|
3242
|
-
signatureFile: z.string().optional().describe('Append a local signature file (plain text or HTML), read on the gog server'),
|
|
3243
|
-
account: accountParam,
|
|
3244
|
-
};
|
|
3245
|
-
|
|
3246
|
-
type ReplyFlags = {
|
|
3247
|
-
body?: string;
|
|
3248
|
-
bodyHtml?: string;
|
|
3249
|
-
bodyHtmlFile?: string;
|
|
3250
|
-
to?: string[];
|
|
3251
|
-
cc?: string[];
|
|
3252
|
-
bcc?: string[];
|
|
3253
|
-
remove?: string[];
|
|
3254
|
-
subject?: string;
|
|
3255
|
-
noQuote?: boolean;
|
|
3256
|
-
attach?: string[];
|
|
3257
|
-
attachInline?: InlineAttachmentInput[];
|
|
3258
|
-
from?: string;
|
|
3259
|
-
autoFromAddressedAlias?: boolean;
|
|
3260
|
-
signature?: boolean;
|
|
3261
|
-
signatureFrom?: string;
|
|
3262
|
-
signatureFile?: string;
|
|
3263
|
-
};
|
|
3264
|
-
|
|
3265
|
-
function appendReplyFlags(args: GogArg[], f: ReplyFlags): void {
|
|
3266
|
-
assertNotBoth('bodyHtml', 'bodyHtmlFile', f.bodyHtml, f.bodyHtmlFile);
|
|
3267
|
-
if (f.body) args.push(payloadArg('body', 'body-file', f.body));
|
|
3268
|
-
if (f.bodyHtml) args.push(payloadArg('body-html', 'body-html-file', f.bodyHtml, 'html'));
|
|
3269
|
-
else if (f.bodyHtmlFile) args.push(`--body-html-file=${f.bodyHtmlFile}`);
|
|
3270
|
-
if (f.to) for (const r of f.to) args.push(`--to=${r}`);
|
|
3271
|
-
if (f.cc) for (const r of f.cc) args.push(`--cc=${r}`);
|
|
3272
|
-
if (f.bcc) for (const r of f.bcc) args.push(`--bcc=${r}`);
|
|
3273
|
-
if (f.remove) for (const r of f.remove) args.push(`--remove=${r}`);
|
|
3274
|
-
if (f.subject) args.push(`--subject=${f.subject}`);
|
|
3275
|
-
if (f.noQuote) args.push('--no-quote');
|
|
3276
|
-
if (f.attach) for (const p of f.attach) args.push(`--attach=${p}`);
|
|
3277
|
-
args.push(...inlineAttachmentArgs('attach', f.attachInline, args)); // see appendDraftFlags
|
|
3278
|
-
if (f.from) args.push(`--from=${f.from}`);
|
|
3279
|
-
if (f.signature) args.push('--signature');
|
|
3280
|
-
if (f.signatureFrom) args.push(`--signature-from=${f.signatureFrom}`);
|
|
3281
|
-
if (f.signatureFile) args.push(`--signature-file=${f.signatureFile}`);
|
|
3282
|
-
args.push(f.autoFromAddressedAlias ? '--auto-from-addressed-alias' : '--auto-from-addressed-alias=false'); // PINNED — see appendDraftFlags
|
|
3283
|
-
}
|
|
3284
|
-
|
|
3285
|
-
server.registerTool('gog_gmail_reply', {
|
|
3286
|
-
description: 'Reply to a Gmail message (sends to the original sender only). Threads off the message and inherits a "Re:" subject and the quoted original by default. For replying to every participant use gog_gmail_reply_all; to reply across many messages matching a query use gog_gmail_autoreply; to stage this same reply without sending it use gog_gmail_drafts_reply, which composes exactly what this tool would send.',
|
|
3287
|
-
annotations: { destructiveHint: true },
|
|
3288
|
-
inputSchema: replySchema,
|
|
3289
|
-
}, async ({ messageId, account, ...flags }) => {
|
|
3290
|
-
const args: GogArg[] = ['gmail', 'reply', messageId];
|
|
3291
|
-
appendReplyFlags(args, flags);
|
|
3292
|
-
return runOrDiagnose(args, { account });
|
|
3293
|
-
});
|
|
3294
|
-
|
|
3295
|
-
server.registerTool('gog_gmail_reply_all', {
|
|
3296
|
-
description: 'Reply to all participants of a Gmail message (sender plus every To/Cc recipient). Same inherited "Re:" subject and quoting as gog_gmail_reply. Use the remove flag to drop specific recipients from the reply-all. To stage it without sending use gog_gmail_drafts_reply_all.',
|
|
3297
|
-
annotations: { destructiveHint: true },
|
|
3298
|
-
inputSchema: replySchema,
|
|
3299
|
-
}, async ({ messageId, account, ...flags }) => {
|
|
3300
|
-
const args: GogArg[] = ['gmail', 'reply-all', messageId];
|
|
3301
|
-
appendReplyFlags(args, flags);
|
|
3302
|
-
return runOrDiagnose(args, { account });
|
|
3303
|
-
});
|
|
3304
|
-
|
|
3305
3199
|
// gog >= 0.36.0: the draft-side twins of reply / reply-all / forward. They
|
|
3306
3200
|
// take the SAME flag set as the send-side commands and share the composition
|
|
3307
|
-
// path with them, so
|
|
3308
|
-
//
|
|
3309
|
-
// SENT.
|
|
3201
|
+
// path with them, so replySchema/appendReplyFlags are imported from the base
|
|
3202
|
+
// package (where gog_gmail_reply itself now lives) rather than re-declared —
|
|
3203
|
+
// the only difference is the subcommand and that NOTHING IS SENT.
|
|
3310
3204
|
//
|
|
3311
3205
|
// These exist because staging a reply used to mean gog_gmail_drafts_create
|
|
3312
3206
|
// with replyToMessageId/replyToThreadId, which threads the draft but does NOT
|
|
@@ -1742,96 +1742,6 @@ describe('gog_gmail_forward', () => {
|
|
|
1742
1742
|
});
|
|
1743
1743
|
});
|
|
1744
1744
|
|
|
1745
|
-
describe('gog_gmail_reply', () => {
|
|
1746
|
-
it('calls runOrDiagnose with messageId and --body', async () => {
|
|
1747
|
-
await harness.callTool('gog_gmail_reply', { messageId: 'm1', body: 'Thanks' });
|
|
1748
|
-
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
1749
|
-
['gmail', 'reply', 'm1', '--body=Thanks', '--auto-from-addressed-alias=false'],
|
|
1750
|
-
{ account: undefined },
|
|
1751
|
-
);
|
|
1752
|
-
});
|
|
1753
|
-
|
|
1754
|
-
it('passes all reply flags including repeatable recipients', async () => {
|
|
1755
|
-
await harness.callTool('gog_gmail_reply', {
|
|
1756
|
-
messageId: 'm1',
|
|
1757
|
-
body: 'Hi',
|
|
1758
|
-
bodyHtml: '<p>Hi</p>',
|
|
1759
|
-
to: ['a@b.com', 'c@d.com'],
|
|
1760
|
-
cc: ['cc@x.com'],
|
|
1761
|
-
bcc: ['bcc@x.com'],
|
|
1762
|
-
remove: ['old@x.com'],
|
|
1763
|
-
subject: 'New subject',
|
|
1764
|
-
noQuote: true,
|
|
1765
|
-
attach: ['/tmp/a.pdf', '/tmp/b.pdf'],
|
|
1766
|
-
from: 'me@x.com',
|
|
1767
|
-
signature: true,
|
|
1768
|
-
signatureFrom: 'alias@x.com',
|
|
1769
|
-
signatureFile: '/tmp/sig.txt',
|
|
1770
|
-
account: 'me@gmail.com',
|
|
1771
|
-
});
|
|
1772
|
-
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
1773
|
-
[
|
|
1774
|
-
'gmail', 'reply', 'm1',
|
|
1775
|
-
'--body=Hi',
|
|
1776
|
-
'--body-html=<p>Hi</p>',
|
|
1777
|
-
'--to=a@b.com',
|
|
1778
|
-
'--to=c@d.com',
|
|
1779
|
-
'--cc=cc@x.com',
|
|
1780
|
-
'--bcc=bcc@x.com',
|
|
1781
|
-
'--remove=old@x.com',
|
|
1782
|
-
'--subject=New subject',
|
|
1783
|
-
'--no-quote',
|
|
1784
|
-
'--attach=/tmp/a.pdf',
|
|
1785
|
-
'--attach=/tmp/b.pdf',
|
|
1786
|
-
'--from=me@x.com',
|
|
1787
|
-
'--signature',
|
|
1788
|
-
'--signature-from=alias@x.com',
|
|
1789
|
-
'--signature-file=/tmp/sig.txt', '--auto-from-addressed-alias=false'
|
|
1790
|
-
],
|
|
1791
|
-
{ account: 'me@gmail.com' },
|
|
1792
|
-
);
|
|
1793
|
-
});
|
|
1794
|
-
|
|
1795
|
-
it('omits --no-quote and --signature when false', async () => {
|
|
1796
|
-
await harness.callTool('gog_gmail_reply', { messageId: 'm1', body: 'Hi', noQuote: false, signature: false });
|
|
1797
|
-
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
1798
|
-
['gmail', 'reply', 'm1', '--body=Hi', '--auto-from-addressed-alias=false'],
|
|
1799
|
-
{ account: undefined },
|
|
1800
|
-
);
|
|
1801
|
-
});
|
|
1802
|
-
});
|
|
1803
|
-
|
|
1804
|
-
describe('gog_gmail_reply_all', () => {
|
|
1805
|
-
it('uses the reply-all subcommand', async () => {
|
|
1806
|
-
await harness.callTool('gog_gmail_reply_all', { messageId: 'm1', body: 'Thanks all' });
|
|
1807
|
-
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
1808
|
-
['gmail', 'reply-all', 'm1', '--body=Thanks all', '--auto-from-addressed-alias=false'],
|
|
1809
|
-
{ account: undefined },
|
|
1810
|
-
);
|
|
1811
|
-
});
|
|
1812
|
-
|
|
1813
|
-
it('passes repeatable recipient and signature flags', async () => {
|
|
1814
|
-
await harness.callTool('gog_gmail_reply_all', {
|
|
1815
|
-
messageId: 'm1',
|
|
1816
|
-
bodyHtml: '<p>Hi</p>',
|
|
1817
|
-
cc: ['x@y.com', 'z@y.com'],
|
|
1818
|
-
remove: ['drop@y.com'],
|
|
1819
|
-
signatureFile: '/tmp/sig.html',
|
|
1820
|
-
});
|
|
1821
|
-
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
1822
|
-
[
|
|
1823
|
-
'gmail', 'reply-all', 'm1',
|
|
1824
|
-
'--body-html=<p>Hi</p>',
|
|
1825
|
-
'--cc=x@y.com',
|
|
1826
|
-
'--cc=z@y.com',
|
|
1827
|
-
'--remove=drop@y.com',
|
|
1828
|
-
'--signature-file=/tmp/sig.html', '--auto-from-addressed-alias=false'
|
|
1829
|
-
],
|
|
1830
|
-
{ account: undefined },
|
|
1831
|
-
);
|
|
1832
|
-
});
|
|
1833
|
-
});
|
|
1834
|
-
|
|
1835
1745
|
// gog 0.36.0 (openclaw/gogcli#977) added the draft-side twins of reply /
|
|
1836
1746
|
// reply-all / forward. The point of these tests is the SUBCOMMAND: the flag
|
|
1837
1747
|
// handling is the send path's, shared verbatim, and a copy of it here would
|
|
@@ -2524,24 +2434,6 @@ describe('large payloads route to file args', () => {
|
|
|
2524
2434
|
expect(args()).not.toContain('--thread-id=t1');
|
|
2525
2435
|
});
|
|
2526
2436
|
|
|
2527
|
-
it('gog_gmail_reply routes a large body and bodyHtml to file args', async () => {
|
|
2528
|
-
await harness.callTool('gog_gmail_reply', { messageId: 'm1', body: big, bodyHtml: bigHtml });
|
|
2529
|
-
expect(args()).toEqual([
|
|
2530
|
-
'gmail', 'reply', 'm1',
|
|
2531
|
-
{ kind: 'file', flag: 'body-file', contents: big, ext: undefined },
|
|
2532
|
-
{ kind: 'file', flag: 'body-html-file', contents: bigHtml, ext: 'html' }, '--auto-from-addressed-alias=false'
|
|
2533
|
-
]);
|
|
2534
|
-
});
|
|
2535
|
-
|
|
2536
|
-
it('gog_gmail_reply_all routes a large body to --body-file, leaving the signature boolean a bare flag', async () => {
|
|
2537
|
-
await harness.callTool('gog_gmail_reply_all', { messageId: 'm1', body: big, signature: true });
|
|
2538
|
-
expect(args()).toEqual([
|
|
2539
|
-
'gmail', 'reply-all', 'm1',
|
|
2540
|
-
{ kind: 'file', flag: 'body-file', contents: big, ext: undefined },
|
|
2541
|
-
'--signature', '--auto-from-addressed-alias=false'
|
|
2542
|
-
]);
|
|
2543
|
-
});
|
|
2544
|
-
|
|
2545
2437
|
it('gog_gmail_forward routes a large note to --note-file', async () => {
|
|
2546
2438
|
await harness.callTool('gog_gmail_forward', { messageId: 'm1', to: 'a@b.com', note: big });
|
|
2547
2439
|
expect(args()).toEqual([
|
|
@@ -2587,33 +2479,6 @@ describe('inline/file param conflicts are rejected before gog runs', () => {
|
|
|
2587
2479
|
expect((res.content[0] as { text: string }).text).toContain('mutually exclusive');
|
|
2588
2480
|
expect(lib.runOrDiagnose).not.toHaveBeenCalled();
|
|
2589
2481
|
});
|
|
2590
|
-
|
|
2591
|
-
it('gog_gmail_reply rejects bodyHtml plus bodyHtmlFile', async () => {
|
|
2592
|
-
const res = await harness.callTool('gog_gmail_reply', {
|
|
2593
|
-
messageId: 'm1', bodyHtml: '<p>Hi</p>', bodyHtmlFile: '/tmp/b.html',
|
|
2594
|
-
});
|
|
2595
|
-
expect(res.isError).toBe(true);
|
|
2596
|
-
expect((res.content[0] as { text: string }).text).toContain('bodyHtml and bodyHtmlFile are mutually exclusive');
|
|
2597
|
-
expect(lib.runOrDiagnose).not.toHaveBeenCalled();
|
|
2598
|
-
});
|
|
2599
|
-
|
|
2600
|
-
it('an empty-string bodyHtml still counts as supplied and conflicts', async () => {
|
|
2601
|
-
// Guards the `!== undefined` check against a falsy-but-present value
|
|
2602
|
-
// sliding through to gog, which rejects the pair regardless of content.
|
|
2603
|
-
const res = await harness.callTool('gog_gmail_reply', {
|
|
2604
|
-
messageId: 'm1', body: 'B', bodyHtml: '', bodyHtmlFile: '/tmp/b.html',
|
|
2605
|
-
});
|
|
2606
|
-
expect(res.isError).toBe(true);
|
|
2607
|
-
expect(lib.runOrDiagnose).not.toHaveBeenCalled();
|
|
2608
|
-
});
|
|
2609
|
-
|
|
2610
|
-
it('bodyHtmlFile alone still passes through as --body-html-file', async () => {
|
|
2611
|
-
await harness.callTool('gog_gmail_reply', { messageId: 'm1', body: 'Hi', bodyHtmlFile: '/tmp/b.html' });
|
|
2612
|
-
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
2613
|
-
['gmail', 'reply', 'm1', '--body=Hi', '--body-html-file=/tmp/b.html', '--auto-from-addressed-alias=false'],
|
|
2614
|
-
{ account: undefined },
|
|
2615
|
-
);
|
|
2616
|
-
});
|
|
2617
2482
|
});
|
|
2618
2483
|
|
|
2619
2484
|
// ---------------------------------------------------------------------------
|
|
@@ -2808,15 +2673,6 @@ describe('gog 0.35.0 — --auto-from-addressed-alias is pinned on every send-sha
|
|
|
2808
2673
|
await harness.callTool('gog_gmail_drafts_update', { draftId: 'd1', subject: 'S', body: 'B', autoFromAddressedAlias: true });
|
|
2809
2674
|
expect(args()).toEqual(['gmail', 'drafts', 'update', 'd1', '--subject=S', '--body=B', '--auto-from-addressed-alias']);
|
|
2810
2675
|
});
|
|
2811
|
-
|
|
2812
|
-
it('gog_gmail_reply and gog_gmail_reply_all pin it', async () => {
|
|
2813
|
-
await harness.callTool('gog_gmail_reply', { messageId: 'm1', body: 'Hi' });
|
|
2814
|
-
expect(args()).toEqual(['gmail', 'reply', 'm1', '--body=Hi', '--auto-from-addressed-alias=false']);
|
|
2815
|
-
vi.clearAllMocks();
|
|
2816
|
-
vi.mocked(lib.runOrDiagnose).mockResolvedValue(rawTextResult('{}'));
|
|
2817
|
-
await harness.callTool('gog_gmail_reply_all', { messageId: 'm1', body: 'Hi', autoFromAddressedAlias: true });
|
|
2818
|
-
expect(args()).toEqual(['gmail', 'reply-all', 'm1', '--body=Hi', '--auto-from-addressed-alias']);
|
|
2819
|
-
});
|
|
2820
2676
|
});
|
|
2821
2677
|
|
|
2822
2678
|
describe('gog 0.35.0 — gog_gmail_import', () => {
|
|
@@ -2871,8 +2727,6 @@ describe('server-side file params never advertise stdin as usable', () => {
|
|
|
2871
2727
|
['gog_gmail_import', 'file'],
|
|
2872
2728
|
['gog_gmail_drafts_create', 'bodyHtmlFile'],
|
|
2873
2729
|
['gog_gmail_drafts_update', 'bodyHtmlFile'],
|
|
2874
|
-
['gog_gmail_reply', 'bodyHtmlFile'],
|
|
2875
|
-
['gog_gmail_reply_all', 'bodyHtmlFile'],
|
|
2876
2730
|
];
|
|
2877
2731
|
|
|
2878
2732
|
async function paramDescriptions(): Promise<Map<string, Record<string, { description?: string }>>> {
|