gogcli-mcp 2.25.0 → 2.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +10 -5
- package/SKILL.md +11 -6
- package/dist/index.js +553 -128
- package/dist/lib.js +480 -89
- package/manifest.json +89 -1
- package/mint.yaml +106 -0
- package/package.json +5 -5
- package/server.json +2 -2
- package/src/gmail-results.ts +29 -2
- package/src/lib.ts +8 -0
- package/src/runner.ts +1 -1
- package/src/server.ts +6 -0
- package/src/tools/appscript.ts +173 -0
- package/src/tools/calendar.ts +56 -2
- package/src/tools/chat.ts +253 -0
- package/src/tools/gmail.ts +136 -5
- package/src/tools/utils.ts +20 -0
- package/src/worker.ts +1 -1
- package/tests/gmail-results.test.ts +47 -0
- package/tests/server.test.ts +61 -0
- package/tests/tools/appscript.test.ts +159 -0
- package/tests/tools/calendar.test.ts +121 -0
- package/tests/tools/chat.test.ts +284 -0
- package/tests/tools/gmail.test.ts +320 -0
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import { registerAppScriptTools } from '../../src/tools/appscript.js';
|
|
3
|
+
import * as runner from '../../src/runner.js';
|
|
4
|
+
import { createTestHarness } from '@chrischall/mcp-utils/test';
|
|
5
|
+
|
|
6
|
+
vi.mock('../../src/runner.js');
|
|
7
|
+
|
|
8
|
+
const setupHandlers = () => createTestHarness(registerAppScriptTools);
|
|
9
|
+
|
|
10
|
+
beforeEach(() => vi.clearAllMocks());
|
|
11
|
+
|
|
12
|
+
describe('gog_appscript_get', () => {
|
|
13
|
+
it('gets project metadata', async () => {
|
|
14
|
+
vi.mocked(runner.run).mockResolvedValue('{}');
|
|
15
|
+
const harness = await setupHandlers();
|
|
16
|
+
await harness.callTool('gog_appscript_get', { scriptId: 'S1' });
|
|
17
|
+
expect(runner.run).toHaveBeenCalledWith(['appscript', 'get', 'S1'], { account: undefined });
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('returns error text on failure', async () => {
|
|
21
|
+
vi.mocked(runner.run).mockRejectedValue(new Error('Get failed'));
|
|
22
|
+
const harness = await setupHandlers();
|
|
23
|
+
const result = await harness.callTool('gog_appscript_get', { scriptId: 'S1' });
|
|
24
|
+
expect(result.content[0].text).toBe('Error: Get failed');
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
describe('gog_appscript_content', () => {
|
|
29
|
+
it('reads the project source', async () => {
|
|
30
|
+
vi.mocked(runner.run).mockResolvedValue('{}');
|
|
31
|
+
const harness = await setupHandlers();
|
|
32
|
+
await harness.callTool('gog_appscript_content', { scriptId: 'S1', account: 'me@x.com' });
|
|
33
|
+
expect(runner.run).toHaveBeenCalledWith(['appscript', 'content', 'S1'], { account: 'me@x.com' });
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe('gog_appscript_pull', () => {
|
|
38
|
+
it('pulls into a directory', async () => {
|
|
39
|
+
vi.mocked(runner.run).mockResolvedValue('{}');
|
|
40
|
+
const harness = await setupHandlers();
|
|
41
|
+
await harness.callTool('gog_appscript_pull', { scriptId: 'S1', dir: '/tmp/proj' });
|
|
42
|
+
expect(runner.run).toHaveBeenCalledWith(['appscript', 'pull', 'S1', '/tmp/proj'], { account: undefined });
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('passes --overwrite', async () => {
|
|
46
|
+
vi.mocked(runner.run).mockResolvedValue('{}');
|
|
47
|
+
const harness = await setupHandlers();
|
|
48
|
+
await harness.callTool('gog_appscript_pull', { scriptId: 'S1', dir: '/tmp/proj', overwrite: true });
|
|
49
|
+
expect(runner.run).toHaveBeenCalledWith(
|
|
50
|
+
['appscript', 'pull', 'S1', '/tmp/proj', '--overwrite'],
|
|
51
|
+
{ account: undefined },
|
|
52
|
+
);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// The directory resolves where gog runs, which is a different machine on the
|
|
56
|
+
// hosted connector. A caller who does not know that gets a "success" whose
|
|
57
|
+
// files they cannot reach.
|
|
58
|
+
it('says in its description where the directory resolves', async () => {
|
|
59
|
+
const { McpServer } = await import('@modelcontextprotocol/sdk/server/mcp.js');
|
|
60
|
+
const server = new McpServer({ name: 'test', version: '0.0.0' });
|
|
61
|
+
const configs = new Map<string, { description?: string }>();
|
|
62
|
+
vi.spyOn(server, 'registerTool').mockImplementation((name, config) => {
|
|
63
|
+
configs.set(name, config as { description?: string });
|
|
64
|
+
return undefined as never;
|
|
65
|
+
});
|
|
66
|
+
registerAppScriptTools(server);
|
|
67
|
+
const desc = configs.get('gog_appscript_pull')?.description ?? '';
|
|
68
|
+
expect(desc).toContain('RESOLVED WHERE GOG RUNS');
|
|
69
|
+
expect(desc).toMatch(/gog_appscript_content/);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe('gog_appscript_create', () => {
|
|
74
|
+
it('creates a standalone project', async () => {
|
|
75
|
+
vi.mocked(runner.run).mockResolvedValue('{}');
|
|
76
|
+
const harness = await setupHandlers();
|
|
77
|
+
await harness.callTool('gog_appscript_create', { title: 'Helpers' });
|
|
78
|
+
expect(runner.run).toHaveBeenCalledWith(['appscript', 'create', '--title=Helpers'], { account: undefined });
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('binds the project to a Drive file', async () => {
|
|
82
|
+
vi.mocked(runner.run).mockResolvedValue('{}');
|
|
83
|
+
const harness = await setupHandlers();
|
|
84
|
+
await harness.callTool('gog_appscript_create', { title: 'Bound', parentId: 'FILE1' });
|
|
85
|
+
expect(runner.run).toHaveBeenCalledWith(
|
|
86
|
+
['appscript', 'create', '--title=Bound', '--parent-id=FILE1'],
|
|
87
|
+
{ account: undefined },
|
|
88
|
+
);
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
describe('gog_appscript_deployments', () => {
|
|
93
|
+
it('lists deployments with pagination', async () => {
|
|
94
|
+
vi.mocked(runner.run).mockResolvedValue('{}');
|
|
95
|
+
const harness = await setupHandlers();
|
|
96
|
+
await harness.callTool('gog_appscript_deployments', { scriptId: 'S1', max: 10, pageToken: 'tok' });
|
|
97
|
+
expect(runner.run).toHaveBeenCalledWith(
|
|
98
|
+
['appscript', 'deployments', 'S1', '--max=10', '--page=tok'],
|
|
99
|
+
{ account: undefined },
|
|
100
|
+
);
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
describe('gog_appscript_versions', () => {
|
|
105
|
+
it('lists versions', async () => {
|
|
106
|
+
vi.mocked(runner.run).mockResolvedValue('{}');
|
|
107
|
+
const harness = await setupHandlers();
|
|
108
|
+
await harness.callTool('gog_appscript_versions', { scriptId: 'S1', all: true });
|
|
109
|
+
expect(runner.run).toHaveBeenCalledWith(['appscript', 'versions', 'S1', '--all'], { account: undefined });
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
describe('gog_appscript_run_function', () => {
|
|
114
|
+
it('runs a deployed function', async () => {
|
|
115
|
+
vi.mocked(runner.run).mockResolvedValue('{}');
|
|
116
|
+
const harness = await setupHandlers();
|
|
117
|
+
await harness.callTool('gog_appscript_run_function', { scriptId: 'S1', functionName: 'doWork' });
|
|
118
|
+
expect(runner.run).toHaveBeenCalledWith(['appscript', 'run', 'S1', 'doWork'], { account: undefined });
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('passes params and --dev-mode', async () => {
|
|
122
|
+
vi.mocked(runner.run).mockResolvedValue('{}');
|
|
123
|
+
const harness = await setupHandlers();
|
|
124
|
+
await harness.callTool('gog_appscript_run_function', {
|
|
125
|
+
scriptId: 'S1', functionName: 'doWork', params: '["a",1]', devMode: true,
|
|
126
|
+
});
|
|
127
|
+
expect(runner.run).toHaveBeenCalledWith(
|
|
128
|
+
['appscript', 'run', 'S1', 'doWork', '--params=["a",1]', '--dev-mode'],
|
|
129
|
+
{ account: undefined },
|
|
130
|
+
);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('rejects params that are not a JSON array before spawning gog', async () => {
|
|
134
|
+
const harness = await setupHandlers();
|
|
135
|
+
const result = await harness.callTool('gog_appscript_run_function', {
|
|
136
|
+
scriptId: 'S1', functionName: 'doWork', params: '{"a":1}',
|
|
137
|
+
});
|
|
138
|
+
expect(result.isError).toBe(true);
|
|
139
|
+
expect(runner.run).not.toHaveBeenCalled();
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('rejects params that are not JSON at all', async () => {
|
|
143
|
+
const harness = await setupHandlers();
|
|
144
|
+
const result = await harness.callTool('gog_appscript_run_function', {
|
|
145
|
+
scriptId: 'S1', functionName: 'doWork', params: 'a,1',
|
|
146
|
+
});
|
|
147
|
+
expect(result.isError).toBe(true);
|
|
148
|
+
expect(runner.run).not.toHaveBeenCalled();
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
describe('gog_appscript_run', () => {
|
|
153
|
+
it('passes the subcommand and args through', async () => {
|
|
154
|
+
vi.mocked(runner.run).mockResolvedValue('{}');
|
|
155
|
+
const harness = await setupHandlers();
|
|
156
|
+
await harness.callTool('gog_appscript_run', { subcommand: 'get', args: ['S1'] });
|
|
157
|
+
expect(runner.run).toHaveBeenCalledWith(['appscript', 'get', 'S1'], { account: undefined });
|
|
158
|
+
});
|
|
159
|
+
});
|
|
@@ -457,3 +457,124 @@ describe('gog_calendar_events — pagination (previously absent entirely)', () =
|
|
|
457
457
|
expect(out).not.toHaveProperty('nextPageToken');
|
|
458
458
|
});
|
|
459
459
|
});
|
|
460
|
+
|
|
461
|
+
// Reminders (gog >= 0.38.0 for --no-reminders, openclaw/gogcli#1002/#1016).
|
|
462
|
+
// Three distinct states share these two params and gog spells each one
|
|
463
|
+
// differently, so each is pinned separately: custom overrides, "no reminders at
|
|
464
|
+
// all", and — on update only — "go back to whatever the calendar says", which
|
|
465
|
+
// is an EMPTY --reminder rather than a flag of its own. All three were verified
|
|
466
|
+
// against a real gog 0.38.0 with --dry-run.
|
|
467
|
+
describe('gog_calendar_create reminders', () => {
|
|
468
|
+
it('passes each reminder as its own repeated flag', async () => {
|
|
469
|
+
vi.mocked(runner.run).mockResolvedValue('{}');
|
|
470
|
+
const harness = await setupHandlers();
|
|
471
|
+
await harness.callTool('gog_calendar_create', {
|
|
472
|
+
calendarId: 'primary',
|
|
473
|
+
summary: 'Standup',
|
|
474
|
+
from: '2026-04-14T09:00:00Z',
|
|
475
|
+
to: '2026-04-14T09:30:00Z',
|
|
476
|
+
reminders: ['popup:30m', 'email:1d'],
|
|
477
|
+
});
|
|
478
|
+
expect(runner.run).toHaveBeenCalledWith(
|
|
479
|
+
['calendar', 'create', 'primary', '--summary=Standup', '--from=2026-04-14T09:00:00Z', '--to=2026-04-14T09:30:00Z',
|
|
480
|
+
'--reminder=popup:30m', '--reminder=email:1d'],
|
|
481
|
+
{ account: undefined },
|
|
482
|
+
);
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
it('passes --no-reminders', async () => {
|
|
486
|
+
vi.mocked(runner.run).mockResolvedValue('{}');
|
|
487
|
+
const harness = await setupHandlers();
|
|
488
|
+
await harness.callTool('gog_calendar_create', {
|
|
489
|
+
calendarId: 'primary',
|
|
490
|
+
summary: 'Quiet',
|
|
491
|
+
from: '2026-04-14T09:00:00Z',
|
|
492
|
+
to: '2026-04-14T09:30:00Z',
|
|
493
|
+
noReminders: true,
|
|
494
|
+
});
|
|
495
|
+
expect(runner.run).toHaveBeenCalledWith(
|
|
496
|
+
['calendar', 'create', 'primary', '--summary=Quiet', '--from=2026-04-14T09:00:00Z', '--to=2026-04-14T09:30:00Z',
|
|
497
|
+
'--no-reminders'],
|
|
498
|
+
{ account: undefined },
|
|
499
|
+
);
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
it('rejects reminders and noReminders together, as gog does', async () => {
|
|
503
|
+
const harness = await setupHandlers();
|
|
504
|
+
const result = await harness.callTool('gog_calendar_create', {
|
|
505
|
+
calendarId: 'primary',
|
|
506
|
+
summary: 'Both',
|
|
507
|
+
from: '2026-04-14T09:00:00Z',
|
|
508
|
+
to: '2026-04-14T09:30:00Z',
|
|
509
|
+
reminders: ['popup:10m'],
|
|
510
|
+
noReminders: true,
|
|
511
|
+
});
|
|
512
|
+
expect(result.isError).toBe(true);
|
|
513
|
+
expect(runner.run).not.toHaveBeenCalled();
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
it('rejects more than the five reminders Google allows', async () => {
|
|
517
|
+
const harness = await setupHandlers();
|
|
518
|
+
const result = await harness.callTool('gog_calendar_create', {
|
|
519
|
+
calendarId: 'primary',
|
|
520
|
+
summary: 'Too many',
|
|
521
|
+
from: '2026-04-14T09:00:00Z',
|
|
522
|
+
to: '2026-04-14T09:30:00Z',
|
|
523
|
+
reminders: ['popup:1m', 'popup:2m', 'popup:3m', 'popup:4m', 'popup:5m', 'popup:6m'],
|
|
524
|
+
});
|
|
525
|
+
expect(result.isError).toBe(true);
|
|
526
|
+
expect(runner.run).not.toHaveBeenCalled();
|
|
527
|
+
});
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
describe('gog_calendar_update reminders', () => {
|
|
531
|
+
it('replaces the overrides', async () => {
|
|
532
|
+
vi.mocked(runner.run).mockResolvedValue('{}');
|
|
533
|
+
const harness = await setupHandlers();
|
|
534
|
+
await harness.callTool('gog_calendar_update', {
|
|
535
|
+
calendarId: 'primary', eventId: 'evt1', reminders: ['popup:15m'],
|
|
536
|
+
});
|
|
537
|
+
expect(runner.run).toHaveBeenCalledWith(
|
|
538
|
+
['calendar', 'update', 'primary', 'evt1', '--reminder=popup:15m'],
|
|
539
|
+
{ account: undefined },
|
|
540
|
+
);
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
// Verified live: `calendar update … --reminder= --dry-run` patches
|
|
544
|
+
// reminders.useDefault=true with overrides cleared.
|
|
545
|
+
it('restores the calendar defaults with an empty reminder list', async () => {
|
|
546
|
+
vi.mocked(runner.run).mockResolvedValue('{}');
|
|
547
|
+
const harness = await setupHandlers();
|
|
548
|
+
await harness.callTool('gog_calendar_update', {
|
|
549
|
+
calendarId: 'primary', eventId: 'evt1', reminders: [],
|
|
550
|
+
});
|
|
551
|
+
expect(runner.run).toHaveBeenCalledWith(
|
|
552
|
+
['calendar', 'update', 'primary', 'evt1', '--reminder='],
|
|
553
|
+
{ account: undefined },
|
|
554
|
+
);
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
it('turns every reminder off', async () => {
|
|
558
|
+
vi.mocked(runner.run).mockResolvedValue('{}');
|
|
559
|
+
const harness = await setupHandlers();
|
|
560
|
+
await harness.callTool('gog_calendar_update', {
|
|
561
|
+
calendarId: 'primary', eventId: 'evt1', noReminders: true,
|
|
562
|
+
});
|
|
563
|
+
expect(runner.run).toHaveBeenCalledWith(
|
|
564
|
+
['calendar', 'update', 'primary', 'evt1', '--no-reminders'],
|
|
565
|
+
{ account: undefined },
|
|
566
|
+
);
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
it('leaves reminders alone when neither param is given', async () => {
|
|
570
|
+
vi.mocked(runner.run).mockResolvedValue('{}');
|
|
571
|
+
const harness = await setupHandlers();
|
|
572
|
+
await harness.callTool('gog_calendar_update', {
|
|
573
|
+
calendarId: 'primary', eventId: 'evt1', summary: 'Renamed',
|
|
574
|
+
});
|
|
575
|
+
expect(runner.run).toHaveBeenCalledWith(
|
|
576
|
+
['calendar', 'update', 'primary', 'evt1', '--summary=Renamed'],
|
|
577
|
+
{ account: undefined },
|
|
578
|
+
);
|
|
579
|
+
});
|
|
580
|
+
});
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import { registerChatTools } from '../../src/tools/chat.js';
|
|
3
|
+
import * as runner from '../../src/runner.js';
|
|
4
|
+
import { createTestHarness } from '@chrischall/mcp-utils/test';
|
|
5
|
+
|
|
6
|
+
vi.mock('../../src/runner.js');
|
|
7
|
+
|
|
8
|
+
const setupHandlers = () => createTestHarness(registerChatTools);
|
|
9
|
+
|
|
10
|
+
beforeEach(() => vi.clearAllMocks());
|
|
11
|
+
|
|
12
|
+
describe('gog_chat_spaces_list', () => {
|
|
13
|
+
it('lists spaces', async () => {
|
|
14
|
+
vi.mocked(runner.run).mockResolvedValue('{"spaces":[]}');
|
|
15
|
+
const harness = await setupHandlers();
|
|
16
|
+
await harness.callTool('gog_chat_spaces_list', {});
|
|
17
|
+
expect(runner.run).toHaveBeenCalledWith(['chat', 'spaces', 'list'], { account: undefined });
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('passes pagination flags', async () => {
|
|
21
|
+
vi.mocked(runner.run).mockResolvedValue('{"spaces":[]}');
|
|
22
|
+
const harness = await setupHandlers();
|
|
23
|
+
await harness.callTool('gog_chat_spaces_list', { max: 20, pageToken: 'tok', all: true });
|
|
24
|
+
expect(runner.run).toHaveBeenCalledWith(
|
|
25
|
+
['chat', 'spaces', 'list', '--max=20', '--page=tok', '--all'],
|
|
26
|
+
{ account: undefined },
|
|
27
|
+
);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('returns error text on failure', async () => {
|
|
31
|
+
vi.mocked(runner.run).mockRejectedValue(new Error('Spaces failed'));
|
|
32
|
+
const harness = await setupHandlers();
|
|
33
|
+
const result = await harness.callTool('gog_chat_spaces_list', {});
|
|
34
|
+
expect(result.content[0].text).toBe('Error: Spaces failed');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// The constraint is the ACCOUNT, not the token, so it has to be in the
|
|
38
|
+
// description a model reads — a source comment would not reach it. Every chat
|
|
39
|
+
// tool carries the note, because any of them can be the first one called.
|
|
40
|
+
it('warns in EVERY description that Chat is Workspace-only', async () => {
|
|
41
|
+
const { McpServer } = await import('@modelcontextprotocol/sdk/server/mcp.js');
|
|
42
|
+
const server = new McpServer({ name: 'test', version: '0.0.0' });
|
|
43
|
+
const configs = new Map<string, { description?: string }>();
|
|
44
|
+
vi.spyOn(server, 'registerTool').mockImplementation((name, config) => {
|
|
45
|
+
configs.set(name, config as { description?: string });
|
|
46
|
+
return undefined as never;
|
|
47
|
+
});
|
|
48
|
+
registerChatTools(server);
|
|
49
|
+
expect(configs.size).toBe(12);
|
|
50
|
+
for (const [name, config] of configs) {
|
|
51
|
+
expect(config.description, name).toMatch(/consumer accounts|Workspace/i);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
describe('gog_chat_spaces_find', () => {
|
|
57
|
+
it('searches by display name', async () => {
|
|
58
|
+
vi.mocked(runner.run).mockResolvedValue('{}');
|
|
59
|
+
const harness = await setupHandlers();
|
|
60
|
+
await harness.callTool('gog_chat_spaces_find', { displayName: 'Team' });
|
|
61
|
+
expect(runner.run).toHaveBeenCalledWith(['chat', 'spaces', 'find', 'Team'], { account: undefined });
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('passes --exact and --max', async () => {
|
|
65
|
+
vi.mocked(runner.run).mockResolvedValue('{}');
|
|
66
|
+
const harness = await setupHandlers();
|
|
67
|
+
await harness.callTool('gog_chat_spaces_find', { displayName: 'Team', exact: true, max: 5 });
|
|
68
|
+
expect(runner.run).toHaveBeenCalledWith(
|
|
69
|
+
['chat', 'spaces', 'find', 'Team', '--exact', '--max=5'],
|
|
70
|
+
{ account: undefined },
|
|
71
|
+
);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
describe('gog_chat_spaces_create', () => {
|
|
76
|
+
it('creates a space with members', async () => {
|
|
77
|
+
vi.mocked(runner.run).mockResolvedValue('{}');
|
|
78
|
+
const harness = await setupHandlers();
|
|
79
|
+
await harness.callTool('gog_chat_spaces_create', { displayName: 'Launch', members: ['a@b.com', 'c@d.com'] });
|
|
80
|
+
expect(runner.run).toHaveBeenCalledWith(
|
|
81
|
+
['chat', 'spaces', 'create', 'Launch', '--member=a@b.com', '--member=c@d.com'],
|
|
82
|
+
{ account: undefined },
|
|
83
|
+
);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('creates an empty space', async () => {
|
|
87
|
+
vi.mocked(runner.run).mockResolvedValue('{}');
|
|
88
|
+
const harness = await setupHandlers();
|
|
89
|
+
await harness.callTool('gog_chat_spaces_create', { displayName: 'Solo' });
|
|
90
|
+
expect(runner.run).toHaveBeenCalledWith(['chat', 'spaces', 'create', 'Solo'], { account: undefined });
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
describe('gog_chat_threads_list', () => {
|
|
95
|
+
it('lists threads in a space', async () => {
|
|
96
|
+
vi.mocked(runner.run).mockResolvedValue('{}');
|
|
97
|
+
const harness = await setupHandlers();
|
|
98
|
+
await harness.callTool('gog_chat_threads_list', { space: 'spaces/AAA', max: 10 });
|
|
99
|
+
expect(runner.run).toHaveBeenCalledWith(
|
|
100
|
+
['chat', 'threads', 'list', 'spaces/AAA', '--max=10'],
|
|
101
|
+
{ account: undefined },
|
|
102
|
+
);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
describe('gog_chat_messages_list', () => {
|
|
107
|
+
it('lists messages in a space', async () => {
|
|
108
|
+
vi.mocked(runner.run).mockResolvedValue('{}');
|
|
109
|
+
const harness = await setupHandlers();
|
|
110
|
+
await harness.callTool('gog_chat_messages_list', { space: 'spaces/AAA' });
|
|
111
|
+
expect(runner.run).toHaveBeenCalledWith(['chat', 'messages', 'list', 'spaces/AAA'], { account: undefined });
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('filters by thread, unread and order', async () => {
|
|
115
|
+
vi.mocked(runner.run).mockResolvedValue('{}');
|
|
116
|
+
const harness = await setupHandlers();
|
|
117
|
+
await harness.callTool('gog_chat_messages_list', {
|
|
118
|
+
space: 'spaces/AAA', thread: 'spaces/AAA/threads/T', unread: true, order: 'createTime desc', max: 50,
|
|
119
|
+
});
|
|
120
|
+
expect(runner.run).toHaveBeenCalledWith(
|
|
121
|
+
['chat', 'messages', 'list', 'spaces/AAA', '--thread=spaces/AAA/threads/T', '--unread',
|
|
122
|
+
'--order=createTime desc', '--max=50'],
|
|
123
|
+
{ account: undefined },
|
|
124
|
+
);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('rejects a sort order Chat does not accept', async () => {
|
|
128
|
+
const harness = await setupHandlers();
|
|
129
|
+
const result = await harness.callTool('gog_chat_messages_list', { space: 'spaces/AAA', order: 'newest' });
|
|
130
|
+
expect(result.isError).toBe(true);
|
|
131
|
+
expect(runner.run).not.toHaveBeenCalled();
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
describe('gog_chat_messages_send', () => {
|
|
136
|
+
it('sends text to a space', async () => {
|
|
137
|
+
vi.mocked(runner.run).mockResolvedValue('{}');
|
|
138
|
+
const harness = await setupHandlers();
|
|
139
|
+
await harness.callTool('gog_chat_messages_send', { space: 'spaces/AAA', text: 'hi' });
|
|
140
|
+
expect(runner.run).toHaveBeenCalledWith(
|
|
141
|
+
['chat', 'messages', 'send', 'spaces/AAA', '--text=hi'],
|
|
142
|
+
{ account: undefined },
|
|
143
|
+
);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('replies in a thread and attaches a server-side path', async () => {
|
|
147
|
+
vi.mocked(runner.run).mockResolvedValue('{}');
|
|
148
|
+
const harness = await setupHandlers();
|
|
149
|
+
await harness.callTool('gog_chat_messages_send', {
|
|
150
|
+
space: 'spaces/AAA', text: 'see this', thread: 'spaces/AAA/threads/T', attach: ['/tmp/a.png'],
|
|
151
|
+
});
|
|
152
|
+
expect(runner.run).toHaveBeenCalledWith(
|
|
153
|
+
['chat', 'messages', 'send', 'spaces/AAA', '--text=see this', '--thread=spaces/AAA/threads/T', '--attach=/tmp/a.png'],
|
|
154
|
+
{ account: undefined },
|
|
155
|
+
);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('carries caller-side bytes as a file arg', async () => {
|
|
159
|
+
vi.mocked(runner.run).mockResolvedValue('{}');
|
|
160
|
+
const harness = await setupHandlers();
|
|
161
|
+
await harness.callTool('gog_chat_messages_send', {
|
|
162
|
+
space: 'spaces/AAA',
|
|
163
|
+
text: 'chart',
|
|
164
|
+
attachInline: [{ filename: 'chart.png', contentBase64: Buffer.from('png').toString('base64') }],
|
|
165
|
+
});
|
|
166
|
+
const args = vi.mocked(runner.run).mock.calls[0][0];
|
|
167
|
+
expect(args).toContainEqual(expect.objectContaining({
|
|
168
|
+
kind: 'file', flag: 'attach', filename: 'chart.png', encoding: 'base64',
|
|
169
|
+
}));
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it('sends an attachment with no text at all', async () => {
|
|
173
|
+
vi.mocked(runner.run).mockResolvedValue('{}');
|
|
174
|
+
const harness = await setupHandlers();
|
|
175
|
+
await harness.callTool('gog_chat_messages_send', { space: 'spaces/AAA', attach: ['/tmp/a.png'] });
|
|
176
|
+
expect(runner.run).toHaveBeenCalledWith(
|
|
177
|
+
['chat', 'messages', 'send', 'spaces/AAA', '--attach=/tmp/a.png'],
|
|
178
|
+
{ account: undefined },
|
|
179
|
+
);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('refuses a message with neither text nor an attachment', async () => {
|
|
183
|
+
const harness = await setupHandlers();
|
|
184
|
+
const result = await harness.callTool('gog_chat_messages_send', { space: 'spaces/AAA' });
|
|
185
|
+
expect(result.isError).toBe(true);
|
|
186
|
+
expect(runner.run).not.toHaveBeenCalled();
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
describe('gog_chat_dm_send', () => {
|
|
191
|
+
it('sends a direct message by email', async () => {
|
|
192
|
+
vi.mocked(runner.run).mockResolvedValue('{}');
|
|
193
|
+
const harness = await setupHandlers();
|
|
194
|
+
await harness.callTool('gog_chat_dm_send', { email: 'a@b.com', text: 'hi' });
|
|
195
|
+
expect(runner.run).toHaveBeenCalledWith(
|
|
196
|
+
['chat', 'dm', 'send', 'a@b.com', '--text=hi'],
|
|
197
|
+
{ account: undefined },
|
|
198
|
+
);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it('replies in an existing DM thread', async () => {
|
|
202
|
+
vi.mocked(runner.run).mockResolvedValue('{}');
|
|
203
|
+
const harness = await setupHandlers();
|
|
204
|
+
await harness.callTool('gog_chat_dm_send', { email: 'a@b.com', text: 'hi', thread: 'spaces/D/threads/T' });
|
|
205
|
+
expect(runner.run).toHaveBeenCalledWith(
|
|
206
|
+
['chat', 'dm', 'send', 'a@b.com', '--text=hi', '--thread=spaces/D/threads/T'],
|
|
207
|
+
{ account: undefined },
|
|
208
|
+
);
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
describe('gog_chat_dm_space', () => {
|
|
213
|
+
it('resolves the DM space for an address', async () => {
|
|
214
|
+
vi.mocked(runner.run).mockResolvedValue('{}');
|
|
215
|
+
const harness = await setupHandlers();
|
|
216
|
+
await harness.callTool('gog_chat_dm_space', { email: 'a@b.com' });
|
|
217
|
+
expect(runner.run).toHaveBeenCalledWith(['chat', 'dm', 'space', 'a@b.com'], { account: undefined });
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
describe('gog_chat_reactions_list', () => {
|
|
222
|
+
it('lists reactions, qualifying a bare message ID with its space', async () => {
|
|
223
|
+
vi.mocked(runner.run).mockResolvedValue('{}');
|
|
224
|
+
const harness = await setupHandlers();
|
|
225
|
+
await harness.callTool('gog_chat_reactions_list', { message: 'MSG', space: 'spaces/AAA', max: 10 });
|
|
226
|
+
expect(runner.run).toHaveBeenCalledWith(
|
|
227
|
+
['chat', 'messages', 'reactions', 'list', 'MSG', '--space=spaces/AAA', '--max=10'],
|
|
228
|
+
{ account: undefined },
|
|
229
|
+
);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it('omits --space when the message is already fully qualified', async () => {
|
|
233
|
+
vi.mocked(runner.run).mockResolvedValue('{}');
|
|
234
|
+
const harness = await setupHandlers();
|
|
235
|
+
await harness.callTool('gog_chat_reactions_list', { message: 'spaces/AAA/messages/M' });
|
|
236
|
+
expect(runner.run).toHaveBeenCalledWith(
|
|
237
|
+
['chat', 'messages', 'reactions', 'list', 'spaces/AAA/messages/M'],
|
|
238
|
+
{ account: undefined },
|
|
239
|
+
);
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
describe('gog_chat_reactions_create', () => {
|
|
244
|
+
it('adds an emoji reaction', async () => {
|
|
245
|
+
vi.mocked(runner.run).mockResolvedValue('{}');
|
|
246
|
+
const harness = await setupHandlers();
|
|
247
|
+
await harness.callTool('gog_chat_reactions_create', { message: 'spaces/AAA/messages/M', emoji: '👍' });
|
|
248
|
+
expect(runner.run).toHaveBeenCalledWith(
|
|
249
|
+
['chat', 'messages', 'reactions', 'create', 'spaces/AAA/messages/M', '👍'],
|
|
250
|
+
{ account: undefined },
|
|
251
|
+
);
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
it('passes --space for a bare message ID', async () => {
|
|
255
|
+
vi.mocked(runner.run).mockResolvedValue('{}');
|
|
256
|
+
const harness = await setupHandlers();
|
|
257
|
+
await harness.callTool('gog_chat_reactions_create', { message: 'M', emoji: '👍', space: 'spaces/AAA' });
|
|
258
|
+
expect(runner.run).toHaveBeenCalledWith(
|
|
259
|
+
['chat', 'messages', 'reactions', 'create', 'M', '👍', '--space=spaces/AAA'],
|
|
260
|
+
{ account: undefined },
|
|
261
|
+
);
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
describe('gog_chat_reactions_delete', () => {
|
|
266
|
+
it('deletes a reaction by resource name', async () => {
|
|
267
|
+
vi.mocked(runner.run).mockResolvedValue('{}');
|
|
268
|
+
const harness = await setupHandlers();
|
|
269
|
+
await harness.callTool('gog_chat_reactions_delete', { reaction: 'spaces/AAA/messages/M/reactions/R' });
|
|
270
|
+
expect(runner.run).toHaveBeenCalledWith(
|
|
271
|
+
['chat', 'messages', 'reactions', 'delete', 'spaces/AAA/messages/M/reactions/R'],
|
|
272
|
+
{ account: undefined },
|
|
273
|
+
);
|
|
274
|
+
});
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
describe('gog_chat_run', () => {
|
|
278
|
+
it('passes the subcommand and args through', async () => {
|
|
279
|
+
vi.mocked(runner.run).mockResolvedValue('{}');
|
|
280
|
+
const harness = await setupHandlers();
|
|
281
|
+
await harness.callTool('gog_chat_run', { subcommand: 'spaces', args: ['list'] });
|
|
282
|
+
expect(runner.run).toHaveBeenCalledWith(['chat', 'spaces', 'list'], { account: undefined });
|
|
283
|
+
});
|
|
284
|
+
});
|