gogcli-mcp 4.2.5 → 4.3.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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/dist/index.js +714 -216
- package/dist/lib.js +750 -216
- package/manifest.json +9 -2
- package/package.json +2 -2
- package/server.json +2 -2
- package/src/arg-guard.ts +68 -0
- package/src/argv.ts +27 -0
- package/src/attachment-root.ts +111 -0
- package/src/attachments.ts +1 -0
- package/src/blob-upload.ts +4 -2
- package/src/file-roots.ts +66 -0
- package/src/gmail-dispatch-guard.ts +56 -16
- package/src/gmail-results.ts +21 -2
- package/src/lib.ts +6 -0
- package/src/run-path-guard.ts +118 -0
- package/src/runner.ts +86 -18
- package/src/tools/api.ts +39 -7
- package/src/tools/appscript.ts +12 -8
- package/src/tools/auth.ts +13 -3
- package/src/tools/calendar.ts +10 -8
- package/src/tools/chat.ts +16 -13
- package/src/tools/classroom.ts +27 -25
- package/src/tools/contacts.ts +5 -3
- package/src/tools/docs.ts +8 -6
- package/src/tools/drive.ts +42 -18
- package/src/tools/gmail.ts +71 -11
- package/src/tools/sheets.ts +10 -8
- package/src/tools/slides.ts +14 -9
- package/src/tools/tasks.ts +7 -5
- package/src/tools/utils.ts +52 -6
- package/tests/arg-guard.test.ts +80 -0
- package/tests/attachment-root.test.ts +130 -0
- package/tests/attachments.test.ts +8 -0
- package/tests/blob-upload.test.ts +4 -3
- package/tests/file-roots.test.ts +101 -0
- package/tests/gmail-dispatch-guard.test.ts +46 -0
- package/tests/gmail-results.test.ts +35 -1
- package/tests/run-path-guard.test.ts +142 -0
- package/tests/runner.test.ts +136 -0
- package/tests/tools/api.test.ts +74 -8
- package/tests/tools/appscript.test.ts +34 -8
- package/tests/tools/auth.test.ts +44 -17
- package/tests/tools/calendar.test.ts +29 -27
- package/tests/tools/chat.test.ts +46 -20
- package/tests/tools/classroom.test.ts +39 -38
- package/tests/tools/contacts.test.ts +3 -2
- package/tests/tools/docs.test.ts +44 -15
- package/tests/tools/drive.test.ts +106 -19
- package/tests/tools/gmail.test.ts +227 -29
- package/tests/tools/run-tool-examples.test.ts +69 -0
- package/tests/tools/sheets.test.ts +16 -15
- package/tests/tools/slides.test.ts +47 -11
- package/tests/tools/tasks.test.ts +7 -6
- package/tests/tools/utils.test.ts +32 -31
- package/vitest.config.ts +5 -0
|
@@ -2,6 +2,8 @@ import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
|
2
2
|
import { registerCalendarTools, CALENDAR_EVENTS_COMPACT_FIELDS } from '../../src/tools/calendar.js';
|
|
3
3
|
import * as runner from '../../src/runner.js';
|
|
4
4
|
import { createTestHarness } from '@chrischall/mcp-utils/test';
|
|
5
|
+
import { pos } from '../../src/argv.js';
|
|
6
|
+
import type { GogArg } from '../../src/runner.js';
|
|
5
7
|
|
|
6
8
|
vi.mock('../../src/runner.js');
|
|
7
9
|
|
|
@@ -10,7 +12,7 @@ const setupHandlers = () => createTestHarness(registerCalendarTools);
|
|
|
10
12
|
// gog_calendar_events answers in the compact rung by default, so every call
|
|
11
13
|
// carries the mask. These keep the window-flag tests below about window flags
|
|
12
14
|
// while still asserting the shipped default rather than an opted-out view.
|
|
13
|
-
const evArgs = (...extra:
|
|
15
|
+
const evArgs = (...extra: GogArg[]) =>
|
|
14
16
|
['calendar', 'events', ...extra, `--fields=${CALENDAR_EVENTS_COMPACT_FIELDS}`];
|
|
15
17
|
const evOpts = { account: undefined, fieldsMask: CALENDAR_EVENTS_COMPACT_FIELDS };
|
|
16
18
|
|
|
@@ -67,7 +69,7 @@ describe('gog_calendar_events', () => {
|
|
|
67
69
|
query: 'standup',
|
|
68
70
|
all: true,
|
|
69
71
|
});
|
|
70
|
-
expect(runner.run).toHaveBeenCalledWith(evArgs('primary', '--from=2026-01-01', '--to=2026-01-31', '--query=standup', '--all'), evOpts);
|
|
72
|
+
expect(runner.run).toHaveBeenCalledWith(evArgs(pos('primary'), '--from=2026-01-01', '--to=2026-01-31', '--query=standup', '--all'), evOpts);
|
|
71
73
|
});
|
|
72
74
|
|
|
73
75
|
it('appends --today on its own', async () => {
|
|
@@ -118,7 +120,7 @@ describe('gog_calendar_get', () => {
|
|
|
118
120
|
vi.mocked(runner.run).mockResolvedValue('{"id":"evt1"}');
|
|
119
121
|
const harness = await setupHandlers();
|
|
120
122
|
await harness.callTool('gog_calendar_get', { calendarId: 'primary', eventId: 'evt1' });
|
|
121
|
-
expect(runner.run).toHaveBeenCalledWith(['calendar', 'event', 'primary', 'evt1'], { account: undefined });
|
|
123
|
+
expect(runner.run).toHaveBeenCalledWith(['calendar', 'event', pos('primary'), pos('evt1')], { account: undefined });
|
|
122
124
|
});
|
|
123
125
|
|
|
124
126
|
it('appends --timezone when provided', async () => {
|
|
@@ -126,7 +128,7 @@ describe('gog_calendar_get', () => {
|
|
|
126
128
|
const harness = await setupHandlers();
|
|
127
129
|
await harness.callTool('gog_calendar_get', { calendarId: 'primary', eventId: 'evt1', timezone: 'local' });
|
|
128
130
|
expect(runner.run).toHaveBeenCalledWith(
|
|
129
|
-
['calendar', 'event', 'primary', 'evt1', '--timezone=local'],
|
|
131
|
+
['calendar', 'event', pos('primary'), pos('evt1'), '--timezone=local'],
|
|
130
132
|
{ account: undefined },
|
|
131
133
|
);
|
|
132
134
|
});
|
|
@@ -150,7 +152,7 @@ describe('gog_calendar_create', () => {
|
|
|
150
152
|
to: '2026-04-14T09:30:00Z',
|
|
151
153
|
});
|
|
152
154
|
expect(runner.run).toHaveBeenCalledWith(
|
|
153
|
-
['calendar', 'create', 'primary', '--summary=Standup', '--from=2026-04-14T09:00:00Z', '--to=2026-04-14T09:30:00Z'],
|
|
155
|
+
['calendar', 'create', pos('primary'), '--summary=Standup', '--from=2026-04-14T09:00:00Z', '--to=2026-04-14T09:30:00Z'],
|
|
154
156
|
{ account: undefined },
|
|
155
157
|
);
|
|
156
158
|
});
|
|
@@ -171,7 +173,7 @@ describe('gog_calendar_create', () => {
|
|
|
171
173
|
});
|
|
172
174
|
expect(runner.run).toHaveBeenCalledWith(
|
|
173
175
|
[
|
|
174
|
-
'calendar', 'create', 'primary',
|
|
176
|
+
'calendar', 'create', pos('primary'),
|
|
175
177
|
'--summary=All-day', '--from=2026-04-14', '--to=2026-04-15',
|
|
176
178
|
'--description=Desc', '--location=NYC', '--attendees=a@b.com,c@d.com', '--all-day',
|
|
177
179
|
'--timezone=America/New_York',
|
|
@@ -195,7 +197,7 @@ describe('gog_calendar_create', () => {
|
|
|
195
197
|
});
|
|
196
198
|
expect(runner.run).toHaveBeenCalledWith(
|
|
197
199
|
[
|
|
198
|
-
'calendar', 'create', 'primary',
|
|
200
|
+
'calendar', 'create', pos('primary'),
|
|
199
201
|
'--summary=Sync', '--from=2026-04-14T09:00:00Z', '--to=2026-04-14T09:30:00Z',
|
|
200
202
|
'--with-zoom',
|
|
201
203
|
],
|
|
@@ -210,7 +212,7 @@ describe('gog_calendar_create', () => {
|
|
|
210
212
|
calendarId: 'primary', summary: 's', from: 'f', to: 't', withZoom: false,
|
|
211
213
|
});
|
|
212
214
|
expect(runner.run).toHaveBeenCalledWith(
|
|
213
|
-
['calendar', 'create', 'primary', '--summary=s', '--from=f', '--to=t'],
|
|
215
|
+
['calendar', 'create', pos('primary'), '--summary=s', '--from=f', '--to=t'],
|
|
214
216
|
{ account: undefined },
|
|
215
217
|
);
|
|
216
218
|
});
|
|
@@ -233,7 +235,7 @@ describe('gog_calendar_update', () => {
|
|
|
233
235
|
summary: 'New Title',
|
|
234
236
|
});
|
|
235
237
|
expect(runner.run).toHaveBeenCalledWith(
|
|
236
|
-
['calendar', 'update', 'primary', 'evt1', '--summary=New Title'],
|
|
238
|
+
['calendar', 'update', pos('primary'), pos('evt1'), '--summary=New Title'],
|
|
237
239
|
{ account: undefined },
|
|
238
240
|
);
|
|
239
241
|
});
|
|
@@ -246,12 +248,12 @@ describe('gog_calendar_update', () => {
|
|
|
246
248
|
calendarId: 'primary', eventId: 'evt1', attachments: ['https://drive.google.com/file/d/a', 'https://x.test/b.pdf'],
|
|
247
249
|
});
|
|
248
250
|
expect(runner.run).toHaveBeenCalledWith(
|
|
249
|
-
['calendar', 'update', 'primary', 'evt1', '--attachment=https://drive.google.com/file/d/a', '--attachment=https://x.test/b.pdf'],
|
|
251
|
+
['calendar', 'update', pos('primary'), pos('evt1'), '--attachment=https://drive.google.com/file/d/a', '--attachment=https://x.test/b.pdf'],
|
|
250
252
|
{ account: undefined },
|
|
251
253
|
);
|
|
252
254
|
await harness.callTool('gog_calendar_update', { calendarId: 'primary', eventId: 'evt1', attachments: [''] });
|
|
253
255
|
expect(runner.run).toHaveBeenCalledWith(
|
|
254
|
-
['calendar', 'update', 'primary', 'evt1', '--attachment='],
|
|
256
|
+
['calendar', 'update', pos('primary'), pos('evt1'), '--attachment='],
|
|
255
257
|
{ account: undefined },
|
|
256
258
|
);
|
|
257
259
|
});
|
|
@@ -271,7 +273,7 @@ describe('gog_calendar_update', () => {
|
|
|
271
273
|
});
|
|
272
274
|
expect(runner.run).toHaveBeenCalledWith(
|
|
273
275
|
[
|
|
274
|
-
'calendar', 'update', 'primary', 'evt1',
|
|
276
|
+
'calendar', 'update', pos('primary'), pos('evt1'),
|
|
275
277
|
'--summary=New', '--from=2026-04-14T09:00:00Z', '--to=2026-04-14T10:00:00Z',
|
|
276
278
|
'--description=Desc', '--location=NYC', '--attendees=a@b.com',
|
|
277
279
|
],
|
|
@@ -290,7 +292,7 @@ describe('gog_calendar_update', () => {
|
|
|
290
292
|
});
|
|
291
293
|
expect(runner.run).toHaveBeenCalledWith(
|
|
292
294
|
[
|
|
293
|
-
'calendar', 'update', 'primary', 'evt1',
|
|
295
|
+
'calendar', 'update', pos('primary'), pos('evt1'),
|
|
294
296
|
'--add-attendee=room@resource.calendar.google.com;resource,x@y.com;optional',
|
|
295
297
|
],
|
|
296
298
|
{ account: undefined },
|
|
@@ -305,7 +307,7 @@ describe('gog_calendar_update', () => {
|
|
|
305
307
|
calendarId: 'primary', eventId: 'evt1', withZoom: true,
|
|
306
308
|
});
|
|
307
309
|
expect(runner.run).toHaveBeenCalledWith(
|
|
308
|
-
['calendar', 'update', 'primary', 'evt1', '--with-zoom'],
|
|
310
|
+
['calendar', 'update', pos('primary'), pos('evt1'), '--with-zoom'],
|
|
309
311
|
{ account: undefined },
|
|
310
312
|
);
|
|
311
313
|
|
|
@@ -315,7 +317,7 @@ describe('gog_calendar_update', () => {
|
|
|
315
317
|
calendarId: 'primary', eventId: 'evt1', regenerateZoom: true,
|
|
316
318
|
});
|
|
317
319
|
expect(runner.run).toHaveBeenCalledWith(
|
|
318
|
-
['calendar', 'update', 'primary', 'evt1', '--regenerate-zoom'],
|
|
320
|
+
['calendar', 'update', pos('primary'), pos('evt1'), '--regenerate-zoom'],
|
|
319
321
|
{ account: undefined },
|
|
320
322
|
);
|
|
321
323
|
|
|
@@ -325,7 +327,7 @@ describe('gog_calendar_update', () => {
|
|
|
325
327
|
calendarId: 'primary', eventId: 'evt1', removeZoom: true,
|
|
326
328
|
});
|
|
327
329
|
expect(runner.run).toHaveBeenCalledWith(
|
|
328
|
-
['calendar', 'update', 'primary', 'evt1', '--remove-zoom'],
|
|
330
|
+
['calendar', 'update', pos('primary'), pos('evt1'), '--remove-zoom'],
|
|
329
331
|
{ account: undefined },
|
|
330
332
|
);
|
|
331
333
|
});
|
|
@@ -338,7 +340,7 @@ describe('gog_calendar_update', () => {
|
|
|
338
340
|
calendarId: 'primary', eventId: 'evt1', removeMeet: true,
|
|
339
341
|
});
|
|
340
342
|
expect(runner.run).toHaveBeenCalledWith(
|
|
341
|
-
['calendar', 'update', 'primary', 'evt1', '--remove-meet'],
|
|
343
|
+
['calendar', 'update', pos('primary'), pos('evt1'), '--remove-meet'],
|
|
342
344
|
{ account: undefined },
|
|
343
345
|
);
|
|
344
346
|
});
|
|
@@ -351,7 +353,7 @@ describe('gog_calendar_update', () => {
|
|
|
351
353
|
withZoom: false, regenerateZoom: false, removeZoom: false,
|
|
352
354
|
});
|
|
353
355
|
expect(runner.run).toHaveBeenCalledWith(
|
|
354
|
-
['calendar', 'update', 'primary', 'evt1'],
|
|
356
|
+
['calendar', 'update', pos('primary'), pos('evt1')],
|
|
355
357
|
{ account: undefined },
|
|
356
358
|
);
|
|
357
359
|
});
|
|
@@ -369,7 +371,7 @@ describe('gog_calendar_delete', () => {
|
|
|
369
371
|
vi.mocked(runner.run).mockResolvedValue('{}');
|
|
370
372
|
const harness = await setupHandlers();
|
|
371
373
|
await harness.callTool('gog_calendar_delete', { calendarId: 'primary', eventId: 'evt1' });
|
|
372
|
-
expect(runner.run).toHaveBeenCalledWith(['calendar', 'delete', 'primary', 'evt1', '--force'], { account: undefined });
|
|
374
|
+
expect(runner.run).toHaveBeenCalledWith(['calendar', 'delete', pos('primary'), pos('evt1'), '--force'], { account: undefined });
|
|
373
375
|
});
|
|
374
376
|
|
|
375
377
|
it('returns error text on failure', async () => {
|
|
@@ -386,7 +388,7 @@ describe('gog_calendar_respond', () => {
|
|
|
386
388
|
const harness = await setupHandlers();
|
|
387
389
|
await harness.callTool('gog_calendar_respond', { calendarId: 'primary', eventId: 'evt1', status: 'accepted' });
|
|
388
390
|
expect(runner.run).toHaveBeenCalledWith(
|
|
389
|
-
['calendar', 'respond', 'primary', 'evt1', '--status=accepted'],
|
|
391
|
+
['calendar', 'respond', pos('primary'), pos('evt1'), '--status=accepted'],
|
|
390
392
|
{ account: undefined },
|
|
391
393
|
);
|
|
392
394
|
});
|
|
@@ -401,7 +403,7 @@ describe('gog_calendar_respond', () => {
|
|
|
401
403
|
comment: 'Can\'t make it',
|
|
402
404
|
});
|
|
403
405
|
expect(runner.run).toHaveBeenCalledWith(
|
|
404
|
-
['calendar', 'respond', 'primary', 'evt1', '--status=declined', '--comment=Can\'t make it'],
|
|
406
|
+
['calendar', 'respond', pos('primary'), pos('evt1'), '--status=declined', '--comment=Can\'t make it'],
|
|
405
407
|
{ account: undefined },
|
|
406
408
|
);
|
|
407
409
|
});
|
|
@@ -500,7 +502,7 @@ describe('gog_calendar_create reminders', () => {
|
|
|
500
502
|
reminders: ['popup:30m', 'email:1d'],
|
|
501
503
|
});
|
|
502
504
|
expect(runner.run).toHaveBeenCalledWith(
|
|
503
|
-
['calendar', 'create', 'primary', '--summary=Standup', '--from=2026-04-14T09:00:00Z', '--to=2026-04-14T09:30:00Z',
|
|
505
|
+
['calendar', 'create', pos('primary'), '--summary=Standup', '--from=2026-04-14T09:00:00Z', '--to=2026-04-14T09:30:00Z',
|
|
504
506
|
'--reminder=popup:30m', '--reminder=email:1d'],
|
|
505
507
|
{ account: undefined },
|
|
506
508
|
);
|
|
@@ -517,7 +519,7 @@ describe('gog_calendar_create reminders', () => {
|
|
|
517
519
|
noReminders: true,
|
|
518
520
|
});
|
|
519
521
|
expect(runner.run).toHaveBeenCalledWith(
|
|
520
|
-
['calendar', 'create', 'primary', '--summary=Quiet', '--from=2026-04-14T09:00:00Z', '--to=2026-04-14T09:30:00Z',
|
|
522
|
+
['calendar', 'create', pos('primary'), '--summary=Quiet', '--from=2026-04-14T09:00:00Z', '--to=2026-04-14T09:30:00Z',
|
|
521
523
|
'--no-reminders'],
|
|
522
524
|
{ account: undefined },
|
|
523
525
|
);
|
|
@@ -559,7 +561,7 @@ describe('gog_calendar_update reminders', () => {
|
|
|
559
561
|
calendarId: 'primary', eventId: 'evt1', reminders: ['popup:15m'],
|
|
560
562
|
});
|
|
561
563
|
expect(runner.run).toHaveBeenCalledWith(
|
|
562
|
-
['calendar', 'update', 'primary', 'evt1', '--reminder=popup:15m'],
|
|
564
|
+
['calendar', 'update', pos('primary'), pos('evt1'), '--reminder=popup:15m'],
|
|
563
565
|
{ account: undefined },
|
|
564
566
|
);
|
|
565
567
|
});
|
|
@@ -573,7 +575,7 @@ describe('gog_calendar_update reminders', () => {
|
|
|
573
575
|
calendarId: 'primary', eventId: 'evt1', reminders: [],
|
|
574
576
|
});
|
|
575
577
|
expect(runner.run).toHaveBeenCalledWith(
|
|
576
|
-
['calendar', 'update', 'primary', 'evt1', '--reminder='],
|
|
578
|
+
['calendar', 'update', pos('primary'), pos('evt1'), '--reminder='],
|
|
577
579
|
{ account: undefined },
|
|
578
580
|
);
|
|
579
581
|
});
|
|
@@ -585,7 +587,7 @@ describe('gog_calendar_update reminders', () => {
|
|
|
585
587
|
calendarId: 'primary', eventId: 'evt1', noReminders: true,
|
|
586
588
|
});
|
|
587
589
|
expect(runner.run).toHaveBeenCalledWith(
|
|
588
|
-
['calendar', 'update', 'primary', 'evt1', '--no-reminders'],
|
|
590
|
+
['calendar', 'update', pos('primary'), pos('evt1'), '--no-reminders'],
|
|
589
591
|
{ account: undefined },
|
|
590
592
|
);
|
|
591
593
|
});
|
|
@@ -597,7 +599,7 @@ describe('gog_calendar_update reminders', () => {
|
|
|
597
599
|
calendarId: 'primary', eventId: 'evt1', summary: 'Renamed',
|
|
598
600
|
});
|
|
599
601
|
expect(runner.run).toHaveBeenCalledWith(
|
|
600
|
-
['calendar', 'update', 'primary', 'evt1', '--summary=Renamed'],
|
|
602
|
+
['calendar', 'update', pos('primary'), pos('evt1'), '--summary=Renamed'],
|
|
601
603
|
{ account: undefined },
|
|
602
604
|
);
|
|
603
605
|
});
|
package/tests/tools/chat.test.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
|
2
2
|
import { registerChatTools } from '../../src/tools/chat.js';
|
|
3
3
|
import * as runner from '../../src/runner.js';
|
|
4
4
|
import { createTestHarness } from '@chrischall/mcp-utils/test';
|
|
5
|
+
import { pos } from '../../src/argv.js';
|
|
5
6
|
|
|
6
7
|
vi.mock('../../src/runner.js');
|
|
7
8
|
|
|
@@ -58,7 +59,7 @@ describe('gog_chat_spaces_find', () => {
|
|
|
58
59
|
vi.mocked(runner.run).mockResolvedValue('{}');
|
|
59
60
|
const harness = await setupHandlers();
|
|
60
61
|
await harness.callTool('gog_chat_spaces_find', { displayName: 'Team' });
|
|
61
|
-
expect(runner.run).toHaveBeenCalledWith(['chat', 'spaces', 'find', 'Team'], { account: undefined });
|
|
62
|
+
expect(runner.run).toHaveBeenCalledWith(['chat', 'spaces', 'find', pos('Team')], { account: undefined });
|
|
62
63
|
});
|
|
63
64
|
|
|
64
65
|
it('passes --exact and --max', async () => {
|
|
@@ -66,7 +67,7 @@ describe('gog_chat_spaces_find', () => {
|
|
|
66
67
|
const harness = await setupHandlers();
|
|
67
68
|
await harness.callTool('gog_chat_spaces_find', { displayName: 'Team', exact: true, max: 5 });
|
|
68
69
|
expect(runner.run).toHaveBeenCalledWith(
|
|
69
|
-
['chat', 'spaces', 'find', 'Team', '--exact', '--max=5'],
|
|
70
|
+
['chat', 'spaces', 'find', pos('Team'), '--exact', '--max=5'],
|
|
70
71
|
{ account: undefined },
|
|
71
72
|
);
|
|
72
73
|
});
|
|
@@ -78,7 +79,7 @@ describe('gog_chat_spaces_create', () => {
|
|
|
78
79
|
const harness = await setupHandlers();
|
|
79
80
|
await harness.callTool('gog_chat_spaces_create', { displayName: 'Launch', members: ['a@b.com', 'c@d.com'] });
|
|
80
81
|
expect(runner.run).toHaveBeenCalledWith(
|
|
81
|
-
['chat', 'spaces', 'create', 'Launch', '--member=a@b.com', '--member=c@d.com'],
|
|
82
|
+
['chat', 'spaces', 'create', pos('Launch'), '--member=a@b.com', '--member=c@d.com'],
|
|
82
83
|
{ account: undefined },
|
|
83
84
|
);
|
|
84
85
|
});
|
|
@@ -87,7 +88,7 @@ describe('gog_chat_spaces_create', () => {
|
|
|
87
88
|
vi.mocked(runner.run).mockResolvedValue('{}');
|
|
88
89
|
const harness = await setupHandlers();
|
|
89
90
|
await harness.callTool('gog_chat_spaces_create', { displayName: 'Solo' });
|
|
90
|
-
expect(runner.run).toHaveBeenCalledWith(['chat', 'spaces', 'create', 'Solo'], { account: undefined });
|
|
91
|
+
expect(runner.run).toHaveBeenCalledWith(['chat', 'spaces', 'create', pos('Solo')], { account: undefined });
|
|
91
92
|
});
|
|
92
93
|
});
|
|
93
94
|
|
|
@@ -97,7 +98,7 @@ describe('gog_chat_threads_list', () => {
|
|
|
97
98
|
const harness = await setupHandlers();
|
|
98
99
|
await harness.callTool('gog_chat_threads_list', { space: 'spaces/AAA', max: 10 });
|
|
99
100
|
expect(runner.run).toHaveBeenCalledWith(
|
|
100
|
-
['chat', 'threads', 'list', 'spaces/AAA', '--max=10'],
|
|
101
|
+
['chat', 'threads', 'list', pos('spaces/AAA'), '--max=10'],
|
|
101
102
|
{ account: undefined },
|
|
102
103
|
);
|
|
103
104
|
});
|
|
@@ -108,7 +109,7 @@ describe('gog_chat_messages_list', () => {
|
|
|
108
109
|
vi.mocked(runner.run).mockResolvedValue('{}');
|
|
109
110
|
const harness = await setupHandlers();
|
|
110
111
|
await harness.callTool('gog_chat_messages_list', { space: 'spaces/AAA' });
|
|
111
|
-
expect(runner.run).toHaveBeenCalledWith(['chat', 'messages', 'list', 'spaces/AAA'], { account: undefined });
|
|
112
|
+
expect(runner.run).toHaveBeenCalledWith(['chat', 'messages', 'list', pos('spaces/AAA')], { account: undefined });
|
|
112
113
|
});
|
|
113
114
|
|
|
114
115
|
it('filters by thread, unread and order', async () => {
|
|
@@ -118,7 +119,7 @@ describe('gog_chat_messages_list', () => {
|
|
|
118
119
|
space: 'spaces/AAA', thread: 'spaces/AAA/threads/T', unread: true, order: 'createTime desc', max: 50,
|
|
119
120
|
});
|
|
120
121
|
expect(runner.run).toHaveBeenCalledWith(
|
|
121
|
-
['chat', 'messages', 'list', 'spaces/AAA', '--thread=spaces/AAA/threads/T', '--unread',
|
|
122
|
+
['chat', 'messages', 'list', pos('spaces/AAA'), '--thread=spaces/AAA/threads/T', '--unread',
|
|
122
123
|
'--order=createTime desc', '--max=50'],
|
|
123
124
|
{ account: undefined },
|
|
124
125
|
);
|
|
@@ -138,7 +139,7 @@ describe('gog_chat_messages_search', () => {
|
|
|
138
139
|
const harness = await setupHandlers();
|
|
139
140
|
await harness.callTool('gog_chat_messages_search', { query: 'project decision' });
|
|
140
141
|
expect(runner.run).toHaveBeenCalledWith(
|
|
141
|
-
['chat', 'messages', 'search', 'project decision'],
|
|
142
|
+
['chat', 'messages', 'search', pos('project decision')],
|
|
142
143
|
{ account: undefined },
|
|
143
144
|
);
|
|
144
145
|
});
|
|
@@ -156,7 +157,7 @@ describe('gog_chat_messages_search', () => {
|
|
|
156
157
|
all: true,
|
|
157
158
|
});
|
|
158
159
|
expect(runner.run).toHaveBeenCalledWith(
|
|
159
|
-
['chat', 'messages', 'search', 'from:alice@example.com budget', '--order=create_time desc',
|
|
160
|
+
['chat', 'messages', 'search', pos('from:alice@example.com budget'), '--order=create_time desc',
|
|
160
161
|
'--view=full', '--markup=markdown', '--max=100', '--page=tok', '--all'],
|
|
161
162
|
{ account: undefined },
|
|
162
163
|
);
|
|
@@ -198,7 +199,7 @@ describe('gog_chat_messages_send', () => {
|
|
|
198
199
|
const harness = await setupHandlers();
|
|
199
200
|
await harness.callTool('gog_chat_messages_send', { space: 'spaces/AAA', text: 'hi' });
|
|
200
201
|
expect(runner.run).toHaveBeenCalledWith(
|
|
201
|
-
['chat', 'messages', 'send', 'spaces/AAA', '--text=hi'],
|
|
202
|
+
['chat', 'messages', 'send', pos('spaces/AAA'), '--text=hi'],
|
|
202
203
|
{ account: undefined },
|
|
203
204
|
);
|
|
204
205
|
});
|
|
@@ -210,7 +211,7 @@ describe('gog_chat_messages_send', () => {
|
|
|
210
211
|
space: 'spaces/AAA', text: 'see this', thread: 'spaces/AAA/threads/T', attach: ['/tmp/a.png'],
|
|
211
212
|
});
|
|
212
213
|
expect(runner.run).toHaveBeenCalledWith(
|
|
213
|
-
['chat', 'messages', 'send', 'spaces/AAA', '--text=see this', '--thread=spaces/AAA/threads/T', '--attach=/tmp/a.png'],
|
|
214
|
+
['chat', 'messages', 'send', pos('spaces/AAA'), '--text=see this', '--thread=spaces/AAA/threads/T', '--attach=/tmp/a.png'],
|
|
214
215
|
{ account: undefined },
|
|
215
216
|
);
|
|
216
217
|
});
|
|
@@ -234,7 +235,7 @@ describe('gog_chat_messages_send', () => {
|
|
|
234
235
|
const harness = await setupHandlers();
|
|
235
236
|
await harness.callTool('gog_chat_messages_send', { space: 'spaces/AAA', attach: ['/tmp/a.png'] });
|
|
236
237
|
expect(runner.run).toHaveBeenCalledWith(
|
|
237
|
-
['chat', 'messages', 'send', 'spaces/AAA', '--attach=/tmp/a.png'],
|
|
238
|
+
['chat', 'messages', 'send', pos('spaces/AAA'), '--attach=/tmp/a.png'],
|
|
238
239
|
{ account: undefined },
|
|
239
240
|
);
|
|
240
241
|
});
|
|
@@ -253,7 +254,7 @@ describe('gog_chat_dm_send', () => {
|
|
|
253
254
|
const harness = await setupHandlers();
|
|
254
255
|
await harness.callTool('gog_chat_dm_send', { email: 'a@b.com', text: 'hi' });
|
|
255
256
|
expect(runner.run).toHaveBeenCalledWith(
|
|
256
|
-
['chat', 'dm', 'send', 'a@b.com', '--text=hi'],
|
|
257
|
+
['chat', 'dm', 'send', pos('a@b.com'), '--text=hi'],
|
|
257
258
|
{ account: undefined },
|
|
258
259
|
);
|
|
259
260
|
});
|
|
@@ -263,7 +264,7 @@ describe('gog_chat_dm_send', () => {
|
|
|
263
264
|
const harness = await setupHandlers();
|
|
264
265
|
await harness.callTool('gog_chat_dm_send', { email: 'a@b.com', text: 'hi', thread: 'spaces/D/threads/T' });
|
|
265
266
|
expect(runner.run).toHaveBeenCalledWith(
|
|
266
|
-
['chat', 'dm', 'send', 'a@b.com', '--text=hi', '--thread=spaces/D/threads/T'],
|
|
267
|
+
['chat', 'dm', 'send', pos('a@b.com'), '--text=hi', '--thread=spaces/D/threads/T'],
|
|
267
268
|
{ account: undefined },
|
|
268
269
|
);
|
|
269
270
|
});
|
|
@@ -274,7 +275,7 @@ describe('gog_chat_dm_space', () => {
|
|
|
274
275
|
vi.mocked(runner.run).mockResolvedValue('{}');
|
|
275
276
|
const harness = await setupHandlers();
|
|
276
277
|
await harness.callTool('gog_chat_dm_space', { email: 'a@b.com' });
|
|
277
|
-
expect(runner.run).toHaveBeenCalledWith(['chat', 'dm', 'space', 'a@b.com'], { account: undefined });
|
|
278
|
+
expect(runner.run).toHaveBeenCalledWith(['chat', 'dm', 'space', pos('a@b.com')], { account: undefined });
|
|
278
279
|
});
|
|
279
280
|
});
|
|
280
281
|
|
|
@@ -284,7 +285,7 @@ describe('gog_chat_reactions_list', () => {
|
|
|
284
285
|
const harness = await setupHandlers();
|
|
285
286
|
await harness.callTool('gog_chat_reactions_list', { message: 'MSG', space: 'spaces/AAA', max: 10 });
|
|
286
287
|
expect(runner.run).toHaveBeenCalledWith(
|
|
287
|
-
['chat', 'messages', 'reactions', 'list', 'MSG', '--space=spaces/AAA', '--max=10'],
|
|
288
|
+
['chat', 'messages', 'reactions', 'list', pos('MSG'), '--space=spaces/AAA', '--max=10'],
|
|
288
289
|
{ account: undefined },
|
|
289
290
|
);
|
|
290
291
|
});
|
|
@@ -294,7 +295,7 @@ describe('gog_chat_reactions_list', () => {
|
|
|
294
295
|
const harness = await setupHandlers();
|
|
295
296
|
await harness.callTool('gog_chat_reactions_list', { message: 'spaces/AAA/messages/M' });
|
|
296
297
|
expect(runner.run).toHaveBeenCalledWith(
|
|
297
|
-
['chat', 'messages', 'reactions', 'list', 'spaces/AAA/messages/M'],
|
|
298
|
+
['chat', 'messages', 'reactions', 'list', pos('spaces/AAA/messages/M')],
|
|
298
299
|
{ account: undefined },
|
|
299
300
|
);
|
|
300
301
|
});
|
|
@@ -306,7 +307,7 @@ describe('gog_chat_reactions_create', () => {
|
|
|
306
307
|
const harness = await setupHandlers();
|
|
307
308
|
await harness.callTool('gog_chat_reactions_create', { message: 'spaces/AAA/messages/M', emoji: '👍' });
|
|
308
309
|
expect(runner.run).toHaveBeenCalledWith(
|
|
309
|
-
['chat', 'messages', 'reactions', 'create', 'spaces/AAA/messages/M', '👍'],
|
|
310
|
+
['chat', 'messages', 'reactions', 'create', pos('spaces/AAA/messages/M'), pos('👍')],
|
|
310
311
|
{ account: undefined },
|
|
311
312
|
);
|
|
312
313
|
});
|
|
@@ -316,7 +317,7 @@ describe('gog_chat_reactions_create', () => {
|
|
|
316
317
|
const harness = await setupHandlers();
|
|
317
318
|
await harness.callTool('gog_chat_reactions_create', { message: 'M', emoji: '👍', space: 'spaces/AAA' });
|
|
318
319
|
expect(runner.run).toHaveBeenCalledWith(
|
|
319
|
-
['chat', 'messages', 'reactions', 'create', 'M', '👍', '--space=spaces/AAA'],
|
|
320
|
+
['chat', 'messages', 'reactions', 'create', pos('M'), pos('👍'), '--space=spaces/AAA'],
|
|
320
321
|
{ account: undefined },
|
|
321
322
|
);
|
|
322
323
|
});
|
|
@@ -328,7 +329,7 @@ describe('gog_chat_reactions_delete', () => {
|
|
|
328
329
|
const harness = await setupHandlers();
|
|
329
330
|
await harness.callTool('gog_chat_reactions_delete', { reaction: 'spaces/AAA/messages/M/reactions/R' });
|
|
330
331
|
expect(runner.run).toHaveBeenCalledWith(
|
|
331
|
-
['chat', 'messages', 'reactions', 'delete', 'spaces/AAA/messages/M/reactions/R'],
|
|
332
|
+
['chat', 'messages', 'reactions', 'delete', pos('spaces/AAA/messages/M/reactions/R')],
|
|
332
333
|
{ account: undefined },
|
|
333
334
|
);
|
|
334
335
|
});
|
|
@@ -342,3 +343,28 @@ describe('gog_chat_run', () => {
|
|
|
342
343
|
expect(runner.run).toHaveBeenCalledWith(['chat', 'spaces', 'list'], { account: undefined });
|
|
343
344
|
});
|
|
344
345
|
});
|
|
346
|
+
|
|
347
|
+
// SEC-3/SEC-4: every model-supplied server path must resolve inside an
|
|
348
|
+
// operator-configured root (GOG_FILE_ROOTS). The suite runs with '/' so the
|
|
349
|
+
// arg-shape tests can use any path; these narrow it.
|
|
350
|
+
async function withFileRoots<T>(roots: string, fn: () => Promise<T>): Promise<T> {
|
|
351
|
+
const prev = process.env.GOG_FILE_ROOTS;
|
|
352
|
+
process.env.GOG_FILE_ROOTS = roots;
|
|
353
|
+
try {
|
|
354
|
+
return await fn();
|
|
355
|
+
} finally {
|
|
356
|
+
process.env.GOG_FILE_ROOTS = prev;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
describe('gog_chat_messages_send — server paths', () => {
|
|
361
|
+
it('refuses an attach path outside GOG_FILE_ROOTS', async () => {
|
|
362
|
+
const harness = await setupHandlers();
|
|
363
|
+
const result = await withFileRoots('/srv/gog-files', () => harness.callTool('gog_chat_messages_send', {
|
|
364
|
+
space: 'spaces/AAA', attach: ['/etc/passwd'],
|
|
365
|
+
}));
|
|
366
|
+
expect(result.isError).toBe(true);
|
|
367
|
+
expect(result.content[0].text).toContain('attach "/etc/passwd" is outside');
|
|
368
|
+
expect(runner.run).not.toHaveBeenCalled();
|
|
369
|
+
});
|
|
370
|
+
});
|