gogcli-mcp-drive 4.2.4 → 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 +441 -127
- package/manifest.json +9 -2
- package/mint.yaml +1 -1
- package/package.json +2 -2
- package/server.json +2 -2
- package/src/tools/drive-extra.ts +39 -36
- package/tests/tools/drive-extra.test.ts +84 -46
- package/vitest.config.ts +5 -0
|
@@ -3,6 +3,7 @@ import { registerExtraDriveTools } from '../../src/tools/drive-extra.js';
|
|
|
3
3
|
import * as lib from '../../../gogcli-mcp/src/lib.js';
|
|
4
4
|
import { createTestHarness, type TestHarness } from '@chrischall/mcp-utils/test';
|
|
5
5
|
import { rawTextResult } from '@chrischall/mcp-utils';
|
|
6
|
+
import { pos } from '../../../gogcli-mcp/src/argv.js';
|
|
6
7
|
|
|
7
8
|
vi.mock('../../../gogcli-mcp/src/lib.js', async (importOriginal) => {
|
|
8
9
|
const actual = await importOriginal<typeof lib>();
|
|
@@ -23,13 +24,13 @@ beforeEach(async () => {
|
|
|
23
24
|
describe('gog_drive_download', () => {
|
|
24
25
|
it('calls runOrDiagnose with fileId only', async () => {
|
|
25
26
|
await harness.callTool('gog_drive_download', { fileId: 'f1' });
|
|
26
|
-
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['drive', 'download', 'f1'], { account: undefined });
|
|
27
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['drive', 'download', pos('f1')], { account: undefined });
|
|
27
28
|
});
|
|
28
29
|
|
|
29
30
|
it('passes --out and --format when provided', async () => {
|
|
30
31
|
await harness.callTool('gog_drive_download', { fileId: 'f1', out: '/tmp/file.pdf', format: 'pdf', account: 'a@b.com' });
|
|
31
32
|
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
32
|
-
['drive', 'download', 'f1', '--out=/tmp/file.pdf', '--format=pdf'],
|
|
33
|
+
['drive', 'download', pos('f1'), '--out=/tmp/file.pdf', '--format=pdf'],
|
|
33
34
|
{ account: 'a@b.com' },
|
|
34
35
|
);
|
|
35
36
|
});
|
|
@@ -37,7 +38,7 @@ describe('gog_drive_download', () => {
|
|
|
37
38
|
it('passes --overwrite when requested', async () => {
|
|
38
39
|
await harness.callTool('gog_drive_download', { fileId: 'f1', out: '/tmp/file.pdf', overwrite: true });
|
|
39
40
|
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
40
|
-
['drive', 'download', 'f1', '--out=/tmp/file.pdf', '--overwrite'],
|
|
41
|
+
['drive', 'download', pos('f1'), '--out=/tmp/file.pdf', '--overwrite'],
|
|
41
42
|
{ account: undefined },
|
|
42
43
|
);
|
|
43
44
|
});
|
|
@@ -46,7 +47,7 @@ describe('gog_drive_download', () => {
|
|
|
46
47
|
describe('gog_drive_upload', () => {
|
|
47
48
|
it('calls runOrDiagnose with localPath only', async () => {
|
|
48
49
|
await harness.callTool('gog_drive_upload', { localPath: '/tmp/x.txt' });
|
|
49
|
-
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['drive', 'upload', '/tmp/x.txt'], { account: undefined });
|
|
50
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['drive', 'upload', pos('/tmp/x.txt')], { account: undefined });
|
|
50
51
|
});
|
|
51
52
|
|
|
52
53
|
it('passes all upload flags', async () => {
|
|
@@ -62,7 +63,7 @@ describe('gog_drive_upload', () => {
|
|
|
62
63
|
});
|
|
63
64
|
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
64
65
|
[
|
|
65
|
-
'drive', 'upload', '/tmp/x.txt',
|
|
66
|
+
'drive', 'upload', pos('/tmp/x.txt'),
|
|
66
67
|
'--name=renamed.txt',
|
|
67
68
|
'--parent=folder1',
|
|
68
69
|
'--replace=file2',
|
|
@@ -77,7 +78,7 @@ describe('gog_drive_upload', () => {
|
|
|
77
78
|
|
|
78
79
|
it('omits boolean flags when false', async () => {
|
|
79
80
|
await harness.callTool('gog_drive_upload', { localPath: '/tmp/x.txt', keepRevisionForever: false, convert: false });
|
|
80
|
-
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['drive', 'upload', '/tmp/x.txt'], { account: undefined });
|
|
81
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['drive', 'upload', pos('/tmp/x.txt')], { account: undefined });
|
|
81
82
|
});
|
|
82
83
|
|
|
83
84
|
// gog 0.35.0 (openclaw/gogcli 1b26124) adds --if-version: an atomic
|
|
@@ -86,7 +87,7 @@ describe('gog_drive_upload', () => {
|
|
|
86
87
|
it('passes --if-version alongside --replace for conditional replacement', async () => {
|
|
87
88
|
await harness.callTool('gog_drive_upload', { localPath: '/tmp/x.txt', replace: 'file2', ifVersion: 7 });
|
|
88
89
|
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
89
|
-
['drive', 'upload', '/tmp/x.txt', '--replace=file2', '--if-version=7'],
|
|
90
|
+
['drive', 'upload', pos('/tmp/x.txt'), '--replace=file2', '--if-version=7'],
|
|
90
91
|
{ account: undefined },
|
|
91
92
|
);
|
|
92
93
|
});
|
|
@@ -179,7 +180,7 @@ describe('gog_drive_upload', () => {
|
|
|
179
180
|
// positional, no file arg, for every local stdio caller that has one.
|
|
180
181
|
it('leaves the localPath path unchanged', async () => {
|
|
181
182
|
await harness.callTool('gog_drive_upload', { localPath: '/tmp/x.txt' });
|
|
182
|
-
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['drive', 'upload', '/tmp/x.txt'], { account: undefined });
|
|
183
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['drive', 'upload', pos('/tmp/x.txt')], { account: undefined });
|
|
183
184
|
});
|
|
184
185
|
});
|
|
185
186
|
|
|
@@ -208,7 +209,7 @@ describe('gog_drive_sync_push', () => {
|
|
|
208
209
|
it('calls runOrDiagnose with localPath and required parent', async () => {
|
|
209
210
|
await harness.callTool('gog_drive_sync_push', { localPath: '/tmp/dir', parent: 'folder1' });
|
|
210
211
|
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
211
|
-
['drive', 'sync', 'push', '/tmp/dir', '--parent=folder1'],
|
|
212
|
+
['drive', 'sync', 'push', pos('/tmp/dir'), '--parent=folder1'],
|
|
212
213
|
{ account: undefined },
|
|
213
214
|
);
|
|
214
215
|
});
|
|
@@ -218,7 +219,7 @@ describe('gog_drive_sync_push', () => {
|
|
|
218
219
|
localPath: '/tmp/dir', parent: 'folder1', dryRun: true, allDrives: false,
|
|
219
220
|
});
|
|
220
221
|
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
221
|
-
['drive', 'sync', 'push', '/tmp/dir', '--parent=folder1', '--dry-run', '--no-all-drives'],
|
|
222
|
+
['drive', 'sync', 'push', pos('/tmp/dir'), '--parent=folder1', '--dry-run', '--no-all-drives'],
|
|
222
223
|
{ account: undefined },
|
|
223
224
|
);
|
|
224
225
|
});
|
|
@@ -228,7 +229,7 @@ describe('gog_drive_sync_push', () => {
|
|
|
228
229
|
localPath: '/tmp/dir', parent: 'folder1', dryRun: false, allDrives: true,
|
|
229
230
|
});
|
|
230
231
|
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
231
|
-
['drive', 'sync', 'push', '/tmp/dir', '--parent=folder1'],
|
|
232
|
+
['drive', 'sync', 'push', pos('/tmp/dir'), '--parent=folder1'],
|
|
232
233
|
{ account: undefined },
|
|
233
234
|
);
|
|
234
235
|
});
|
|
@@ -237,13 +238,13 @@ describe('gog_drive_sync_push', () => {
|
|
|
237
238
|
describe('gog_drive_copy', () => {
|
|
238
239
|
it('calls runOrDiagnose with fileId and name', async () => {
|
|
239
240
|
await harness.callTool('gog_drive_copy', { fileId: 'f1', name: 'Copy' });
|
|
240
|
-
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['drive', 'copy', 'f1', 'Copy'], { account: undefined });
|
|
241
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['drive', 'copy', pos('f1'), pos('Copy')], { account: undefined });
|
|
241
242
|
});
|
|
242
243
|
|
|
243
244
|
it('passes --parent when provided', async () => {
|
|
244
245
|
await harness.callTool('gog_drive_copy', { fileId: 'f1', name: 'Copy', parent: 'folder1' });
|
|
245
246
|
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
246
|
-
['drive', 'copy', 'f1', 'Copy', '--parent=folder1'],
|
|
247
|
+
['drive', 'copy', pos('f1'), pos('Copy'), '--parent=folder1'],
|
|
247
248
|
{ account: undefined },
|
|
248
249
|
);
|
|
249
250
|
});
|
|
@@ -252,25 +253,25 @@ describe('gog_drive_copy', () => {
|
|
|
252
253
|
describe('gog_drive_url', () => {
|
|
253
254
|
it('calls runOrDiagnose with single fileId', async () => {
|
|
254
255
|
await harness.callTool('gog_drive_url', { fileIds: ['f1'] });
|
|
255
|
-
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['drive', 'url', 'f1'], { account: undefined });
|
|
256
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['drive', 'url', pos('f1')], { account: undefined });
|
|
256
257
|
});
|
|
257
258
|
|
|
258
259
|
it('calls runOrDiagnose with multiple fileIds', async () => {
|
|
259
260
|
await harness.callTool('gog_drive_url', { fileIds: ['f1', 'f2', 'f3'] });
|
|
260
|
-
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['drive', 'url', 'f1', 'f2', 'f3'], { account: undefined });
|
|
261
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['drive', 'url', pos('f1'), pos('f2'), pos('f3')], { account: undefined });
|
|
261
262
|
});
|
|
262
263
|
});
|
|
263
264
|
|
|
264
265
|
describe('gog_drive_permissions', () => {
|
|
265
266
|
it('calls runOrDiagnose with fileId', async () => {
|
|
266
267
|
await harness.callTool('gog_drive_permissions', { fileId: 'f1' });
|
|
267
|
-
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['drive', 'permissions', 'f1'], { account: undefined });
|
|
268
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['drive', 'permissions', pos('f1')], { account: undefined });
|
|
268
269
|
});
|
|
269
270
|
|
|
270
271
|
it('passes --max and --page when provided', async () => {
|
|
271
272
|
await harness.callTool('gog_drive_permissions', { fileId: 'f1', max: 50, page: 'tok' });
|
|
272
273
|
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
273
|
-
['drive', 'permissions', 'f1', '--max=50', '--page=tok'],
|
|
274
|
+
['drive', 'permissions', pos('f1'), '--max=50', '--page=tok'],
|
|
274
275
|
{ account: undefined },
|
|
275
276
|
);
|
|
276
277
|
});
|
|
@@ -280,7 +281,7 @@ describe('gog_drive_unshare', () => {
|
|
|
280
281
|
it('calls runOrDiagnose with fileId and permissionId', async () => {
|
|
281
282
|
await harness.callTool('gog_drive_unshare', { fileId: 'f1', permissionId: 'p1' });
|
|
282
283
|
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
283
|
-
['drive', 'unshare', 'f1', 'p1', '--force'],
|
|
284
|
+
['drive', 'unshare', pos('f1'), pos('p1'), '--force'],
|
|
284
285
|
{ account: undefined },
|
|
285
286
|
);
|
|
286
287
|
});
|
|
@@ -310,7 +311,7 @@ describe('gog_drive_comments_list', () => {
|
|
|
310
311
|
it('calls runOrDiagnose with fileId', async () => {
|
|
311
312
|
await harness.callTool('gog_drive_comments_list', { fileId: 'f1' });
|
|
312
313
|
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
313
|
-
['drive', 'comments', 'list', 'f1'],
|
|
314
|
+
['drive', 'comments', 'list', pos('f1')],
|
|
314
315
|
{ account: undefined },
|
|
315
316
|
);
|
|
316
317
|
});
|
|
@@ -321,7 +322,7 @@ describe('gog_drive_comments_list', () => {
|
|
|
321
322
|
fileId: 'f1', since: '2026-06-01T00:00:00Z', includeQuoted: true, max: 5, page: 'tok', all: true,
|
|
322
323
|
});
|
|
323
324
|
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
324
|
-
['drive', 'comments', 'list', 'f1', '--since=2026-06-01T00:00:00Z', '--include-quoted', '--max=5', '--page=tok', '--all'],
|
|
325
|
+
['drive', 'comments', 'list', pos('f1'), '--since=2026-06-01T00:00:00Z', '--include-quoted', '--max=5', '--page=tok', '--all'],
|
|
325
326
|
{ account: undefined },
|
|
326
327
|
);
|
|
327
328
|
});
|
|
@@ -331,7 +332,7 @@ describe('gog_drive_comments_get', () => {
|
|
|
331
332
|
it('calls runOrDiagnose with fileId and commentId', async () => {
|
|
332
333
|
await harness.callTool('gog_drive_comments_get', { fileId: 'f1', commentId: 'c1' });
|
|
333
334
|
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
334
|
-
['drive', 'comments', 'get', 'f1', 'c1'],
|
|
335
|
+
['drive', 'comments', 'get', pos('f1'), pos('c1')],
|
|
335
336
|
{ account: undefined },
|
|
336
337
|
);
|
|
337
338
|
});
|
|
@@ -341,7 +342,7 @@ describe('gog_drive_comments_add', () => {
|
|
|
341
342
|
it('calls runOrDiagnose with fileId and content', async () => {
|
|
342
343
|
await harness.callTool('gog_drive_comments_add', { fileId: 'f1', content: 'LGTM' });
|
|
343
344
|
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
344
|
-
['drive', 'comments', 'create', 'f1', 'LGTM'],
|
|
345
|
+
['drive', 'comments', 'create', pos('f1'), pos('LGTM')],
|
|
345
346
|
{ account: undefined },
|
|
346
347
|
);
|
|
347
348
|
});
|
|
@@ -351,7 +352,7 @@ describe('gog_drive_comments_update', () => {
|
|
|
351
352
|
it('calls runOrDiagnose with fileId, commentId, and content', async () => {
|
|
352
353
|
await harness.callTool('gog_drive_comments_update', { fileId: 'f1', commentId: 'c1', content: 'edited' });
|
|
353
354
|
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
354
|
-
['drive', 'comments', 'update', 'f1', 'c1', 'edited'],
|
|
355
|
+
['drive', 'comments', 'update', pos('f1'), pos('c1'), pos('edited')],
|
|
355
356
|
{ account: undefined },
|
|
356
357
|
);
|
|
357
358
|
});
|
|
@@ -361,7 +362,7 @@ describe('gog_drive_comments_delete', () => {
|
|
|
361
362
|
it('calls runOrDiagnose with fileId and commentId', async () => {
|
|
362
363
|
await harness.callTool('gog_drive_comments_delete', { fileId: 'f1', commentId: 'c1' });
|
|
363
364
|
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
364
|
-
['drive', 'comments', 'delete', 'f1', 'c1', '--force'],
|
|
365
|
+
['drive', 'comments', 'delete', pos('f1'), pos('c1'), '--force'],
|
|
365
366
|
{ account: undefined },
|
|
366
367
|
);
|
|
367
368
|
});
|
|
@@ -371,7 +372,7 @@ describe('gog_drive_comments_reply', () => {
|
|
|
371
372
|
it('calls runOrDiagnose with fileId, commentId, and content', async () => {
|
|
372
373
|
await harness.callTool('gog_drive_comments_reply', { fileId: 'f1', commentId: 'c1', content: 'thanks' });
|
|
373
374
|
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
374
|
-
['drive', 'comments', 'reply', 'f1', 'c1', 'thanks'],
|
|
375
|
+
['drive', 'comments', 'reply', pos('f1'), pos('c1'), pos('thanks')],
|
|
375
376
|
{ account: undefined },
|
|
376
377
|
);
|
|
377
378
|
});
|
|
@@ -382,7 +383,7 @@ describe('gog_drive_comments_reply', () => {
|
|
|
382
383
|
fileId: 'f1', commentId: 'c1', content: 'lgtm, resolving', action: 'resolve',
|
|
383
384
|
});
|
|
384
385
|
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
385
|
-
['drive', 'comments', 'reply', 'f1', 'c1', 'lgtm, resolving', '--action=resolve'],
|
|
386
|
+
['drive', 'comments', 'reply', pos('f1'), pos('c1'), pos('lgtm, resolving'), '--action=resolve'],
|
|
386
387
|
{ account: undefined },
|
|
387
388
|
);
|
|
388
389
|
});
|
|
@@ -392,7 +393,7 @@ describe('gog_drive_comments_reply', () => {
|
|
|
392
393
|
fileId: 'f1', commentId: 'c1', content: 'actually wait', action: 'reopen',
|
|
393
394
|
});
|
|
394
395
|
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
395
|
-
['drive', 'comments', 'reply', 'f1', 'c1', 'actually wait', '--action=reopen'],
|
|
396
|
+
['drive', 'comments', 'reply', pos('f1'), pos('c1'), pos('actually wait'), '--action=reopen'],
|
|
396
397
|
{ account: undefined },
|
|
397
398
|
);
|
|
398
399
|
});
|
|
@@ -404,7 +405,7 @@ describe('gog_drive_comments_resolve', () => {
|
|
|
404
405
|
it('calls runOrDiagnose with fileId and commentId', async () => {
|
|
405
406
|
await harness.callTool('gog_drive_comments_resolve', { fileId: 'f1', commentId: 'c1' });
|
|
406
407
|
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
407
|
-
['drive', 'comments', 'resolve', 'f1', 'c1'],
|
|
408
|
+
['drive', 'comments', 'resolve', pos('f1'), pos('c1')],
|
|
408
409
|
{ account: undefined },
|
|
409
410
|
);
|
|
410
411
|
});
|
|
@@ -412,7 +413,7 @@ describe('gog_drive_comments_resolve', () => {
|
|
|
412
413
|
it('forwards account', async () => {
|
|
413
414
|
await harness.callTool('gog_drive_comments_resolve', { fileId: 'f1', commentId: 'c1', account: 'a@b.com' });
|
|
414
415
|
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
415
|
-
['drive', 'comments', 'resolve', 'f1', 'c1'],
|
|
416
|
+
['drive', 'comments', 'resolve', pos('f1'), pos('c1')],
|
|
416
417
|
{ account: 'a@b.com' },
|
|
417
418
|
);
|
|
418
419
|
});
|
|
@@ -422,7 +423,7 @@ describe('gog_drive_comments_reopen', () => {
|
|
|
422
423
|
it('calls runOrDiagnose with fileId and commentId', async () => {
|
|
423
424
|
await harness.callTool('gog_drive_comments_reopen', { fileId: 'f1', commentId: 'c1' });
|
|
424
425
|
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
425
|
-
['drive', 'comments', 'reopen', 'f1', 'c1'],
|
|
426
|
+
['drive', 'comments', 'reopen', pos('f1'), pos('c1')],
|
|
426
427
|
{ account: undefined },
|
|
427
428
|
);
|
|
428
429
|
});
|
|
@@ -559,7 +560,7 @@ describe('gog_drive_labels_list', () => {
|
|
|
559
560
|
describe('gog_drive_labels_get', () => {
|
|
560
561
|
it('calls runOrDiagnose with name only', async () => {
|
|
561
562
|
await harness.callTool('gog_drive_labels_get', { name: 'labels/abc' });
|
|
562
|
-
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['drive', 'labels', 'get', 'labels/abc'], { account: undefined });
|
|
563
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['drive', 'labels', 'get', pos('labels/abc')], { account: undefined });
|
|
563
564
|
});
|
|
564
565
|
|
|
565
566
|
it('passes all flags', async () => {
|
|
@@ -567,14 +568,14 @@ describe('gog_drive_labels_get', () => {
|
|
|
567
568
|
name: 'labels/abc', language: 'en', view: 'LABEL_VIEW_BASIC', adminAccess: true,
|
|
568
569
|
});
|
|
569
570
|
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
570
|
-
['drive', 'labels', 'get', 'labels/abc', '--language=en', '--view=LABEL_VIEW_BASIC', '--admin-access'],
|
|
571
|
+
['drive', 'labels', 'get', pos('labels/abc'), '--language=en', '--view=LABEL_VIEW_BASIC', '--admin-access'],
|
|
571
572
|
{ account: undefined },
|
|
572
573
|
);
|
|
573
574
|
});
|
|
574
575
|
|
|
575
576
|
it('omits --admin-access when false', async () => {
|
|
576
577
|
await harness.callTool('gog_drive_labels_get', { name: 'labels/abc', adminAccess: false });
|
|
577
|
-
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['drive', 'labels', 'get', 'labels/abc'], { account: undefined });
|
|
578
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['drive', 'labels', 'get', pos('labels/abc')], { account: undefined });
|
|
578
579
|
});
|
|
579
580
|
});
|
|
580
581
|
|
|
@@ -582,7 +583,7 @@ describe('gog_drive_labels_file_list', () => {
|
|
|
582
583
|
it('calls runOrDiagnose with fileId only', async () => {
|
|
583
584
|
await harness.callTool('gog_drive_labels_file_list', { fileId: 'f1' });
|
|
584
585
|
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
585
|
-
['drive', 'labels', 'file', 'list', 'f1'],
|
|
586
|
+
['drive', 'labels', 'file', 'list', pos('f1')],
|
|
586
587
|
{ account: undefined },
|
|
587
588
|
);
|
|
588
589
|
});
|
|
@@ -590,7 +591,7 @@ describe('gog_drive_labels_file_list', () => {
|
|
|
590
591
|
it('passes --max and --page when provided', async () => {
|
|
591
592
|
await harness.callTool('gog_drive_labels_file_list', { fileId: 'f1', max: 25, page: 'tok' });
|
|
592
593
|
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
593
|
-
['drive', 'labels', 'file', 'list', 'f1', '--max=25', '--page=tok'],
|
|
594
|
+
['drive', 'labels', 'file', 'list', pos('f1'), '--max=25', '--page=tok'],
|
|
594
595
|
{ account: undefined },
|
|
595
596
|
);
|
|
596
597
|
});
|
|
@@ -600,7 +601,7 @@ describe('gog_drive_labels_file_apply', () => {
|
|
|
600
601
|
it('calls runOrDiagnose with fileId and labelId only', async () => {
|
|
601
602
|
await harness.callTool('gog_drive_labels_file_apply', { fileId: 'f1', labelId: 'l1' });
|
|
602
603
|
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
603
|
-
['drive', 'labels', 'file', 'apply', 'f1', 'l1'],
|
|
604
|
+
['drive', 'labels', 'file', 'apply', pos('f1'), pos('l1')],
|
|
604
605
|
{ account: undefined },
|
|
605
606
|
);
|
|
606
607
|
});
|
|
@@ -619,7 +620,7 @@ describe('gog_drive_labels_file_apply', () => {
|
|
|
619
620
|
});
|
|
620
621
|
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
621
622
|
[
|
|
622
|
-
'drive', 'labels', 'file', 'apply', 'f1', 'l1',
|
|
623
|
+
'drive', 'labels', 'file', 'apply', pos('f1'), pos('l1'),
|
|
623
624
|
'--text=t1=hello', '--text=t2=world',
|
|
624
625
|
'--selection=s1=c1,c2',
|
|
625
626
|
'--integer=i1=42',
|
|
@@ -637,7 +638,7 @@ describe('gog_drive_labels_file_apply', () => {
|
|
|
637
638
|
fileId: 'f1', labelId: 'l1', text: [], selection: [], integer: [], date: [], user: [], unset: [],
|
|
638
639
|
});
|
|
639
640
|
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
640
|
-
['drive', 'labels', 'file', 'apply', 'f1', 'l1'],
|
|
641
|
+
['drive', 'labels', 'file', 'apply', pos('f1'), pos('l1')],
|
|
641
642
|
{ account: undefined },
|
|
642
643
|
);
|
|
643
644
|
});
|
|
@@ -647,7 +648,7 @@ describe('gog_drive_labels_file_remove', () => {
|
|
|
647
648
|
it('calls runOrDiagnose with fileId and labelId', async () => {
|
|
648
649
|
await harness.callTool('gog_drive_labels_file_remove', { fileId: 'f1', labelId: 'l1' });
|
|
649
650
|
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
650
|
-
['drive', 'labels', 'file', 'remove', 'f1', 'l1', '--force'],
|
|
651
|
+
['drive', 'labels', 'file', 'remove', pos('f1'), pos('l1'), '--force'],
|
|
651
652
|
{ account: undefined },
|
|
652
653
|
);
|
|
653
654
|
});
|
|
@@ -732,7 +733,7 @@ describe('gog_drive_audit_user', () => {
|
|
|
732
733
|
it('calls runOrDiagnose with user only', async () => {
|
|
733
734
|
await harness.callTool('gog_drive_audit_user', { user: 'a@b.com' });
|
|
734
735
|
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
735
|
-
['drive', 'audit', 'user', 'a@b.com'],
|
|
736
|
+
['drive', 'audit', 'user', pos('a@b.com')],
|
|
736
737
|
{ account: undefined },
|
|
737
738
|
);
|
|
738
739
|
});
|
|
@@ -742,7 +743,7 @@ describe('gog_drive_audit_user', () => {
|
|
|
742
743
|
user: 'a@b.com', file: 'f1', parent: 'fo1', depth: 3, max: 100, noAllDrives: true,
|
|
743
744
|
});
|
|
744
745
|
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
745
|
-
['drive', 'audit', 'user', 'a@b.com', '--file=f1', '--parent=fo1', '--depth=3', '--max=100', '--no-all-drives'],
|
|
746
|
+
['drive', 'audit', 'user', pos('a@b.com'), '--file=f1', '--parent=fo1', '--depth=3', '--max=100', '--no-all-drives'],
|
|
746
747
|
{ account: undefined },
|
|
747
748
|
);
|
|
748
749
|
});
|
|
@@ -750,7 +751,7 @@ describe('gog_drive_audit_user', () => {
|
|
|
750
751
|
it('handles depth/max=0 and omits noAllDrives when false', async () => {
|
|
751
752
|
await harness.callTool('gog_drive_audit_user', { user: 'a@b.com', depth: 0, max: 0, noAllDrives: false });
|
|
752
753
|
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
753
|
-
['drive', 'audit', 'user', 'a@b.com', '--depth=0', '--max=0'],
|
|
754
|
+
['drive', 'audit', 'user', pos('a@b.com'), '--depth=0', '--max=0'],
|
|
754
755
|
{ account: undefined },
|
|
755
756
|
);
|
|
756
757
|
});
|
|
@@ -761,7 +762,7 @@ describe('gog_drive_revisions_list', () => {
|
|
|
761
762
|
it('lists revisions with pagination', async () => {
|
|
762
763
|
await harness.callTool('gog_drive_revisions_list', { fileId: 'f1', max: 10, page: 'tok', all: true });
|
|
763
764
|
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
764
|
-
['drive', 'revisions', 'list', 'f1', '--max=10', '--page=tok', '--all'],
|
|
765
|
+
['drive', 'revisions', 'list', pos('f1'), '--max=10', '--page=tok', '--all'],
|
|
765
766
|
{ account: undefined },
|
|
766
767
|
);
|
|
767
768
|
});
|
|
@@ -771,7 +772,7 @@ describe('gog_drive_revisions_get', () => {
|
|
|
771
772
|
it('gets one revision', async () => {
|
|
772
773
|
await harness.callTool('gog_drive_revisions_get', { fileId: 'f1', revisionId: 'r3', account: 'a@b.com' });
|
|
773
774
|
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
774
|
-
['drive', 'revisions', 'get', 'f1', 'r3'],
|
|
775
|
+
['drive', 'revisions', 'get', pos('f1'), pos('r3')],
|
|
775
776
|
{ account: 'a@b.com' },
|
|
776
777
|
);
|
|
777
778
|
});
|
|
@@ -782,7 +783,7 @@ describe('gog_drive_shortcut_create', () => {
|
|
|
782
783
|
it('creates a shortcut to a target in a folder', async () => {
|
|
783
784
|
await harness.callTool('gog_drive_shortcut_create', { targetId: 'f1', parent: 'folder9' });
|
|
784
785
|
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
785
|
-
['drive', 'shortcut', 'create', 'f1', '--parent=folder9'],
|
|
786
|
+
['drive', 'shortcut', 'create', pos('f1'), '--parent=folder9'],
|
|
786
787
|
{ account: undefined },
|
|
787
788
|
);
|
|
788
789
|
});
|
|
@@ -790,7 +791,7 @@ describe('gog_drive_shortcut_create', () => {
|
|
|
790
791
|
it('passes an explicit shortcut name', async () => {
|
|
791
792
|
await harness.callTool('gog_drive_shortcut_create', { targetId: 'f1', parent: 'folder9', name: 'My Link', account: 'a@b.com' });
|
|
792
793
|
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
793
|
-
['drive', 'shortcut', 'create', 'f1', '--parent=folder9', '--name=My Link'],
|
|
794
|
+
['drive', 'shortcut', 'create', pos('f1'), '--parent=folder9', '--name=My Link'],
|
|
794
795
|
{ account: 'a@b.com' },
|
|
795
796
|
);
|
|
796
797
|
});
|
|
@@ -810,3 +811,40 @@ describe('gog_drive_upload ifVersion description', () => {
|
|
|
810
811
|
expect(desc).toMatch(/number/i);
|
|
811
812
|
});
|
|
812
813
|
});
|
|
814
|
+
|
|
815
|
+
// SEC-3/SEC-4: every model-supplied server path must resolve inside an
|
|
816
|
+
// operator-configured root (GOG_FILE_ROOTS). The suite runs with '/' so the
|
|
817
|
+
// arg-shape tests can use any path; these narrow it.
|
|
818
|
+
async function withFileRoots<T>(roots: string, fn: () => Promise<T>): Promise<T> {
|
|
819
|
+
const prev = process.env.GOG_FILE_ROOTS;
|
|
820
|
+
process.env.GOG_FILE_ROOTS = roots;
|
|
821
|
+
try {
|
|
822
|
+
return await fn();
|
|
823
|
+
} finally {
|
|
824
|
+
process.env.GOG_FILE_ROOTS = prev;
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
describe('server paths are confined to GOG_FILE_ROOTS', () => {
|
|
829
|
+
it.each([
|
|
830
|
+
['gog_drive_download', {"fileId": "f1", "out": "/Users/me/.zshrc", "overwrite": true}, 'out'],
|
|
831
|
+
['gog_drive_upload', {"localPath": "/Users/me/.config/gogcli/credentials.json"}, 'localPath'],
|
|
832
|
+
['gog_drive_sync_push', {"localPath": "/Users/me/.ssh", "parent": "p"}, 'localPath'],
|
|
833
|
+
] as Array<[string, Record<string, unknown>, string]>)('%s refuses %j outside the roots', async (tool, args, param) => {
|
|
834
|
+
const h = harness;
|
|
835
|
+
const result = await withFileRoots('/srv/gog-files', () => h.callTool(tool, args));
|
|
836
|
+
expect(result.isError).toBe(true);
|
|
837
|
+
expect(result.content[0].text).toMatch(new RegExp(`${param} ".*" is outside the directories`));
|
|
838
|
+
expect(lib.runOrDiagnose).not.toHaveBeenCalled();
|
|
839
|
+
});
|
|
840
|
+
|
|
841
|
+
// Clients auto-approve readOnlyHint tools; these write (and can overwrite)
|
|
842
|
+
// files on the gog host, so they must not claim to be read-only.
|
|
843
|
+
it.each(["gog_drive_download"])('%s is not advertised as read-only and is marked destructive', async (name) => {
|
|
844
|
+
const h = harness;
|
|
845
|
+
const { tools } = await h.client.listTools();
|
|
846
|
+
const tool = tools.find((t) => t.name === name)!;
|
|
847
|
+
expect(tool.annotations?.readOnlyHint).not.toBe(true);
|
|
848
|
+
expect(tool.annotations?.destructiveHint).toBe(true);
|
|
849
|
+
});
|
|
850
|
+
});
|
package/vitest.config.ts
CHANGED
|
@@ -2,6 +2,11 @@ import { defineConfig } from 'vitest/config';
|
|
|
2
2
|
|
|
3
3
|
export default defineConfig({
|
|
4
4
|
test: {
|
|
5
|
+
// The arg-shape tests pass arbitrary server paths; confinement is exercised
|
|
6
|
+
// explicitly by the tests that narrow GOG_FILE_ROOTS themselves.
|
|
7
|
+
env: {
|
|
8
|
+
GOG_FILE_ROOTS: '/',
|
|
9
|
+
},
|
|
5
10
|
coverage: {
|
|
6
11
|
provider: 'v8',
|
|
7
12
|
include: ['src/**/*.ts'],
|