gogcli-mcp-gmail 2.0.3

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.
@@ -0,0 +1,606 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
+ import { registerExtraGmailTools } from '../../src/tools/gmail-extra.js';
4
+ import * as lib from '../../../gogcli-mcp/src/lib.js';
5
+
6
+ vi.mock('../../../gogcli-mcp/src/lib.js', async (importOriginal) => {
7
+ const actual = await importOriginal<typeof lib>();
8
+ return {
9
+ ...actual,
10
+ runOrDiagnose: vi.fn(),
11
+ };
12
+ });
13
+
14
+ type ToolHandler = (args: Record<string, unknown>) => Promise<{ content: Array<{ type: string; text: string }> }>;
15
+
16
+ function toText(text: string) {
17
+ return { content: [{ type: 'text', text }] };
18
+ }
19
+
20
+ function setupHandlers(): Map<string, ToolHandler> {
21
+ const server = new McpServer({ name: 'test', version: '0.0.0' });
22
+ const handlers = new Map<string, ToolHandler>();
23
+ vi.spyOn(server, 'registerTool').mockImplementation((name, _config, cb) => {
24
+ handlers.set(name, cb as ToolHandler);
25
+ return undefined as never;
26
+ });
27
+ registerExtraGmailTools(server);
28
+ return handlers;
29
+ }
30
+
31
+ let handlers: Map<string, ToolHandler>;
32
+
33
+ beforeEach(() => {
34
+ vi.clearAllMocks();
35
+ vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
36
+ handlers = setupHandlers();
37
+ });
38
+
39
+ describe('gog_gmail_raw', () => {
40
+ it('calls runOrDiagnose with messageId', async () => {
41
+ await handlers.get('gog_gmail_raw')!({ messageId: 'm1' });
42
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['gmail', 'raw', 'm1'], { account: undefined });
43
+ });
44
+
45
+ it('passes --format and --pretty when provided', async () => {
46
+ await handlers.get('gog_gmail_raw')!({ messageId: 'm1', format: 'metadata', pretty: true });
47
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
48
+ ['gmail', 'raw', 'm1', '--format=metadata', '--pretty'],
49
+ { account: undefined },
50
+ );
51
+ });
52
+
53
+ it('omits --pretty when false', async () => {
54
+ await handlers.get('gog_gmail_raw')!({ messageId: 'm1', pretty: false });
55
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['gmail', 'raw', 'm1'], { account: undefined });
56
+ });
57
+ });
58
+
59
+ describe('gog_gmail_attachment', () => {
60
+ it('calls runOrDiagnose with messageId and attachmentId', async () => {
61
+ await handlers.get('gog_gmail_attachment')!({ messageId: 'm1', attachmentId: 'a1' });
62
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
63
+ ['gmail', 'attachment', 'm1', 'a1'],
64
+ { account: undefined },
65
+ );
66
+ });
67
+
68
+ it('passes --out and --name when provided', async () => {
69
+ await handlers.get('gog_gmail_attachment')!({
70
+ messageId: 'm1',
71
+ attachmentId: 'a1',
72
+ out: '/tmp/file.pdf',
73
+ name: 'report.pdf',
74
+ });
75
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
76
+ ['gmail', 'attachment', 'm1', 'a1', '--out=/tmp/file.pdf', '--name=report.pdf'],
77
+ { account: undefined },
78
+ );
79
+ });
80
+ });
81
+
82
+ describe('gog_gmail_url', () => {
83
+ it('calls runOrDiagnose with a single threadId', async () => {
84
+ await handlers.get('gog_gmail_url')!({ threadIds: ['t1'] });
85
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['gmail', 'url', 't1'], { account: undefined });
86
+ });
87
+
88
+ it('calls runOrDiagnose with multiple threadIds', async () => {
89
+ await handlers.get('gog_gmail_url')!({ threadIds: ['t1', 't2', 't3'] });
90
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
91
+ ['gmail', 'url', 't1', 't2', 't3'],
92
+ { account: undefined },
93
+ );
94
+ });
95
+ });
96
+
97
+ describe('gog_gmail_history', () => {
98
+ it('calls runOrDiagnose with no flags', async () => {
99
+ await handlers.get('gog_gmail_history')!({});
100
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['gmail', 'history'], { account: undefined });
101
+ });
102
+
103
+ it('passes all history flags', async () => {
104
+ await handlers.get('gog_gmail_history')!({ since: '12345', max: 50, page: 'tok', all: true });
105
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
106
+ ['gmail', 'history', '--since=12345', '--max=50', '--page=tok', '--all'],
107
+ { account: undefined },
108
+ );
109
+ });
110
+
111
+ it('omits --all when false', async () => {
112
+ await handlers.get('gog_gmail_history')!({ all: false });
113
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['gmail', 'history'], { account: undefined });
114
+ });
115
+ });
116
+
117
+ describe('bulk action tools (archive, mark_read, mark_unread, trash)', () => {
118
+ const bulkTools = [
119
+ { tool: 'gog_gmail_archive', cmd: 'archive' },
120
+ { tool: 'gog_gmail_mark_read', cmd: 'mark-read' },
121
+ { tool: 'gog_gmail_mark_unread', cmd: 'unread' },
122
+ { tool: 'gog_gmail_trash', cmd: 'trash' },
123
+ ];
124
+
125
+ for (const { tool, cmd } of bulkTools) {
126
+ describe(tool, () => {
127
+ it('passes messageIds as positional args', async () => {
128
+ await handlers.get(tool)!({ messageIds: ['m1', 'm2'] });
129
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
130
+ ['gmail', cmd, 'm1', 'm2'],
131
+ { account: undefined },
132
+ );
133
+ });
134
+
135
+ it('passes --query and --max', async () => {
136
+ await handlers.get(tool)!({ query: 'is:unread older_than:7d', max: 50 });
137
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
138
+ ['gmail', cmd, '--query=is:unread older_than:7d', '--max=50'],
139
+ { account: undefined },
140
+ );
141
+ });
142
+
143
+ it('passes both positional ids and flags together', async () => {
144
+ await handlers.get(tool)!({ messageIds: ['m1'], max: 10 });
145
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
146
+ ['gmail', cmd, 'm1', '--max=10'],
147
+ { account: undefined },
148
+ );
149
+ });
150
+ });
151
+ }
152
+ });
153
+
154
+ describe('gog_gmail_message_modify', () => {
155
+ it('calls runOrDiagnose with messageId and label changes', async () => {
156
+ await handlers.get('gog_gmail_message_modify')!({
157
+ messageId: 'm1',
158
+ add: 'STARRED,IMPORTANT',
159
+ remove: 'INBOX',
160
+ });
161
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
162
+ ['gmail', 'messages', 'modify', 'm1', '--add=STARRED,IMPORTANT', '--remove=INBOX'],
163
+ { account: undefined },
164
+ );
165
+ });
166
+
167
+ it('omits flags when not provided', async () => {
168
+ await handlers.get('gog_gmail_message_modify')!({ messageId: 'm1' });
169
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
170
+ ['gmail', 'messages', 'modify', 'm1'],
171
+ { account: undefined },
172
+ );
173
+ });
174
+ });
175
+
176
+ describe('gog_gmail_batch_delete', () => {
177
+ it('calls runOrDiagnose with messageIds as positional args', async () => {
178
+ await handlers.get('gog_gmail_batch_delete')!({ messageIds: ['m1', 'm2', 'm3'] });
179
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
180
+ ['gmail', 'batch', 'delete', 'm1', 'm2', 'm3'],
181
+ { account: undefined },
182
+ );
183
+ });
184
+ });
185
+
186
+ describe('gog_gmail_batch_modify', () => {
187
+ it('calls runOrDiagnose with messageIds and label flags', async () => {
188
+ await handlers.get('gog_gmail_batch_modify')!({
189
+ messageIds: ['m1', 'm2'],
190
+ add: 'STARRED',
191
+ remove: 'INBOX',
192
+ });
193
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
194
+ ['gmail', 'batch', 'modify', 'm1', 'm2', '--add=STARRED', '--remove=INBOX'],
195
+ { account: undefined },
196
+ );
197
+ });
198
+
199
+ it('omits label flags when not provided', async () => {
200
+ await handlers.get('gog_gmail_batch_modify')!({ messageIds: ['m1'] });
201
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
202
+ ['gmail', 'batch', 'modify', 'm1'],
203
+ { account: undefined },
204
+ );
205
+ });
206
+ });
207
+
208
+ describe('gog_gmail_thread_get', () => {
209
+ it('calls runOrDiagnose with threadId', async () => {
210
+ await handlers.get('gog_gmail_thread_get')!({ threadId: 't1' });
211
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
212
+ ['gmail', 'thread', 'get', 't1'],
213
+ { account: undefined },
214
+ );
215
+ });
216
+
217
+ it('passes all flags', async () => {
218
+ await handlers.get('gog_gmail_thread_get')!({
219
+ threadId: 't1',
220
+ download: true,
221
+ full: true,
222
+ sanitizeContent: true,
223
+ outDir: '/tmp/atts',
224
+ });
225
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
226
+ ['gmail', 'thread', 'get', 't1', '--download', '--full', '--sanitize-content', '--out-dir=/tmp/atts'],
227
+ { account: undefined },
228
+ );
229
+ });
230
+
231
+ it('omits boolean flags when false', async () => {
232
+ await handlers.get('gog_gmail_thread_get')!({
233
+ threadId: 't1',
234
+ download: false,
235
+ full: false,
236
+ sanitizeContent: false,
237
+ });
238
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
239
+ ['gmail', 'thread', 'get', 't1'],
240
+ { account: undefined },
241
+ );
242
+ });
243
+ });
244
+
245
+ describe('gog_gmail_thread_modify', () => {
246
+ it('calls runOrDiagnose with threadId and label flags', async () => {
247
+ await handlers.get('gog_gmail_thread_modify')!({
248
+ threadId: 't1',
249
+ add: 'IMPORTANT',
250
+ remove: 'INBOX',
251
+ });
252
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
253
+ ['gmail', 'thread', 'modify', 't1', '--add=IMPORTANT', '--remove=INBOX'],
254
+ { account: undefined },
255
+ );
256
+ });
257
+
258
+ it('omits label flags when not provided', async () => {
259
+ await handlers.get('gog_gmail_thread_modify')!({ threadId: 't1' });
260
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
261
+ ['gmail', 'thread', 'modify', 't1'],
262
+ { account: undefined },
263
+ );
264
+ });
265
+ });
266
+
267
+ describe('gog_gmail_thread_attachments', () => {
268
+ it('calls runOrDiagnose with threadId', async () => {
269
+ await handlers.get('gog_gmail_thread_attachments')!({ threadId: 't1' });
270
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
271
+ ['gmail', 'thread', 'attachments', 't1'],
272
+ { account: undefined },
273
+ );
274
+ });
275
+
276
+ it('passes --download and --out-dir when provided', async () => {
277
+ await handlers.get('gog_gmail_thread_attachments')!({
278
+ threadId: 't1',
279
+ download: true,
280
+ outDir: '/tmp/atts',
281
+ });
282
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
283
+ ['gmail', 'thread', 'attachments', 't1', '--download', '--out-dir=/tmp/atts'],
284
+ { account: undefined },
285
+ );
286
+ });
287
+ });
288
+
289
+ describe('gog_gmail_labels_list', () => {
290
+ it('calls runOrDiagnose with no args', async () => {
291
+ await handlers.get('gog_gmail_labels_list')!({});
292
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
293
+ ['gmail', 'labels', 'list'],
294
+ { account: undefined },
295
+ );
296
+ });
297
+
298
+ it('forwards account', async () => {
299
+ await handlers.get('gog_gmail_labels_list')!({ account: 'a@b.com' });
300
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
301
+ ['gmail', 'labels', 'list'],
302
+ { account: 'a@b.com' },
303
+ );
304
+ });
305
+ });
306
+
307
+ describe('gog_gmail_labels_get', () => {
308
+ it('calls runOrDiagnose with labelIdOrName', async () => {
309
+ await handlers.get('gog_gmail_labels_get')!({ labelIdOrName: 'INBOX' });
310
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
311
+ ['gmail', 'labels', 'get', 'INBOX'],
312
+ { account: undefined },
313
+ );
314
+ });
315
+ });
316
+
317
+ describe('gog_gmail_labels_create', () => {
318
+ it('calls runOrDiagnose with name', async () => {
319
+ await handlers.get('gog_gmail_labels_create')!({ name: 'Newsletter' });
320
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
321
+ ['gmail', 'labels', 'create', 'Newsletter'],
322
+ { account: undefined },
323
+ );
324
+ });
325
+ });
326
+
327
+ describe('gog_gmail_labels_rename', () => {
328
+ it('calls runOrDiagnose with old and new names', async () => {
329
+ await handlers.get('gog_gmail_labels_rename')!({ labelIdOrName: 'Old', newName: 'New' });
330
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
331
+ ['gmail', 'labels', 'rename', 'Old', 'New'],
332
+ { account: undefined },
333
+ );
334
+ });
335
+ });
336
+
337
+ describe('gog_gmail_labels_delete', () => {
338
+ it('calls runOrDiagnose with labelIdOrName', async () => {
339
+ await handlers.get('gog_gmail_labels_delete')!({ labelIdOrName: 'Trash-Me' });
340
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
341
+ ['gmail', 'labels', 'delete', 'Trash-Me'],
342
+ { account: undefined },
343
+ );
344
+ });
345
+ });
346
+
347
+ describe('gog_gmail_labels_modify', () => {
348
+ it('calls runOrDiagnose with threadIds and label flags', async () => {
349
+ await handlers.get('gog_gmail_labels_modify')!({
350
+ threadIds: ['t1', 't2'],
351
+ add: 'Newsletter',
352
+ remove: 'INBOX',
353
+ });
354
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
355
+ ['gmail', 'labels', 'modify', 't1', 't2', '--add=Newsletter', '--remove=INBOX'],
356
+ { account: undefined },
357
+ );
358
+ });
359
+
360
+ it('omits label flags when not provided', async () => {
361
+ await handlers.get('gog_gmail_labels_modify')!({ threadIds: ['t1'] });
362
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
363
+ ['gmail', 'labels', 'modify', 't1'],
364
+ { account: undefined },
365
+ );
366
+ });
367
+ });
368
+
369
+ describe('gog_gmail_drafts_list', () => {
370
+ it('calls runOrDiagnose with no flags', async () => {
371
+ await handlers.get('gog_gmail_drafts_list')!({});
372
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
373
+ ['gmail', 'drafts', 'list'],
374
+ { account: undefined },
375
+ );
376
+ });
377
+
378
+ it('passes pagination flags', async () => {
379
+ await handlers.get('gog_gmail_drafts_list')!({ max: 50, page: 'tok', all: true });
380
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
381
+ ['gmail', 'drafts', 'list', '--max=50', '--page=tok', '--all'],
382
+ { account: undefined },
383
+ );
384
+ });
385
+ });
386
+
387
+ describe('gog_gmail_drafts_get', () => {
388
+ it('calls runOrDiagnose with draftId', async () => {
389
+ await handlers.get('gog_gmail_drafts_get')!({ draftId: 'd1' });
390
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
391
+ ['gmail', 'drafts', 'get', 'd1'],
392
+ { account: undefined },
393
+ );
394
+ });
395
+
396
+ it('passes --download when true', async () => {
397
+ await handlers.get('gog_gmail_drafts_get')!({ draftId: 'd1', download: true });
398
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
399
+ ['gmail', 'drafts', 'get', 'd1', '--download'],
400
+ { account: undefined },
401
+ );
402
+ });
403
+ });
404
+
405
+ describe('gog_gmail_drafts_create', () => {
406
+ it('calls runOrDiagnose with minimal required flags', async () => {
407
+ await handlers.get('gog_gmail_drafts_create')!({
408
+ subject: 'Hi',
409
+ body: 'Hello',
410
+ });
411
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
412
+ ['gmail', 'drafts', 'create', '--subject=Hi', '--body=Hello'],
413
+ { account: undefined },
414
+ );
415
+ });
416
+
417
+ it('passes all flags including attachments', async () => {
418
+ await handlers.get('gog_gmail_drafts_create')!({
419
+ to: 'a@b.com,c@d.com',
420
+ cc: 'cc@x.com',
421
+ bcc: 'bcc@x.com',
422
+ subject: 'Hi',
423
+ body: 'Hello',
424
+ bodyHtml: '<p>Hi</p>',
425
+ replyToMessageId: 'm1',
426
+ replyTo: 'rt@x.com',
427
+ quote: true,
428
+ attach: ['/tmp/a.pdf', '/tmp/b.pdf'],
429
+ from: 'me@x.com',
430
+ });
431
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
432
+ [
433
+ 'gmail', 'drafts', 'create',
434
+ '--to=a@b.com,c@d.com',
435
+ '--cc=cc@x.com',
436
+ '--bcc=bcc@x.com',
437
+ '--subject=Hi',
438
+ '--body=Hello',
439
+ '--body-html=<p>Hi</p>',
440
+ '--reply-to-message-id=m1',
441
+ '--reply-to=rt@x.com',
442
+ '--quote',
443
+ '--attach=/tmp/a.pdf',
444
+ '--attach=/tmp/b.pdf',
445
+ '--from=me@x.com',
446
+ ],
447
+ { account: undefined },
448
+ );
449
+ });
450
+ });
451
+
452
+ describe('gog_gmail_drafts_update', () => {
453
+ it('calls runOrDiagnose with draftId and updated fields', async () => {
454
+ await handlers.get('gog_gmail_drafts_update')!({
455
+ draftId: 'd1',
456
+ subject: 'New subject',
457
+ body: 'New body',
458
+ });
459
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
460
+ ['gmail', 'drafts', 'update', 'd1', '--subject=New subject', '--body=New body'],
461
+ { account: undefined },
462
+ );
463
+ });
464
+
465
+ it('passes attachments as repeatable flags', async () => {
466
+ await handlers.get('gog_gmail_drafts_update')!({
467
+ draftId: 'd1',
468
+ subject: 'S',
469
+ body: 'B',
470
+ attach: ['/tmp/x.pdf'],
471
+ });
472
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
473
+ ['gmail', 'drafts', 'update', 'd1', '--subject=S', '--body=B', '--attach=/tmp/x.pdf'],
474
+ { account: undefined },
475
+ );
476
+ });
477
+ });
478
+
479
+ describe('gog_gmail_drafts_delete', () => {
480
+ it('calls runOrDiagnose with draftId', async () => {
481
+ await handlers.get('gog_gmail_drafts_delete')!({ draftId: 'd1' });
482
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
483
+ ['gmail', 'drafts', 'delete', 'd1'],
484
+ { account: undefined },
485
+ );
486
+ });
487
+ });
488
+
489
+ describe('gog_gmail_drafts_send', () => {
490
+ it('calls runOrDiagnose with draftId', async () => {
491
+ await handlers.get('gog_gmail_drafts_send')!({ draftId: 'd1' });
492
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
493
+ ['gmail', 'drafts', 'send', 'd1'],
494
+ { account: undefined },
495
+ );
496
+ });
497
+ });
498
+
499
+ describe('gog_gmail_forward', () => {
500
+ it('calls runOrDiagnose with messageId and required --to', async () => {
501
+ await handlers.get('gog_gmail_forward')!({ messageId: 'm1', to: 'a@b.com' });
502
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
503
+ ['gmail', 'forward', 'm1', '--to=a@b.com'],
504
+ { account: undefined },
505
+ );
506
+ });
507
+
508
+ it('passes all forward flags', async () => {
509
+ await handlers.get('gog_gmail_forward')!({
510
+ messageId: 'm1',
511
+ to: 'a@b.com',
512
+ cc: 'cc@x.com',
513
+ bcc: 'bcc@x.com',
514
+ note: 'FYI',
515
+ from: 'me@x.com',
516
+ skipAttachments: true,
517
+ });
518
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
519
+ [
520
+ 'gmail', 'forward', 'm1',
521
+ '--to=a@b.com',
522
+ '--cc=cc@x.com',
523
+ '--bcc=bcc@x.com',
524
+ '--note=FYI',
525
+ '--from=me@x.com',
526
+ '--skip-attachments',
527
+ ],
528
+ { account: undefined },
529
+ );
530
+ });
531
+
532
+ it('omits --skip-attachments when false', async () => {
533
+ await handlers.get('gog_gmail_forward')!({ messageId: 'm1', to: 'a@b.com', skipAttachments: false });
534
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
535
+ ['gmail', 'forward', 'm1', '--to=a@b.com'],
536
+ { account: undefined },
537
+ );
538
+ });
539
+ });
540
+
541
+ describe('gog_gmail_autoreply', () => {
542
+ it('calls runOrDiagnose with query and --body', async () => {
543
+ await handlers.get('gog_gmail_autoreply')!({ query: 'is:unread', body: 'Thanks' });
544
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
545
+ ['gmail', 'autoreply', 'is:unread', '--body=Thanks'],
546
+ { account: undefined },
547
+ );
548
+ });
549
+
550
+ it('passes all autoreply flags', async () => {
551
+ await handlers.get('gog_gmail_autoreply')!({
552
+ query: 'is:unread',
553
+ max: 50,
554
+ subject: 'Re: out of office',
555
+ body: 'I am out',
556
+ bodyHtml: '<p>OOO</p>',
557
+ from: 'me@x.com',
558
+ replyTo: 'rt@x.com',
559
+ label: 'OOO-Replied',
560
+ archive: true,
561
+ markRead: true,
562
+ skipBulk: true,
563
+ allowSelf: true,
564
+ });
565
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
566
+ [
567
+ 'gmail', 'autoreply', 'is:unread',
568
+ '--max=50',
569
+ '--subject=Re: out of office',
570
+ '--body=I am out',
571
+ '--body-html=<p>OOO</p>',
572
+ '--from=me@x.com',
573
+ '--reply-to=rt@x.com',
574
+ '--label=OOO-Replied',
575
+ '--archive',
576
+ '--mark-read',
577
+ '--skip-bulk',
578
+ '--allow-self',
579
+ ],
580
+ { account: undefined },
581
+ );
582
+ });
583
+
584
+ it('omits boolean flags when false', async () => {
585
+ await handlers.get('gog_gmail_autoreply')!({
586
+ query: 'is:unread',
587
+ body: 'Thanks',
588
+ archive: false,
589
+ markRead: false,
590
+ skipBulk: false,
591
+ allowSelf: false,
592
+ });
593
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
594
+ ['gmail', 'autoreply', 'is:unread', '--body=Thanks'],
595
+ { account: undefined },
596
+ );
597
+ });
598
+
599
+ it('supports HTML-only body (no plain --body)', async () => {
600
+ await handlers.get('gog_gmail_autoreply')!({ query: 'is:unread', bodyHtml: '<p>Hi</p>' });
601
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
602
+ ['gmail', 'autoreply', 'is:unread', '--body-html=<p>Hi</p>'],
603
+ { account: undefined },
604
+ );
605
+ });
606
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,9 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist",
5
+ "rootDir": "../"
6
+ },
7
+ "include": ["src/**/*", "../gogcli-mcp/src/**/*"],
8
+ "exclude": ["node_modules", "dist"]
9
+ }
@@ -0,0 +1,17 @@
1
+ import { defineConfig } from 'vitest/config';
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ coverage: {
6
+ provider: 'v8',
7
+ include: ['src/**/*.ts'],
8
+ exclude: ['src/index.ts'],
9
+ thresholds: {
10
+ lines: 100,
11
+ functions: 100,
12
+ branches: 100,
13
+ statements: 100,
14
+ },
15
+ },
16
+ },
17
+ });