gogcli-mcp-sheets 2.6.0 → 2.7.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.
@@ -7,7 +7,7 @@
7
7
  },
8
8
  "metadata": {
9
9
  "description": "Extended Google Sheets for Claude via gogcli — auth + full Sheets support",
10
- "version": "2.6.0"
10
+ "version": "2.7.0"
11
11
  },
12
12
  "plugins": [
13
13
  {
@@ -15,7 +15,7 @@
15
15
  "displayName": "gogcli (Sheets)",
16
16
  "source": "./",
17
17
  "description": "Extended Google Sheets for Claude via gogcli — auth + full Sheets support",
18
- "version": "2.6.0",
18
+ "version": "2.7.0",
19
19
  "author": {
20
20
  "name": "Chris Hall"
21
21
  },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "gogcli-mcp-sheets",
3
3
  "displayName": "gogcli (Sheets)",
4
- "version": "2.6.0",
4
+ "version": "2.7.0",
5
5
  "description": "Extended Google Sheets for Claude via gogcli — auth + full Sheets support",
6
6
  "author": {
7
7
  "name": "Chris Hall",
package/dist/index.js CHANGED
@@ -31399,7 +31399,7 @@ function registerSheetsTools(server2) {
31399
31399
  }
31400
31400
 
31401
31401
  // ../gogcli-mcp/src/server.ts
31402
- var VERSION = true ? "2.6.0" : "0.0.0";
31402
+ var VERSION = true ? "2.7.0" : "0.0.0";
31403
31403
  function createServer(options) {
31404
31404
  return new McpServer({
31405
31405
  name: options?.name ?? "gogcli",
@@ -31552,7 +31552,7 @@ function registerExtraSheetsTools(server2) {
31552
31552
  account: accountParam
31553
31553
  }
31554
31554
  }, async ({ spreadsheetId, sheet, dimension, start, count, after, inheritFromBefore, account }) => {
31555
- const effectiveStart = after ? start + 1 : start;
31555
+ const effectiveStart = start + 1;
31556
31556
  const args = ["sheets", "insert", spreadsheetId, sheet, dimension, String(effectiveStart)];
31557
31557
  if (count !== void 0) args.push(`--count=${count}`);
31558
31558
  if (after) args.push("--after");
@@ -31815,6 +31815,71 @@ ${result.content[0].text}` }] };
31815
31815
  if (cellsJson) args.push(`--cells-json=${cellsJson}`);
31816
31816
  return runOrDiagnose(args, { account });
31817
31817
  });
31818
+ server2.registerTool("gog_sheets_validation_get", {
31819
+ description: "Read the data-validation rules (dropdowns, checkboxes, number/date conditions, custom formulas) applied to a range.",
31820
+ annotations: { readOnlyHint: true },
31821
+ inputSchema: {
31822
+ spreadsheetId: external_exports.string().describe("Spreadsheet ID"),
31823
+ range: external_exports.string().describe("Range to inspect (e.g. Sheet1!A1:A10)"),
31824
+ account: accountParam
31825
+ }
31826
+ }, async ({ spreadsheetId, range, account }) => {
31827
+ return runOrDiagnose(["sheets", "validation", "get", spreadsheetId, range], { account });
31828
+ });
31829
+ server2.registerTool("gog_sheets_validation_set", {
31830
+ description: "Set a data-validation rule on a range \u2014 dropdowns (ONE_OF_LIST / ONE_OF_RANGE), checkboxes (BOOLEAN), number/date conditions, or custom formulas. Overwrites any existing rule on the range. Repeat values for list entries or between-bounds.",
31831
+ annotations: { destructiveHint: true },
31832
+ inputSchema: {
31833
+ spreadsheetId: external_exports.string().describe("Spreadsheet ID"),
31834
+ range: external_exports.string().describe("Range to apply the rule to (e.g. Sheet1!A1:A10)"),
31835
+ type: external_exports.string().describe("Condition type, e.g. ONE_OF_LIST, ONE_OF_RANGE, NUMBER_BETWEEN, NUMBER_GREATER, DATE_AFTER, BOOLEAN, CUSTOM_FORMULA"),
31836
+ values: external_exports.array(external_exports.string()).optional().describe("Condition values (repeatable): list entries for ONE_OF_LIST, two bounds for *_BETWEEN, a range for ONE_OF_RANGE, a formula for CUSTOM_FORMULA"),
31837
+ strict: external_exports.boolean().optional().describe("Reject invalid input instead of showing a warning"),
31838
+ inputMessage: external_exports.string().optional().describe("Message shown when the cell is selected"),
31839
+ showCustomUi: external_exports.boolean().optional().describe("Show dropdown or checkbox UI where supported"),
31840
+ filteredRowsIncluded: external_exports.boolean().optional().describe("Apply the rule to filtered rows too; required for table-managed dropdown columns"),
31841
+ account: accountParam
31842
+ }
31843
+ }, async ({ spreadsheetId, range, type, values, strict, inputMessage, showCustomUi, filteredRowsIncluded, account }) => {
31844
+ const args = ["sheets", "validation", "set", spreadsheetId, range, `--type=${type}`];
31845
+ if (values) for (const v of values) args.push(`--value=${v}`);
31846
+ if (strict) args.push("--strict");
31847
+ if (inputMessage) args.push(`--input-message=${inputMessage}`);
31848
+ if (showCustomUi) args.push("--show-custom-ui");
31849
+ if (filteredRowsIncluded) args.push("--filtered-rows-included");
31850
+ return runOrDiagnose(args, { account });
31851
+ });
31852
+ server2.registerTool("gog_sheets_validation_clear", {
31853
+ description: "Remove all data-validation rules from a range.",
31854
+ annotations: { destructiveHint: true },
31855
+ inputSchema: {
31856
+ spreadsheetId: external_exports.string().describe("Spreadsheet ID"),
31857
+ range: external_exports.string().describe("Range to clear (e.g. Sheet1!A1:A10)"),
31858
+ filteredRowsIncluded: external_exports.boolean().optional().describe("Clear rules from filtered rows too; required for table-managed dropdown columns"),
31859
+ account: accountParam
31860
+ }
31861
+ }, async ({ spreadsheetId, range, filteredRowsIncluded, account }) => {
31862
+ const args = ["sheets", "validation", "clear", spreadsheetId, range];
31863
+ if (filteredRowsIncluded) args.push("--filtered-rows-included");
31864
+ return runOrDiagnose(args, { account });
31865
+ });
31866
+ server2.registerTool("gog_sheets_delete_dimension", {
31867
+ description: "Delete a span of rows or columns, table-aware: intersecting table objects are preserved (shrunk) along with their remaining data, instead of being corrupted as with a raw DeleteDimension batch update. Target by A1 range (e.g. Sheet1!5:7 or Sheet1!C:D) or by sheet name plus start/end.",
31868
+ annotations: { destructiveHint: true },
31869
+ inputSchema: {
31870
+ spreadsheetId: external_exports.string().describe("Spreadsheet ID"),
31871
+ rangeOrSheet: external_exports.string().describe("A1 range covering the rows/columns to delete, or a sheet name (then start/end are required)"),
31872
+ dimension: external_exports.enum(["ROWS", "COLUMNS"]).describe("Dimension to delete"),
31873
+ start: external_exports.number().int().optional().describe("First row/column to delete (1-based, inclusive; required with a sheet-name target)"),
31874
+ end: external_exports.number().int().optional().describe("Last row/column to delete (1-based, inclusive; required with a sheet-name target)"),
31875
+ account: accountParam
31876
+ }
31877
+ }, async ({ spreadsheetId, rangeOrSheet, dimension, start, end, account }) => {
31878
+ const args = ["sheets", "delete-dimension", spreadsheetId, rangeOrSheet, `--dimension=${dimension}`];
31879
+ if (start !== void 0) args.push(`--start=${start}`);
31880
+ if (end !== void 0) args.push(`--end=${end}`);
31881
+ return runOrDiagnose(args, { account });
31882
+ });
31818
31883
  server2.registerTool("gog_sheets_named_ranges_list", {
31819
31884
  description: "List all named ranges in a spreadsheet.",
31820
31885
  annotations: { readOnlyHint: true },
@@ -32037,7 +32102,7 @@ ${result.content[0].text}` }] };
32037
32102
  return runOrDiagnose(["sheets", "table", "clear", spreadsheetId, tableId], { account });
32038
32103
  });
32039
32104
  server2.registerTool("gog_sheets_table_delete", {
32040
- description: "Delete a Google Sheets table. WARNING: the underlying `gog`/Sheets behaviour is that deleting a table also DESTROYS every cell value in the table range \u2014 not just the table styling/columns. By default this tool prevents that data loss by emulating the Sheets UI's \"Convert to range\": it reads the table's cells (values AND formulas) first, deletes the table, then restores the data into the now-plain range. Formatting, banding, and data-validation dropdowns are still lost (gog cannot round-trip those yet). Set keep_data=false to delete the table AND wipe its cell data (the raw destructive behaviour). To preserve everything, snapshot the whole spreadsheet first with gog_sheets_snapshot.",
32105
+ description: "Delete a Google Sheets table. WARNING: the underlying `gog`/Sheets behaviour is that deleting a table also DESTROYS every cell value in the table range \u2014 not just the table styling/columns. By default this tool prevents that data loss by emulating the Sheets UI's \"Convert to range\": it reads the table's cells (values AND formulas) first, deletes the table, then restores the data into the now-plain range. Formatting and banding are still lost; data-validation dropdowns are also not restored automatically, but you can snapshot them first with gog_sheets_validation_get and re-apply with gog_sheets_validation_set. Set keep_data=false to delete the table AND wipe its cell data (the raw destructive behaviour). To preserve everything, snapshot the whole spreadsheet first with gog_sheets_snapshot.",
32041
32106
  annotations: { destructiveHint: true },
32042
32107
  inputSchema: {
32043
32108
  spreadsheetId: external_exports.string().describe("Spreadsheet ID"),
package/manifest.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "manifest_version": "0.3",
4
4
  "name": "gogcli-mcp-sheets",
5
5
  "display_name": "gogcli (Sheets)",
6
- "version": "2.6.0",
6
+ "version": "2.7.0",
7
7
  "description": "Extended Google Sheets for Claude via gogcli — auth + full Sheets support",
8
8
  "author": {
9
9
  "name": "Chris Hall",
@@ -184,6 +184,22 @@
184
184
  "name": "gog_sheets_links_set",
185
185
  "description": "Set native cell hyperlinks: single link, multi-link rich-text cell (runsJson), or batch (cellsJson)."
186
186
  },
187
+ {
188
+ "name": "gog_sheets_validation_get",
189
+ "description": "Read data-validation rules (dropdowns, checkboxes, conditions) on a range."
190
+ },
191
+ {
192
+ "name": "gog_sheets_validation_set",
193
+ "description": "Set a data-validation rule on a range: dropdowns, checkboxes, number/date conditions, or custom formulas."
194
+ },
195
+ {
196
+ "name": "gog_sheets_validation_clear",
197
+ "description": "Remove all data-validation rules from a range."
198
+ },
199
+ {
200
+ "name": "gog_sheets_delete_dimension",
201
+ "description": "Delete a row or column span, table-aware: intersecting tables are shrunk and their remaining data preserved."
202
+ },
187
203
  {
188
204
  "name": "gog_sheets_named_ranges_list",
189
205
  "description": "List all named ranges in a spreadsheet."
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gogcli-mcp-sheets",
3
- "version": "2.6.0",
3
+ "version": "2.7.0",
4
4
  "mcpName": "io.github.chrischall/gogcli-mcp-sheets",
5
5
  "description": "Extended Google Sheets MCP server via gogcli — all base tools plus full Sheets support",
6
6
  "author": "Claude Code (AI) <https://www.anthropic.com/claude>",
package/server.json CHANGED
@@ -7,12 +7,12 @@
7
7
  "source": "github",
8
8
  "subfolder": "packages/gogcli-mcp-sheets"
9
9
  },
10
- "version": "2.6.0",
10
+ "version": "2.7.0",
11
11
  "packages": [
12
12
  {
13
13
  "registryType": "npm",
14
14
  "identifier": "gogcli-mcp-sheets",
15
- "version": "2.6.0",
15
+ "version": "2.7.0",
16
16
  "transport": {
17
17
  "type": "stdio"
18
18
  },
@@ -182,11 +182,12 @@ export function registerExtraSheetsTools(server: McpServer): void {
182
182
  account: accountParam,
183
183
  },
184
184
  }, async ({ spreadsheetId, sheet, dimension, start, count, after, inheritFromBefore, account }) => {
185
- // The underlying `gog sheets insert` treats `start` as 1-based with `--after` shifting +1.
186
- // To honor our 0-based `start` and make `after:true` actually shift the insertion to start+1
187
- // (issue #42), we send `start+1` when `after:true`. With after:false the CLI's `start-1`
188
- // conversion lands the insertion at our 0-based start.
189
- const effectiveStart = after ? start + 1 : start;
185
+ // gog's positional `<start>` is 1-based, and it rejects 0 ("start must be >= 1").
186
+ // Without --after it inserts *before* the position, so API start_index = positional - 1;
187
+ // with --after it inserts *after*, so API start_index = positional. Our `start` is 0-based,
188
+ // and the contract is: after:false lands at start_index=start, after:true at start_index=start+1.
189
+ // Both cases reduce to sending positional = start + 1 (issue #42 + the off-by-one this fixes).
190
+ const effectiveStart = start + 1;
190
191
  const args = ['sheets', 'insert', spreadsheetId, sheet, dimension, String(effectiveStart)];
191
192
  if (count !== undefined) args.push(`--count=${count}`);
192
193
  if (after) args.push('--after');
@@ -465,6 +466,75 @@ export function registerExtraSheetsTools(server: McpServer): void {
465
466
  return runOrDiagnose(args, { account });
466
467
  });
467
468
 
469
+ server.registerTool('gog_sheets_validation_get', {
470
+ description: 'Read the data-validation rules (dropdowns, checkboxes, number/date conditions, custom formulas) applied to a range.',
471
+ annotations: { readOnlyHint: true },
472
+ inputSchema: {
473
+ spreadsheetId: z.string().describe('Spreadsheet ID'),
474
+ range: z.string().describe('Range to inspect (e.g. Sheet1!A1:A10)'),
475
+ account: accountParam,
476
+ },
477
+ }, async ({ spreadsheetId, range, account }) => {
478
+ return runOrDiagnose(['sheets', 'validation', 'get', spreadsheetId, range], { account });
479
+ });
480
+
481
+ server.registerTool('gog_sheets_validation_set', {
482
+ description: 'Set a data-validation rule on a range — dropdowns (ONE_OF_LIST / ONE_OF_RANGE), checkboxes (BOOLEAN), number/date conditions, or custom formulas. Overwrites any existing rule on the range. Repeat values for list entries or between-bounds.',
483
+ annotations: { destructiveHint: true },
484
+ inputSchema: {
485
+ spreadsheetId: z.string().describe('Spreadsheet ID'),
486
+ range: z.string().describe('Range to apply the rule to (e.g. Sheet1!A1:A10)'),
487
+ type: z.string().describe('Condition type, e.g. ONE_OF_LIST, ONE_OF_RANGE, NUMBER_BETWEEN, NUMBER_GREATER, DATE_AFTER, BOOLEAN, CUSTOM_FORMULA'),
488
+ values: z.array(z.string()).optional().describe('Condition values (repeatable): list entries for ONE_OF_LIST, two bounds for *_BETWEEN, a range for ONE_OF_RANGE, a formula for CUSTOM_FORMULA'),
489
+ strict: z.boolean().optional().describe('Reject invalid input instead of showing a warning'),
490
+ inputMessage: z.string().optional().describe('Message shown when the cell is selected'),
491
+ showCustomUi: z.boolean().optional().describe('Show dropdown or checkbox UI where supported'),
492
+ filteredRowsIncluded: z.boolean().optional().describe('Apply the rule to filtered rows too; required for table-managed dropdown columns'),
493
+ account: accountParam,
494
+ },
495
+ }, async ({ spreadsheetId, range, type, values, strict, inputMessage, showCustomUi, filteredRowsIncluded, account }) => {
496
+ const args = ['sheets', 'validation', 'set', spreadsheetId, range, `--type=${type}`];
497
+ if (values) for (const v of values) args.push(`--value=${v}`);
498
+ if (strict) args.push('--strict');
499
+ if (inputMessage) args.push(`--input-message=${inputMessage}`);
500
+ if (showCustomUi) args.push('--show-custom-ui');
501
+ if (filteredRowsIncluded) args.push('--filtered-rows-included');
502
+ return runOrDiagnose(args, { account });
503
+ });
504
+
505
+ server.registerTool('gog_sheets_validation_clear', {
506
+ description: 'Remove all data-validation rules from a range.',
507
+ annotations: { destructiveHint: true },
508
+ inputSchema: {
509
+ spreadsheetId: z.string().describe('Spreadsheet ID'),
510
+ range: z.string().describe('Range to clear (e.g. Sheet1!A1:A10)'),
511
+ filteredRowsIncluded: z.boolean().optional().describe('Clear rules from filtered rows too; required for table-managed dropdown columns'),
512
+ account: accountParam,
513
+ },
514
+ }, async ({ spreadsheetId, range, filteredRowsIncluded, account }) => {
515
+ const args = ['sheets', 'validation', 'clear', spreadsheetId, range];
516
+ if (filteredRowsIncluded) args.push('--filtered-rows-included');
517
+ return runOrDiagnose(args, { account });
518
+ });
519
+
520
+ server.registerTool('gog_sheets_delete_dimension', {
521
+ description: 'Delete a span of rows or columns, table-aware: intersecting table objects are preserved (shrunk) along with their remaining data, instead of being corrupted as with a raw DeleteDimension batch update. Target by A1 range (e.g. Sheet1!5:7 or Sheet1!C:D) or by sheet name plus start/end.',
522
+ annotations: { destructiveHint: true },
523
+ inputSchema: {
524
+ spreadsheetId: z.string().describe('Spreadsheet ID'),
525
+ rangeOrSheet: z.string().describe('A1 range covering the rows/columns to delete, or a sheet name (then start/end are required)'),
526
+ dimension: z.enum(['ROWS', 'COLUMNS']).describe('Dimension to delete'),
527
+ start: z.number().int().optional().describe('First row/column to delete (1-based, inclusive; required with a sheet-name target)'),
528
+ end: z.number().int().optional().describe('Last row/column to delete (1-based, inclusive; required with a sheet-name target)'),
529
+ account: accountParam,
530
+ },
531
+ }, async ({ spreadsheetId, rangeOrSheet, dimension, start, end, account }) => {
532
+ const args = ['sheets', 'delete-dimension', spreadsheetId, rangeOrSheet, `--dimension=${dimension}`];
533
+ if (start !== undefined) args.push(`--start=${start}`);
534
+ if (end !== undefined) args.push(`--end=${end}`);
535
+ return runOrDiagnose(args, { account });
536
+ });
537
+
468
538
  server.registerTool('gog_sheets_named_ranges_list', {
469
539
  description: 'List all named ranges in a spreadsheet.',
470
540
  annotations: { readOnlyHint: true },
@@ -711,7 +781,7 @@ export function registerExtraSheetsTools(server: McpServer): void {
711
781
  description:
712
782
  'Delete a Google Sheets table. WARNING: the underlying `gog`/Sheets behaviour is that deleting a table also DESTROYS every cell value in the table range — not just the table styling/columns. ' +
713
783
  'By default this tool prevents that data loss by emulating the Sheets UI\'s "Convert to range": it reads the table\'s cells (values AND formulas) first, deletes the table, then restores the data into the now-plain range. ' +
714
- 'Formatting, banding, and data-validation dropdowns are still lost (gog cannot round-trip those yet). ' +
784
+ 'Formatting and banding are still lost; data-validation dropdowns are also not restored automatically, but you can snapshot them first with gog_sheets_validation_get and re-apply with gog_sheets_validation_set. ' +
715
785
  'Set keep_data=false to delete the table AND wipe its cell data (the raw destructive behaviour). To preserve everything, snapshot the whole spreadsheet first with gog_sheets_snapshot.',
716
786
  annotations: { destructiveHint: true },
717
787
  inputSchema: {
@@ -114,11 +114,23 @@ describe('gog_sheets_freeze', () => {
114
114
 
115
115
  // 7. insert
116
116
  describe('gog_sheets_insert', () => {
117
- it('calls runOrDiagnose with required args', async () => {
117
+ it('shifts our 0-based start by +1 so the new dimension lands AT start (after:false)', async () => {
118
+ // gog's positional <start> is 1-based and, without --after, it inserts *before* it,
119
+ // so API start_index = positional - 1. To land at our 0-based start we send start+1.
120
+ // start=5 → positional 6 → API start_index 5.
118
121
  vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
119
122
  const handlers = setupHandlers();
120
123
  await handlers.get('gog_sheets_insert')!({ spreadsheetId: 'sid', sheet: 'Sheet1', dimension: 'ROWS', start: 5 });
121
- expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'insert', 'sid', 'Sheet1', 'ROWS', '5'], { account: undefined });
124
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'insert', 'sid', 'Sheet1', 'ROWS', '6'], { account: undefined });
125
+ });
126
+
127
+ it('bug: start=1, after:false inserts before 0-based index 1 (new row 2), not at the top', async () => {
128
+ // Regression for the reported "range.startIndex must be set" / wrong-placement bug.
129
+ // start=1 → positional 2 → gog API start_index 1 (insert before the 2nd row).
130
+ vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
131
+ const handlers = setupHandlers();
132
+ await handlers.get('gog_sheets_insert')!({ spreadsheetId: 'sid', sheet: 'S1', dimension: 'ROWS', start: 1, count: 1 });
133
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'insert', 'sid', 'S1', 'ROWS', '2', '--count=1'], { account: undefined });
122
134
  });
123
135
 
124
136
  it('shifts start by +1 when after:true so the new dimension lands AFTER start', async () => {
@@ -142,14 +154,17 @@ describe('gog_sheets_insert', () => {
142
154
  vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
143
155
  const handlers = setupHandlers();
144
156
  await handlers.get('gog_sheets_insert')!({ spreadsheetId: 'sid', sheet: 'S1', dimension: 'ROWS', start: 0, count: 0 });
145
- expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'insert', 'sid', 'S1', 'ROWS', '0', '--count=0'], { account: undefined });
157
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'insert', 'sid', 'S1', 'ROWS', '1', '--count=0'], { account: undefined });
146
158
  });
147
159
 
148
- it('omits --after and does not shift start when after:false', async () => {
160
+ it('omits --after and sends start+1 so start=0 inserts at the very top (after:false)', async () => {
161
+ // start=0 → positional 1 → gog API start_index 0. Sending positional 0 would make gog
162
+ // reject the call outright ("start must be >= 1"), so the +1 shift is what makes a
163
+ // top-of-sheet insert reachable at all.
149
164
  vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
150
165
  const handlers = setupHandlers();
151
166
  await handlers.get('gog_sheets_insert')!({ spreadsheetId: 'sid', sheet: 'S1', dimension: 'ROWS', start: 0, after: false });
152
- expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'insert', 'sid', 'S1', 'ROWS', '0'], { account: undefined });
167
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'insert', 'sid', 'S1', 'ROWS', '1'], { account: undefined });
153
168
  });
154
169
 
155
170
  it('passes --inherit-from-before=true when set', async () => {
@@ -1269,3 +1284,82 @@ describe('gog_sheets_snapshot', () => {
1269
1284
  expect(lib.runOrDiagnose).toHaveBeenCalledWith(['drive', 'copy', 'sid', 'Backup A', '--parent=folder1'], { account: 'a@b.com' });
1270
1285
  });
1271
1286
  });
1287
+
1288
+ // gog 0.24.0
1289
+ describe('gog_sheets_validation_get', () => {
1290
+ it('reads validation rules for a range', async () => {
1291
+ vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
1292
+ const handlers = setupHandlers();
1293
+ await handlers.get('gog_sheets_validation_get')!({ spreadsheetId: 'sid', range: 'Sheet1!A1:A10' });
1294
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
1295
+ ['sheets', 'validation', 'get', 'sid', 'Sheet1!A1:A10'],
1296
+ { account: undefined },
1297
+ );
1298
+ });
1299
+ });
1300
+
1301
+ describe('gog_sheets_validation_set', () => {
1302
+ it('sets a dropdown rule with repeated values', async () => {
1303
+ vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
1304
+ const handlers = setupHandlers();
1305
+ await handlers.get('gog_sheets_validation_set')!({
1306
+ spreadsheetId: 'sid', range: 'A1:A10', type: 'ONE_OF_LIST', values: ['Red', 'Green'],
1307
+ });
1308
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
1309
+ ['sheets', 'validation', 'set', 'sid', 'A1:A10', '--type=ONE_OF_LIST', '--value=Red', '--value=Green'],
1310
+ { account: undefined },
1311
+ );
1312
+ });
1313
+
1314
+ it('passes strict, input message, custom UI and filtered-rows flags', async () => {
1315
+ vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
1316
+ const handlers = setupHandlers();
1317
+ await handlers.get('gog_sheets_validation_set')!({
1318
+ spreadsheetId: 'sid', range: 'A1', type: 'NUMBER_BETWEEN', values: ['1', '10'],
1319
+ strict: true, inputMessage: 'Pick 1-10', showCustomUi: true, filteredRowsIncluded: true,
1320
+ });
1321
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
1322
+ ['sheets', 'validation', 'set', 'sid', 'A1', '--type=NUMBER_BETWEEN', '--value=1', '--value=10',
1323
+ '--strict', '--input-message=Pick 1-10', '--show-custom-ui', '--filtered-rows-included'],
1324
+ { account: undefined },
1325
+ );
1326
+ });
1327
+ });
1328
+
1329
+ describe('gog_sheets_validation_clear', () => {
1330
+ it('clears rules from a range', async () => {
1331
+ vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
1332
+ const handlers = setupHandlers();
1333
+ await handlers.get('gog_sheets_validation_clear')!({ spreadsheetId: 'sid', range: 'A1:A10', filteredRowsIncluded: true });
1334
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
1335
+ ['sheets', 'validation', 'clear', 'sid', 'A1:A10', '--filtered-rows-included'],
1336
+ { account: undefined },
1337
+ );
1338
+ });
1339
+ });
1340
+
1341
+ describe('gog_sheets_delete_dimension', () => {
1342
+ it('deletes a row span from a sheet target', async () => {
1343
+ vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
1344
+ const handlers = setupHandlers();
1345
+ await handlers.get('gog_sheets_delete_dimension')!({
1346
+ spreadsheetId: 'sid', rangeOrSheet: 'Sheet1', dimension: 'ROWS', start: 5, end: 7,
1347
+ });
1348
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
1349
+ ['sheets', 'delete-dimension', 'sid', 'Sheet1', '--dimension=ROWS', '--start=5', '--end=7'],
1350
+ { account: undefined },
1351
+ );
1352
+ });
1353
+
1354
+ it('deletes columns by A1 range without start/end', async () => {
1355
+ vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
1356
+ const handlers = setupHandlers();
1357
+ await handlers.get('gog_sheets_delete_dimension')!({
1358
+ spreadsheetId: 'sid', rangeOrSheet: 'Sheet1!C:D', dimension: 'COLUMNS', account: 'a@b.com',
1359
+ });
1360
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
1361
+ ['sheets', 'delete-dimension', 'sid', 'Sheet1!C:D', '--dimension=COLUMNS'],
1362
+ { account: 'a@b.com' },
1363
+ );
1364
+ });
1365
+ });