gogcli-mcp-sheets 1.0.6
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/package.json +26 -0
- package/src/index.ts +10 -0
- package/src/tools/sheets-extra.ts +340 -0
- package/tests/tools/sheets-extra.test.ts +385 -0
- package/tsconfig.json +11 -0
- package/vitest.config.ts +17 -0
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "gogcli-mcp-sheets",
|
|
3
|
+
"version": "1.0.6",
|
|
4
|
+
"description": "Extended Google Sheets MCP server via gogcli — all base tools plus full Sheets support",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"gogcli-mcp-sheets": "dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"exports": {
|
|
10
|
+
".": "./dist/index.js"
|
|
11
|
+
},
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "tsc --noEmit && npm run bundle",
|
|
14
|
+
"bundle": "node ../../scripts/bundle.js src/index.ts dist/index.js",
|
|
15
|
+
"typecheck": "tsc --noEmit",
|
|
16
|
+
"test": "vitest run",
|
|
17
|
+
"test:watch": "vitest",
|
|
18
|
+
"test:coverage": "vitest run --coverage"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"gogcli-mcp": "*",
|
|
22
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
23
|
+
"zod": "^4.3.6"
|
|
24
|
+
},
|
|
25
|
+
"license": "MIT"
|
|
26
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
|
+
import { createBaseServer } from 'gogcli-mcp/lib';
|
|
4
|
+
import { registerExtraSheetsTools } from './tools/sheets-extra.js';
|
|
5
|
+
|
|
6
|
+
const server = createBaseServer({ name: 'gogcli-sheets' });
|
|
7
|
+
registerExtraSheetsTools(server);
|
|
8
|
+
|
|
9
|
+
const transport = new StdioServerTransport();
|
|
10
|
+
await server.connect(transport);
|
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { accountParam, runOrDiagnose } from 'gogcli-mcp/lib';
|
|
4
|
+
|
|
5
|
+
export function registerExtraSheetsTools(server: McpServer): void {
|
|
6
|
+
// 1. Add tab
|
|
7
|
+
server.registerTool('gog_sheets_add_tab', {
|
|
8
|
+
description: 'Add a new sheet tab to a spreadsheet.',
|
|
9
|
+
inputSchema: {
|
|
10
|
+
spreadsheetId: z.string().describe('Spreadsheet ID (from the URL)'),
|
|
11
|
+
tabName: z.string().describe('Name for the new tab'),
|
|
12
|
+
account: accountParam,
|
|
13
|
+
},
|
|
14
|
+
}, async ({ spreadsheetId, tabName, account }) => {
|
|
15
|
+
return runOrDiagnose(['sheets', 'add-tab', spreadsheetId, tabName], { account });
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
// 2. Delete tab
|
|
19
|
+
server.registerTool('gog_sheets_delete_tab', {
|
|
20
|
+
description: 'Delete a sheet tab from a spreadsheet.',
|
|
21
|
+
annotations: { destructiveHint: true },
|
|
22
|
+
inputSchema: {
|
|
23
|
+
spreadsheetId: z.string().describe('Spreadsheet ID'),
|
|
24
|
+
tabName: z.string().describe('Name of the tab to delete'),
|
|
25
|
+
account: accountParam,
|
|
26
|
+
},
|
|
27
|
+
}, async ({ spreadsheetId, tabName, account }) => {
|
|
28
|
+
return runOrDiagnose(['sheets', 'delete-tab', spreadsheetId, tabName], { account });
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// 3. Rename tab
|
|
32
|
+
server.registerTool('gog_sheets_rename_tab', {
|
|
33
|
+
description: 'Rename a sheet tab.',
|
|
34
|
+
annotations: { destructiveHint: true },
|
|
35
|
+
inputSchema: {
|
|
36
|
+
spreadsheetId: z.string().describe('Spreadsheet ID'),
|
|
37
|
+
oldName: z.string().describe('Current tab name'),
|
|
38
|
+
newName: z.string().describe('New tab name'),
|
|
39
|
+
account: accountParam,
|
|
40
|
+
},
|
|
41
|
+
}, async ({ spreadsheetId, oldName, newName, account }) => {
|
|
42
|
+
return runOrDiagnose(['sheets', 'rename-tab', spreadsheetId, oldName, newName], { account });
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// 4. Copy spreadsheet
|
|
46
|
+
server.registerTool('gog_sheets_copy', {
|
|
47
|
+
description: 'Copy a spreadsheet to a new spreadsheet with the given title.',
|
|
48
|
+
inputSchema: {
|
|
49
|
+
spreadsheetId: z.string().describe('Spreadsheet ID to copy'),
|
|
50
|
+
title: z.string().describe('Title for the new copy'),
|
|
51
|
+
parent: z.string().optional().describe('Parent folder ID to place the copy in'),
|
|
52
|
+
account: accountParam,
|
|
53
|
+
},
|
|
54
|
+
}, async ({ spreadsheetId, title, parent, account }) => {
|
|
55
|
+
const args = ['sheets', 'copy', spreadsheetId, title];
|
|
56
|
+
if (parent) args.push(`--parent=${parent}`);
|
|
57
|
+
return runOrDiagnose(args, { account });
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// 5. Export spreadsheet
|
|
61
|
+
server.registerTool('gog_sheets_export', {
|
|
62
|
+
description: 'Export a spreadsheet as CSV, TSV, or PDF.',
|
|
63
|
+
annotations: { readOnlyHint: true },
|
|
64
|
+
inputSchema: {
|
|
65
|
+
spreadsheetId: z.string().describe('Spreadsheet ID'),
|
|
66
|
+
format: z.string().optional().describe('Export format: csv, tsv, pdf (default: csv)'),
|
|
67
|
+
out: z.string().optional().describe('Output file path'),
|
|
68
|
+
account: accountParam,
|
|
69
|
+
},
|
|
70
|
+
}, async ({ spreadsheetId, format, out, account }) => {
|
|
71
|
+
const args = ['sheets', 'export', spreadsheetId];
|
|
72
|
+
if (format) args.push(`--format=${format}`);
|
|
73
|
+
if (out) args.push(`--out=${out}`);
|
|
74
|
+
return runOrDiagnose(args, { account });
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// 6. Freeze rows/columns
|
|
78
|
+
server.registerTool('gog_sheets_freeze', {
|
|
79
|
+
description: 'Freeze rows and/or columns in a sheet.',
|
|
80
|
+
annotations: { destructiveHint: true },
|
|
81
|
+
inputSchema: {
|
|
82
|
+
spreadsheetId: z.string().describe('Spreadsheet ID'),
|
|
83
|
+
rows: z.number().optional().describe('Number of rows to freeze'),
|
|
84
|
+
cols: z.number().optional().describe('Number of columns to freeze'),
|
|
85
|
+
sheet: z.string().optional().describe('Sheet tab name (default: first sheet)'),
|
|
86
|
+
account: accountParam,
|
|
87
|
+
},
|
|
88
|
+
}, async ({ spreadsheetId, rows, cols, sheet, account }) => {
|
|
89
|
+
const args = ['sheets', 'freeze', spreadsheetId];
|
|
90
|
+
if (rows !== undefined) args.push(`--rows=${rows}`);
|
|
91
|
+
if (cols !== undefined) args.push(`--cols=${cols}`);
|
|
92
|
+
if (sheet) args.push(`--sheet=${sheet}`);
|
|
93
|
+
return runOrDiagnose(args, { account });
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// 7. Insert rows/columns
|
|
97
|
+
server.registerTool('gog_sheets_insert', {
|
|
98
|
+
description: 'Insert rows or columns into a sheet.',
|
|
99
|
+
annotations: { destructiveHint: true },
|
|
100
|
+
inputSchema: {
|
|
101
|
+
spreadsheetId: z.string().describe('Spreadsheet ID'),
|
|
102
|
+
sheet: z.string().describe('Sheet tab name'),
|
|
103
|
+
dimension: z.string().describe('Dimension to insert: ROWS or COLUMNS'),
|
|
104
|
+
start: z.number().describe('Start index (0-based)'),
|
|
105
|
+
count: z.number().optional().describe('Number of rows/columns to insert (default: 1)'),
|
|
106
|
+
after: z.boolean().optional().describe('Insert after the start index instead of before'),
|
|
107
|
+
account: accountParam,
|
|
108
|
+
},
|
|
109
|
+
}, async ({ spreadsheetId, sheet, dimension, start, count, after, account }) => {
|
|
110
|
+
const args = ['sheets', 'insert', spreadsheetId, sheet, dimension, String(start)];
|
|
111
|
+
if (count !== undefined) args.push(`--count=${count}`);
|
|
112
|
+
if (after) args.push('--after');
|
|
113
|
+
return runOrDiagnose(args, { account });
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
// 8. Merge cells
|
|
117
|
+
server.registerTool('gog_sheets_merge', {
|
|
118
|
+
description: 'Merge cells in a range.',
|
|
119
|
+
annotations: { destructiveHint: true },
|
|
120
|
+
inputSchema: {
|
|
121
|
+
spreadsheetId: z.string().describe('Spreadsheet ID'),
|
|
122
|
+
range: z.string().describe('Range to merge (e.g. Sheet1!A1:C3)'),
|
|
123
|
+
type: z.string().optional().describe('Merge type: MERGE_ALL, MERGE_COLUMNS, MERGE_ROWS'),
|
|
124
|
+
account: accountParam,
|
|
125
|
+
},
|
|
126
|
+
}, async ({ spreadsheetId, range, type, account }) => {
|
|
127
|
+
const args = ['sheets', 'merge', spreadsheetId, range];
|
|
128
|
+
if (type) args.push(`--type=${type}`);
|
|
129
|
+
return runOrDiagnose(args, { account });
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
// 9. Unmerge cells
|
|
133
|
+
server.registerTool('gog_sheets_unmerge', {
|
|
134
|
+
description: 'Unmerge previously merged cells in a range.',
|
|
135
|
+
annotations: { destructiveHint: true },
|
|
136
|
+
inputSchema: {
|
|
137
|
+
spreadsheetId: z.string().describe('Spreadsheet ID'),
|
|
138
|
+
range: z.string().describe('Range to unmerge (e.g. Sheet1!A1:C3)'),
|
|
139
|
+
account: accountParam,
|
|
140
|
+
},
|
|
141
|
+
}, async ({ spreadsheetId, range, account }) => {
|
|
142
|
+
return runOrDiagnose(['sheets', 'unmerge', spreadsheetId, range], { account });
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// 10. Format cells
|
|
146
|
+
server.registerTool('gog_sheets_format', {
|
|
147
|
+
description: 'Apply cell formatting (bold, colors, alignment, etc.) to a range. Pass format as a JSON CellFormat object.',
|
|
148
|
+
annotations: { destructiveHint: true },
|
|
149
|
+
inputSchema: {
|
|
150
|
+
spreadsheetId: z.string().describe('Spreadsheet ID'),
|
|
151
|
+
range: z.string().describe('Range to format (e.g. Sheet1!A1:C3)'),
|
|
152
|
+
formatJson: z.string().describe('JSON CellFormat object (e.g. {"textFormat":{"bold":true}})'),
|
|
153
|
+
formatFields: z.string().optional().describe('Comma-separated field mask (e.g. textFormat.bold,backgroundColor)'),
|
|
154
|
+
account: accountParam,
|
|
155
|
+
},
|
|
156
|
+
}, async ({ spreadsheetId, range, formatJson, formatFields, account }) => {
|
|
157
|
+
const args = ['sheets', 'format', spreadsheetId, range, `--format-json=${formatJson}`];
|
|
158
|
+
if (formatFields) args.push(`--format-fields=${formatFields}`);
|
|
159
|
+
return runOrDiagnose(args, { account });
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// 11. Number format
|
|
163
|
+
server.registerTool('gog_sheets_number_format', {
|
|
164
|
+
description: 'Set number format on a range (currency, percentage, date, etc.).',
|
|
165
|
+
annotations: { destructiveHint: true },
|
|
166
|
+
inputSchema: {
|
|
167
|
+
spreadsheetId: z.string().describe('Spreadsheet ID'),
|
|
168
|
+
range: z.string().describe('Range to format (e.g. Sheet1!A1:A10)'),
|
|
169
|
+
type: z.string().optional().describe('Format type: NUMBER, CURRENCY, PERCENT, DATE, TIME, SCIENTIFIC, etc.'),
|
|
170
|
+
pattern: z.string().optional().describe('Custom format pattern (e.g. "#,##0.00", "yyyy-mm-dd")'),
|
|
171
|
+
account: accountParam,
|
|
172
|
+
},
|
|
173
|
+
}, async ({ spreadsheetId, range, type, pattern, account }) => {
|
|
174
|
+
const args = ['sheets', 'number-format', spreadsheetId, range];
|
|
175
|
+
if (type) args.push(`--type=${type}`);
|
|
176
|
+
if (pattern) args.push(`--pattern=${pattern}`);
|
|
177
|
+
return runOrDiagnose(args, { account });
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
// 12. Read format
|
|
181
|
+
server.registerTool('gog_sheets_read_format', {
|
|
182
|
+
description: 'Read cell formatting for a range.',
|
|
183
|
+
annotations: { readOnlyHint: true },
|
|
184
|
+
inputSchema: {
|
|
185
|
+
spreadsheetId: z.string().describe('Spreadsheet ID'),
|
|
186
|
+
range: z.string().describe('Range to read formatting from'),
|
|
187
|
+
effective: z.boolean().optional().describe('Return effective (computed) format including inherited styles'),
|
|
188
|
+
account: accountParam,
|
|
189
|
+
},
|
|
190
|
+
}, async ({ spreadsheetId, range, effective, account }) => {
|
|
191
|
+
const args = ['sheets', 'read-format', spreadsheetId, range];
|
|
192
|
+
if (effective) args.push('--effective');
|
|
193
|
+
return runOrDiagnose(args, { account });
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
// 13. Resize columns
|
|
197
|
+
server.registerTool('gog_sheets_resize_columns', {
|
|
198
|
+
description: 'Resize column widths. Use --auto for auto-fit or --width for a specific pixel width.',
|
|
199
|
+
annotations: { destructiveHint: true },
|
|
200
|
+
inputSchema: {
|
|
201
|
+
spreadsheetId: z.string().describe('Spreadsheet ID'),
|
|
202
|
+
columns: z.string().describe('Column range (e.g. A:C, or Sheet1!A:C)'),
|
|
203
|
+
width: z.number().optional().describe('Width in pixels'),
|
|
204
|
+
auto: z.boolean().optional().describe('Auto-fit column width to content'),
|
|
205
|
+
account: accountParam,
|
|
206
|
+
},
|
|
207
|
+
}, async ({ spreadsheetId, columns, width, auto, account }) => {
|
|
208
|
+
const args = ['sheets', 'resize-columns', spreadsheetId, columns];
|
|
209
|
+
if (width !== undefined) args.push(`--width=${width}`);
|
|
210
|
+
if (auto) args.push('--auto');
|
|
211
|
+
return runOrDiagnose(args, { account });
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
// 14. Resize rows
|
|
215
|
+
server.registerTool('gog_sheets_resize_rows', {
|
|
216
|
+
description: 'Resize row heights. Use --auto for auto-fit or --height for a specific pixel height.',
|
|
217
|
+
annotations: { destructiveHint: true },
|
|
218
|
+
inputSchema: {
|
|
219
|
+
spreadsheetId: z.string().describe('Spreadsheet ID'),
|
|
220
|
+
rows: z.string().describe('Row range (e.g. 1:10, or Sheet1!1:10)'),
|
|
221
|
+
height: z.number().optional().describe('Height in pixels'),
|
|
222
|
+
auto: z.boolean().optional().describe('Auto-fit row height to content'),
|
|
223
|
+
account: accountParam,
|
|
224
|
+
},
|
|
225
|
+
}, async ({ spreadsheetId, rows, height, auto, account }) => {
|
|
226
|
+
const args = ['sheets', 'resize-rows', spreadsheetId, rows];
|
|
227
|
+
if (height !== undefined) args.push(`--height=${height}`);
|
|
228
|
+
if (auto) args.push('--auto');
|
|
229
|
+
return runOrDiagnose(args, { account });
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
// 15. Notes (read)
|
|
233
|
+
server.registerTool('gog_sheets_notes', {
|
|
234
|
+
description: 'Read cell notes in a range.',
|
|
235
|
+
annotations: { readOnlyHint: true },
|
|
236
|
+
inputSchema: {
|
|
237
|
+
spreadsheetId: z.string().describe('Spreadsheet ID'),
|
|
238
|
+
range: z.string().describe('Range to read notes from (e.g. Sheet1!A1:B5)'),
|
|
239
|
+
account: accountParam,
|
|
240
|
+
},
|
|
241
|
+
}, async ({ spreadsheetId, range, account }) => {
|
|
242
|
+
return runOrDiagnose(['sheets', 'notes', spreadsheetId, range], { account });
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
// 16. Update note
|
|
246
|
+
server.registerTool('gog_sheets_update_note', {
|
|
247
|
+
description: 'Add, update, or clear a cell note. Pass an empty string to clear the note.',
|
|
248
|
+
annotations: { destructiveHint: true },
|
|
249
|
+
inputSchema: {
|
|
250
|
+
spreadsheetId: z.string().describe('Spreadsheet ID'),
|
|
251
|
+
range: z.string().describe('Cell or range to set the note on (e.g. Sheet1!A1)'),
|
|
252
|
+
note: z.string().describe('Note text (empty string clears the note)'),
|
|
253
|
+
account: accountParam,
|
|
254
|
+
},
|
|
255
|
+
}, async ({ spreadsheetId, range, note, account }) => {
|
|
256
|
+
return runOrDiagnose(['sheets', 'update-note', spreadsheetId, range, `--note=${note}`], { account });
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
// 17. Links
|
|
260
|
+
server.registerTool('gog_sheets_links', {
|
|
261
|
+
description: 'List hyperlinks in a range.',
|
|
262
|
+
annotations: { readOnlyHint: true },
|
|
263
|
+
inputSchema: {
|
|
264
|
+
spreadsheetId: z.string().describe('Spreadsheet ID'),
|
|
265
|
+
range: z.string().describe('Range to scan for hyperlinks (e.g. Sheet1!A1:Z100)'),
|
|
266
|
+
account: accountParam,
|
|
267
|
+
},
|
|
268
|
+
}, async ({ spreadsheetId, range, account }) => {
|
|
269
|
+
return runOrDiagnose(['sheets', 'links', spreadsheetId, range], { account });
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
// 18. Named ranges list
|
|
273
|
+
server.registerTool('gog_sheets_named_ranges_list', {
|
|
274
|
+
description: 'List all named ranges in a spreadsheet.',
|
|
275
|
+
annotations: { readOnlyHint: true },
|
|
276
|
+
inputSchema: {
|
|
277
|
+
spreadsheetId: z.string().describe('Spreadsheet ID'),
|
|
278
|
+
account: accountParam,
|
|
279
|
+
},
|
|
280
|
+
}, async ({ spreadsheetId, account }) => {
|
|
281
|
+
return runOrDiagnose(['sheets', 'named-ranges', 'list', spreadsheetId], { account });
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
// 19. Named ranges get
|
|
285
|
+
server.registerTool('gog_sheets_named_ranges_get', {
|
|
286
|
+
description: 'Get a named range by name or ID.',
|
|
287
|
+
annotations: { readOnlyHint: true },
|
|
288
|
+
inputSchema: {
|
|
289
|
+
spreadsheetId: z.string().describe('Spreadsheet ID'),
|
|
290
|
+
nameOrId: z.string().describe('Named range name or ID'),
|
|
291
|
+
account: accountParam,
|
|
292
|
+
},
|
|
293
|
+
}, async ({ spreadsheetId, nameOrId, account }) => {
|
|
294
|
+
return runOrDiagnose(['sheets', 'named-ranges', 'get', spreadsheetId, nameOrId], { account });
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
// 20. Named ranges add
|
|
298
|
+
server.registerTool('gog_sheets_named_ranges_add', {
|
|
299
|
+
description: 'Create a new named range.',
|
|
300
|
+
inputSchema: {
|
|
301
|
+
spreadsheetId: z.string().describe('Spreadsheet ID'),
|
|
302
|
+
name: z.string().describe('Name for the range'),
|
|
303
|
+
range: z.string().describe('Cell range (e.g. Sheet1!A1:B10)'),
|
|
304
|
+
account: accountParam,
|
|
305
|
+
},
|
|
306
|
+
}, async ({ spreadsheetId, name, range, account }) => {
|
|
307
|
+
return runOrDiagnose(['sheets', 'named-ranges', 'add', spreadsheetId, name, range], { account });
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
// 21. Named ranges update
|
|
311
|
+
server.registerTool('gog_sheets_named_ranges_update', {
|
|
312
|
+
description: 'Update a named range (change its name, range, or both).',
|
|
313
|
+
annotations: { destructiveHint: true },
|
|
314
|
+
inputSchema: {
|
|
315
|
+
spreadsheetId: z.string().describe('Spreadsheet ID'),
|
|
316
|
+
nameOrId: z.string().describe('Current named range name or ID'),
|
|
317
|
+
name: z.string().optional().describe('New name'),
|
|
318
|
+
range: z.string().optional().describe('New cell range'),
|
|
319
|
+
account: accountParam,
|
|
320
|
+
},
|
|
321
|
+
}, async ({ spreadsheetId, nameOrId, name, range, account }) => {
|
|
322
|
+
const args = ['sheets', 'named-ranges', 'update', spreadsheetId, nameOrId];
|
|
323
|
+
if (name) args.push(`--name=${name}`);
|
|
324
|
+
if (range) args.push(`--range=${range}`);
|
|
325
|
+
return runOrDiagnose(args, { account });
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
// 22. Named ranges delete
|
|
329
|
+
server.registerTool('gog_sheets_named_ranges_delete', {
|
|
330
|
+
description: 'Delete a named range.',
|
|
331
|
+
annotations: { destructiveHint: true },
|
|
332
|
+
inputSchema: {
|
|
333
|
+
spreadsheetId: z.string().describe('Spreadsheet ID'),
|
|
334
|
+
nameOrId: z.string().describe('Named range name or ID to delete'),
|
|
335
|
+
account: accountParam,
|
|
336
|
+
},
|
|
337
|
+
}, async ({ spreadsheetId, nameOrId, account }) => {
|
|
338
|
+
return runOrDiagnose(['sheets', 'named-ranges', 'delete', spreadsheetId, nameOrId], { account });
|
|
339
|
+
});
|
|
340
|
+
}
|
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
|
+
import { registerExtraSheetsTools } from '../../src/tools/sheets-extra.js';
|
|
4
|
+
import * as lib from 'gogcli-mcp/lib';
|
|
5
|
+
|
|
6
|
+
vi.mock('gogcli-mcp/lib', async (importOriginal) => {
|
|
7
|
+
const actual = await importOriginal<typeof lib>();
|
|
8
|
+
return {
|
|
9
|
+
...actual,
|
|
10
|
+
runOrDiagnose: vi.fn(),
|
|
11
|
+
};
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
type ToolHandler = (args: Record<string, unknown>) => Promise<{ content: Array<{ type: string; text: string }> }>;
|
|
15
|
+
|
|
16
|
+
function toText(text: string) {
|
|
17
|
+
return { content: [{ type: 'text', text }] };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function setupHandlers(): Map<string, ToolHandler> {
|
|
21
|
+
const server = new McpServer({ name: 'test', version: '0.0.0' });
|
|
22
|
+
const handlers = new Map<string, ToolHandler>();
|
|
23
|
+
vi.spyOn(server, 'registerTool').mockImplementation((name, _config, cb) => {
|
|
24
|
+
handlers.set(name, cb as ToolHandler);
|
|
25
|
+
return undefined as never;
|
|
26
|
+
});
|
|
27
|
+
registerExtraSheetsTools(server);
|
|
28
|
+
return handlers;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
beforeEach(() => vi.clearAllMocks());
|
|
32
|
+
|
|
33
|
+
// 1. add-tab
|
|
34
|
+
describe('gog_sheets_add_tab', () => {
|
|
35
|
+
it('calls runOrDiagnose with correct args', async () => {
|
|
36
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
37
|
+
const handlers = setupHandlers();
|
|
38
|
+
await handlers.get('gog_sheets_add_tab')!({ spreadsheetId: 'sid', tabName: 'NewTab' });
|
|
39
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'add-tab', 'sid', 'NewTab'], { account: undefined });
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('forwards account', async () => {
|
|
43
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
44
|
+
const handlers = setupHandlers();
|
|
45
|
+
await handlers.get('gog_sheets_add_tab')!({ spreadsheetId: 'sid', tabName: 'T', account: 'a@b.com' });
|
|
46
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'add-tab', 'sid', 'T'], { account: 'a@b.com' });
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// 2. delete-tab
|
|
51
|
+
describe('gog_sheets_delete_tab', () => {
|
|
52
|
+
it('calls runOrDiagnose with correct args', async () => {
|
|
53
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
54
|
+
const handlers = setupHandlers();
|
|
55
|
+
await handlers.get('gog_sheets_delete_tab')!({ spreadsheetId: 'sid', tabName: 'OldTab' });
|
|
56
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'delete-tab', 'sid', 'OldTab'], { account: undefined });
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// 3. rename-tab
|
|
61
|
+
describe('gog_sheets_rename_tab', () => {
|
|
62
|
+
it('calls runOrDiagnose with correct args', async () => {
|
|
63
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
64
|
+
const handlers = setupHandlers();
|
|
65
|
+
await handlers.get('gog_sheets_rename_tab')!({ spreadsheetId: 'sid', oldName: 'Old', newName: 'New' });
|
|
66
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'rename-tab', 'sid', 'Old', 'New'], { account: undefined });
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// 4. copy
|
|
71
|
+
describe('gog_sheets_copy', () => {
|
|
72
|
+
it('calls runOrDiagnose with correct args', async () => {
|
|
73
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
74
|
+
const handlers = setupHandlers();
|
|
75
|
+
await handlers.get('gog_sheets_copy')!({ spreadsheetId: 'sid', title: 'Copy' });
|
|
76
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'copy', 'sid', 'Copy'], { account: undefined });
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('includes --parent when provided', async () => {
|
|
80
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
81
|
+
const handlers = setupHandlers();
|
|
82
|
+
await handlers.get('gog_sheets_copy')!({ spreadsheetId: 'sid', title: 'Copy', parent: 'folderId' });
|
|
83
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'copy', 'sid', 'Copy', '--parent=folderId'], { account: undefined });
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// 5. export
|
|
88
|
+
describe('gog_sheets_export', () => {
|
|
89
|
+
it('calls runOrDiagnose with correct args', async () => {
|
|
90
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
91
|
+
const handlers = setupHandlers();
|
|
92
|
+
await handlers.get('gog_sheets_export')!({ spreadsheetId: 'sid' });
|
|
93
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'export', 'sid'], { account: undefined });
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('includes --format and --out when provided', async () => {
|
|
97
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
98
|
+
const handlers = setupHandlers();
|
|
99
|
+
await handlers.get('gog_sheets_export')!({ spreadsheetId: 'sid', format: 'pdf', out: '/tmp/out.pdf' });
|
|
100
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'export', 'sid', '--format=pdf', '--out=/tmp/out.pdf'], { account: undefined });
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// 6. freeze
|
|
105
|
+
describe('gog_sheets_freeze', () => {
|
|
106
|
+
it('calls runOrDiagnose with no optional flags', async () => {
|
|
107
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
108
|
+
const handlers = setupHandlers();
|
|
109
|
+
await handlers.get('gog_sheets_freeze')!({ spreadsheetId: 'sid' });
|
|
110
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'freeze', 'sid'], { account: undefined });
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('includes --rows, --cols, --sheet when provided', async () => {
|
|
114
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
115
|
+
const handlers = setupHandlers();
|
|
116
|
+
await handlers.get('gog_sheets_freeze')!({ spreadsheetId: 'sid', rows: 1, cols: 2, sheet: 'Data' });
|
|
117
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'freeze', 'sid', '--rows=1', '--cols=2', '--sheet=Data'], { account: undefined });
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('includes --rows=0 when rows is 0', async () => {
|
|
121
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
122
|
+
const handlers = setupHandlers();
|
|
123
|
+
await handlers.get('gog_sheets_freeze')!({ spreadsheetId: 'sid', rows: 0 });
|
|
124
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'freeze', 'sid', '--rows=0'], { account: undefined });
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
// 7. insert
|
|
129
|
+
describe('gog_sheets_insert', () => {
|
|
130
|
+
it('calls runOrDiagnose with required args', async () => {
|
|
131
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
132
|
+
const handlers = setupHandlers();
|
|
133
|
+
await handlers.get('gog_sheets_insert')!({ spreadsheetId: 'sid', sheet: 'Sheet1', dimension: 'ROWS', start: 5 });
|
|
134
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'insert', 'sid', 'Sheet1', 'ROWS', '5'], { account: undefined });
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('includes --count and --after when provided', async () => {
|
|
138
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
139
|
+
const handlers = setupHandlers();
|
|
140
|
+
await handlers.get('gog_sheets_insert')!({ spreadsheetId: 'sid', sheet: 'S1', dimension: 'COLUMNS', start: 0, count: 3, after: true });
|
|
141
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'insert', 'sid', 'S1', 'COLUMNS', '0', '--count=3', '--after'], { account: undefined });
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('includes --count=0 when count is 0', async () => {
|
|
145
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
146
|
+
const handlers = setupHandlers();
|
|
147
|
+
await handlers.get('gog_sheets_insert')!({ spreadsheetId: 'sid', sheet: 'S1', dimension: 'ROWS', start: 0, count: 0 });
|
|
148
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'insert', 'sid', 'S1', 'ROWS', '0', '--count=0'], { account: undefined });
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('omits --after when false', async () => {
|
|
152
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
153
|
+
const handlers = setupHandlers();
|
|
154
|
+
await handlers.get('gog_sheets_insert')!({ spreadsheetId: 'sid', sheet: 'S1', dimension: 'ROWS', start: 0, after: false });
|
|
155
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'insert', 'sid', 'S1', 'ROWS', '0'], { account: undefined });
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
// 8. merge
|
|
160
|
+
describe('gog_sheets_merge', () => {
|
|
161
|
+
it('calls runOrDiagnose with correct args', async () => {
|
|
162
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
163
|
+
const handlers = setupHandlers();
|
|
164
|
+
await handlers.get('gog_sheets_merge')!({ spreadsheetId: 'sid', range: 'A1:C3' });
|
|
165
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'merge', 'sid', 'A1:C3'], { account: undefined });
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it('includes --type when provided', async () => {
|
|
169
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
170
|
+
const handlers = setupHandlers();
|
|
171
|
+
await handlers.get('gog_sheets_merge')!({ spreadsheetId: 'sid', range: 'A1:C3', type: 'MERGE_COLUMNS' });
|
|
172
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'merge', 'sid', 'A1:C3', '--type=MERGE_COLUMNS'], { account: undefined });
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
// 9. unmerge
|
|
177
|
+
describe('gog_sheets_unmerge', () => {
|
|
178
|
+
it('calls runOrDiagnose with correct args', async () => {
|
|
179
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
180
|
+
const handlers = setupHandlers();
|
|
181
|
+
await handlers.get('gog_sheets_unmerge')!({ spreadsheetId: 'sid', range: 'A1:C3' });
|
|
182
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'unmerge', 'sid', 'A1:C3'], { account: undefined });
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
// 10. format
|
|
187
|
+
describe('gog_sheets_format', () => {
|
|
188
|
+
it('calls runOrDiagnose with correct args', async () => {
|
|
189
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
190
|
+
const handlers = setupHandlers();
|
|
191
|
+
const fj = '{"textFormat":{"bold":true}}';
|
|
192
|
+
await handlers.get('gog_sheets_format')!({ spreadsheetId: 'sid', range: 'A1:B2', formatJson: fj });
|
|
193
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'format', 'sid', 'A1:B2', `--format-json=${fj}`], { account: undefined });
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it('includes --format-fields when provided', async () => {
|
|
197
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
198
|
+
const handlers = setupHandlers();
|
|
199
|
+
await handlers.get('gog_sheets_format')!({ spreadsheetId: 'sid', range: 'A1', formatJson: '{}', formatFields: 'textFormat.bold' });
|
|
200
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'format', 'sid', 'A1', '--format-json={}', '--format-fields=textFormat.bold'], { account: undefined });
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
// 11. number-format
|
|
205
|
+
describe('gog_sheets_number_format', () => {
|
|
206
|
+
it('calls runOrDiagnose with correct args', async () => {
|
|
207
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
208
|
+
const handlers = setupHandlers();
|
|
209
|
+
await handlers.get('gog_sheets_number_format')!({ spreadsheetId: 'sid', range: 'A1:A10' });
|
|
210
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'number-format', 'sid', 'A1:A10'], { account: undefined });
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it('includes --type and --pattern when provided', async () => {
|
|
214
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
215
|
+
const handlers = setupHandlers();
|
|
216
|
+
await handlers.get('gog_sheets_number_format')!({ spreadsheetId: 'sid', range: 'A1', type: 'CURRENCY', pattern: '#,##0.00' });
|
|
217
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'number-format', 'sid', 'A1', '--type=CURRENCY', '--pattern=#,##0.00'], { account: undefined });
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
// 12. read-format
|
|
222
|
+
describe('gog_sheets_read_format', () => {
|
|
223
|
+
it('calls runOrDiagnose with correct args', async () => {
|
|
224
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
225
|
+
const handlers = setupHandlers();
|
|
226
|
+
await handlers.get('gog_sheets_read_format')!({ spreadsheetId: 'sid', range: 'A1:B2' });
|
|
227
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'read-format', 'sid', 'A1:B2'], { account: undefined });
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it('includes --effective when true', async () => {
|
|
231
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
232
|
+
const handlers = setupHandlers();
|
|
233
|
+
await handlers.get('gog_sheets_read_format')!({ spreadsheetId: 'sid', range: 'A1', effective: true });
|
|
234
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'read-format', 'sid', 'A1', '--effective'], { account: undefined });
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it('omits --effective when false', async () => {
|
|
238
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
239
|
+
const handlers = setupHandlers();
|
|
240
|
+
await handlers.get('gog_sheets_read_format')!({ spreadsheetId: 'sid', range: 'A1', effective: false });
|
|
241
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'read-format', 'sid', 'A1'], { account: undefined });
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
// 13. resize-columns
|
|
246
|
+
describe('gog_sheets_resize_columns', () => {
|
|
247
|
+
it('calls runOrDiagnose with correct args', async () => {
|
|
248
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
249
|
+
const handlers = setupHandlers();
|
|
250
|
+
await handlers.get('gog_sheets_resize_columns')!({ spreadsheetId: 'sid', columns: 'A:C' });
|
|
251
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'resize-columns', 'sid', 'A:C'], { account: undefined });
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
it('includes --width and --auto when provided', async () => {
|
|
255
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
256
|
+
const handlers = setupHandlers();
|
|
257
|
+
await handlers.get('gog_sheets_resize_columns')!({ spreadsheetId: 'sid', columns: 'A:C', width: 200, auto: true });
|
|
258
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'resize-columns', 'sid', 'A:C', '--width=200', '--auto'], { account: undefined });
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it('includes --width=0 when width is 0', async () => {
|
|
262
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
263
|
+
const handlers = setupHandlers();
|
|
264
|
+
await handlers.get('gog_sheets_resize_columns')!({ spreadsheetId: 'sid', columns: 'A:A', width: 0 });
|
|
265
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'resize-columns', 'sid', 'A:A', '--width=0'], { account: undefined });
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
// 14. resize-rows
|
|
270
|
+
describe('gog_sheets_resize_rows', () => {
|
|
271
|
+
it('calls runOrDiagnose with correct args', async () => {
|
|
272
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
273
|
+
const handlers = setupHandlers();
|
|
274
|
+
await handlers.get('gog_sheets_resize_rows')!({ spreadsheetId: 'sid', rows: '1:10' });
|
|
275
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'resize-rows', 'sid', '1:10'], { account: undefined });
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
it('includes --height and --auto when provided', async () => {
|
|
279
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
280
|
+
const handlers = setupHandlers();
|
|
281
|
+
await handlers.get('gog_sheets_resize_rows')!({ spreadsheetId: 'sid', rows: '1:5', height: 40, auto: true });
|
|
282
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'resize-rows', 'sid', '1:5', '--height=40', '--auto'], { account: undefined });
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it('includes --height=0 when height is 0', async () => {
|
|
286
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
287
|
+
const handlers = setupHandlers();
|
|
288
|
+
await handlers.get('gog_sheets_resize_rows')!({ spreadsheetId: 'sid', rows: '1:1', height: 0 });
|
|
289
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'resize-rows', 'sid', '1:1', '--height=0'], { account: undefined });
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
// 15. notes
|
|
294
|
+
describe('gog_sheets_notes', () => {
|
|
295
|
+
it('calls runOrDiagnose with correct args', async () => {
|
|
296
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
297
|
+
const handlers = setupHandlers();
|
|
298
|
+
await handlers.get('gog_sheets_notes')!({ spreadsheetId: 'sid', range: 'A1:B5' });
|
|
299
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'notes', 'sid', 'A1:B5'], { account: undefined });
|
|
300
|
+
});
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
// 16. update-note
|
|
304
|
+
describe('gog_sheets_update_note', () => {
|
|
305
|
+
it('calls runOrDiagnose with correct args', async () => {
|
|
306
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
307
|
+
const handlers = setupHandlers();
|
|
308
|
+
await handlers.get('gog_sheets_update_note')!({ spreadsheetId: 'sid', range: 'A1', note: 'Hello' });
|
|
309
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'update-note', 'sid', 'A1', '--note=Hello'], { account: undefined });
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
it('passes empty string to clear note', async () => {
|
|
313
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
314
|
+
const handlers = setupHandlers();
|
|
315
|
+
await handlers.get('gog_sheets_update_note')!({ spreadsheetId: 'sid', range: 'A1', note: '' });
|
|
316
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'update-note', 'sid', 'A1', '--note='], { account: undefined });
|
|
317
|
+
});
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
// 17. links
|
|
321
|
+
describe('gog_sheets_links', () => {
|
|
322
|
+
it('calls runOrDiagnose with correct args', async () => {
|
|
323
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
324
|
+
const handlers = setupHandlers();
|
|
325
|
+
await handlers.get('gog_sheets_links')!({ spreadsheetId: 'sid', range: 'A1:Z100' });
|
|
326
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'links', 'sid', 'A1:Z100'], { account: undefined });
|
|
327
|
+
});
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
// 18. named-ranges list
|
|
331
|
+
describe('gog_sheets_named_ranges_list', () => {
|
|
332
|
+
it('calls runOrDiagnose with correct args', async () => {
|
|
333
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
334
|
+
const handlers = setupHandlers();
|
|
335
|
+
await handlers.get('gog_sheets_named_ranges_list')!({ spreadsheetId: 'sid' });
|
|
336
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'named-ranges', 'list', 'sid'], { account: undefined });
|
|
337
|
+
});
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
// 19. named-ranges get
|
|
341
|
+
describe('gog_sheets_named_ranges_get', () => {
|
|
342
|
+
it('calls runOrDiagnose with correct args', async () => {
|
|
343
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
344
|
+
const handlers = setupHandlers();
|
|
345
|
+
await handlers.get('gog_sheets_named_ranges_get')!({ spreadsheetId: 'sid', nameOrId: 'MyRange' });
|
|
346
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'named-ranges', 'get', 'sid', 'MyRange'], { account: undefined });
|
|
347
|
+
});
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
// 20. named-ranges add
|
|
351
|
+
describe('gog_sheets_named_ranges_add', () => {
|
|
352
|
+
it('calls runOrDiagnose with correct args', async () => {
|
|
353
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
354
|
+
const handlers = setupHandlers();
|
|
355
|
+
await handlers.get('gog_sheets_named_ranges_add')!({ spreadsheetId: 'sid', name: 'Totals', range: 'Sheet1!A1:B10' });
|
|
356
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'named-ranges', 'add', 'sid', 'Totals', 'Sheet1!A1:B10'], { account: undefined });
|
|
357
|
+
});
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
// 21. named-ranges update
|
|
361
|
+
describe('gog_sheets_named_ranges_update', () => {
|
|
362
|
+
it('calls runOrDiagnose with no optional flags', async () => {
|
|
363
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
364
|
+
const handlers = setupHandlers();
|
|
365
|
+
await handlers.get('gog_sheets_named_ranges_update')!({ spreadsheetId: 'sid', nameOrId: 'MyRange' });
|
|
366
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'named-ranges', 'update', 'sid', 'MyRange'], { account: undefined });
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
it('includes --name and --range when provided', async () => {
|
|
370
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
371
|
+
const handlers = setupHandlers();
|
|
372
|
+
await handlers.get('gog_sheets_named_ranges_update')!({ spreadsheetId: 'sid', nameOrId: 'MyRange', name: 'NewName', range: 'Sheet1!C1:D5' });
|
|
373
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'named-ranges', 'update', 'sid', 'MyRange', '--name=NewName', '--range=Sheet1!C1:D5'], { account: undefined });
|
|
374
|
+
});
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
// 22. named-ranges delete
|
|
378
|
+
describe('gog_sheets_named_ranges_delete', () => {
|
|
379
|
+
it('calls runOrDiagnose with correct args', async () => {
|
|
380
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
381
|
+
const handlers = setupHandlers();
|
|
382
|
+
await handlers.get('gog_sheets_named_ranges_delete')!({ spreadsheetId: 'sid', nameOrId: 'OldRange' });
|
|
383
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'named-ranges', 'delete', 'sid', 'OldRange'], { account: undefined });
|
|
384
|
+
});
|
|
385
|
+
});
|
package/tsconfig.json
ADDED
package/vitest.config.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { defineConfig } from 'vitest/config';
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
test: {
|
|
5
|
+
coverage: {
|
|
6
|
+
provider: 'v8',
|
|
7
|
+
include: ['src/**/*.ts'],
|
|
8
|
+
exclude: ['src/index.ts'],
|
|
9
|
+
thresholds: {
|
|
10
|
+
lines: 100,
|
|
11
|
+
functions: 100,
|
|
12
|
+
branches: 100,
|
|
13
|
+
statements: 100,
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
});
|