gogcli-mcp-sheets 2.2.0 → 2.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.
@@ -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.2.0"
10
+ "version": "2.3.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.2.0",
18
+ "version": "2.3.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.2.0",
4
+ "version": "2.3.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.2.0" : "0.0.0";
31402
+ var VERSION = true ? "2.3.0" : "0.0.0";
31403
31403
  function createServer(options) {
31404
31404
  return new McpServer({
31405
31405
  name: options?.name ?? "gogcli",
@@ -31869,6 +31869,248 @@ ${result.content[0].text}` }] };
31869
31869
  { account }
31870
31870
  );
31871
31871
  });
31872
+ server2.registerTool("gog_sheets_chart_list", {
31873
+ description: "List embedded charts in a spreadsheet (chartId, type, position).",
31874
+ annotations: { readOnlyHint: true },
31875
+ inputSchema: {
31876
+ spreadsheetId: external_exports.string().describe("Spreadsheet ID"),
31877
+ account: accountParam
31878
+ }
31879
+ }, async ({ spreadsheetId, account }) => {
31880
+ return runOrDiagnose(["sheets", "chart", "list", spreadsheetId], { account });
31881
+ });
31882
+ server2.registerTool("gog_sheets_chart_get", {
31883
+ description: "Get the full definition (spec + position) of a single chart by its numeric chart ID.",
31884
+ annotations: { readOnlyHint: true },
31885
+ inputSchema: {
31886
+ spreadsheetId: external_exports.string().describe("Spreadsheet ID"),
31887
+ chartId: external_exports.string().describe("Numeric chart ID (from gog_sheets_chart_list)"),
31888
+ account: accountParam
31889
+ }
31890
+ }, async ({ spreadsheetId, chartId, account }) => {
31891
+ return runOrDiagnose(["sheets", "chart", "get", spreadsheetId, chartId], { account });
31892
+ });
31893
+ server2.registerTool("gog_sheets_chart_create", {
31894
+ description: "Create an embedded chart from a JSON spec. specJson is a Sheets API ChartSpec (or full EmbeddedChart) \u2014 inline or @/path/to/file.json. Anchor the chart with sheet + anchor (A1 cell), and optionally size it with width/height pixels.",
31895
+ inputSchema: {
31896
+ spreadsheetId: external_exports.string().describe("Spreadsheet ID"),
31897
+ specJson: external_exports.string().describe("ChartSpec or EmbeddedChart JSON (inline or @file)"),
31898
+ sheet: external_exports.string().optional().describe("Sheet name for the anchor (resolved to sheetId)"),
31899
+ anchor: external_exports.string().optional().describe("Anchor cell in A1 notation (e.g. A1, E10)"),
31900
+ width: external_exports.number().optional().describe("Chart width in pixels (default: 600)"),
31901
+ height: external_exports.number().optional().describe("Chart height in pixels (default: 371)"),
31902
+ account: accountParam
31903
+ }
31904
+ }, async ({ spreadsheetId, specJson, sheet, anchor, width, height, account }) => {
31905
+ const args = ["sheets", "chart", "create", spreadsheetId, `--spec-json=${specJson}`];
31906
+ if (sheet) args.push(`--sheet=${sheet}`);
31907
+ if (anchor) args.push(`--anchor=${anchor}`);
31908
+ if (width !== void 0) args.push(`--width=${width}`);
31909
+ if (height !== void 0) args.push(`--height=${height}`);
31910
+ return runOrDiagnose(args, { account });
31911
+ });
31912
+ server2.registerTool("gog_sheets_chart_update", {
31913
+ description: "Replace a chart spec by chart ID. specJson is a Sheets API ChartSpec (or full EmbeddedChart) \u2014 inline or @/path/to/file.json.",
31914
+ annotations: { destructiveHint: true },
31915
+ inputSchema: {
31916
+ spreadsheetId: external_exports.string().describe("Spreadsheet ID"),
31917
+ chartId: external_exports.string().describe("Numeric chart ID to update"),
31918
+ specJson: external_exports.string().describe("ChartSpec or EmbeddedChart JSON (inline or @file)"),
31919
+ account: accountParam
31920
+ }
31921
+ }, async ({ spreadsheetId, chartId, specJson, account }) => {
31922
+ return runOrDiagnose(
31923
+ ["sheets", "chart", "update", spreadsheetId, chartId, `--spec-json=${specJson}`],
31924
+ { account }
31925
+ );
31926
+ });
31927
+ server2.registerTool("gog_sheets_chart_delete", {
31928
+ description: "Delete a chart by its numeric chart ID.",
31929
+ annotations: { destructiveHint: true },
31930
+ inputSchema: {
31931
+ spreadsheetId: external_exports.string().describe("Spreadsheet ID"),
31932
+ chartId: external_exports.string().describe("Numeric chart ID to delete"),
31933
+ account: accountParam
31934
+ }
31935
+ }, async ({ spreadsheetId, chartId, account }) => {
31936
+ return runOrDiagnose(["sheets", "chart", "delete", spreadsheetId, chartId], { account });
31937
+ });
31938
+ server2.registerTool("gog_sheets_table_list", {
31939
+ description: "List Google Sheets tables in a spreadsheet (tableId, name, range).",
31940
+ annotations: { readOnlyHint: true },
31941
+ inputSchema: {
31942
+ spreadsheetId: external_exports.string().describe("Spreadsheet ID"),
31943
+ account: accountParam
31944
+ }
31945
+ }, async ({ spreadsheetId, account }) => {
31946
+ return runOrDiagnose(["sheets", "table", "list", spreadsheetId], { account });
31947
+ });
31948
+ server2.registerTool("gog_sheets_table_get", {
31949
+ description: "Get a single Google Sheets table (definition + columns) by its table ID.",
31950
+ annotations: { readOnlyHint: true },
31951
+ inputSchema: {
31952
+ spreadsheetId: external_exports.string().describe("Spreadsheet ID"),
31953
+ tableId: external_exports.string().describe("Table ID (from gog_sheets_table_list)"),
31954
+ account: accountParam
31955
+ }
31956
+ }, async ({ spreadsheetId, tableId, account }) => {
31957
+ return runOrDiagnose(["sheets", "table", "get", spreadsheetId, tableId], { account });
31958
+ });
31959
+ server2.registerTool("gog_sheets_table_create", {
31960
+ description: "Create a Google Sheets table over a range. columnsJson is a JSON array of column definitions (each {columnName, columnType?}); valid columnType values: TEXT, DOUBLE, BOOLEAN, DATE, DROPDOWN. Inline JSON or @/path/to/file.json.",
31961
+ inputSchema: {
31962
+ spreadsheetId: external_exports.string().describe("Spreadsheet ID"),
31963
+ range: external_exports.string().describe("Range the table covers (e.g. Sheet1!A1:D20)"),
31964
+ name: external_exports.string().describe("Table name"),
31965
+ columnsJson: external_exports.string().describe("Column definitions as JSON array or @file (columnName + optional columnType)"),
31966
+ account: accountParam
31967
+ }
31968
+ }, async ({ spreadsheetId, range, name, columnsJson, account }) => {
31969
+ return runOrDiagnose(
31970
+ ["sheets", "table", "create", spreadsheetId, range, `--name=${name}`, `--columns-json=${columnsJson}`],
31971
+ { account }
31972
+ );
31973
+ });
31974
+ server2.registerTool("gog_sheets_table_append", {
31975
+ description: "Append data rows to a table. valuesJson is a JSON 2D array of row values.",
31976
+ inputSchema: {
31977
+ spreadsheetId: external_exports.string().describe("Spreadsheet ID"),
31978
+ tableId: external_exports.string().describe("Table ID to append to"),
31979
+ valuesJson: external_exports.string().describe('Values as JSON 2D array (e.g. [["a",1],["b",2]])'),
31980
+ input: external_exports.enum(["RAW", "USER_ENTERED"]).optional().describe("Value input option (default: USER_ENTERED)"),
31981
+ account: accountParam
31982
+ }
31983
+ }, async ({ spreadsheetId, tableId, valuesJson, input, account }) => {
31984
+ const args = ["sheets", "table", "append", spreadsheetId, tableId, `--values-json=${valuesJson}`];
31985
+ if (input) args.push(`--input=${input}`);
31986
+ return runOrDiagnose(args, { account });
31987
+ });
31988
+ server2.registerTool("gog_sheets_table_clear", {
31989
+ description: "Clear all data rows from a table (keeps the table and its columns).",
31990
+ annotations: { destructiveHint: true },
31991
+ inputSchema: {
31992
+ spreadsheetId: external_exports.string().describe("Spreadsheet ID"),
31993
+ tableId: external_exports.string().describe("Table ID to clear"),
31994
+ account: accountParam
31995
+ }
31996
+ }, async ({ spreadsheetId, tableId, account }) => {
31997
+ return runOrDiagnose(["sheets", "table", "clear", spreadsheetId, tableId], { account });
31998
+ });
31999
+ server2.registerTool("gog_sheets_table_delete", {
32000
+ description: "Delete a table by its table ID.",
32001
+ annotations: { destructiveHint: true },
32002
+ inputSchema: {
32003
+ spreadsheetId: external_exports.string().describe("Spreadsheet ID"),
32004
+ tableId: external_exports.string().describe("Table ID to delete"),
32005
+ account: accountParam
32006
+ }
32007
+ }, async ({ spreadsheetId, tableId, account }) => {
32008
+ return runOrDiagnose(["sheets", "table", "delete", spreadsheetId, tableId], { account });
32009
+ });
32010
+ server2.registerTool("gog_sheets_banding_list", {
32011
+ description: "List alternating-color banded ranges. Optionally scope to a single sheet.",
32012
+ annotations: { readOnlyHint: true },
32013
+ inputSchema: {
32014
+ spreadsheetId: external_exports.string().describe("Spreadsheet ID"),
32015
+ sheet: external_exports.string().optional().describe("Only list banding from this sheet"),
32016
+ account: accountParam
32017
+ }
32018
+ }, async ({ spreadsheetId, sheet, account }) => {
32019
+ const args = ["sheets", "banding", "list", spreadsheetId];
32020
+ if (sheet) args.push(`--sheet=${sheet}`);
32021
+ return runOrDiagnose(args, { account });
32022
+ });
32023
+ server2.registerTool("gog_sheets_banding_set", {
32024
+ description: "Apply alternating colors to a range. Provide rowPropertiesJson and/or columnPropertiesJson \u2014 each a Sheets API BandingProperties JSON object ({headerColor, firstBandColor, secondBandColor, footerColor}). At least one is required.",
32025
+ inputSchema: {
32026
+ spreadsheetId: external_exports.string().describe("Spreadsheet ID"),
32027
+ range: external_exports.string().describe("Range to band (e.g. Sheet1!A1:D20)"),
32028
+ rowPropertiesJson: external_exports.string().optional().describe("BandingProperties JSON for row colors"),
32029
+ columnPropertiesJson: external_exports.string().optional().describe("BandingProperties JSON for column colors"),
32030
+ account: accountParam
32031
+ }
32032
+ }, async ({ spreadsheetId, range, rowPropertiesJson, columnPropertiesJson, account }) => {
32033
+ const args = ["sheets", "banding", "set", spreadsheetId, range];
32034
+ if (rowPropertiesJson) args.push(`--row-properties-json=${rowPropertiesJson}`);
32035
+ if (columnPropertiesJson) args.push(`--column-properties-json=${columnPropertiesJson}`);
32036
+ return runOrDiagnose(args, { account });
32037
+ });
32038
+ server2.registerTool("gog_sheets_banding_clear", {
32039
+ description: "Remove alternating-color banding. Pass id to remove a single banded range, or all:true with sheet to remove every banding on that sheet.",
32040
+ annotations: { destructiveHint: true },
32041
+ inputSchema: {
32042
+ spreadsheetId: external_exports.string().describe("Spreadsheet ID"),
32043
+ id: external_exports.number().optional().describe("Banded range ID to remove"),
32044
+ all: external_exports.boolean().optional().describe("Remove all banding from the sheet (requires sheet)"),
32045
+ sheet: external_exports.string().optional().describe("Sheet name (used with all:true)"),
32046
+ account: accountParam
32047
+ }
32048
+ }, async ({ spreadsheetId, id, all, sheet, account }) => {
32049
+ const args = ["sheets", "banding", "clear", spreadsheetId];
32050
+ if (id !== void 0) args.push(`--id=${id}`);
32051
+ if (all) args.push("--all");
32052
+ if (sheet) args.push(`--sheet=${sheet}`);
32053
+ return runOrDiagnose(args, { account });
32054
+ });
32055
+ server2.registerTool("gog_sheets_conditional_format_list", {
32056
+ description: "List conditional formatting rules. Optionally scope to a single sheet.",
32057
+ annotations: { readOnlyHint: true },
32058
+ inputSchema: {
32059
+ spreadsheetId: external_exports.string().describe("Spreadsheet ID"),
32060
+ sheet: external_exports.string().optional().describe("Only list rules from this sheet"),
32061
+ account: accountParam
32062
+ }
32063
+ }, async ({ spreadsheetId, sheet, account }) => {
32064
+ const args = ["sheets", "conditional-format", "list", spreadsheetId];
32065
+ if (sheet) args.push(`--sheet=${sheet}`);
32066
+ return runOrDiagnose(args, { account });
32067
+ });
32068
+ server2.registerTool("gog_sheets_conditional_format_add", {
32069
+ description: "Add a conditional formatting rule to a range. type picks the condition; expr is its value/formula (omit for blank/not-blank). formatJson is the CellFormat to apply when the condition matches (inline or @file). Use formatFields to force-send zero/false fields (e.g. backgroundColor,textFormat.bold).",
32070
+ inputSchema: {
32071
+ spreadsheetId: external_exports.string().describe("Spreadsheet ID"),
32072
+ range: external_exports.string().describe("Range the rule applies to (e.g. Sheet1!A1:A100)"),
32073
+ type: external_exports.enum([
32074
+ "text-eq",
32075
+ "text-contains",
32076
+ "text-starts-with",
32077
+ "text-ends-with",
32078
+ "number-eq",
32079
+ "number-gt",
32080
+ "number-gte",
32081
+ "number-lt",
32082
+ "number-lte",
32083
+ "blank",
32084
+ "not-blank",
32085
+ "custom-formula"
32086
+ ]).describe("Rule type"),
32087
+ formatJson: external_exports.string().describe("CellFormat JSON to apply when the condition matches (inline or @file)"),
32088
+ expr: external_exports.string().optional().describe("Expression value or custom formula (omit for blank/not-blank)"),
32089
+ formatFields: external_exports.string().optional().describe("Format field mask for force-sending zero/false fields (e.g. backgroundColor,textFormat.bold)"),
32090
+ account: accountParam
32091
+ }
32092
+ }, async ({ spreadsheetId, range, type, formatJson, expr, formatFields, account }) => {
32093
+ const args = ["sheets", "conditional-format", "add", spreadsheetId, range, `--type=${type}`, `--format-json=${formatJson}`];
32094
+ if (expr !== void 0) args.push(`--expr=${expr}`);
32095
+ if (formatFields) args.push(`--format-fields=${formatFields}`);
32096
+ return runOrDiagnose(args, { account });
32097
+ });
32098
+ server2.registerTool("gog_sheets_conditional_format_clear", {
32099
+ description: "Remove conditional formatting rules from a sheet. Pass index to remove a single rule by its 0-based index, or all:true to remove every rule on the sheet.",
32100
+ annotations: { destructiveHint: true },
32101
+ inputSchema: {
32102
+ spreadsheetId: external_exports.string().describe("Spreadsheet ID"),
32103
+ sheet: external_exports.string().describe("Sheet name to clear rules from"),
32104
+ index: external_exports.number().optional().describe("0-based rule index to remove"),
32105
+ all: external_exports.boolean().optional().describe("Remove all conditional formatting rules from the sheet"),
32106
+ account: accountParam
32107
+ }
32108
+ }, async ({ spreadsheetId, sheet, index, all, account }) => {
32109
+ const args = ["sheets", "conditional-format", "clear", spreadsheetId, `--sheet=${sheet}`];
32110
+ if (index !== void 0) args.push(`--index=${index}`);
32111
+ if (all) args.push("--all");
32112
+ return runOrDiagnose(args, { account });
32113
+ });
31872
32114
  }
31873
32115
 
31874
32116
  // src/index.ts
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.2.0",
6
+ "version": "2.3.0",
7
7
  "description": "Extended Google Sheets for Claude via gogcli — auth + full Sheets support",
8
8
  "author": {
9
9
  "name": "Chris Hall",
@@ -207,6 +207,74 @@
207
207
  {
208
208
  "name": "gog_sheets_reorder_tab",
209
209
  "description": "Move a tab to a specific 0-based position."
210
+ },
211
+ {
212
+ "name": "gog_sheets_chart_list",
213
+ "description": "List embedded charts in a spreadsheet."
214
+ },
215
+ {
216
+ "name": "gog_sheets_chart_get",
217
+ "description": "Get a chart's full definition (spec + position) by chart ID."
218
+ },
219
+ {
220
+ "name": "gog_sheets_chart_create",
221
+ "description": "Create an embedded chart from a JSON ChartSpec."
222
+ },
223
+ {
224
+ "name": "gog_sheets_chart_update",
225
+ "description": "Replace a chart's spec by chart ID."
226
+ },
227
+ {
228
+ "name": "gog_sheets_chart_delete",
229
+ "description": "Delete a chart by chart ID."
230
+ },
231
+ {
232
+ "name": "gog_sheets_table_list",
233
+ "description": "List Google Sheets tables in a spreadsheet."
234
+ },
235
+ {
236
+ "name": "gog_sheets_table_get",
237
+ "description": "Get a Google Sheets table by table ID."
238
+ },
239
+ {
240
+ "name": "gog_sheets_table_create",
241
+ "description": "Create a Google Sheets table over a range with typed columns."
242
+ },
243
+ {
244
+ "name": "gog_sheets_table_append",
245
+ "description": "Append data rows to a table."
246
+ },
247
+ {
248
+ "name": "gog_sheets_table_clear",
249
+ "description": "Clear all data rows from a table."
250
+ },
251
+ {
252
+ "name": "gog_sheets_table_delete",
253
+ "description": "Delete a table by table ID."
254
+ },
255
+ {
256
+ "name": "gog_sheets_banding_list",
257
+ "description": "List alternating-color banded ranges."
258
+ },
259
+ {
260
+ "name": "gog_sheets_banding_set",
261
+ "description": "Apply alternating colors to a range."
262
+ },
263
+ {
264
+ "name": "gog_sheets_banding_clear",
265
+ "description": "Remove alternating-color banding by ID or for a whole sheet."
266
+ },
267
+ {
268
+ "name": "gog_sheets_conditional_format_list",
269
+ "description": "List conditional formatting rules."
270
+ },
271
+ {
272
+ "name": "gog_sheets_conditional_format_add",
273
+ "description": "Add a conditional formatting rule to a range."
274
+ },
275
+ {
276
+ "name": "gog_sheets_conditional_format_clear",
277
+ "description": "Remove conditional formatting rules from a sheet."
210
278
  }
211
279
  ],
212
280
  "compatibility": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gogcli-mcp-sheets",
3
- "version": "2.2.0",
3
+ "version": "2.3.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.2.0",
10
+ "version": "2.3.0",
11
11
  "packages": [
12
12
  {
13
13
  "registryType": "npm",
14
14
  "identifier": "gogcli-mcp-sheets",
15
- "version": "2.2.0",
15
+ "version": "2.3.0",
16
16
  "transport": {
17
17
  "type": "stdio"
18
18
  },
@@ -520,4 +520,262 @@ export function registerExtraSheetsTools(server: McpServer): void {
520
520
  { account },
521
521
  );
522
522
  });
523
+
524
+ // ---- Charts (gog 0.19.0) ----
525
+
526
+ server.registerTool('gog_sheets_chart_list', {
527
+ description: 'List embedded charts in a spreadsheet (chartId, type, position).',
528
+ annotations: { readOnlyHint: true },
529
+ inputSchema: {
530
+ spreadsheetId: z.string().describe('Spreadsheet ID'),
531
+ account: accountParam,
532
+ },
533
+ }, async ({ spreadsheetId, account }) => {
534
+ return runOrDiagnose(['sheets', 'chart', 'list', spreadsheetId], { account });
535
+ });
536
+
537
+ server.registerTool('gog_sheets_chart_get', {
538
+ description: 'Get the full definition (spec + position) of a single chart by its numeric chart ID.',
539
+ annotations: { readOnlyHint: true },
540
+ inputSchema: {
541
+ spreadsheetId: z.string().describe('Spreadsheet ID'),
542
+ chartId: z.string().describe('Numeric chart ID (from gog_sheets_chart_list)'),
543
+ account: accountParam,
544
+ },
545
+ }, async ({ spreadsheetId, chartId, account }) => {
546
+ return runOrDiagnose(['sheets', 'chart', 'get', spreadsheetId, chartId], { account });
547
+ });
548
+
549
+ server.registerTool('gog_sheets_chart_create', {
550
+ description: 'Create an embedded chart from a JSON spec. specJson is a Sheets API ChartSpec (or full EmbeddedChart) — inline or @/path/to/file.json. Anchor the chart with sheet + anchor (A1 cell), and optionally size it with width/height pixels.',
551
+ inputSchema: {
552
+ spreadsheetId: z.string().describe('Spreadsheet ID'),
553
+ specJson: z.string().describe('ChartSpec or EmbeddedChart JSON (inline or @file)'),
554
+ sheet: z.string().optional().describe('Sheet name for the anchor (resolved to sheetId)'),
555
+ anchor: z.string().optional().describe('Anchor cell in A1 notation (e.g. A1, E10)'),
556
+ width: z.number().optional().describe('Chart width in pixels (default: 600)'),
557
+ height: z.number().optional().describe('Chart height in pixels (default: 371)'),
558
+ account: accountParam,
559
+ },
560
+ }, async ({ spreadsheetId, specJson, sheet, anchor, width, height, account }) => {
561
+ const args = ['sheets', 'chart', 'create', spreadsheetId, `--spec-json=${specJson}`];
562
+ if (sheet) args.push(`--sheet=${sheet}`);
563
+ if (anchor) args.push(`--anchor=${anchor}`);
564
+ if (width !== undefined) args.push(`--width=${width}`);
565
+ if (height !== undefined) args.push(`--height=${height}`);
566
+ return runOrDiagnose(args, { account });
567
+ });
568
+
569
+ server.registerTool('gog_sheets_chart_update', {
570
+ description: 'Replace a chart spec by chart ID. specJson is a Sheets API ChartSpec (or full EmbeddedChart) — inline or @/path/to/file.json.',
571
+ annotations: { destructiveHint: true },
572
+ inputSchema: {
573
+ spreadsheetId: z.string().describe('Spreadsheet ID'),
574
+ chartId: z.string().describe('Numeric chart ID to update'),
575
+ specJson: z.string().describe('ChartSpec or EmbeddedChart JSON (inline or @file)'),
576
+ account: accountParam,
577
+ },
578
+ }, async ({ spreadsheetId, chartId, specJson, account }) => {
579
+ return runOrDiagnose(
580
+ ['sheets', 'chart', 'update', spreadsheetId, chartId, `--spec-json=${specJson}`],
581
+ { account },
582
+ );
583
+ });
584
+
585
+ server.registerTool('gog_sheets_chart_delete', {
586
+ description: 'Delete a chart by its numeric chart ID.',
587
+ annotations: { destructiveHint: true },
588
+ inputSchema: {
589
+ spreadsheetId: z.string().describe('Spreadsheet ID'),
590
+ chartId: z.string().describe('Numeric chart ID to delete'),
591
+ account: accountParam,
592
+ },
593
+ }, async ({ spreadsheetId, chartId, account }) => {
594
+ return runOrDiagnose(['sheets', 'chart', 'delete', spreadsheetId, chartId], { account });
595
+ });
596
+
597
+ // ---- Tables (gog 0.19.0) ----
598
+
599
+ server.registerTool('gog_sheets_table_list', {
600
+ description: 'List Google Sheets tables in a spreadsheet (tableId, name, range).',
601
+ annotations: { readOnlyHint: true },
602
+ inputSchema: {
603
+ spreadsheetId: z.string().describe('Spreadsheet ID'),
604
+ account: accountParam,
605
+ },
606
+ }, async ({ spreadsheetId, account }) => {
607
+ return runOrDiagnose(['sheets', 'table', 'list', spreadsheetId], { account });
608
+ });
609
+
610
+ server.registerTool('gog_sheets_table_get', {
611
+ description: 'Get a single Google Sheets table (definition + columns) by its table ID.',
612
+ annotations: { readOnlyHint: true },
613
+ inputSchema: {
614
+ spreadsheetId: z.string().describe('Spreadsheet ID'),
615
+ tableId: z.string().describe('Table ID (from gog_sheets_table_list)'),
616
+ account: accountParam,
617
+ },
618
+ }, async ({ spreadsheetId, tableId, account }) => {
619
+ return runOrDiagnose(['sheets', 'table', 'get', spreadsheetId, tableId], { account });
620
+ });
621
+
622
+ server.registerTool('gog_sheets_table_create', {
623
+ description: 'Create a Google Sheets table over a range. columnsJson is a JSON array of column definitions (each {columnName, columnType?}); valid columnType values: TEXT, DOUBLE, BOOLEAN, DATE, DROPDOWN. Inline JSON or @/path/to/file.json.',
624
+ inputSchema: {
625
+ spreadsheetId: z.string().describe('Spreadsheet ID'),
626
+ range: z.string().describe('Range the table covers (e.g. Sheet1!A1:D20)'),
627
+ name: z.string().describe('Table name'),
628
+ columnsJson: z.string().describe('Column definitions as JSON array or @file (columnName + optional columnType)'),
629
+ account: accountParam,
630
+ },
631
+ }, async ({ spreadsheetId, range, name, columnsJson, account }) => {
632
+ return runOrDiagnose(
633
+ ['sheets', 'table', 'create', spreadsheetId, range, `--name=${name}`, `--columns-json=${columnsJson}`],
634
+ { account },
635
+ );
636
+ });
637
+
638
+ server.registerTool('gog_sheets_table_append', {
639
+ description: 'Append data rows to a table. valuesJson is a JSON 2D array of row values.',
640
+ inputSchema: {
641
+ spreadsheetId: z.string().describe('Spreadsheet ID'),
642
+ tableId: z.string().describe('Table ID to append to'),
643
+ valuesJson: z.string().describe('Values as JSON 2D array (e.g. [["a",1],["b",2]])'),
644
+ input: z.enum(['RAW', 'USER_ENTERED']).optional().describe('Value input option (default: USER_ENTERED)'),
645
+ account: accountParam,
646
+ },
647
+ }, async ({ spreadsheetId, tableId, valuesJson, input, account }) => {
648
+ const args = ['sheets', 'table', 'append', spreadsheetId, tableId, `--values-json=${valuesJson}`];
649
+ if (input) args.push(`--input=${input}`);
650
+ return runOrDiagnose(args, { account });
651
+ });
652
+
653
+ server.registerTool('gog_sheets_table_clear', {
654
+ description: 'Clear all data rows from a table (keeps the table and its columns).',
655
+ annotations: { destructiveHint: true },
656
+ inputSchema: {
657
+ spreadsheetId: z.string().describe('Spreadsheet ID'),
658
+ tableId: z.string().describe('Table ID to clear'),
659
+ account: accountParam,
660
+ },
661
+ }, async ({ spreadsheetId, tableId, account }) => {
662
+ return runOrDiagnose(['sheets', 'table', 'clear', spreadsheetId, tableId], { account });
663
+ });
664
+
665
+ server.registerTool('gog_sheets_table_delete', {
666
+ description: 'Delete a table by its table ID.',
667
+ annotations: { destructiveHint: true },
668
+ inputSchema: {
669
+ spreadsheetId: z.string().describe('Spreadsheet ID'),
670
+ tableId: z.string().describe('Table ID to delete'),
671
+ account: accountParam,
672
+ },
673
+ }, async ({ spreadsheetId, tableId, account }) => {
674
+ return runOrDiagnose(['sheets', 'table', 'delete', spreadsheetId, tableId], { account });
675
+ });
676
+
677
+ // ---- Banding / alternating colors (gog 0.19.0) ----
678
+
679
+ server.registerTool('gog_sheets_banding_list', {
680
+ description: 'List alternating-color banded ranges. Optionally scope to a single sheet.',
681
+ annotations: { readOnlyHint: true },
682
+ inputSchema: {
683
+ spreadsheetId: z.string().describe('Spreadsheet ID'),
684
+ sheet: z.string().optional().describe('Only list banding from this sheet'),
685
+ account: accountParam,
686
+ },
687
+ }, async ({ spreadsheetId, sheet, account }) => {
688
+ const args = ['sheets', 'banding', 'list', spreadsheetId];
689
+ if (sheet) args.push(`--sheet=${sheet}`);
690
+ return runOrDiagnose(args, { account });
691
+ });
692
+
693
+ server.registerTool('gog_sheets_banding_set', {
694
+ description: 'Apply alternating colors to a range. Provide rowPropertiesJson and/or columnPropertiesJson — each a Sheets API BandingProperties JSON object ({headerColor, firstBandColor, secondBandColor, footerColor}). At least one is required.',
695
+ inputSchema: {
696
+ spreadsheetId: z.string().describe('Spreadsheet ID'),
697
+ range: z.string().describe('Range to band (e.g. Sheet1!A1:D20)'),
698
+ rowPropertiesJson: z.string().optional().describe('BandingProperties JSON for row colors'),
699
+ columnPropertiesJson: z.string().optional().describe('BandingProperties JSON for column colors'),
700
+ account: accountParam,
701
+ },
702
+ }, async ({ spreadsheetId, range, rowPropertiesJson, columnPropertiesJson, account }) => {
703
+ const args = ['sheets', 'banding', 'set', spreadsheetId, range];
704
+ if (rowPropertiesJson) args.push(`--row-properties-json=${rowPropertiesJson}`);
705
+ if (columnPropertiesJson) args.push(`--column-properties-json=${columnPropertiesJson}`);
706
+ return runOrDiagnose(args, { account });
707
+ });
708
+
709
+ server.registerTool('gog_sheets_banding_clear', {
710
+ description: 'Remove alternating-color banding. Pass id to remove a single banded range, or all:true with sheet to remove every banding on that sheet.',
711
+ annotations: { destructiveHint: true },
712
+ inputSchema: {
713
+ spreadsheetId: z.string().describe('Spreadsheet ID'),
714
+ id: z.number().optional().describe('Banded range ID to remove'),
715
+ all: z.boolean().optional().describe('Remove all banding from the sheet (requires sheet)'),
716
+ sheet: z.string().optional().describe('Sheet name (used with all:true)'),
717
+ account: accountParam,
718
+ },
719
+ }, async ({ spreadsheetId, id, all, sheet, account }) => {
720
+ const args = ['sheets', 'banding', 'clear', spreadsheetId];
721
+ if (id !== undefined) args.push(`--id=${id}`);
722
+ if (all) args.push('--all');
723
+ if (sheet) args.push(`--sheet=${sheet}`);
724
+ return runOrDiagnose(args, { account });
725
+ });
726
+
727
+ // ---- Conditional formatting (gog 0.19.0) ----
728
+
729
+ server.registerTool('gog_sheets_conditional_format_list', {
730
+ description: 'List conditional formatting rules. Optionally scope to a single sheet.',
731
+ annotations: { readOnlyHint: true },
732
+ inputSchema: {
733
+ spreadsheetId: z.string().describe('Spreadsheet ID'),
734
+ sheet: z.string().optional().describe('Only list rules from this sheet'),
735
+ account: accountParam,
736
+ },
737
+ }, async ({ spreadsheetId, sheet, account }) => {
738
+ const args = ['sheets', 'conditional-format', 'list', spreadsheetId];
739
+ if (sheet) args.push(`--sheet=${sheet}`);
740
+ return runOrDiagnose(args, { account });
741
+ });
742
+
743
+ server.registerTool('gog_sheets_conditional_format_add', {
744
+ description: 'Add a conditional formatting rule to a range. type picks the condition; expr is its value/formula (omit for blank/not-blank). formatJson is the CellFormat to apply when the condition matches (inline or @file). Use formatFields to force-send zero/false fields (e.g. backgroundColor,textFormat.bold).',
745
+ inputSchema: {
746
+ spreadsheetId: z.string().describe('Spreadsheet ID'),
747
+ range: z.string().describe('Range the rule applies to (e.g. Sheet1!A1:A100)'),
748
+ type: z.enum([
749
+ 'text-eq', 'text-contains', 'text-starts-with', 'text-ends-with',
750
+ 'number-eq', 'number-gt', 'number-gte', 'number-lt', 'number-lte',
751
+ 'blank', 'not-blank', 'custom-formula',
752
+ ]).describe('Rule type'),
753
+ formatJson: z.string().describe('CellFormat JSON to apply when the condition matches (inline or @file)'),
754
+ expr: z.string().optional().describe('Expression value or custom formula (omit for blank/not-blank)'),
755
+ formatFields: z.string().optional().describe('Format field mask for force-sending zero/false fields (e.g. backgroundColor,textFormat.bold)'),
756
+ account: accountParam,
757
+ },
758
+ }, async ({ spreadsheetId, range, type, formatJson, expr, formatFields, account }) => {
759
+ const args = ['sheets', 'conditional-format', 'add', spreadsheetId, range, `--type=${type}`, `--format-json=${formatJson}`];
760
+ if (expr !== undefined) args.push(`--expr=${expr}`);
761
+ if (formatFields) args.push(`--format-fields=${formatFields}`);
762
+ return runOrDiagnose(args, { account });
763
+ });
764
+
765
+ server.registerTool('gog_sheets_conditional_format_clear', {
766
+ description: 'Remove conditional formatting rules from a sheet. Pass index to remove a single rule by its 0-based index, or all:true to remove every rule on the sheet.',
767
+ annotations: { destructiveHint: true },
768
+ inputSchema: {
769
+ spreadsheetId: z.string().describe('Spreadsheet ID'),
770
+ sheet: z.string().describe('Sheet name to clear rules from'),
771
+ index: z.number().optional().describe('0-based rule index to remove'),
772
+ all: z.boolean().optional().describe('Remove all conditional formatting rules from the sheet'),
773
+ account: accountParam,
774
+ },
775
+ }, async ({ spreadsheetId, sheet, index, all, account }) => {
776
+ const args = ['sheets', 'conditional-format', 'clear', spreadsheetId, `--sheet=${sheet}`];
777
+ if (index !== undefined) args.push(`--index=${index}`);
778
+ if (all) args.push('--all');
779
+ return runOrDiagnose(args, { account });
780
+ });
523
781
  }
@@ -487,6 +487,18 @@ describe('gog_sheets_number_format', () => {
487
487
  expect(result.content[0].text).toMatch(/warning/i);
488
488
  });
489
489
 
490
+ it('does not warn when every cell is null or empty (no small integers seen)', async () => {
491
+ const handlers = setupHandlers();
492
+ vi.mocked(lib.runOrDiagnose).mockImplementation(async (args: string[]) => {
493
+ if (args[1] === 'get') {
494
+ return toText(JSON.stringify({ range: 'A1:A3', values: [[null], [''], [null]] }));
495
+ }
496
+ return toText('{"ok":true}');
497
+ });
498
+ const result = await handlers.get('gog_sheets_number_format')!({ spreadsheetId: 'sid', range: 'A1:A3', type: 'DATE' });
499
+ expect(result.content[0].text).not.toMatch(/warning/i);
500
+ });
501
+
490
502
  it('proceeds with format when peek fails (does not block)', async () => {
491
503
  const handlers = setupHandlers();
492
504
  vi.mocked(lib.runOrDiagnose).mockImplementation(async (args: string[]) => {
@@ -775,3 +787,325 @@ describe('gog_sheets_reorder_tab', () => {
775
787
  );
776
788
  });
777
789
  });
790
+
791
+ // 25. chart list (gog 0.19.0)
792
+ describe('gog_sheets_chart_list', () => {
793
+ it('calls runOrDiagnose with correct args', async () => {
794
+ vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
795
+ const handlers = setupHandlers();
796
+ await handlers.get('gog_sheets_chart_list')!({ spreadsheetId: 'sid' });
797
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'chart', 'list', 'sid'], { account: undefined });
798
+ });
799
+
800
+ it('forwards account', async () => {
801
+ vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
802
+ const handlers = setupHandlers();
803
+ await handlers.get('gog_sheets_chart_list')!({ spreadsheetId: 'sid', account: 'a@b.com' });
804
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'chart', 'list', 'sid'], { account: 'a@b.com' });
805
+ });
806
+ });
807
+
808
+ // 26. chart get
809
+ describe('gog_sheets_chart_get', () => {
810
+ it('calls runOrDiagnose with correct args', async () => {
811
+ vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
812
+ const handlers = setupHandlers();
813
+ await handlers.get('gog_sheets_chart_get')!({ spreadsheetId: 'sid', chartId: '12345' });
814
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'chart', 'get', 'sid', '12345'], { account: undefined });
815
+ });
816
+ });
817
+
818
+ // 27. chart create
819
+ describe('gog_sheets_chart_create', () => {
820
+ it('calls runOrDiagnose with only --spec-json', async () => {
821
+ vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
822
+ const handlers = setupHandlers();
823
+ const spec = '{"basicChart":{"chartType":"COLUMN"}}';
824
+ await handlers.get('gog_sheets_chart_create')!({ spreadsheetId: 'sid', specJson: spec });
825
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
826
+ ['sheets', 'chart', 'create', 'sid', `--spec-json=${spec}`],
827
+ { account: undefined },
828
+ );
829
+ });
830
+
831
+ it('includes --sheet, --anchor, --width, --height when provided', async () => {
832
+ vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
833
+ const handlers = setupHandlers();
834
+ await handlers.get('gog_sheets_chart_create')!({
835
+ spreadsheetId: 'sid', specJson: '{}', sheet: 'Data', anchor: 'E10', width: 800, height: 400,
836
+ });
837
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
838
+ ['sheets', 'chart', 'create', 'sid', '--spec-json={}', '--sheet=Data', '--anchor=E10', '--width=800', '--height=400'],
839
+ { account: undefined },
840
+ );
841
+ });
842
+
843
+ it('includes --width=0 and --height=0 when zero', async () => {
844
+ vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
845
+ const handlers = setupHandlers();
846
+ await handlers.get('gog_sheets_chart_create')!({ spreadsheetId: 'sid', specJson: '{}', width: 0, height: 0 });
847
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
848
+ ['sheets', 'chart', 'create', 'sid', '--spec-json={}', '--width=0', '--height=0'],
849
+ { account: undefined },
850
+ );
851
+ });
852
+ });
853
+
854
+ // 28. chart update
855
+ describe('gog_sheets_chart_update', () => {
856
+ it('calls runOrDiagnose with chartId and --spec-json', async () => {
857
+ vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
858
+ const handlers = setupHandlers();
859
+ await handlers.get('gog_sheets_chart_update')!({ spreadsheetId: 'sid', chartId: '99', specJson: '{}' });
860
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
861
+ ['sheets', 'chart', 'update', 'sid', '99', '--spec-json={}'],
862
+ { account: undefined },
863
+ );
864
+ });
865
+ });
866
+
867
+ // 29. chart delete
868
+ describe('gog_sheets_chart_delete', () => {
869
+ it('calls runOrDiagnose with correct args', async () => {
870
+ vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
871
+ const handlers = setupHandlers();
872
+ await handlers.get('gog_sheets_chart_delete')!({ spreadsheetId: 'sid', chartId: '7' });
873
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'chart', 'delete', 'sid', '7'], { account: undefined });
874
+ });
875
+ });
876
+
877
+ // 30. table list
878
+ describe('gog_sheets_table_list', () => {
879
+ it('calls runOrDiagnose with correct args', async () => {
880
+ vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
881
+ const handlers = setupHandlers();
882
+ await handlers.get('gog_sheets_table_list')!({ spreadsheetId: 'sid' });
883
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'table', 'list', 'sid'], { account: undefined });
884
+ });
885
+ });
886
+
887
+ // 31. table get
888
+ describe('gog_sheets_table_get', () => {
889
+ it('calls runOrDiagnose with correct args', async () => {
890
+ vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
891
+ const handlers = setupHandlers();
892
+ await handlers.get('gog_sheets_table_get')!({ spreadsheetId: 'sid', tableId: 't1' });
893
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'table', 'get', 'sid', 't1'], { account: undefined });
894
+ });
895
+ });
896
+
897
+ // 32. table create
898
+ describe('gog_sheets_table_create', () => {
899
+ it('calls runOrDiagnose with range, --name, --columns-json', async () => {
900
+ vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
901
+ const handlers = setupHandlers();
902
+ const cols = '[{"columnName":"Name","columnType":"TEXT"}]';
903
+ await handlers.get('gog_sheets_table_create')!({ spreadsheetId: 'sid', range: 'Sheet1!A1:B10', name: 'People', columnsJson: cols });
904
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
905
+ ['sheets', 'table', 'create', 'sid', 'Sheet1!A1:B10', '--name=People', `--columns-json=${cols}`],
906
+ { account: undefined },
907
+ );
908
+ });
909
+ });
910
+
911
+ // 33. table append
912
+ describe('gog_sheets_table_append', () => {
913
+ it('calls runOrDiagnose with --values-json (no --input)', async () => {
914
+ vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
915
+ const handlers = setupHandlers();
916
+ await handlers.get('gog_sheets_table_append')!({ spreadsheetId: 'sid', tableId: 't1', valuesJson: '[["a",1]]' });
917
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
918
+ ['sheets', 'table', 'append', 'sid', 't1', '--values-json=[["a",1]]'],
919
+ { account: undefined },
920
+ );
921
+ });
922
+
923
+ it('includes --input when provided', async () => {
924
+ vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
925
+ const handlers = setupHandlers();
926
+ await handlers.get('gog_sheets_table_append')!({ spreadsheetId: 'sid', tableId: 't1', valuesJson: '[]', input: 'RAW' });
927
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
928
+ ['sheets', 'table', 'append', 'sid', 't1', '--values-json=[]', '--input=RAW'],
929
+ { account: undefined },
930
+ );
931
+ });
932
+ });
933
+
934
+ // 34. table clear
935
+ describe('gog_sheets_table_clear', () => {
936
+ it('calls runOrDiagnose with correct args', async () => {
937
+ vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
938
+ const handlers = setupHandlers();
939
+ await handlers.get('gog_sheets_table_clear')!({ spreadsheetId: 'sid', tableId: 't1' });
940
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'table', 'clear', 'sid', 't1'], { account: undefined });
941
+ });
942
+ });
943
+
944
+ // 35. table delete
945
+ describe('gog_sheets_table_delete', () => {
946
+ it('calls runOrDiagnose with correct args', async () => {
947
+ vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
948
+ const handlers = setupHandlers();
949
+ await handlers.get('gog_sheets_table_delete')!({ spreadsheetId: 'sid', tableId: 't1' });
950
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'table', 'delete', 'sid', 't1'], { account: undefined });
951
+ });
952
+ });
953
+
954
+ // 36. banding list
955
+ describe('gog_sheets_banding_list', () => {
956
+ it('calls runOrDiagnose with no --sheet', async () => {
957
+ vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
958
+ const handlers = setupHandlers();
959
+ await handlers.get('gog_sheets_banding_list')!({ spreadsheetId: 'sid' });
960
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'banding', 'list', 'sid'], { account: undefined });
961
+ });
962
+
963
+ it('includes --sheet when provided', async () => {
964
+ vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
965
+ const handlers = setupHandlers();
966
+ await handlers.get('gog_sheets_banding_list')!({ spreadsheetId: 'sid', sheet: 'Data' });
967
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'banding', 'list', 'sid', '--sheet=Data'], { account: undefined });
968
+ });
969
+ });
970
+
971
+ // 37. banding set
972
+ describe('gog_sheets_banding_set', () => {
973
+ it('calls runOrDiagnose with range only', async () => {
974
+ vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
975
+ const handlers = setupHandlers();
976
+ await handlers.get('gog_sheets_banding_set')!({ spreadsheetId: 'sid', range: 'Sheet1!A1:D20' });
977
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'banding', 'set', 'sid', 'Sheet1!A1:D20'], { account: undefined });
978
+ });
979
+
980
+ it('includes --row-properties-json and --column-properties-json when provided', async () => {
981
+ vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
982
+ const handlers = setupHandlers();
983
+ await handlers.get('gog_sheets_banding_set')!({
984
+ spreadsheetId: 'sid', range: 'A1:D20',
985
+ rowPropertiesJson: '{"firstBandColor":{}}',
986
+ columnPropertiesJson: '{"secondBandColor":{}}',
987
+ });
988
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
989
+ ['sheets', 'banding', 'set', 'sid', 'A1:D20', '--row-properties-json={"firstBandColor":{}}', '--column-properties-json={"secondBandColor":{}}'],
990
+ { account: undefined },
991
+ );
992
+ });
993
+ });
994
+
995
+ // 38. banding clear
996
+ describe('gog_sheets_banding_clear', () => {
997
+ it('calls runOrDiagnose with no optional flags', async () => {
998
+ vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
999
+ const handlers = setupHandlers();
1000
+ await handlers.get('gog_sheets_banding_clear')!({ spreadsheetId: 'sid' });
1001
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'banding', 'clear', 'sid'], { account: undefined });
1002
+ });
1003
+
1004
+ it('includes --id when provided (including 0)', async () => {
1005
+ vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
1006
+ const handlers = setupHandlers();
1007
+ await handlers.get('gog_sheets_banding_clear')!({ spreadsheetId: 'sid', id: 0 });
1008
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'banding', 'clear', 'sid', '--id=0'], { account: undefined });
1009
+ });
1010
+
1011
+ it('includes --all and --sheet when clearing a whole sheet', async () => {
1012
+ vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
1013
+ const handlers = setupHandlers();
1014
+ await handlers.get('gog_sheets_banding_clear')!({ spreadsheetId: 'sid', all: true, sheet: 'Data' });
1015
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'banding', 'clear', 'sid', '--all', '--sheet=Data'], { account: undefined });
1016
+ });
1017
+
1018
+ it('omits --all when false', async () => {
1019
+ vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
1020
+ const handlers = setupHandlers();
1021
+ await handlers.get('gog_sheets_banding_clear')!({ spreadsheetId: 'sid', all: false, id: 5 });
1022
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'banding', 'clear', 'sid', '--id=5'], { account: undefined });
1023
+ });
1024
+ });
1025
+
1026
+ // 39. conditional-format list
1027
+ describe('gog_sheets_conditional_format_list', () => {
1028
+ it('calls runOrDiagnose with no --sheet', async () => {
1029
+ vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
1030
+ const handlers = setupHandlers();
1031
+ await handlers.get('gog_sheets_conditional_format_list')!({ spreadsheetId: 'sid' });
1032
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'conditional-format', 'list', 'sid'], { account: undefined });
1033
+ });
1034
+
1035
+ it('includes --sheet when provided', async () => {
1036
+ vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
1037
+ const handlers = setupHandlers();
1038
+ await handlers.get('gog_sheets_conditional_format_list')!({ spreadsheetId: 'sid', sheet: 'Data' });
1039
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'conditional-format', 'list', 'sid', '--sheet=Data'], { account: undefined });
1040
+ });
1041
+ });
1042
+
1043
+ // 40. conditional-format add
1044
+ describe('gog_sheets_conditional_format_add', () => {
1045
+ it('calls runOrDiagnose with type and --format-json (no expr/fields)', async () => {
1046
+ vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
1047
+ const handlers = setupHandlers();
1048
+ await handlers.get('gog_sheets_conditional_format_add')!({
1049
+ spreadsheetId: 'sid', range: 'A1:A100', type: 'not-blank', formatJson: '{"backgroundColor":{}}',
1050
+ });
1051
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
1052
+ ['sheets', 'conditional-format', 'add', 'sid', 'A1:A100', '--type=not-blank', '--format-json={"backgroundColor":{}}'],
1053
+ { account: undefined },
1054
+ );
1055
+ });
1056
+
1057
+ it('includes --expr and --format-fields when provided', async () => {
1058
+ vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
1059
+ const handlers = setupHandlers();
1060
+ await handlers.get('gog_sheets_conditional_format_add')!({
1061
+ spreadsheetId: 'sid', range: 'B1:B50', type: 'number-gt', expr: '100', formatJson: '{}', formatFields: 'backgroundColor,textFormat.bold',
1062
+ });
1063
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
1064
+ ['sheets', 'conditional-format', 'add', 'sid', 'B1:B50', '--type=number-gt', '--format-json={}', '--expr=100', '--format-fields=backgroundColor,textFormat.bold'],
1065
+ { account: undefined },
1066
+ );
1067
+ });
1068
+
1069
+ it('includes --expr when it is an empty string', async () => {
1070
+ vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
1071
+ const handlers = setupHandlers();
1072
+ await handlers.get('gog_sheets_conditional_format_add')!({
1073
+ spreadsheetId: 'sid', range: 'A1', type: 'text-eq', expr: '', formatJson: '{}',
1074
+ });
1075
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
1076
+ ['sheets', 'conditional-format', 'add', 'sid', 'A1', '--type=text-eq', '--format-json={}', '--expr='],
1077
+ { account: undefined },
1078
+ );
1079
+ });
1080
+ });
1081
+
1082
+ // 41. conditional-format clear
1083
+ describe('gog_sheets_conditional_format_clear', () => {
1084
+ it('calls runOrDiagnose with --sheet only', async () => {
1085
+ vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
1086
+ const handlers = setupHandlers();
1087
+ await handlers.get('gog_sheets_conditional_format_clear')!({ spreadsheetId: 'sid', sheet: 'Data' });
1088
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'conditional-format', 'clear', 'sid', '--sheet=Data'], { account: undefined });
1089
+ });
1090
+
1091
+ it('includes --index when provided (including 0)', async () => {
1092
+ vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
1093
+ const handlers = setupHandlers();
1094
+ await handlers.get('gog_sheets_conditional_format_clear')!({ spreadsheetId: 'sid', sheet: 'Data', index: 0 });
1095
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'conditional-format', 'clear', 'sid', '--sheet=Data', '--index=0'], { account: undefined });
1096
+ });
1097
+
1098
+ it('includes --all when true', async () => {
1099
+ vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
1100
+ const handlers = setupHandlers();
1101
+ await handlers.get('gog_sheets_conditional_format_clear')!({ spreadsheetId: 'sid', sheet: 'Data', all: true });
1102
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'conditional-format', 'clear', 'sid', '--sheet=Data', '--all'], { account: undefined });
1103
+ });
1104
+
1105
+ it('omits --all when false', async () => {
1106
+ vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
1107
+ const handlers = setupHandlers();
1108
+ await handlers.get('gog_sheets_conditional_format_clear')!({ spreadsheetId: 'sid', sheet: 'Data', all: false, index: 2 });
1109
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'conditional-format', 'clear', 'sid', '--sheet=Data', '--index=2'], { account: undefined });
1110
+ });
1111
+ });