gogcli-mcp-classroom 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 +424 -123
- package/manifest.json +9 -2
- package/mint.yaml +1 -1
- package/package.json +2 -2
- package/server.json +2 -2
- package/src/tools/classroom-extra.ts +21 -20
- package/tests/tools/classroom-extra.test.ts +24 -23
- package/vitest.config.ts +5 -0
package/manifest.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"manifest_version": "0.3",
|
|
4
4
|
"name": "gogcli-mcp-classroom",
|
|
5
5
|
"display_name": "gogcli (Classroom)",
|
|
6
|
-
"version": "4.
|
|
6
|
+
"version": "4.3.0",
|
|
7
7
|
"description": "Extended Google Classroom for Claude via gogcli — auth + full Classroom support (courses, rosters, coursework, submissions, announcements, topics, invitations)",
|
|
8
8
|
"author": {
|
|
9
9
|
"name": "Chris Hall",
|
|
@@ -39,7 +39,8 @@
|
|
|
39
39
|
],
|
|
40
40
|
"env": {
|
|
41
41
|
"GOG_ACCOUNT": "${user_config.gog_account}",
|
|
42
|
-
"GOG_PATH": "${user_config.gog_path}"
|
|
42
|
+
"GOG_PATH": "${user_config.gog_path}",
|
|
43
|
+
"GOG_FILE_ROOTS": "${user_config.gog_file_roots}"
|
|
43
44
|
}
|
|
44
45
|
}
|
|
45
46
|
},
|
|
@@ -55,6 +56,12 @@
|
|
|
55
56
|
"title": "gog Executable Path",
|
|
56
57
|
"description": "Path to the gog executable (optional — defaults to 'gog' on your PATH)",
|
|
57
58
|
"required": false
|
|
59
|
+
},
|
|
60
|
+
"gog_file_roots": {
|
|
61
|
+
"type": "string",
|
|
62
|
+
"title": "Allowed file directories",
|
|
63
|
+
"description": "Directories tools may read files from (attachments, uploads) or write files to (exports, downloads), separated by ':' (';' on Windows). Optional — defaults to ~/gogcli-mcp-files",
|
|
64
|
+
"required": false
|
|
58
65
|
}
|
|
59
66
|
},
|
|
60
67
|
"tools": [
|
package/mint.yaml
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gogcli-mcp-classroom",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.3.0",
|
|
4
4
|
"mcpName": "io.github.chrischall/gogcli-mcp-classroom",
|
|
5
5
|
"description": "Extended Google Classroom MCP server via gogcli — auth + full Classroom support",
|
|
6
6
|
"author": "Claude Code (AI) <https://www.anthropic.com/claude>",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"test:coverage": "vitest run --coverage"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@chrischall/mcp-utils": "^2.
|
|
27
|
+
"@chrischall/mcp-utils": "^2.4.0",
|
|
28
28
|
"@modelcontextprotocol/server": "^2.0.0",
|
|
29
29
|
"zod": "^4.6.1"
|
|
30
30
|
},
|
package/server.json
CHANGED
|
@@ -7,12 +7,12 @@
|
|
|
7
7
|
"source": "github",
|
|
8
8
|
"subfolder": "packages/gogcli-mcp-classroom"
|
|
9
9
|
},
|
|
10
|
-
"version": "4.
|
|
10
|
+
"version": "4.3.0",
|
|
11
11
|
"packages": [
|
|
12
12
|
{
|
|
13
13
|
"registryType": "npm",
|
|
14
14
|
"identifier": "gogcli-mcp-classroom",
|
|
15
|
-
"version": "4.
|
|
15
|
+
"version": "4.3.0",
|
|
16
16
|
"transport": {
|
|
17
17
|
"type": "stdio"
|
|
18
18
|
},
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { McpServer } from '@modelcontextprotocol/server';
|
|
2
2
|
import { z } from 'zod';
|
|
3
|
-
import { accountParam, runOrDiagnose } from '../../../gogcli-mcp/src/lib.js';
|
|
3
|
+
import { accountParam, runOrDiagnose, pos } from '../../../gogcli-mcp/src/lib.js';
|
|
4
|
+
import type { GogArg } from '../../../gogcli-mcp/src/lib.js';
|
|
4
5
|
|
|
5
6
|
const courseState = z.enum(['ACTIVE', 'ARCHIVED', 'PROVISIONED', 'DECLINED', 'SUSPENDED']);
|
|
6
7
|
const workState = z.enum(['PUBLISHED', 'DRAFT']);
|
|
@@ -41,7 +42,7 @@ export function registerExtraClassroomTools(server: McpServer): void {
|
|
|
41
42
|
account: accountParam,
|
|
42
43
|
}),
|
|
43
44
|
}, async ({ name, owner, section, descriptionHeading, description, room, state, account }) => {
|
|
44
|
-
const args = ['classroom', 'courses', 'create', `--name=${name}`];
|
|
45
|
+
const args: GogArg[] = ['classroom', 'courses', 'create', `--name=${name}`];
|
|
45
46
|
if (owner) args.push(`--owner=${owner}`);
|
|
46
47
|
if (section) args.push(`--section=${section}`);
|
|
47
48
|
if (descriptionHeading) args.push(`--description-heading=${descriptionHeading}`);
|
|
@@ -61,7 +62,7 @@ export function registerExtraClassroomTools(server: McpServer): void {
|
|
|
61
62
|
account: accountParam,
|
|
62
63
|
}),
|
|
63
64
|
}, async ({ courseId, name, owner, section, descriptionHeading, description, room, state, account }) => {
|
|
64
|
-
const args = ['classroom', 'courses', 'update', courseId];
|
|
65
|
+
const args: GogArg[] = ['classroom', 'courses', 'update', pos(courseId)];
|
|
65
66
|
if (name) args.push(`--name=${name}`);
|
|
66
67
|
if (owner) args.push(`--owner=${owner}`);
|
|
67
68
|
if (section) args.push(`--section=${section}`);
|
|
@@ -80,7 +81,7 @@ export function registerExtraClassroomTools(server: McpServer): void {
|
|
|
80
81
|
account: accountParam,
|
|
81
82
|
}),
|
|
82
83
|
}, async ({ courseId, account }) => {
|
|
83
|
-
return runOrDiagnose(['classroom', 'courses', 'delete', courseId, '--force'], { account }); // gog gates this op; without --force the runner's --no-input makes it refuse
|
|
84
|
+
return runOrDiagnose(['classroom', 'courses', 'delete', pos(courseId), '--force'], { account }); // gog gates this op; without --force the runner's --no-input makes it refuse
|
|
84
85
|
});
|
|
85
86
|
|
|
86
87
|
server.registerTool('gog_classroom_courses_archive', {
|
|
@@ -91,7 +92,7 @@ export function registerExtraClassroomTools(server: McpServer): void {
|
|
|
91
92
|
account: accountParam,
|
|
92
93
|
}),
|
|
93
94
|
}, async ({ courseId, account }) => {
|
|
94
|
-
return runOrDiagnose(['classroom', 'courses', 'archive', courseId], { account });
|
|
95
|
+
return runOrDiagnose(['classroom', 'courses', 'archive', pos(courseId)], { account });
|
|
95
96
|
});
|
|
96
97
|
|
|
97
98
|
server.registerTool('gog_classroom_courses_unarchive', {
|
|
@@ -102,7 +103,7 @@ export function registerExtraClassroomTools(server: McpServer): void {
|
|
|
102
103
|
account: accountParam,
|
|
103
104
|
}),
|
|
104
105
|
}, async ({ courseId, account }) => {
|
|
105
|
-
return runOrDiagnose(['classroom', 'courses', 'unarchive', courseId], { account });
|
|
106
|
+
return runOrDiagnose(['classroom', 'courses', 'unarchive', pos(courseId)], { account });
|
|
106
107
|
});
|
|
107
108
|
|
|
108
109
|
server.registerTool('gog_classroom_students_add', {
|
|
@@ -115,7 +116,7 @@ export function registerExtraClassroomTools(server: McpServer): void {
|
|
|
115
116
|
account: accountParam,
|
|
116
117
|
}),
|
|
117
118
|
}, async ({ courseId, userId, enrollmentCode, account }) => {
|
|
118
|
-
const args = ['classroom', 'students', 'add', courseId, userId];
|
|
119
|
+
const args: GogArg[] = ['classroom', 'students', 'add', pos(courseId), pos(userId)];
|
|
119
120
|
if (enrollmentCode) args.push(`--enrollment-code=${enrollmentCode}`);
|
|
120
121
|
return runOrDiagnose(args, { account });
|
|
121
122
|
});
|
|
@@ -129,7 +130,7 @@ export function registerExtraClassroomTools(server: McpServer): void {
|
|
|
129
130
|
account: accountParam,
|
|
130
131
|
}),
|
|
131
132
|
}, async ({ courseId, userId, account }) => {
|
|
132
|
-
return runOrDiagnose(['classroom', 'students', 'remove', courseId, userId, '--force'], { account }); // gog gates this op; without --force the runner's --no-input makes it refuse
|
|
133
|
+
return runOrDiagnose(['classroom', 'students', 'remove', pos(courseId), pos(userId), '--force'], { account }); // gog gates this op; without --force the runner's --no-input makes it refuse
|
|
133
134
|
});
|
|
134
135
|
|
|
135
136
|
server.registerTool('gog_classroom_teachers_add', {
|
|
@@ -141,7 +142,7 @@ export function registerExtraClassroomTools(server: McpServer): void {
|
|
|
141
142
|
account: accountParam,
|
|
142
143
|
}),
|
|
143
144
|
}, async ({ courseId, userId, account }) => {
|
|
144
|
-
return runOrDiagnose(['classroom', 'teachers', 'add', courseId, userId], { account });
|
|
145
|
+
return runOrDiagnose(['classroom', 'teachers', 'add', pos(courseId), pos(userId)], { account });
|
|
145
146
|
});
|
|
146
147
|
|
|
147
148
|
server.registerTool('gog_classroom_teachers_remove', {
|
|
@@ -153,7 +154,7 @@ export function registerExtraClassroomTools(server: McpServer): void {
|
|
|
153
154
|
account: accountParam,
|
|
154
155
|
}),
|
|
155
156
|
}, async ({ courseId, userId, account }) => {
|
|
156
|
-
return runOrDiagnose(['classroom', 'teachers', 'remove', courseId, userId, '--force'], { account }); // gog gates this op; without --force the runner's --no-input makes it refuse
|
|
157
|
+
return runOrDiagnose(['classroom', 'teachers', 'remove', pos(courseId), pos(userId), '--force'], { account }); // gog gates this op; without --force the runner's --no-input makes it refuse
|
|
157
158
|
});
|
|
158
159
|
|
|
159
160
|
server.registerTool('gog_classroom_coursework_create', {
|
|
@@ -166,7 +167,7 @@ export function registerExtraClassroomTools(server: McpServer): void {
|
|
|
166
167
|
account: accountParam,
|
|
167
168
|
}),
|
|
168
169
|
}, async ({ courseId, title, description, type, state, maxPoints, due, dueDate, dueTime, scheduled, topic, account }) => {
|
|
169
|
-
const args = ['classroom', 'coursework', 'create', courseId, `--title=${title}`];
|
|
170
|
+
const args: GogArg[] = ['classroom', 'coursework', 'create', pos(courseId), `--title=${title}`];
|
|
170
171
|
if (description) args.push(`--description=${description}`);
|
|
171
172
|
if (type) args.push(`--type=${type}`);
|
|
172
173
|
if (state) args.push(`--state=${state}`);
|
|
@@ -190,7 +191,7 @@ export function registerExtraClassroomTools(server: McpServer): void {
|
|
|
190
191
|
account: accountParam,
|
|
191
192
|
}),
|
|
192
193
|
}, async ({ courseId, courseworkId, title, description, type, state, maxPoints, due, dueDate, dueTime, scheduled, topic, account }) => {
|
|
193
|
-
const args = ['classroom', 'coursework', 'update', courseId, courseworkId];
|
|
194
|
+
const args: GogArg[] = ['classroom', 'coursework', 'update', pos(courseId), pos(courseworkId)];
|
|
194
195
|
if (title) args.push(`--title=${title}`);
|
|
195
196
|
if (description) args.push(`--description=${description}`);
|
|
196
197
|
if (type) args.push(`--type=${type}`);
|
|
@@ -213,7 +214,7 @@ export function registerExtraClassroomTools(server: McpServer): void {
|
|
|
213
214
|
account: accountParam,
|
|
214
215
|
}),
|
|
215
216
|
}, async ({ courseId, courseworkId, account }) => {
|
|
216
|
-
return runOrDiagnose(['classroom', 'coursework', 'delete', courseId, courseworkId, '--force'], { account }); // gog gates this op; without --force the runner's --no-input makes it refuse
|
|
217
|
+
return runOrDiagnose(['classroom', 'coursework', 'delete', pos(courseId), pos(courseworkId), '--force'], { account }); // gog gates this op; without --force the runner's --no-input makes it refuse
|
|
217
218
|
});
|
|
218
219
|
|
|
219
220
|
server.registerTool('gog_classroom_announcements_update', {
|
|
@@ -228,7 +229,7 @@ export function registerExtraClassroomTools(server: McpServer): void {
|
|
|
228
229
|
account: accountParam,
|
|
229
230
|
}),
|
|
230
231
|
}, async ({ courseId, announcementId, text, state, scheduled, account }) => {
|
|
231
|
-
const args = ['classroom', 'announcements', 'update', courseId, announcementId];
|
|
232
|
+
const args: GogArg[] = ['classroom', 'announcements', 'update', pos(courseId), pos(announcementId)];
|
|
232
233
|
if (text) args.push(`--text=${text}`);
|
|
233
234
|
if (state) args.push(`--state=${state}`);
|
|
234
235
|
if (scheduled) args.push(`--scheduled=${scheduled}`);
|
|
@@ -244,7 +245,7 @@ export function registerExtraClassroomTools(server: McpServer): void {
|
|
|
244
245
|
account: accountParam,
|
|
245
246
|
}),
|
|
246
247
|
}, async ({ courseId, announcementId, account }) => {
|
|
247
|
-
return runOrDiagnose(['classroom', 'announcements', 'delete', courseId, announcementId, '--force'], { account }); // gog gates this op; without --force the runner's --no-input makes it refuse
|
|
248
|
+
return runOrDiagnose(['classroom', 'announcements', 'delete', pos(courseId), pos(announcementId), '--force'], { account }); // gog gates this op; without --force the runner's --no-input makes it refuse
|
|
248
249
|
});
|
|
249
250
|
|
|
250
251
|
server.registerTool('gog_classroom_topics_create', {
|
|
@@ -256,7 +257,7 @@ export function registerExtraClassroomTools(server: McpServer): void {
|
|
|
256
257
|
account: accountParam,
|
|
257
258
|
}),
|
|
258
259
|
}, async ({ courseId, name, account }) => {
|
|
259
|
-
return runOrDiagnose(['classroom', 'topics', 'create', courseId, `--name=${name}`], { account });
|
|
260
|
+
return runOrDiagnose(['classroom', 'topics', 'create', pos(courseId), `--name=${name}`], { account });
|
|
260
261
|
});
|
|
261
262
|
|
|
262
263
|
server.registerTool('gog_classroom_topics_update', {
|
|
@@ -269,7 +270,7 @@ export function registerExtraClassroomTools(server: McpServer): void {
|
|
|
269
270
|
account: accountParam,
|
|
270
271
|
}),
|
|
271
272
|
}, async ({ courseId, topicId, name, account }) => {
|
|
272
|
-
return runOrDiagnose(['classroom', 'topics', 'update', courseId, topicId, `--name=${name}`], { account });
|
|
273
|
+
return runOrDiagnose(['classroom', 'topics', 'update', pos(courseId), pos(topicId), `--name=${name}`], { account });
|
|
273
274
|
});
|
|
274
275
|
|
|
275
276
|
server.registerTool('gog_classroom_topics_delete', {
|
|
@@ -281,7 +282,7 @@ export function registerExtraClassroomTools(server: McpServer): void {
|
|
|
281
282
|
account: accountParam,
|
|
282
283
|
}),
|
|
283
284
|
}, async ({ courseId, topicId, account }) => {
|
|
284
|
-
return runOrDiagnose(['classroom', 'topics', 'delete', courseId, topicId, '--force'], { account }); // gog gates this op; without --force the runner's --no-input makes it refuse
|
|
285
|
+
return runOrDiagnose(['classroom', 'topics', 'delete', pos(courseId), pos(topicId), '--force'], { account }); // gog gates this op; without --force the runner's --no-input makes it refuse
|
|
285
286
|
});
|
|
286
287
|
|
|
287
288
|
server.registerTool('gog_classroom_invitations_create', {
|
|
@@ -294,7 +295,7 @@ export function registerExtraClassroomTools(server: McpServer): void {
|
|
|
294
295
|
account: accountParam,
|
|
295
296
|
}),
|
|
296
297
|
}, async ({ courseId, userId, role, account }) => {
|
|
297
|
-
return runOrDiagnose(['classroom', 'invitations', 'create', courseId, userId, `--role=${role}`], { account });
|
|
298
|
+
return runOrDiagnose(['classroom', 'invitations', 'create', pos(courseId), pos(userId), `--role=${role}`], { account });
|
|
298
299
|
});
|
|
299
300
|
|
|
300
301
|
server.registerTool('gog_classroom_invitations_delete', {
|
|
@@ -305,6 +306,6 @@ export function registerExtraClassroomTools(server: McpServer): void {
|
|
|
305
306
|
account: accountParam,
|
|
306
307
|
}),
|
|
307
308
|
}, async ({ invitationId, account }) => {
|
|
308
|
-
return runOrDiagnose(['classroom', 'invitations', 'delete', invitationId, '--force'], { account }); // gog gates this op; without --force the runner's --no-input makes it refuse
|
|
309
|
+
return runOrDiagnose(['classroom', 'invitations', 'delete', pos(invitationId), '--force'], { account }); // gog gates this op; without --force the runner's --no-input makes it refuse
|
|
309
310
|
});
|
|
310
311
|
}
|
|
@@ -3,6 +3,7 @@ import { registerExtraClassroomTools } from '../../src/tools/classroom-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>();
|
|
@@ -49,7 +50,7 @@ describe('gog_classroom_courses_create', () => {
|
|
|
49
50
|
describe('gog_classroom_courses_update', () => {
|
|
50
51
|
it('calls runOrDiagnose with courseId only', async () => {
|
|
51
52
|
await harness.callTool('gog_classroom_courses_update', { courseId: 'c1' });
|
|
52
|
-
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['classroom', 'courses', 'update', 'c1'], { account: undefined });
|
|
53
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['classroom', 'courses', 'update', pos('c1')], { account: undefined });
|
|
53
54
|
});
|
|
54
55
|
|
|
55
56
|
it('passes all optional flags', async () => {
|
|
@@ -64,7 +65,7 @@ describe('gog_classroom_courses_update', () => {
|
|
|
64
65
|
state: 'ARCHIVED',
|
|
65
66
|
});
|
|
66
67
|
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
67
|
-
['classroom', 'courses', 'update', 'c1', '--name=New Name', '--owner=me', '--section=B', '--description-heading=Heading', '--description=Desc', '--room=R2', '--state=ARCHIVED'],
|
|
68
|
+
['classroom', 'courses', 'update', pos('c1'), '--name=New Name', '--owner=me', '--section=B', '--description-heading=Heading', '--description=Desc', '--room=R2', '--state=ARCHIVED'],
|
|
68
69
|
{ account: undefined },
|
|
69
70
|
);
|
|
70
71
|
});
|
|
@@ -73,34 +74,34 @@ describe('gog_classroom_courses_update', () => {
|
|
|
73
74
|
describe('gog_classroom_courses_delete', () => {
|
|
74
75
|
it('calls runOrDiagnose with courseId', async () => {
|
|
75
76
|
await harness.callTool('gog_classroom_courses_delete', { courseId: 'c1' });
|
|
76
|
-
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['classroom', 'courses', 'delete', 'c1', '--force'], { account: undefined });
|
|
77
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['classroom', 'courses', 'delete', pos('c1'), '--force'], { account: undefined });
|
|
77
78
|
});
|
|
78
79
|
});
|
|
79
80
|
|
|
80
81
|
describe('gog_classroom_courses_archive', () => {
|
|
81
82
|
it('calls runOrDiagnose with courseId', async () => {
|
|
82
83
|
await harness.callTool('gog_classroom_courses_archive', { courseId: 'c1' });
|
|
83
|
-
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['classroom', 'courses', 'archive', 'c1'], { account: undefined });
|
|
84
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['classroom', 'courses', 'archive', pos('c1')], { account: undefined });
|
|
84
85
|
});
|
|
85
86
|
});
|
|
86
87
|
|
|
87
88
|
describe('gog_classroom_courses_unarchive', () => {
|
|
88
89
|
it('calls runOrDiagnose with courseId', async () => {
|
|
89
90
|
await harness.callTool('gog_classroom_courses_unarchive', { courseId: 'c1' });
|
|
90
|
-
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['classroom', 'courses', 'unarchive', 'c1'], { account: undefined });
|
|
91
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['classroom', 'courses', 'unarchive', pos('c1')], { account: undefined });
|
|
91
92
|
});
|
|
92
93
|
});
|
|
93
94
|
|
|
94
95
|
describe('gog_classroom_students_add', () => {
|
|
95
96
|
it('calls runOrDiagnose with courseId and userId', async () => {
|
|
96
97
|
await harness.callTool('gog_classroom_students_add', { courseId: 'c1', userId: 'u1' });
|
|
97
|
-
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['classroom', 'students', 'add', 'c1', 'u1'], { account: undefined });
|
|
98
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['classroom', 'students', 'add', pos('c1'), pos('u1')], { account: undefined });
|
|
98
99
|
});
|
|
99
100
|
|
|
100
101
|
it('passes --enrollment-code when provided', async () => {
|
|
101
102
|
await harness.callTool('gog_classroom_students_add', { courseId: 'c1', userId: 'u1', enrollmentCode: 'abc123' });
|
|
102
103
|
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
103
|
-
['classroom', 'students', 'add', 'c1', 'u1', '--enrollment-code=abc123'],
|
|
104
|
+
['classroom', 'students', 'add', pos('c1'), pos('u1'), '--enrollment-code=abc123'],
|
|
104
105
|
{ account: undefined },
|
|
105
106
|
);
|
|
106
107
|
});
|
|
@@ -109,21 +110,21 @@ describe('gog_classroom_students_add', () => {
|
|
|
109
110
|
describe('gog_classroom_students_remove', () => {
|
|
110
111
|
it('calls runOrDiagnose with courseId and userId', async () => {
|
|
111
112
|
await harness.callTool('gog_classroom_students_remove', { courseId: 'c1', userId: 'u1' });
|
|
112
|
-
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['classroom', 'students', 'remove', 'c1', 'u1', '--force'], { account: undefined });
|
|
113
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['classroom', 'students', 'remove', pos('c1'), pos('u1'), '--force'], { account: undefined });
|
|
113
114
|
});
|
|
114
115
|
});
|
|
115
116
|
|
|
116
117
|
describe('gog_classroom_teachers_add', () => {
|
|
117
118
|
it('calls runOrDiagnose with courseId and userId', async () => {
|
|
118
119
|
await harness.callTool('gog_classroom_teachers_add', { courseId: 'c1', userId: 'u1' });
|
|
119
|
-
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['classroom', 'teachers', 'add', 'c1', 'u1'], { account: undefined });
|
|
120
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['classroom', 'teachers', 'add', pos('c1'), pos('u1')], { account: undefined });
|
|
120
121
|
});
|
|
121
122
|
});
|
|
122
123
|
|
|
123
124
|
describe('gog_classroom_teachers_remove', () => {
|
|
124
125
|
it('calls runOrDiagnose with courseId and userId', async () => {
|
|
125
126
|
await harness.callTool('gog_classroom_teachers_remove', { courseId: 'c1', userId: 'u1' });
|
|
126
|
-
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['classroom', 'teachers', 'remove', 'c1', 'u1', '--force'], { account: undefined });
|
|
127
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['classroom', 'teachers', 'remove', pos('c1'), pos('u1'), '--force'], { account: undefined });
|
|
127
128
|
});
|
|
128
129
|
});
|
|
129
130
|
|
|
@@ -131,7 +132,7 @@ describe('gog_classroom_coursework_create', () => {
|
|
|
131
132
|
it('calls runOrDiagnose with required title only', async () => {
|
|
132
133
|
await harness.callTool('gog_classroom_coursework_create', { courseId: 'c1', title: 'HW1' });
|
|
133
134
|
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
134
|
-
['classroom', 'coursework', 'create', 'c1', '--title=HW1'],
|
|
135
|
+
['classroom', 'coursework', 'create', pos('c1'), '--title=HW1'],
|
|
135
136
|
{ account: undefined },
|
|
136
137
|
);
|
|
137
138
|
});
|
|
@@ -151,7 +152,7 @@ describe('gog_classroom_coursework_create', () => {
|
|
|
151
152
|
topic: 't1',
|
|
152
153
|
});
|
|
153
154
|
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
154
|
-
['classroom', 'coursework', 'create', 'c1', '--title=HW1', '--description=Chapter 1', '--type=ASSIGNMENT', '--state=PUBLISHED', '--max-points=100', '--due=2026-05-01T23:59', '--due-date=2026-05-01', '--due-time=23:59', '--scheduled=2026-04-30T12:00', '--topic=t1'],
|
|
155
|
+
['classroom', 'coursework', 'create', pos('c1'), '--title=HW1', '--description=Chapter 1', '--type=ASSIGNMENT', '--state=PUBLISHED', '--max-points=100', '--due=2026-05-01T23:59', '--due-date=2026-05-01', '--due-time=23:59', '--scheduled=2026-04-30T12:00', '--topic=t1'],
|
|
155
156
|
{ account: undefined },
|
|
156
157
|
);
|
|
157
158
|
});
|
|
@@ -160,7 +161,7 @@ describe('gog_classroom_coursework_create', () => {
|
|
|
160
161
|
describe('gog_classroom_coursework_update', () => {
|
|
161
162
|
it('calls runOrDiagnose with ids only', async () => {
|
|
162
163
|
await harness.callTool('gog_classroom_coursework_update', { courseId: 'c1', courseworkId: 'w1' });
|
|
163
|
-
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['classroom', 'coursework', 'update', 'c1', 'w1'], { account: undefined });
|
|
164
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['classroom', 'coursework', 'update', pos('c1'), pos('w1')], { account: undefined });
|
|
164
165
|
});
|
|
165
166
|
|
|
166
167
|
it('passes all optional flags', async () => {
|
|
@@ -179,7 +180,7 @@ describe('gog_classroom_coursework_update', () => {
|
|
|
179
180
|
topic: 't1',
|
|
180
181
|
});
|
|
181
182
|
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
182
|
-
['classroom', 'coursework', 'update', 'c1', 'w1', '--title=New Title', '--description=Desc', '--type=SHORT_ANSWER_QUESTION', '--state=DRAFT', '--max-points=50', '--due=2026-05-01T23:59', '--due-date=2026-05-01', '--due-time=23:59', '--scheduled=2026-04-30T12:00', '--topic=t1'],
|
|
183
|
+
['classroom', 'coursework', 'update', pos('c1'), pos('w1'), '--title=New Title', '--description=Desc', '--type=SHORT_ANSWER_QUESTION', '--state=DRAFT', '--max-points=50', '--due=2026-05-01T23:59', '--due-date=2026-05-01', '--due-time=23:59', '--scheduled=2026-04-30T12:00', '--topic=t1'],
|
|
183
184
|
{ account: undefined },
|
|
184
185
|
);
|
|
185
186
|
});
|
|
@@ -188,14 +189,14 @@ describe('gog_classroom_coursework_update', () => {
|
|
|
188
189
|
describe('gog_classroom_coursework_delete', () => {
|
|
189
190
|
it('calls runOrDiagnose with ids', async () => {
|
|
190
191
|
await harness.callTool('gog_classroom_coursework_delete', { courseId: 'c1', courseworkId: 'w1' });
|
|
191
|
-
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['classroom', 'coursework', 'delete', 'c1', 'w1', '--force'], { account: undefined });
|
|
192
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['classroom', 'coursework', 'delete', pos('c1'), pos('w1'), '--force'], { account: undefined });
|
|
192
193
|
});
|
|
193
194
|
});
|
|
194
195
|
|
|
195
196
|
describe('gog_classroom_announcements_update', () => {
|
|
196
197
|
it('calls runOrDiagnose with ids only', async () => {
|
|
197
198
|
await harness.callTool('gog_classroom_announcements_update', { courseId: 'c1', announcementId: 'a1' });
|
|
198
|
-
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['classroom', 'announcements', 'update', 'c1', 'a1'], { account: undefined });
|
|
199
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['classroom', 'announcements', 'update', pos('c1'), pos('a1')], { account: undefined });
|
|
199
200
|
});
|
|
200
201
|
|
|
201
202
|
it('passes all optional flags', async () => {
|
|
@@ -207,7 +208,7 @@ describe('gog_classroom_announcements_update', () => {
|
|
|
207
208
|
scheduled: '2026-05-01T12:00',
|
|
208
209
|
});
|
|
209
210
|
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
210
|
-
['classroom', 'announcements', 'update', 'c1', 'a1', '--text=edited', '--state=PUBLISHED', '--scheduled=2026-05-01T12:00'],
|
|
211
|
+
['classroom', 'announcements', 'update', pos('c1'), pos('a1'), '--text=edited', '--state=PUBLISHED', '--scheduled=2026-05-01T12:00'],
|
|
211
212
|
{ account: undefined },
|
|
212
213
|
);
|
|
213
214
|
});
|
|
@@ -216,7 +217,7 @@ describe('gog_classroom_announcements_update', () => {
|
|
|
216
217
|
describe('gog_classroom_announcements_delete', () => {
|
|
217
218
|
it('calls runOrDiagnose with ids', async () => {
|
|
218
219
|
await harness.callTool('gog_classroom_announcements_delete', { courseId: 'c1', announcementId: 'a1' });
|
|
219
|
-
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['classroom', 'announcements', 'delete', 'c1', 'a1', '--force'], { account: undefined });
|
|
220
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['classroom', 'announcements', 'delete', pos('c1'), pos('a1'), '--force'], { account: undefined });
|
|
220
221
|
});
|
|
221
222
|
});
|
|
222
223
|
|
|
@@ -224,7 +225,7 @@ describe('gog_classroom_topics_create', () => {
|
|
|
224
225
|
it('calls runOrDiagnose with courseId and name', async () => {
|
|
225
226
|
await harness.callTool('gog_classroom_topics_create', { courseId: 'c1', name: 'Week 1' });
|
|
226
227
|
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
227
|
-
['classroom', 'topics', 'create', 'c1', '--name=Week 1'],
|
|
228
|
+
['classroom', 'topics', 'create', pos('c1'), '--name=Week 1'],
|
|
228
229
|
{ account: undefined },
|
|
229
230
|
);
|
|
230
231
|
});
|
|
@@ -234,7 +235,7 @@ describe('gog_classroom_topics_update', () => {
|
|
|
234
235
|
it('calls runOrDiagnose with ids and name', async () => {
|
|
235
236
|
await harness.callTool('gog_classroom_topics_update', { courseId: 'c1', topicId: 't1', name: 'Week 2' });
|
|
236
237
|
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
237
|
-
['classroom', 'topics', 'update', 'c1', 't1', '--name=Week 2'],
|
|
238
|
+
['classroom', 'topics', 'update', pos('c1'), pos('t1'), '--name=Week 2'],
|
|
238
239
|
{ account: undefined },
|
|
239
240
|
);
|
|
240
241
|
});
|
|
@@ -243,7 +244,7 @@ describe('gog_classroom_topics_update', () => {
|
|
|
243
244
|
describe('gog_classroom_topics_delete', () => {
|
|
244
245
|
it('calls runOrDiagnose with ids', async () => {
|
|
245
246
|
await harness.callTool('gog_classroom_topics_delete', { courseId: 'c1', topicId: 't1' });
|
|
246
|
-
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['classroom', 'topics', 'delete', 'c1', 't1', '--force'], { account: undefined });
|
|
247
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['classroom', 'topics', 'delete', pos('c1'), pos('t1'), '--force'], { account: undefined });
|
|
247
248
|
});
|
|
248
249
|
});
|
|
249
250
|
|
|
@@ -251,7 +252,7 @@ describe('gog_classroom_invitations_create', () => {
|
|
|
251
252
|
it('calls runOrDiagnose with courseId, userId, role', async () => {
|
|
252
253
|
await harness.callTool('gog_classroom_invitations_create', { courseId: 'c1', userId: 'u1', role: 'STUDENT' });
|
|
253
254
|
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
254
|
-
['classroom', 'invitations', 'create', 'c1', 'u1', '--role=STUDENT'],
|
|
255
|
+
['classroom', 'invitations', 'create', pos('c1'), pos('u1'), '--role=STUDENT'],
|
|
255
256
|
{ account: undefined },
|
|
256
257
|
);
|
|
257
258
|
});
|
|
@@ -260,6 +261,6 @@ describe('gog_classroom_invitations_create', () => {
|
|
|
260
261
|
describe('gog_classroom_invitations_delete', () => {
|
|
261
262
|
it('calls runOrDiagnose with invitationId', async () => {
|
|
262
263
|
await harness.callTool('gog_classroom_invitations_delete', { invitationId: 'i1' });
|
|
263
|
-
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['classroom', 'invitations', 'delete', 'i1', '--force'], { account: undefined });
|
|
264
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['classroom', 'invitations', 'delete', pos('i1'), '--force'], { account: undefined });
|
|
264
265
|
});
|
|
265
266
|
});
|
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'],
|