gogcli-mcp-gmail 2.21.1 → 2.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -4
- package/SKILL.md +24 -3
- package/dist/index.js +200 -22
- package/manifest.json +6 -2
- package/package.json +1 -1
- package/src/tools/gmail-extra.ts +197 -27
- package/tests/tools/attachment-index-resolve.test.ts +76 -0
- package/tests/tools/gmail-extra.test.ts +350 -48
|
@@ -96,7 +96,7 @@ describe('gog_gmail_attachment', () => {
|
|
|
96
96
|
stubGog({ meta: PDF_LIST, download: { path: '/tmp/gog-attachments/m1/attachment', bytes: 99723, contentBase64: PDF_B64 } });
|
|
97
97
|
const res = await call({});
|
|
98
98
|
// download to the temp path first, then the metadata read to resolve the name.
|
|
99
|
-
expect(dlArgs()).toEqual(['gmail', 'attachment', 'm1', 'a1', '--inline', '--out=/tmp/gog-attachments/m1/attachment', '--name=attachment']);
|
|
99
|
+
expect(dlArgs()).toEqual(['gmail', 'attachment', 'm1', 'a1', '--use-indexed-attachment-ids=false', '--inline', '--inline-max-bytes=3145728', '--out=/tmp/gog-attachments/m1/attachment', '--name=attachment']);
|
|
100
100
|
expect(gotGet()).toBe(true);
|
|
101
101
|
const payload = JSON.parse(textOf(res));
|
|
102
102
|
expect(payload).toMatchObject({
|
|
@@ -135,14 +135,14 @@ describe('gog_gmail_attachment', () => {
|
|
|
135
135
|
stubGog({ download: { path: '/tmp/gog-attachments/m1/report.pdf', bytes: 12, contentBase64: PDF_B64 } });
|
|
136
136
|
await call({ name: 'report.pdf' });
|
|
137
137
|
expect(gotGet()).toBe(false);
|
|
138
|
-
expect(dlArgs()).toEqual(['gmail', 'attachment', 'm1', 'a1', '--inline', '--out=/tmp/gog-attachments/m1/report.pdf', '--name=report.pdf']);
|
|
138
|
+
expect(dlArgs()).toEqual(['gmail', 'attachment', 'm1', 'a1', '--use-indexed-attachment-ids=false', '--inline', '--inline-max-bytes=3145728', '--out=/tmp/gog-attachments/m1/report.pdf', '--name=report.pdf']);
|
|
139
139
|
});
|
|
140
140
|
|
|
141
141
|
it('a named non-image on the connector skips --inline (headed straight to Drive)', async () => {
|
|
142
142
|
stubGog({ download: { path: '/tmp/gog-attachments/m1/report.pdf', bytes: 12 }, drive: { file: { id: 'F9' } } });
|
|
143
143
|
await asConnector(() => call({ name: 'report.pdf' }));
|
|
144
144
|
expect(gotGet()).toBe(false);
|
|
145
|
-
expect(dlArgs()).toEqual(['gmail', 'attachment', 'm1', 'a1', '--out=/tmp/gog-attachments/m1/report.pdf', '--name=report.pdf']);
|
|
145
|
+
expect(dlArgs()).toEqual(['gmail', 'attachment', 'm1', 'a1', '--use-indexed-attachment-ids=false', '--inline-max-bytes=3145728', '--out=/tmp/gog-attachments/m1/report.pdf', '--name=report.pdf']);
|
|
146
146
|
});
|
|
147
147
|
|
|
148
148
|
it('resolves the real filename by size and sanitizes path separators (no traversal)', async () => {
|
|
@@ -235,7 +235,7 @@ describe('gog_gmail_attachment', () => {
|
|
|
235
235
|
it('deliver=drive skips --inline and uploads, honoring driveFolder and name', async () => {
|
|
236
236
|
stubGog({ download: { path: '/tmp/gog-attachments/m1/renamed.png', bytes: 24 }, drive: { file: { id: 'F2', webViewLink: 'https://drive.google.com/file/d/F2/view' } } });
|
|
237
237
|
const res = await call({ deliver: 'drive', driveFolder: 'DIR9', name: 'renamed.png', account: 'me@x.com' });
|
|
238
|
-
expect(dlArgs()).toEqual(['gmail', 'attachment', 'm1', 'a1', '--out=/tmp/gog-attachments/m1/renamed.png', '--name=renamed.png']);
|
|
238
|
+
expect(dlArgs()).toEqual(['gmail', 'attachment', 'm1', 'a1', '--use-indexed-attachment-ids=false', '--inline-max-bytes=3145728', '--out=/tmp/gog-attachments/m1/renamed.png', '--name=renamed.png']);
|
|
239
239
|
expect(lib.run).toHaveBeenCalledWith(
|
|
240
240
|
['drive', 'upload', '/tmp/gog-attachments/m1/renamed.png', '--json', '--parent=DIR9', '--name=renamed.png'], { account: 'me@x.com' });
|
|
241
241
|
expect(JSON.parse(textOf(res))).toMatchObject({ deliveredVia: 'drive', id: 'F2' });
|
|
@@ -268,14 +268,14 @@ describe('gog_gmail_attachment', () => {
|
|
|
268
268
|
it('honors a caller out on stdio', async () => {
|
|
269
269
|
stubGog({ download: { path: '/home/me/x.png', bytes: 24, contentBase64: PNG_B64 } });
|
|
270
270
|
await call({ out: '/home/me/x.png', name: 'x.png' });
|
|
271
|
-
expect(dlArgs()).toEqual(['gmail', 'attachment', 'm1', 'a1', '--inline', '--out=/home/me/x.png', '--name=x.png']);
|
|
271
|
+
expect(dlArgs()).toEqual(['gmail', 'attachment', 'm1', 'a1', '--use-indexed-attachment-ids=false', '--inline', '--inline-max-bytes=3145728', '--out=/home/me/x.png', '--name=x.png']);
|
|
272
272
|
});
|
|
273
273
|
|
|
274
274
|
it('ignores a caller out on the connector and notes it', async () => {
|
|
275
275
|
stubGog({ download: { path: '/tmp/gog-attachments/m1/report.pdf', bytes: 12 }, drive: { file: { id: 'F3' } } });
|
|
276
276
|
const res = await asConnector(() => call({ out: '/home/claude/report.pdf', name: 'report.pdf' }));
|
|
277
277
|
// download used the temp path, NOT the caller's /home/claude path.
|
|
278
|
-
expect(dlArgs()).toEqual(['gmail', 'attachment', 'm1', 'a1', '--out=/tmp/gog-attachments/m1/report.pdf', '--name=report.pdf']);
|
|
278
|
+
expect(dlArgs()).toEqual(['gmail', 'attachment', 'm1', 'a1', '--use-indexed-attachment-ids=false', '--inline-max-bytes=3145728', '--out=/tmp/gog-attachments/m1/report.pdf', '--name=report.pdf']);
|
|
279
279
|
expect(textOf(res)).toContain('`out` was ignored');
|
|
280
280
|
});
|
|
281
281
|
|
|
@@ -473,7 +473,7 @@ describe('gog_gmail_thread_get', () => {
|
|
|
473
473
|
it('calls runOrDiagnose with threadId', async () => {
|
|
474
474
|
await harness.callTool('gog_gmail_thread_get', { threadId: 't1' });
|
|
475
475
|
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
476
|
-
['gmail', 'thread', 'get', 't1'],
|
|
476
|
+
['gmail', 'thread', 'get', 't1', '--use-indexed-attachment-ids=false'],
|
|
477
477
|
{ account: undefined },
|
|
478
478
|
);
|
|
479
479
|
});
|
|
@@ -487,7 +487,7 @@ describe('gog_gmail_thread_get', () => {
|
|
|
487
487
|
outDir: '/tmp/atts',
|
|
488
488
|
});
|
|
489
489
|
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
490
|
-
['gmail', 'thread', 'get', 't1', '--download', '--full', '--sanitize-content', '--out-dir=/tmp/atts'],
|
|
490
|
+
['gmail', 'thread', 'get', 't1', '--download', '--full', '--sanitize-content', '--out-dir=/tmp/atts', '--use-indexed-attachment-ids=false'],
|
|
491
491
|
{ account: undefined },
|
|
492
492
|
);
|
|
493
493
|
});
|
|
@@ -500,7 +500,7 @@ describe('gog_gmail_thread_get', () => {
|
|
|
500
500
|
sanitizeContent: false,
|
|
501
501
|
});
|
|
502
502
|
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
503
|
-
['gmail', 'thread', 'get', 't1'],
|
|
503
|
+
['gmail', 'thread', 'get', 't1', '--use-indexed-attachment-ids=false'],
|
|
504
504
|
{ account: undefined },
|
|
505
505
|
);
|
|
506
506
|
});
|
|
@@ -527,7 +527,7 @@ describe('gog_gmail_thread_get', () => {
|
|
|
527
527
|
vi.mocked(lib.runOrDiagnose).mockResolvedValueOnce(rawTextResult(THREAD));
|
|
528
528
|
const result = await harness.callTool('gog_gmail_thread_get', { threadId: 't1', latestN: 2 });
|
|
529
529
|
// latestN is wrapper-side; no CLI flag is added
|
|
530
|
-
expect(vi.mocked(lib.runOrDiagnose).mock.calls[0]![0]).toEqual(['gmail', 'thread', 'get', 't1']);
|
|
530
|
+
expect(vi.mocked(lib.runOrDiagnose).mock.calls[0]![0]).toEqual(['gmail', 'thread', 'get', 't1', '--use-indexed-attachment-ids=false']);
|
|
531
531
|
const parsed = JSON.parse(result.content[0].text);
|
|
532
532
|
expect(parsed.thread.messages.map((m: { id: string }) => m.id)).toEqual(['m2', 'm3']);
|
|
533
533
|
});
|
|
@@ -598,7 +598,7 @@ describe('gog_gmail_thread_attachments', () => {
|
|
|
598
598
|
it('calls runOrDiagnose with threadId', async () => {
|
|
599
599
|
await harness.callTool('gog_gmail_thread_attachments', { threadId: 't1' });
|
|
600
600
|
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
601
|
-
['gmail', 'thread', 'attachments', 't1'],
|
|
601
|
+
['gmail', 'thread', 'attachments', 't1', '--use-indexed-attachment-ids=false'],
|
|
602
602
|
{ account: undefined },
|
|
603
603
|
);
|
|
604
604
|
});
|
|
@@ -610,7 +610,7 @@ describe('gog_gmail_thread_attachments', () => {
|
|
|
610
610
|
outDir: '/tmp/atts',
|
|
611
611
|
});
|
|
612
612
|
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
613
|
-
['gmail', 'thread', 'attachments', 't1', '--download', '--out-dir=/tmp/atts'],
|
|
613
|
+
['gmail', 'thread', 'attachments', 't1', '--download', '--out-dir=/tmp/atts', '--use-indexed-attachment-ids=false'],
|
|
614
614
|
{ account: undefined },
|
|
615
615
|
);
|
|
616
616
|
});
|
|
@@ -718,7 +718,7 @@ describe('gog_gmail_drafts_get', () => {
|
|
|
718
718
|
it('calls runOrDiagnose with draftId', async () => {
|
|
719
719
|
await harness.callTool('gog_gmail_drafts_get', { draftId: 'd1' });
|
|
720
720
|
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
721
|
-
['gmail', 'drafts', 'get', 'd1'],
|
|
721
|
+
['gmail', 'drafts', 'get', 'd1', '--use-indexed-attachment-ids=false'],
|
|
722
722
|
{ account: undefined },
|
|
723
723
|
);
|
|
724
724
|
});
|
|
@@ -726,7 +726,7 @@ describe('gog_gmail_drafts_get', () => {
|
|
|
726
726
|
it('passes --download when true', async () => {
|
|
727
727
|
await harness.callTool('gog_gmail_drafts_get', { draftId: 'd1', download: true });
|
|
728
728
|
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
729
|
-
['gmail', 'drafts', 'get', 'd1', '--download'],
|
|
729
|
+
['gmail', 'drafts', 'get', 'd1', '--download', '--use-indexed-attachment-ids=false'],
|
|
730
730
|
{ account: undefined },
|
|
731
731
|
);
|
|
732
732
|
});
|
|
@@ -739,7 +739,7 @@ describe('gog_gmail_drafts_create', () => {
|
|
|
739
739
|
body: 'Hello',
|
|
740
740
|
});
|
|
741
741
|
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
742
|
-
['gmail', 'drafts', 'create', '--subject=Hi', '--body=Hello'],
|
|
742
|
+
['gmail', 'drafts', 'create', '--subject=Hi', '--body=Hello', '--auto-from-addressed-alias=false'],
|
|
743
743
|
{ account: undefined },
|
|
744
744
|
);
|
|
745
745
|
});
|
|
@@ -772,7 +772,7 @@ describe('gog_gmail_drafts_create', () => {
|
|
|
772
772
|
'--quote',
|
|
773
773
|
'--attach=/tmp/a.pdf',
|
|
774
774
|
'--attach=/tmp/b.pdf',
|
|
775
|
-
'--from=me@x.com',
|
|
775
|
+
'--from=me@x.com', '--auto-from-addressed-alias=false'
|
|
776
776
|
],
|
|
777
777
|
{ account: undefined },
|
|
778
778
|
);
|
|
@@ -785,7 +785,7 @@ describe('gog_gmail_drafts_create', () => {
|
|
|
785
785
|
bodyHtmlFile: '/tmp/body.html',
|
|
786
786
|
});
|
|
787
787
|
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
788
|
-
['gmail', 'drafts', 'create', '--subject=Hi', '--body=Hello', '--body-html-file=/tmp/body.html'],
|
|
788
|
+
['gmail', 'drafts', 'create', '--subject=Hi', '--body=Hello', '--body-html-file=/tmp/body.html', '--auto-from-addressed-alias=false'],
|
|
789
789
|
{ account: undefined },
|
|
790
790
|
);
|
|
791
791
|
});
|
|
@@ -798,7 +798,7 @@ describe('gog_gmail_drafts_create', () => {
|
|
|
798
798
|
replyAll: true,
|
|
799
799
|
});
|
|
800
800
|
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
801
|
-
['gmail', 'drafts', 'create', '--subject=Re: Hi', '--body=Hello all', '--thread-id=t1', '--reply-all'],
|
|
801
|
+
['gmail', 'drafts', 'create', '--subject=Re: Hi', '--body=Hello all', '--thread-id=t1', '--reply-all', '--auto-from-addressed-alias=false'],
|
|
802
802
|
{ account: undefined },
|
|
803
803
|
);
|
|
804
804
|
});
|
|
@@ -809,7 +809,7 @@ describe('gog_gmail_drafts_create', () => {
|
|
|
809
809
|
subject: 'Hi', body: 'Hello', omitRecipients: true,
|
|
810
810
|
});
|
|
811
811
|
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
812
|
-
['gmail', 'drafts', 'create', '--subject=Hi', '--body=Hello'],
|
|
812
|
+
['gmail', 'drafts', 'create', '--subject=Hi', '--body=Hello', '--auto-from-addressed-alias=false'],
|
|
813
813
|
{ account: undefined },
|
|
814
814
|
);
|
|
815
815
|
});
|
|
@@ -822,9 +822,9 @@ describe('gog_gmail_drafts_create', () => {
|
|
|
822
822
|
subject: 'Hi', body: 'Hello', returnFull: true,
|
|
823
823
|
});
|
|
824
824
|
expect(lib.runOrDiagnose).toHaveBeenNthCalledWith(1,
|
|
825
|
-
['gmail', 'drafts', 'create', '--subject=Hi', '--body=Hello'], { account: undefined });
|
|
825
|
+
['gmail', 'drafts', 'create', '--subject=Hi', '--body=Hello', '--auto-from-addressed-alias=false'], { account: undefined });
|
|
826
826
|
expect(lib.runOrDiagnose).toHaveBeenNthCalledWith(2,
|
|
827
|
-
['gmail', 'drafts', 'get', 'd9'], { account: undefined });
|
|
827
|
+
['gmail', 'drafts', 'get', 'd9', '--use-indexed-attachment-ids=false'], { account: undefined });
|
|
828
828
|
expect(result.content[0].text).toContain('"subject":"Hi"');
|
|
829
829
|
});
|
|
830
830
|
|
|
@@ -859,7 +859,7 @@ describe('gmail draft reply threading (native --thread-id)', () => {
|
|
|
859
859
|
// gog resolves the thread's latest-message headers itself — no extra fetch.
|
|
860
860
|
expect(lib.runOrDiagnose).toHaveBeenCalledTimes(1);
|
|
861
861
|
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
862
|
-
['gmail', 'drafts', 'create', '--subject=Re: roof', '--body=Sounds good', '--thread-id=19dffe06f9668b28'],
|
|
862
|
+
['gmail', 'drafts', 'create', '--subject=Re: roof', '--body=Sounds good', '--thread-id=19dffe06f9668b28', '--auto-from-addressed-alias=false'],
|
|
863
863
|
{ account: 'me@x.com' },
|
|
864
864
|
);
|
|
865
865
|
});
|
|
@@ -869,7 +869,7 @@ describe('gmail draft reply threading (native --thread-id)', () => {
|
|
|
869
869
|
draftId: 'd1', subject: 'S', body: 'B', replyToThreadId: 't1',
|
|
870
870
|
});
|
|
871
871
|
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
872
|
-
['gmail', 'drafts', 'update', 'd1', '--subject=S', '--body=B', '--thread-id=t1'],
|
|
872
|
+
['gmail', 'drafts', 'update', 'd1', '--subject=S', '--body=B', '--thread-id=t1', '--auto-from-addressed-alias=false'],
|
|
873
873
|
{ account: undefined },
|
|
874
874
|
);
|
|
875
875
|
});
|
|
@@ -880,7 +880,7 @@ describe('gmail draft reply threading (native --thread-id)', () => {
|
|
|
880
880
|
});
|
|
881
881
|
expect(lib.runOrDiagnose).toHaveBeenCalledTimes(1);
|
|
882
882
|
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
883
|
-
['gmail', 'drafts', 'create', '--subject=S', '--body=B', '--reply-to-message-id=mExplicit'],
|
|
883
|
+
['gmail', 'drafts', 'create', '--subject=S', '--body=B', '--reply-to-message-id=mExplicit', '--auto-from-addressed-alias=false'],
|
|
884
884
|
{ account: undefined },
|
|
885
885
|
);
|
|
886
886
|
});
|
|
@@ -894,7 +894,7 @@ describe('gog_gmail_drafts_update', () => {
|
|
|
894
894
|
body: 'New body',
|
|
895
895
|
});
|
|
896
896
|
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
897
|
-
['gmail', 'drafts', 'update', 'd1', '--subject=New subject', '--body=New body'],
|
|
897
|
+
['gmail', 'drafts', 'update', 'd1', '--subject=New subject', '--body=New body', '--auto-from-addressed-alias=false'],
|
|
898
898
|
{ account: undefined },
|
|
899
899
|
);
|
|
900
900
|
});
|
|
@@ -907,7 +907,7 @@ describe('gog_gmail_drafts_update', () => {
|
|
|
907
907
|
attach: ['/tmp/x.pdf'],
|
|
908
908
|
});
|
|
909
909
|
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
910
|
-
['gmail', 'drafts', 'update', 'd1', '--subject=S', '--body=B', '--attach=/tmp/x.pdf'],
|
|
910
|
+
['gmail', 'drafts', 'update', 'd1', '--subject=S', '--body=B', '--attach=/tmp/x.pdf', '--auto-from-addressed-alias=false'],
|
|
911
911
|
{ account: undefined },
|
|
912
912
|
);
|
|
913
913
|
});
|
|
@@ -917,7 +917,7 @@ describe('gog_gmail_drafts_update', () => {
|
|
|
917
917
|
draftId: 'd1', to: 'a@b.com', subject: 'S', body: 'B', omitRecipients: true,
|
|
918
918
|
});
|
|
919
919
|
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
920
|
-
['gmail', 'drafts', 'update', 'd1', '--subject=S', '--body=B'],
|
|
920
|
+
['gmail', 'drafts', 'update', 'd1', '--subject=S', '--body=B', '--auto-from-addressed-alias=false'],
|
|
921
921
|
{ account: undefined },
|
|
922
922
|
);
|
|
923
923
|
});
|
|
@@ -927,7 +927,7 @@ describe('gog_gmail_drafts_update', () => {
|
|
|
927
927
|
draftId: 'd1', subject: 'S', body: 'B', clearAttachments: true,
|
|
928
928
|
});
|
|
929
929
|
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
930
|
-
['gmail', 'drafts', 'update', 'd1', '--subject=S', '--body=B', '--clear-attachments'],
|
|
930
|
+
['gmail', 'drafts', 'update', 'd1', '--subject=S', '--body=B', '--auto-from-addressed-alias=false', '--clear-attachments'],
|
|
931
931
|
{ account: undefined },
|
|
932
932
|
);
|
|
933
933
|
});
|
|
@@ -937,7 +937,7 @@ describe('gog_gmail_drafts_update', () => {
|
|
|
937
937
|
draftId: 'd1', subject: 'S', body: 'B', clearReplyContext: true,
|
|
938
938
|
});
|
|
939
939
|
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
940
|
-
['gmail', 'drafts', 'update', 'd1', '--subject=S', '--body=B', '--clear-reply-context'],
|
|
940
|
+
['gmail', 'drafts', 'update', 'd1', '--subject=S', '--body=B', '--auto-from-addressed-alias=false', '--clear-reply-context'],
|
|
941
941
|
{ account: undefined },
|
|
942
942
|
);
|
|
943
943
|
});
|
|
@@ -961,7 +961,7 @@ describe('gog_gmail_drafts_update', () => {
|
|
|
961
961
|
});
|
|
962
962
|
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
963
963
|
['gmail', 'drafts', 'update', 'd1', '--subject=S', '--body=B',
|
|
964
|
-
'--clear-attachments', '--clear-reply-context'],
|
|
964
|
+
'--auto-from-addressed-alias=false', '--clear-attachments', '--clear-reply-context'],
|
|
965
965
|
{ account: undefined },
|
|
966
966
|
);
|
|
967
967
|
});
|
|
@@ -974,7 +974,7 @@ describe('gog_gmail_drafts_update', () => {
|
|
|
974
974
|
draftId: 'd1', subject: 'S', body: 'B', returnFull: true,
|
|
975
975
|
});
|
|
976
976
|
expect(lib.runOrDiagnose).toHaveBeenNthCalledWith(2,
|
|
977
|
-
['gmail', 'drafts', 'get', 'd1'], { account: undefined });
|
|
977
|
+
['gmail', 'drafts', 'get', 'd1', '--use-indexed-attachment-ids=false'], { account: undefined });
|
|
978
978
|
expect(result.content[0].text).toContain('"subject":"S"');
|
|
979
979
|
});
|
|
980
980
|
|
|
@@ -1071,7 +1071,7 @@ describe('gog_gmail_reply', () => {
|
|
|
1071
1071
|
it('calls runOrDiagnose with messageId and --body', async () => {
|
|
1072
1072
|
await harness.callTool('gog_gmail_reply', { messageId: 'm1', body: 'Thanks' });
|
|
1073
1073
|
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
1074
|
-
['gmail', 'reply', 'm1', '--body=Thanks'],
|
|
1074
|
+
['gmail', 'reply', 'm1', '--body=Thanks', '--auto-from-addressed-alias=false'],
|
|
1075
1075
|
{ account: undefined },
|
|
1076
1076
|
);
|
|
1077
1077
|
});
|
|
@@ -1111,7 +1111,7 @@ describe('gog_gmail_reply', () => {
|
|
|
1111
1111
|
'--from=me@x.com',
|
|
1112
1112
|
'--signature',
|
|
1113
1113
|
'--signature-from=alias@x.com',
|
|
1114
|
-
'--signature-file=/tmp/sig.txt',
|
|
1114
|
+
'--signature-file=/tmp/sig.txt', '--auto-from-addressed-alias=false'
|
|
1115
1115
|
],
|
|
1116
1116
|
{ account: 'me@gmail.com' },
|
|
1117
1117
|
);
|
|
@@ -1120,7 +1120,7 @@ describe('gog_gmail_reply', () => {
|
|
|
1120
1120
|
it('omits --no-quote and --signature when false', async () => {
|
|
1121
1121
|
await harness.callTool('gog_gmail_reply', { messageId: 'm1', body: 'Hi', noQuote: false, signature: false });
|
|
1122
1122
|
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
1123
|
-
['gmail', 'reply', 'm1', '--body=Hi'],
|
|
1123
|
+
['gmail', 'reply', 'm1', '--body=Hi', '--auto-from-addressed-alias=false'],
|
|
1124
1124
|
{ account: undefined },
|
|
1125
1125
|
);
|
|
1126
1126
|
});
|
|
@@ -1130,7 +1130,7 @@ describe('gog_gmail_reply_all', () => {
|
|
|
1130
1130
|
it('uses the reply-all subcommand', async () => {
|
|
1131
1131
|
await harness.callTool('gog_gmail_reply_all', { messageId: 'm1', body: 'Thanks all' });
|
|
1132
1132
|
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
1133
|
-
['gmail', 'reply-all', 'm1', '--body=Thanks all'],
|
|
1133
|
+
['gmail', 'reply-all', 'm1', '--body=Thanks all', '--auto-from-addressed-alias=false'],
|
|
1134
1134
|
{ account: undefined },
|
|
1135
1135
|
);
|
|
1136
1136
|
});
|
|
@@ -1150,7 +1150,7 @@ describe('gog_gmail_reply_all', () => {
|
|
|
1150
1150
|
'--cc=x@y.com',
|
|
1151
1151
|
'--cc=z@y.com',
|
|
1152
1152
|
'--remove=drop@y.com',
|
|
1153
|
-
'--signature-file=/tmp/sig.html',
|
|
1153
|
+
'--signature-file=/tmp/sig.html', '--auto-from-addressed-alias=false'
|
|
1154
1154
|
],
|
|
1155
1155
|
{ account: undefined },
|
|
1156
1156
|
);
|
|
@@ -1228,7 +1228,7 @@ describe('gog_gmail_messages_search', () => {
|
|
|
1228
1228
|
it('calls runOrDiagnose with just the query', async () => {
|
|
1229
1229
|
await harness.callTool('gog_gmail_messages_search', { query: 'from:alice' });
|
|
1230
1230
|
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
1231
|
-
['gmail', 'messages', 'search', 'from:alice'],
|
|
1231
|
+
['gmail', 'messages', 'search', 'from:alice', '--include-attachments=false', '--use-indexed-attachment-ids=false'],
|
|
1232
1232
|
{ account: undefined },
|
|
1233
1233
|
);
|
|
1234
1234
|
});
|
|
@@ -1245,7 +1245,7 @@ describe('gog_gmail_messages_search', () => {
|
|
|
1245
1245
|
account: 'me@x.com',
|
|
1246
1246
|
});
|
|
1247
1247
|
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
1248
|
-
['gmail', 'messages', 'search', 'is:unread', '--max=10', '--page=tok', '--all', '--include-body', '--full', '--body-format=html'],
|
|
1248
|
+
['gmail', 'messages', 'search', 'is:unread', '--max=10', '--page=tok', '--all', '--include-body', '--full', '--body-format=html', '--include-attachments=false', '--use-indexed-attachment-ids=false'],
|
|
1249
1249
|
{ account: 'me@x.com' },
|
|
1250
1250
|
);
|
|
1251
1251
|
});
|
|
@@ -1253,7 +1253,7 @@ describe('gog_gmail_messages_search', () => {
|
|
|
1253
1253
|
it('omits flags when false/absent', async () => {
|
|
1254
1254
|
await harness.callTool('gog_gmail_messages_search', { query: 'x', all: false, includeBody: false, full: false });
|
|
1255
1255
|
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
1256
|
-
['gmail', 'messages', 'search', 'x'],
|
|
1256
|
+
['gmail', 'messages', 'search', 'x', '--include-attachments=false', '--use-indexed-attachment-ids=false'],
|
|
1257
1257
|
{ account: undefined },
|
|
1258
1258
|
);
|
|
1259
1259
|
});
|
|
@@ -1550,7 +1550,7 @@ describe('large payloads route to file args', () => {
|
|
|
1550
1550
|
it('keeps a body at exactly the threshold inline', async () => {
|
|
1551
1551
|
const atLimit = 'x'.repeat(lib.PAYLOAD_INLINE_MAX);
|
|
1552
1552
|
await harness.callTool('gog_gmail_drafts_create', { subject: 'S', body: atLimit });
|
|
1553
|
-
expect(args()).toEqual(['gmail', 'drafts', 'create', '--subject=S', `--body=${atLimit}
|
|
1553
|
+
expect(args()).toEqual(['gmail', 'drafts', 'create', '--subject=S', `--body=${atLimit}`, '--auto-from-addressed-alias=false']);
|
|
1554
1554
|
});
|
|
1555
1555
|
|
|
1556
1556
|
it('measures bytes, not characters, so a multibyte body crosses earlier', async () => {
|
|
@@ -1568,7 +1568,7 @@ describe('large payloads route to file args', () => {
|
|
|
1568
1568
|
expect(args()).toEqual([
|
|
1569
1569
|
'gmail', 'drafts', 'create',
|
|
1570
1570
|
'--subject=S',
|
|
1571
|
-
{ kind: 'file', flag: 'body-file', contents: big, ext: undefined },
|
|
1571
|
+
{ kind: 'file', flag: 'body-file', contents: big, ext: undefined }, '--auto-from-addressed-alias=false'
|
|
1572
1572
|
]);
|
|
1573
1573
|
});
|
|
1574
1574
|
|
|
@@ -1579,7 +1579,7 @@ describe('large payloads route to file args', () => {
|
|
|
1579
1579
|
'gmail', 'drafts', 'create',
|
|
1580
1580
|
'--subject=S',
|
|
1581
1581
|
'--body=plain',
|
|
1582
|
-
{ kind: 'file', flag: 'body-html-file', contents: bigHtml, ext: 'html' },
|
|
1582
|
+
{ kind: 'file', flag: 'body-html-file', contents: bigHtml, ext: 'html' }, '--auto-from-addressed-alias=false'
|
|
1583
1583
|
]);
|
|
1584
1584
|
});
|
|
1585
1585
|
|
|
@@ -1588,7 +1588,7 @@ describe('large payloads route to file args', () => {
|
|
|
1588
1588
|
expect(args()).toEqual([
|
|
1589
1589
|
'gmail', 'drafts', 'update', 'd1',
|
|
1590
1590
|
'--subject=S',
|
|
1591
|
-
{ kind: 'file', flag: 'body-file', contents: big, ext: undefined },
|
|
1591
|
+
{ kind: 'file', flag: 'body-file', contents: big, ext: undefined }, '--auto-from-addressed-alias=false'
|
|
1592
1592
|
]);
|
|
1593
1593
|
});
|
|
1594
1594
|
|
|
@@ -1599,7 +1599,7 @@ describe('large payloads route to file args', () => {
|
|
|
1599
1599
|
expect(args()).toEqual([
|
|
1600
1600
|
'gmail', 'drafts', 'create',
|
|
1601
1601
|
'--subject=S',
|
|
1602
|
-
{ kind: 'file', flag: 'body-file', contents: big, ext: undefined },
|
|
1602
|
+
{ kind: 'file', flag: 'body-file', contents: big, ext: undefined }, '--auto-from-addressed-alias=false'
|
|
1603
1603
|
]);
|
|
1604
1604
|
});
|
|
1605
1605
|
|
|
@@ -1612,7 +1612,7 @@ describe('large payloads route to file args', () => {
|
|
|
1612
1612
|
'--subject=S',
|
|
1613
1613
|
{ kind: 'file', flag: 'body-file', contents: big, ext: undefined },
|
|
1614
1614
|
'--thread-id=t1',
|
|
1615
|
-
'--attach=/tmp/a.pdf',
|
|
1615
|
+
'--attach=/tmp/a.pdf', '--auto-from-addressed-alias=false'
|
|
1616
1616
|
]);
|
|
1617
1617
|
});
|
|
1618
1618
|
|
|
@@ -1629,7 +1629,7 @@ describe('large payloads route to file args', () => {
|
|
|
1629
1629
|
expect(args()).toEqual([
|
|
1630
1630
|
'gmail', 'reply', 'm1',
|
|
1631
1631
|
{ kind: 'file', flag: 'body-file', contents: big, ext: undefined },
|
|
1632
|
-
{ kind: 'file', flag: 'body-html-file', contents: bigHtml, ext: 'html' },
|
|
1632
|
+
{ kind: 'file', flag: 'body-html-file', contents: bigHtml, ext: 'html' }, '--auto-from-addressed-alias=false'
|
|
1633
1633
|
]);
|
|
1634
1634
|
});
|
|
1635
1635
|
|
|
@@ -1638,7 +1638,7 @@ describe('large payloads route to file args', () => {
|
|
|
1638
1638
|
expect(args()).toEqual([
|
|
1639
1639
|
'gmail', 'reply-all', 'm1',
|
|
1640
1640
|
{ kind: 'file', flag: 'body-file', contents: big, ext: undefined },
|
|
1641
|
-
'--signature',
|
|
1641
|
+
'--signature', '--auto-from-addressed-alias=false'
|
|
1642
1642
|
]);
|
|
1643
1643
|
});
|
|
1644
1644
|
|
|
@@ -1710,8 +1710,310 @@ describe('inline/file param conflicts are rejected before gog runs', () => {
|
|
|
1710
1710
|
it('bodyHtmlFile alone still passes through as --body-html-file', async () => {
|
|
1711
1711
|
await harness.callTool('gog_gmail_reply', { messageId: 'm1', body: 'Hi', bodyHtmlFile: '/tmp/b.html' });
|
|
1712
1712
|
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
1713
|
-
['gmail', 'reply', 'm1', '--body=Hi', '--body-html-file=/tmp/b.html'],
|
|
1713
|
+
['gmail', 'reply', 'm1', '--body=Hi', '--body-html-file=/tmp/b.html', '--auto-from-addressed-alias=false'],
|
|
1714
1714
|
{ account: undefined },
|
|
1715
1715
|
);
|
|
1716
1716
|
});
|
|
1717
1717
|
});
|
|
1718
|
+
|
|
1719
|
+
// ---------------------------------------------------------------------------
|
|
1720
|
+
// gog 0.35.0 capabilities (MIN_GOG_VERSION floor).
|
|
1721
|
+
//
|
|
1722
|
+
// Every flag below is env-bound in gog, so an ambient GOG_GMAIL_* var on the
|
|
1723
|
+
// host silently changes the SHAPE of the output (or, for the attachment
|
|
1724
|
+
// download, makes gog reject the caller's own argument). runner.ts strips only
|
|
1725
|
+
// *_TOKEN/*_SECRET/*_API_KEY/*_PRIVATE_KEY, and on the remote runner the child
|
|
1726
|
+
// env belongs to a backend this wrapper does not control — so the tests below
|
|
1727
|
+
// assert the flag is PINNED on every call, not merely pushed when true.
|
|
1728
|
+
// ---------------------------------------------------------------------------
|
|
1729
|
+
|
|
1730
|
+
describe('gog 0.35.0 — indexed attachment ids (gog_gmail_attachment)', () => {
|
|
1731
|
+
const PDF_B64 = 'JVBERi0xLjUKJVBFRgo=';
|
|
1732
|
+
const INDEXED_LIST = {
|
|
1733
|
+
attachments: [
|
|
1734
|
+
{ filename: 'cover.png', mimeType: 'image/png', attachmentIndex: 0, size: 11 },
|
|
1735
|
+
{ filename: 'Guest_Copy.pdf', mimeType: 'application/pdf', attachmentIndex: 1, size: 99723 },
|
|
1736
|
+
],
|
|
1737
|
+
};
|
|
1738
|
+
|
|
1739
|
+
function stub(opts: { meta?: unknown; metaError?: Error; download?: unknown; downloadError?: unknown }): void {
|
|
1740
|
+
vi.mocked(lib.run).mockImplementation(async (args) => {
|
|
1741
|
+
const a = args as string[];
|
|
1742
|
+
if (a[0] === 'gmail' && a[1] === 'get') {
|
|
1743
|
+
if (opts.metaError) throw opts.metaError;
|
|
1744
|
+
return JSON.stringify(opts.meta ?? { attachments: [] });
|
|
1745
|
+
}
|
|
1746
|
+
if (a[0] === 'gmail' && a[1] === 'attachment') {
|
|
1747
|
+
if (opts.downloadError) throw opts.downloadError;
|
|
1748
|
+
return JSON.stringify(opts.download ?? {});
|
|
1749
|
+
}
|
|
1750
|
+
return '{}';
|
|
1751
|
+
});
|
|
1752
|
+
}
|
|
1753
|
+
const runArgs = () => vi.mocked(lib.run).mock.calls.map((c) => c[0] as string[]);
|
|
1754
|
+
const dlArgs = () => runArgs().find((a) => a[1] === 'attachment')!;
|
|
1755
|
+
const textOf = (res: { content: unknown[] }) => (res.content[0] as { text: string }).text;
|
|
1756
|
+
|
|
1757
|
+
it('resolves the real filename BEFORE the download and never runs the size heuristic', async () => {
|
|
1758
|
+
stub({ meta: INDEXED_LIST, download: { path: '/tmp/gog-attachments/m1/Guest_Copy.pdf', bytes: 99723, contentBase64: PDF_B64 } });
|
|
1759
|
+
const res = await harness.callTool('gog_gmail_attachment', { messageId: 'm1', attachmentIndex: 1 });
|
|
1760
|
+
// exactly one metadata read, and it happens BEFORE the download.
|
|
1761
|
+
expect(runArgs().map((a) => a.slice(0, 2))).toEqual([['gmail', 'get'], ['gmail', 'attachment']]);
|
|
1762
|
+
expect(runArgs()[0]).toEqual(['gmail', 'get', 'm1', '--use-indexed-attachment-ids']);
|
|
1763
|
+
// the index rides in the positional slot, and gog is told to read it as one.
|
|
1764
|
+
expect(dlArgs()).toEqual([
|
|
1765
|
+
'gmail', 'attachment', 'm1', '1', '--use-indexed-attachment-ids', '--inline', '--inline-max-bytes=3145728',
|
|
1766
|
+
'--out=/tmp/gog-attachments/m1/Guest_Copy.pdf', '--name=Guest_Copy.pdf',
|
|
1767
|
+
]);
|
|
1768
|
+
expect(JSON.parse(textOf(res))).toMatchObject({ fileName: 'Guest_Copy.pdf', mimeType: 'application/pdf' });
|
|
1769
|
+
});
|
|
1770
|
+
|
|
1771
|
+
it('pins the mode OFF on the legacy attachmentId path', async () => {
|
|
1772
|
+
stub({ download: { path: '/tmp/gog-attachments/m1/x.pdf', bytes: 12, contentBase64: PDF_B64 } });
|
|
1773
|
+
await harness.callTool('gog_gmail_attachment', { messageId: 'm1', attachmentId: 'OPAQUE1', name: 'x.pdf' });
|
|
1774
|
+
expect(dlArgs()).toEqual([
|
|
1775
|
+
'gmail', 'attachment', 'm1', 'OPAQUE1', '--use-indexed-attachment-ids=false', '--inline', '--inline-max-bytes=3145728',
|
|
1776
|
+
'--out=/tmp/gog-attachments/m1/x.pdf', '--name=x.pdf',
|
|
1777
|
+
]);
|
|
1778
|
+
});
|
|
1779
|
+
|
|
1780
|
+
it('rejects both attachmentId and attachmentIndex', async () => {
|
|
1781
|
+
const res = await harness.callTool('gog_gmail_attachment', { messageId: 'm1', attachmentId: 'a1', attachmentIndex: 0 });
|
|
1782
|
+
expect(res.isError).toBe(true);
|
|
1783
|
+
expect(textOf(res)).toContain('exactly one');
|
|
1784
|
+
expect(lib.run).not.toHaveBeenCalled();
|
|
1785
|
+
});
|
|
1786
|
+
|
|
1787
|
+
it('rejects neither attachmentId nor attachmentIndex', async () => {
|
|
1788
|
+
const res = await harness.callTool('gog_gmail_attachment', { messageId: 'm1' });
|
|
1789
|
+
expect(res.isError).toBe(true);
|
|
1790
|
+
expect(textOf(res)).toContain('exactly one');
|
|
1791
|
+
expect(lib.run).not.toHaveBeenCalled();
|
|
1792
|
+
});
|
|
1793
|
+
|
|
1794
|
+
it('a caller-supplied name skips the index lookup', async () => {
|
|
1795
|
+
stub({ meta: INDEXED_LIST, download: { path: '/tmp/gog-attachments/m1/mine.pdf', bytes: 5, contentBase64: PDF_B64 } });
|
|
1796
|
+
await harness.callTool('gog_gmail_attachment', { messageId: 'm1', attachmentIndex: 1, name: 'mine.pdf' });
|
|
1797
|
+
expect(runArgs().some((a) => a[1] === 'get')).toBe(false);
|
|
1798
|
+
});
|
|
1799
|
+
|
|
1800
|
+
it('survives an index lookup failure without falling back to the size heuristic', async () => {
|
|
1801
|
+
stub({ metaError: new Error('get failed'), download: { path: '/tmp/gog-attachments/m1/attachment', bytes: 12, contentBase64: PDF_B64 } });
|
|
1802
|
+
const res = await harness.callTool('gog_gmail_attachment', { messageId: 'm1', attachmentIndex: 0 });
|
|
1803
|
+
// only the (failed) pre-download lookup — resolveBySize must not run after it.
|
|
1804
|
+
expect(runArgs().filter((a) => a[1] === 'get')).toHaveLength(1);
|
|
1805
|
+
expect(JSON.parse(textOf(res))).toMatchObject({ fileName: 'attachment.pdf', mimeType: 'application/pdf' });
|
|
1806
|
+
});
|
|
1807
|
+
|
|
1808
|
+
it('tolerates a message whose listing carries no attachments array', async () => {
|
|
1809
|
+
stub({ meta: {}, download: { path: '/tmp/gog-attachments/m1/attachment', bytes: 12, contentBase64: PDF_B64 } });
|
|
1810
|
+
const res = await harness.callTool('gog_gmail_attachment', { messageId: 'm1', attachmentIndex: 3 });
|
|
1811
|
+
expect(JSON.parse(textOf(res))).toMatchObject({ fileName: 'attachment.pdf' });
|
|
1812
|
+
});
|
|
1813
|
+
|
|
1814
|
+
it('uses the filename/mimeType gog itself reports on an --inline download', async () => {
|
|
1815
|
+
// gog >= 0.34 returns the part metadata alongside the bytes; prefer it over
|
|
1816
|
+
// any wrapper-side guess.
|
|
1817
|
+
stub({ meta: { attachments: [{}] }, download: {
|
|
1818
|
+
path: '/tmp/gog-attachments/m1/attachment', bytes: 12, contentBase64: PDF_B64,
|
|
1819
|
+
filename: 'From_Gog.pdf', mimeType: 'application/pdf',
|
|
1820
|
+
} });
|
|
1821
|
+
const res = await harness.callTool('gog_gmail_attachment', { messageId: 'm1', attachmentIndex: 0 });
|
|
1822
|
+
expect(JSON.parse(textOf(res))).toMatchObject({ fileName: 'From_Gog.pdf', mimeType: 'application/pdf' });
|
|
1823
|
+
});
|
|
1824
|
+
|
|
1825
|
+
it('prefers a caller name over the one gog reports', async () => {
|
|
1826
|
+
stub({ download: {
|
|
1827
|
+
path: '/tmp/gog-attachments/m1/mine.pdf', bytes: 12, contentBase64: PDF_B64,
|
|
1828
|
+
filename: 'From_Gog.pdf', mimeType: 'application/pdf',
|
|
1829
|
+
} });
|
|
1830
|
+
const res = await harness.callTool('gog_gmail_attachment', { messageId: 'm1', attachmentIndex: 0, name: 'mine.pdf' });
|
|
1831
|
+
expect(JSON.parse(textOf(res))).toMatchObject({ fileName: 'mine.pdf', mimeType: 'application/pdf' });
|
|
1832
|
+
});
|
|
1833
|
+
|
|
1834
|
+
it('passes inlineMaxBytes through as --inline-max-bytes', async () => {
|
|
1835
|
+
stub({ meta: INDEXED_LIST, download: { path: '/tmp/gog-attachments/m1/cover.png', bytes: 11 } });
|
|
1836
|
+
await harness.callTool('gog_gmail_attachment', { messageId: 'm1', attachmentIndex: 0, inlineMaxBytes: 1048576 });
|
|
1837
|
+
expect(dlArgs()).toEqual([
|
|
1838
|
+
'gmail', 'attachment', 'm1', '0', '--use-indexed-attachment-ids', '--inline', '--inline-max-bytes=1048576',
|
|
1839
|
+
'--out=/tmp/gog-attachments/m1/cover.png', '--name=cover.png',
|
|
1840
|
+
]);
|
|
1841
|
+
});
|
|
1842
|
+
|
|
1843
|
+
it('does not mangle an indexed failure message while redacting (no bare-digit substitution)', async () => {
|
|
1844
|
+
stub({ downloadError: new Error('Command failed: gog gmail attachment m1 0\nquota exceeded: 1000 requests') });
|
|
1845
|
+
await harness.callTool('gog_gmail_attachment', { messageId: 'm1', attachmentIndex: 0 });
|
|
1846
|
+
const passed = (vi.mocked(lib.diagnose).mock.calls[0]![0] as Error).message;
|
|
1847
|
+
expect(passed).toBe('quota exceeded: 1000 requests');
|
|
1848
|
+
});
|
|
1849
|
+
});
|
|
1850
|
+
|
|
1851
|
+
describe('gog 0.35.0 — indexed ids are pinned on every listing that emits attachments', () => {
|
|
1852
|
+
const args = () => vi.mocked(lib.runOrDiagnose).mock.calls[0]![0] as string[];
|
|
1853
|
+
|
|
1854
|
+
it('gog_gmail_thread_get pins the mode off by default and on when asked', async () => {
|
|
1855
|
+
await harness.callTool('gog_gmail_thread_get', { threadId: 't1' });
|
|
1856
|
+
expect(args()).toEqual(['gmail', 'thread', 'get', 't1', '--use-indexed-attachment-ids=false']);
|
|
1857
|
+
vi.clearAllMocks();
|
|
1858
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(rawTextResult('{}'));
|
|
1859
|
+
await harness.callTool('gog_gmail_thread_get', { threadId: 't1', useIndexedAttachmentIds: true });
|
|
1860
|
+
expect(args()).toEqual(['gmail', 'thread', 'get', 't1', '--use-indexed-attachment-ids']);
|
|
1861
|
+
});
|
|
1862
|
+
|
|
1863
|
+
it('gog_gmail_thread_attachments pins the mode — a flat list needs per-message indexes', async () => {
|
|
1864
|
+
await harness.callTool('gog_gmail_thread_attachments', { threadId: 't1' });
|
|
1865
|
+
expect(args()).toEqual(['gmail', 'thread', 'attachments', 't1', '--use-indexed-attachment-ids=false']);
|
|
1866
|
+
vi.clearAllMocks();
|
|
1867
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(rawTextResult('{}'));
|
|
1868
|
+
await harness.callTool('gog_gmail_thread_attachments', { threadId: 't1', useIndexedAttachmentIds: true, download: true });
|
|
1869
|
+
expect(args()).toEqual(['gmail', 'thread', 'attachments', 't1', '--download', '--use-indexed-attachment-ids']);
|
|
1870
|
+
});
|
|
1871
|
+
|
|
1872
|
+
it('gog_gmail_drafts_get pins the mode', async () => {
|
|
1873
|
+
await harness.callTool('gog_gmail_drafts_get', { draftId: 'd1' });
|
|
1874
|
+
expect(args()).toEqual(['gmail', 'drafts', 'get', 'd1', '--use-indexed-attachment-ids=false']);
|
|
1875
|
+
vi.clearAllMocks();
|
|
1876
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(rawTextResult('{}'));
|
|
1877
|
+
await harness.callTool('gog_gmail_drafts_get', { draftId: 'd1', useIndexedAttachmentIds: true, download: true });
|
|
1878
|
+
expect(args()).toEqual(['gmail', 'drafts', 'get', 'd1', '--download', '--use-indexed-attachment-ids']);
|
|
1879
|
+
});
|
|
1880
|
+
|
|
1881
|
+
it('gog_gmail_messages_search pins BOTH attachment-shaping flags', async () => {
|
|
1882
|
+
await harness.callTool('gog_gmail_messages_search', { query: 'x' });
|
|
1883
|
+
expect(args()).toEqual([
|
|
1884
|
+
'gmail', 'messages', 'search', 'x', '--include-attachments=false', '--use-indexed-attachment-ids=false',
|
|
1885
|
+
]);
|
|
1886
|
+
vi.clearAllMocks();
|
|
1887
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(rawTextResult('{}'));
|
|
1888
|
+
await harness.callTool('gog_gmail_messages_search', { query: 'x', includeAttachments: true, useIndexedAttachmentIds: true });
|
|
1889
|
+
expect(args()).toEqual([
|
|
1890
|
+
'gmail', 'messages', 'search', 'x', '--include-attachments', '--use-indexed-attachment-ids',
|
|
1891
|
+
]);
|
|
1892
|
+
});
|
|
1893
|
+
});
|
|
1894
|
+
|
|
1895
|
+
describe('gog 0.35.0 — --auto-from-addressed-alias is pinned on every send-shaped write', () => {
|
|
1896
|
+
const args = () => vi.mocked(lib.runOrDiagnose).mock.calls[0]![0] as string[];
|
|
1897
|
+
|
|
1898
|
+
it('gog_gmail_drafts_create pins it off, and sets it when asked', async () => {
|
|
1899
|
+
await harness.callTool('gog_gmail_drafts_create', { subject: 'S', body: 'B' });
|
|
1900
|
+
expect(args()).toEqual(['gmail', 'drafts', 'create', '--subject=S', '--body=B', '--auto-from-addressed-alias=false']);
|
|
1901
|
+
vi.clearAllMocks();
|
|
1902
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(rawTextResult('{}'));
|
|
1903
|
+
await harness.callTool('gog_gmail_drafts_create', { subject: 'S', body: 'B', autoFromAddressedAlias: true });
|
|
1904
|
+
expect(args()).toEqual(['gmail', 'drafts', 'create', '--subject=S', '--body=B', '--auto-from-addressed-alias']);
|
|
1905
|
+
});
|
|
1906
|
+
|
|
1907
|
+
it('gog_gmail_drafts_update pins it', async () => {
|
|
1908
|
+
await harness.callTool('gog_gmail_drafts_update', { draftId: 'd1', subject: 'S', body: 'B', autoFromAddressedAlias: true });
|
|
1909
|
+
expect(args()).toEqual(['gmail', 'drafts', 'update', 'd1', '--subject=S', '--body=B', '--auto-from-addressed-alias']);
|
|
1910
|
+
});
|
|
1911
|
+
|
|
1912
|
+
it('gog_gmail_reply and gog_gmail_reply_all pin it', async () => {
|
|
1913
|
+
await harness.callTool('gog_gmail_reply', { messageId: 'm1', body: 'Hi' });
|
|
1914
|
+
expect(args()).toEqual(['gmail', 'reply', 'm1', '--body=Hi', '--auto-from-addressed-alias=false']);
|
|
1915
|
+
vi.clearAllMocks();
|
|
1916
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(rawTextResult('{}'));
|
|
1917
|
+
await harness.callTool('gog_gmail_reply_all', { messageId: 'm1', body: 'Hi', autoFromAddressedAlias: true });
|
|
1918
|
+
expect(args()).toEqual(['gmail', 'reply-all', 'm1', '--body=Hi', '--auto-from-addressed-alias']);
|
|
1919
|
+
});
|
|
1920
|
+
});
|
|
1921
|
+
|
|
1922
|
+
describe('gog 0.35.0 — gog_gmail_import', () => {
|
|
1923
|
+
const args = () => vi.mocked(lib.runOrDiagnose).mock.calls[0]![0] as string[];
|
|
1924
|
+
|
|
1925
|
+
it('imports a file with no options', async () => {
|
|
1926
|
+
await harness.callTool('gog_gmail_import', { file: '/tmp/msg.eml' });
|
|
1927
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['gmail', 'import', '/tmp/msg.eml'], { account: undefined });
|
|
1928
|
+
});
|
|
1929
|
+
|
|
1930
|
+
it('passes every option, repeating --label', async () => {
|
|
1931
|
+
await harness.callTool('gog_gmail_import', {
|
|
1932
|
+
file: '/srv/exports/archived.eml',
|
|
1933
|
+
labels: ['INBOX', 'Archive/2026'],
|
|
1934
|
+
internalDateSource: 'receivedTime',
|
|
1935
|
+
neverMarkSpam: true,
|
|
1936
|
+
processForCalendar: true,
|
|
1937
|
+
account: 'me@x.com',
|
|
1938
|
+
});
|
|
1939
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
1940
|
+
['gmail', 'import', '/srv/exports/archived.eml', '--label=INBOX', '--label=Archive/2026', '--internal-date-source=receivedTime', '--never-mark-spam', '--process-for-calendar'],
|
|
1941
|
+
{ account: 'me@x.com' },
|
|
1942
|
+
);
|
|
1943
|
+
});
|
|
1944
|
+
|
|
1945
|
+
it('omits flags that are false or absent', async () => {
|
|
1946
|
+
await harness.callTool('gog_gmail_import', { file: '/tmp/m.eml', neverMarkSpam: false, processForCalendar: false });
|
|
1947
|
+
expect(args()).toEqual(['gmail', 'import', '/tmp/m.eml']);
|
|
1948
|
+
});
|
|
1949
|
+
|
|
1950
|
+
it('rejects an internalDateSource outside gog\'s enum', async () => {
|
|
1951
|
+
const res = await harness.callTool('gog_gmail_import', { file: '/tmp/m.eml', internalDateSource: 'yesterday' });
|
|
1952
|
+
expect(res.isError).toBe(true);
|
|
1953
|
+
});
|
|
1954
|
+
|
|
1955
|
+
it('never appends --force — gog gates no confirmation on import', async () => {
|
|
1956
|
+
await harness.callTool('gog_gmail_import', { file: '/tmp/m.eml' });
|
|
1957
|
+
expect(args()).not.toContain('--force');
|
|
1958
|
+
});
|
|
1959
|
+
});
|
|
1960
|
+
|
|
1961
|
+
// gog reads a "-" path argument with io.ReadAll(stdinReader(ctx)) — os.Stdin,
|
|
1962
|
+
// unaffected by --no-input (gmail_import.go:102, gmail_body_input.go:45 at
|
|
1963
|
+
// upstream-v0.35.0). runner.ts's spawnGog spawns with default stdio and never
|
|
1964
|
+
// writes to or ends child.stdin, so gog blocks forever on a read that never
|
|
1965
|
+
// EOFs: `spawn('gog', ['gmail','import','-','--dry-run','--json','--no-input'])`
|
|
1966
|
+
// was still running with no output after 5s. Under run() that burns the whole
|
|
1967
|
+
// 30s timeout and returns "gog timed out after 30s". No description may offer
|
|
1968
|
+
// it as a usable option.
|
|
1969
|
+
describe('server-side file params never advertise stdin as usable', () => {
|
|
1970
|
+
const STDIN_PARAMS: Array<[tool: string, param: string]> = [
|
|
1971
|
+
['gog_gmail_import', 'file'],
|
|
1972
|
+
['gog_gmail_drafts_create', 'bodyHtmlFile'],
|
|
1973
|
+
['gog_gmail_drafts_update', 'bodyHtmlFile'],
|
|
1974
|
+
['gog_gmail_reply', 'bodyHtmlFile'],
|
|
1975
|
+
['gog_gmail_reply_all', 'bodyHtmlFile'],
|
|
1976
|
+
];
|
|
1977
|
+
|
|
1978
|
+
async function paramDescriptions(): Promise<Map<string, Record<string, { description?: string }>>> {
|
|
1979
|
+
const { McpServer } = await import('@modelcontextprotocol/sdk/server/mcp.js');
|
|
1980
|
+
const server = new McpServer({ name: 'test', version: '0.0.0' });
|
|
1981
|
+
const configs = new Map<string, Record<string, { description?: string }>>();
|
|
1982
|
+
vi.spyOn(server, 'registerTool').mockImplementation((name, config) => {
|
|
1983
|
+
configs.set(name, (config as { inputSchema: Record<string, { description?: string }> }).inputSchema);
|
|
1984
|
+
return undefined as never;
|
|
1985
|
+
});
|
|
1986
|
+
registerExtraGmailTools(server);
|
|
1987
|
+
return configs;
|
|
1988
|
+
}
|
|
1989
|
+
|
|
1990
|
+
it.each(STDIN_PARAMS)('%s.%s warns that stdin hangs instead of offering it', async (tool, param) => {
|
|
1991
|
+
const schema = (await paramDescriptions()).get(tool);
|
|
1992
|
+
const desc = schema?.[param]?.description ?? '';
|
|
1993
|
+
expect(desc).not.toBe('');
|
|
1994
|
+
expect(desc).not.toMatch(/(?:or|use)\s+"?-"?\s+(?:for|to read)/i);
|
|
1995
|
+
expect(desc).toMatch(/stdin/i);
|
|
1996
|
+
expect(desc).toMatch(/hang|never writes/i);
|
|
1997
|
+
});
|
|
1998
|
+
});
|
|
1999
|
+
|
|
2000
|
+
// Every env-bound gog flag this tool depends on is pinned on the call, because
|
|
2001
|
+
// the child env on the remote runner belongs to a backend we do not control.
|
|
2002
|
+
// --inline-max-bytes is declared env:"GOG_GMAIL_INLINE_MAX_BYTES"
|
|
2003
|
+
// (gmail_attachment.go:27 at upstream-v0.35.0), so an ambient value would
|
|
2004
|
+
// silently decide whether contentBase64 comes back at all.
|
|
2005
|
+
describe('gog_gmail_attachment pins --inline-max-bytes', () => {
|
|
2006
|
+
const args = () => vi.mocked(lib.run).mock.calls[0]![0] as string[];
|
|
2007
|
+
|
|
2008
|
+
it('pins gog\'s default when the caller supplies nothing', async () => {
|
|
2009
|
+
vi.mocked(lib.run).mockResolvedValue(JSON.stringify({ bytes: 10, contentBase64: 'AAAA', mimeType: 'image/png', filename: 'a.png' }));
|
|
2010
|
+
await harness.callTool('gog_gmail_attachment', { messageId: 'm1', attachmentId: 'a1', deliver: 'inline' });
|
|
2011
|
+
expect(args()).toContain('--inline-max-bytes=3145728');
|
|
2012
|
+
});
|
|
2013
|
+
|
|
2014
|
+
it('pins the caller\'s value when supplied', async () => {
|
|
2015
|
+
vi.mocked(lib.run).mockResolvedValue(JSON.stringify({ bytes: 10, contentBase64: 'AAAA', mimeType: 'image/png', filename: 'a.png' }));
|
|
2016
|
+
await harness.callTool('gog_gmail_attachment', { messageId: 'm1', attachmentId: 'a1', deliver: 'inline', inlineMaxBytes: 99 });
|
|
2017
|
+
expect(args()).toContain('--inline-max-bytes=99');
|
|
2018
|
+
});
|
|
2019
|
+
});
|