gogcli-mcp-sheets 2.0.9 → 2.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +2 -2
- package/SKILL.md +2 -2
- package/dist/index.js +110 -12
- package/manifest.json +6 -2
- package/package.json +1 -1
- package/server.json +2 -2
- package/src/tools/sheets-extra.ts +110 -6
- package/tests/tools/sheets-extra.test.ts +119 -6
|
@@ -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.0.
|
|
10
|
+
"version": "2.0.11"
|
|
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.0.
|
|
18
|
+
"version": "2.0.11",
|
|
19
19
|
"author": {
|
|
20
20
|
"name": "Chris Hall"
|
|
21
21
|
},
|
package/README.md
CHANGED
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
> [!WARNING]
|
|
4
4
|
> **AI-developed project.** This codebase was built and is actively maintained by [Claude Code](https://www.anthropic.com/claude). Review all code and tool permissions before use.
|
|
5
5
|
|
|
6
|
-
Extended Google Sheets [MCP](https://modelcontextprotocol.io) server via [gogcli](https://github.com/
|
|
6
|
+
Extended Google Sheets [MCP](https://modelcontextprotocol.io) server via [gogcli](https://github.com/openclaw/gogcli). Includes auth tools plus 22 additional dedicated Sheets tools for tab management, formatting, named ranges, and more.
|
|
7
7
|
|
|
8
8
|
## Requirements
|
|
9
9
|
|
|
10
|
-
- [gogcli](https://github.com/
|
|
10
|
+
- [gogcli](https://github.com/openclaw/gogcli) installed and authenticated
|
|
11
11
|
- Node.js 18+
|
|
12
12
|
|
|
13
13
|
```bash
|
package/SKILL.md
CHANGED
|
@@ -5,13 +5,13 @@ description: Use when the user asks to read, write, format, or manage Google She
|
|
|
5
5
|
|
|
6
6
|
# gogcli-mcp-sheets
|
|
7
7
|
|
|
8
|
-
Extended Google Sheets MCP server via [gogcli](https://github.com/
|
|
8
|
+
Extended Google Sheets MCP server via [gogcli](https://github.com/openclaw/gogcli) — 35 tools: auth + 8 base Sheets + 22 extra dedicated Sheets tools.
|
|
9
9
|
|
|
10
10
|
- **Source:** [github.com/chrischall/gogcli-mcp](https://github.com/chrischall/gogcli-mcp)
|
|
11
11
|
|
|
12
12
|
## Requirements
|
|
13
13
|
|
|
14
|
-
- [gogcli](https://github.com/
|
|
14
|
+
- [gogcli](https://github.com/openclaw/gogcli) installed and authenticated
|
|
15
15
|
- Node.js 18 or later
|
|
16
16
|
|
|
17
17
|
## Setup
|
package/dist/index.js
CHANGED
|
@@ -31063,7 +31063,7 @@ async function run(args, options = {}) {
|
|
|
31063
31063
|
settled = true;
|
|
31064
31064
|
if (err.code === "ENOENT") {
|
|
31065
31065
|
reject(new Error(
|
|
31066
|
-
"gog executable not found. Install gogcli (https://github.com/
|
|
31066
|
+
"gog executable not found. Install gogcli (https://github.com/openclaw/gogcli) or set GOG_PATH in your MCP client config to the absolute binary path (run `which gog` in a terminal to find it)."
|
|
31067
31067
|
));
|
|
31068
31068
|
return;
|
|
31069
31069
|
}
|
|
@@ -31219,6 +31219,7 @@ function registerAuthTools(server2) {
|
|
|
31219
31219
|
}
|
|
31220
31220
|
|
|
31221
31221
|
// ../gogcli-mcp/src/tools/sheets.ts
|
|
31222
|
+
var cellValueParam = external_exports.union([external_exports.string(), external_exports.number(), external_exports.boolean(), external_exports.null()]);
|
|
31222
31223
|
function registerSheetsTools(server2) {
|
|
31223
31224
|
server2.registerTool("gog_sheets_get", {
|
|
31224
31225
|
description: 'Read values from a Google Sheets range. Returns a JSON object with a "values" array of rows.',
|
|
@@ -31232,12 +31233,12 @@ function registerSheetsTools(server2) {
|
|
|
31232
31233
|
return runOrDiagnose(["sheets", "get", spreadsheetId, range], { account });
|
|
31233
31234
|
});
|
|
31234
31235
|
server2.registerTool("gog_sheets_update", {
|
|
31235
|
-
description:
|
|
31236
|
+
description: 'Write values to a Google Sheets range, overwriting existing content. Values may be strings, numbers, booleans, or null. Strings starting with "=" are interpreted as formulas (e.g. "=SUM(A1:A10)").',
|
|
31236
31237
|
annotations: { destructiveHint: true },
|
|
31237
31238
|
inputSchema: {
|
|
31238
31239
|
spreadsheetId: external_exports.string().describe("Spreadsheet ID (from the URL)"),
|
|
31239
31240
|
range: external_exports.string().describe("Top-left cell or range in A1 notation, e.g. Sheet1!A1"),
|
|
31240
|
-
values: external_exports.array(external_exports.array(
|
|
31241
|
+
values: external_exports.array(external_exports.array(cellValueParam)).describe('2D array of values (rows of columns). Cells may be string/number/boolean/null; strings starting with "=" are formulas.'),
|
|
31241
31242
|
account: accountParam
|
|
31242
31243
|
}
|
|
31243
31244
|
}, async ({ spreadsheetId, range, values, account }) => {
|
|
@@ -31247,12 +31248,12 @@ function registerSheetsTools(server2) {
|
|
|
31247
31248
|
);
|
|
31248
31249
|
});
|
|
31249
31250
|
server2.registerTool("gog_sheets_append", {
|
|
31250
|
-
description:
|
|
31251
|
+
description: 'Append rows to a Google Sheet after the last row with data in the given range. Values may be strings, numbers, booleans, or null. Strings starting with "=" are interpreted as formulas.',
|
|
31251
31252
|
annotations: { destructiveHint: true },
|
|
31252
31253
|
inputSchema: {
|
|
31253
31254
|
spreadsheetId: external_exports.string().describe("Spreadsheet ID (from the URL)"),
|
|
31254
31255
|
range: external_exports.string().describe("Range indicating which sheet/columns to append to, e.g. Sheet1!A:C"),
|
|
31255
|
-
values: external_exports.array(external_exports.array(
|
|
31256
|
+
values: external_exports.array(external_exports.array(cellValueParam)).describe('2D array of rows to append. Cells may be string/number/boolean/null; strings starting with "=" are formulas.'),
|
|
31256
31257
|
account: accountParam
|
|
31257
31258
|
}
|
|
31258
31259
|
}, async ({ spreadsheetId, range, values, account }) => {
|
|
@@ -31308,7 +31309,7 @@ function registerSheetsTools(server2) {
|
|
|
31308
31309
|
}
|
|
31309
31310
|
|
|
31310
31311
|
// ../gogcli-mcp/src/server.ts
|
|
31311
|
-
var VERSION = true ? "2.0.
|
|
31312
|
+
var VERSION = true ? "2.0.11" : "0.0.0";
|
|
31312
31313
|
function createServer(options) {
|
|
31313
31314
|
return new McpServer({
|
|
31314
31315
|
name: options?.name ?? "gogcli",
|
|
@@ -31317,7 +31318,31 @@ function createServer(options) {
|
|
|
31317
31318
|
}
|
|
31318
31319
|
|
|
31319
31320
|
// src/tools/sheets-extra.ts
|
|
31321
|
+
function hexToRgb(hex3) {
|
|
31322
|
+
let h = hex3.trim().replace(/^#/, "");
|
|
31323
|
+
if (h.length === 3) h = h[0] + h[0] + h[1] + h[1] + h[2] + h[2];
|
|
31324
|
+
if (!/^[0-9a-fA-F]{6}$/.test(h)) return null;
|
|
31325
|
+
const n = parseInt(h, 16);
|
|
31326
|
+
return {
|
|
31327
|
+
red: (n >> 16 & 255) / 255,
|
|
31328
|
+
green: (n >> 8 & 255) / 255,
|
|
31329
|
+
blue: (n & 255) / 255
|
|
31330
|
+
};
|
|
31331
|
+
}
|
|
31320
31332
|
function registerExtraSheetsTools(server2) {
|
|
31333
|
+
server2.registerTool("gog_sheets_list_tabs", {
|
|
31334
|
+
description: "List tabs (sheets) in a spreadsheet with their titles, sheetIds, and indices. A friendlier view than gog_sheets_metadata when you only need the tab list \u2014 useful for restructuring a workbook over a long agent session without losing track of names.",
|
|
31335
|
+
annotations: { readOnlyHint: true },
|
|
31336
|
+
inputSchema: {
|
|
31337
|
+
spreadsheetId: external_exports.string().describe("Spreadsheet ID (from the URL)"),
|
|
31338
|
+
account: accountParam
|
|
31339
|
+
}
|
|
31340
|
+
}, async ({ spreadsheetId, account }) => {
|
|
31341
|
+
return runOrDiagnose(
|
|
31342
|
+
["sheets", "metadata", spreadsheetId, "--select=sheets.properties.sheetId,sheets.properties.title,sheets.properties.index,sheets.properties.gridProperties"],
|
|
31343
|
+
{ account }
|
|
31344
|
+
);
|
|
31345
|
+
});
|
|
31321
31346
|
server2.registerTool("gog_sheets_add_tab", {
|
|
31322
31347
|
description: "Add a new sheet tab to a spreadsheet.",
|
|
31323
31348
|
inputSchema: {
|
|
@@ -31440,19 +31465,92 @@ function registerExtraSheetsTools(server2) {
|
|
|
31440
31465
|
return runOrDiagnose(["sheets", "unmerge", spreadsheetId, range], { account });
|
|
31441
31466
|
});
|
|
31442
31467
|
server2.registerTool("gog_sheets_format", {
|
|
31443
|
-
description: "Apply cell formatting (bold,
|
|
31468
|
+
description: "Apply cell formatting to a range. The named flags (bold, italic, backgroundColor, etc.) compose into a Sheets API CellFormat \u2014 use them for the 90% case. For full API control, pass formatJson + formatFields (Sheets CellFormat + field mask) directly.",
|
|
31444
31469
|
annotations: { destructiveHint: true },
|
|
31445
31470
|
inputSchema: {
|
|
31446
31471
|
spreadsheetId: external_exports.string().describe("Spreadsheet ID"),
|
|
31447
31472
|
range: external_exports.string().describe("Range to format (e.g. Sheet1!A1:C3)"),
|
|
31448
|
-
|
|
31449
|
-
|
|
31473
|
+
bold: external_exports.boolean().optional().describe("Set bold"),
|
|
31474
|
+
italic: external_exports.boolean().optional().describe("Set italic"),
|
|
31475
|
+
underline: external_exports.boolean().optional().describe("Set underline"),
|
|
31476
|
+
strikethrough: external_exports.boolean().optional().describe("Set strikethrough"),
|
|
31477
|
+
fontSize: external_exports.number().optional().describe("Font size in points"),
|
|
31478
|
+
fontFamily: external_exports.string().optional().describe("Font family (e.g. Arial)"),
|
|
31479
|
+
textColor: external_exports.string().optional().describe("Text color as #RRGGBB or #RGB"),
|
|
31480
|
+
backgroundColor: external_exports.string().optional().describe("Cell background color as #RRGGBB or #RGB"),
|
|
31481
|
+
horizontalAlignment: external_exports.enum(["LEFT", "CENTER", "RIGHT"]).optional().describe("Horizontal alignment"),
|
|
31482
|
+
verticalAlignment: external_exports.enum(["TOP", "MIDDLE", "BOTTOM"]).optional().describe("Vertical alignment"),
|
|
31483
|
+
wrapStrategy: external_exports.enum(["OVERFLOW_CELL", "LEGACY_WRAP", "CLIP", "WRAP"]).optional().describe("Text wrap strategy"),
|
|
31484
|
+
formatJson: external_exports.string().optional().describe("Escape hatch: raw CellFormat JSON. When provided, named flags above are ignored and formatJson is sent as-is."),
|
|
31485
|
+
formatFields: external_exports.string().optional().describe("Comma-separated field mask (e.g. textFormat.bold,backgroundColor). Required when using formatJson; auto-computed when using named flags."),
|
|
31450
31486
|
account: accountParam
|
|
31451
31487
|
}
|
|
31452
|
-
}, async (
|
|
31453
|
-
const
|
|
31488
|
+
}, async (rawArgs) => {
|
|
31489
|
+
const a = rawArgs;
|
|
31490
|
+
let formatJson = a.formatJson;
|
|
31491
|
+
let formatFields = a.formatFields;
|
|
31492
|
+
if (!formatJson) {
|
|
31493
|
+
const cellFormat = {};
|
|
31494
|
+
const textFormat = {};
|
|
31495
|
+
const fields = [];
|
|
31496
|
+
if (a.bold !== void 0) {
|
|
31497
|
+
textFormat.bold = a.bold;
|
|
31498
|
+
fields.push("textFormat.bold");
|
|
31499
|
+
}
|
|
31500
|
+
if (a.italic !== void 0) {
|
|
31501
|
+
textFormat.italic = a.italic;
|
|
31502
|
+
fields.push("textFormat.italic");
|
|
31503
|
+
}
|
|
31504
|
+
if (a.underline !== void 0) {
|
|
31505
|
+
textFormat.underline = a.underline;
|
|
31506
|
+
fields.push("textFormat.underline");
|
|
31507
|
+
}
|
|
31508
|
+
if (a.strikethrough !== void 0) {
|
|
31509
|
+
textFormat.strikethrough = a.strikethrough;
|
|
31510
|
+
fields.push("textFormat.strikethrough");
|
|
31511
|
+
}
|
|
31512
|
+
if (a.fontSize !== void 0) {
|
|
31513
|
+
textFormat.fontSize = a.fontSize;
|
|
31514
|
+
fields.push("textFormat.fontSize");
|
|
31515
|
+
}
|
|
31516
|
+
if (a.fontFamily !== void 0) {
|
|
31517
|
+
textFormat.fontFamily = a.fontFamily;
|
|
31518
|
+
fields.push("textFormat.fontFamily");
|
|
31519
|
+
}
|
|
31520
|
+
if (a.textColor !== void 0) {
|
|
31521
|
+
const rgb = hexToRgb(a.textColor);
|
|
31522
|
+
if (!rgb) throw new Error(`Invalid textColor: ${a.textColor} (expected #RRGGBB or #RGB)`);
|
|
31523
|
+
textFormat.foregroundColor = rgb;
|
|
31524
|
+
fields.push("textFormat.foregroundColor");
|
|
31525
|
+
}
|
|
31526
|
+
if (Object.keys(textFormat).length > 0) cellFormat.textFormat = textFormat;
|
|
31527
|
+
if (a.backgroundColor !== void 0) {
|
|
31528
|
+
const rgb = hexToRgb(a.backgroundColor);
|
|
31529
|
+
if (!rgb) throw new Error(`Invalid backgroundColor: ${a.backgroundColor} (expected #RRGGBB or #RGB)`);
|
|
31530
|
+
cellFormat.backgroundColor = rgb;
|
|
31531
|
+
fields.push("backgroundColor");
|
|
31532
|
+
}
|
|
31533
|
+
if (a.horizontalAlignment !== void 0) {
|
|
31534
|
+
cellFormat.horizontalAlignment = a.horizontalAlignment;
|
|
31535
|
+
fields.push("horizontalAlignment");
|
|
31536
|
+
}
|
|
31537
|
+
if (a.verticalAlignment !== void 0) {
|
|
31538
|
+
cellFormat.verticalAlignment = a.verticalAlignment;
|
|
31539
|
+
fields.push("verticalAlignment");
|
|
31540
|
+
}
|
|
31541
|
+
if (a.wrapStrategy !== void 0) {
|
|
31542
|
+
cellFormat.wrapStrategy = a.wrapStrategy;
|
|
31543
|
+
fields.push("wrapStrategy");
|
|
31544
|
+
}
|
|
31545
|
+
if (Object.keys(cellFormat).length === 0) {
|
|
31546
|
+
throw new Error("gog_sheets_format requires at least one named flag or a formatJson value");
|
|
31547
|
+
}
|
|
31548
|
+
formatJson = JSON.stringify(cellFormat);
|
|
31549
|
+
if (!formatFields) formatFields = fields.join(",");
|
|
31550
|
+
}
|
|
31551
|
+
const args = ["sheets", "format", a.spreadsheetId, a.range, `--format-json=${formatJson}`];
|
|
31454
31552
|
if (formatFields) args.push(`--format-fields=${formatFields}`);
|
|
31455
|
-
return runOrDiagnose(args, { account });
|
|
31553
|
+
return runOrDiagnose(args, { account: a.account });
|
|
31456
31554
|
});
|
|
31457
31555
|
server2.registerTool("gog_sheets_number_format", {
|
|
31458
31556
|
description: "Set number format on a range (currency, percentage, date, etc.).",
|
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.0.
|
|
6
|
+
"version": "2.0.11",
|
|
7
7
|
"description": "Extended Google Sheets for Claude via gogcli — auth + full Sheets support",
|
|
8
8
|
"author": {
|
|
9
9
|
"name": "Chris Hall",
|
|
@@ -146,7 +146,7 @@
|
|
|
146
146
|
},
|
|
147
147
|
{
|
|
148
148
|
"name": "gog_sheets_format",
|
|
149
|
-
"description": "Apply cell formatting (bold,
|
|
149
|
+
"description": "Apply cell formatting to a range. Named flags (bold, italic, backgroundColor, etc.) compose to a Sheets CellFormat; pass formatJson for full control."
|
|
150
150
|
},
|
|
151
151
|
{
|
|
152
152
|
"name": "gog_sheets_number_format",
|
|
@@ -195,6 +195,10 @@
|
|
|
195
195
|
{
|
|
196
196
|
"name": "gog_sheets_named_ranges_delete",
|
|
197
197
|
"description": "Delete a named range."
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
"name": "gog_sheets_list_tabs",
|
|
201
|
+
"description": "List tabs in a spreadsheet (sheetId, title, index, gridProperties)"
|
|
198
202
|
}
|
|
199
203
|
],
|
|
200
204
|
"compatibility": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gogcli-mcp-sheets",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.11",
|
|
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.0.
|
|
10
|
+
"version": "2.0.11",
|
|
11
11
|
"packages": [
|
|
12
12
|
{
|
|
13
13
|
"registryType": "npm",
|
|
14
14
|
"identifier": "gogcli-mcp-sheets",
|
|
15
|
-
"version": "2.0.
|
|
15
|
+
"version": "2.0.11",
|
|
16
16
|
"transport": {
|
|
17
17
|
"type": "stdio"
|
|
18
18
|
},
|
|
@@ -2,8 +2,39 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
import { accountParam, runOrDiagnose } from '../../../gogcli-mcp/src/lib.js';
|
|
4
4
|
|
|
5
|
+
// Convert a CSS-style hex color ("#FFF5D9", "#FD9", "FFF5D9") to the
|
|
6
|
+
// {red, green, blue} 0-1 float triple that Sheets API CellFormat expects.
|
|
7
|
+
// Returns null on unparseable input — caller decides whether to fall back
|
|
8
|
+
// or error.
|
|
9
|
+
export function hexToRgb(hex: string): { red: number; green: number; blue: number } | null {
|
|
10
|
+
let h = hex.trim().replace(/^#/, '');
|
|
11
|
+
if (h.length === 3) h = h[0]! + h[0]! + h[1]! + h[1]! + h[2]! + h[2]!;
|
|
12
|
+
if (!/^[0-9a-fA-F]{6}$/.test(h)) return null;
|
|
13
|
+
const n = parseInt(h, 16);
|
|
14
|
+
return {
|
|
15
|
+
red: ((n >> 16) & 0xff) / 255,
|
|
16
|
+
green: ((n >> 8) & 0xff) / 255,
|
|
17
|
+
blue: (n & 0xff) / 255,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
5
21
|
export function registerExtraSheetsTools(server: McpServer): void {
|
|
6
22
|
|
|
23
|
+
server.registerTool('gog_sheets_list_tabs', {
|
|
24
|
+
description: 'List tabs (sheets) in a spreadsheet with their titles, sheetIds, and indices. A friendlier view than gog_sheets_metadata when you only need the tab list — useful for restructuring a workbook over a long agent session without losing track of names.',
|
|
25
|
+
annotations: { readOnlyHint: true },
|
|
26
|
+
inputSchema: {
|
|
27
|
+
spreadsheetId: z.string().describe('Spreadsheet ID (from the URL)'),
|
|
28
|
+
account: accountParam,
|
|
29
|
+
},
|
|
30
|
+
}, async ({ spreadsheetId, account }) => {
|
|
31
|
+
// jq projection keeps the response compact: sheetId, title, index, gridProperties only.
|
|
32
|
+
return runOrDiagnose(
|
|
33
|
+
['sheets', 'metadata', spreadsheetId, '--select=sheets.properties.sheetId,sheets.properties.title,sheets.properties.index,sheets.properties.gridProperties'],
|
|
34
|
+
{ account },
|
|
35
|
+
);
|
|
36
|
+
});
|
|
37
|
+
|
|
7
38
|
server.registerTool('gog_sheets_add_tab', {
|
|
8
39
|
description: 'Add a new sheet tab to a spreadsheet.',
|
|
9
40
|
inputSchema: {
|
|
@@ -135,19 +166,92 @@ export function registerExtraSheetsTools(server: McpServer): void {
|
|
|
135
166
|
});
|
|
136
167
|
|
|
137
168
|
server.registerTool('gog_sheets_format', {
|
|
138
|
-
description: 'Apply cell formatting (bold,
|
|
169
|
+
description: 'Apply cell formatting to a range. The named flags (bold, italic, backgroundColor, etc.) compose into a Sheets API CellFormat — use them for the 90% case. For full API control, pass formatJson + formatFields (Sheets CellFormat + field mask) directly.',
|
|
139
170
|
annotations: { destructiveHint: true },
|
|
140
171
|
inputSchema: {
|
|
141
172
|
spreadsheetId: z.string().describe('Spreadsheet ID'),
|
|
142
173
|
range: z.string().describe('Range to format (e.g. Sheet1!A1:C3)'),
|
|
143
|
-
|
|
144
|
-
|
|
174
|
+
bold: z.boolean().optional().describe('Set bold'),
|
|
175
|
+
italic: z.boolean().optional().describe('Set italic'),
|
|
176
|
+
underline: z.boolean().optional().describe('Set underline'),
|
|
177
|
+
strikethrough: z.boolean().optional().describe('Set strikethrough'),
|
|
178
|
+
fontSize: z.number().optional().describe('Font size in points'),
|
|
179
|
+
fontFamily: z.string().optional().describe('Font family (e.g. Arial)'),
|
|
180
|
+
textColor: z.string().optional().describe('Text color as #RRGGBB or #RGB'),
|
|
181
|
+
backgroundColor: z.string().optional().describe('Cell background color as #RRGGBB or #RGB'),
|
|
182
|
+
horizontalAlignment: z.enum(['LEFT', 'CENTER', 'RIGHT']).optional().describe('Horizontal alignment'),
|
|
183
|
+
verticalAlignment: z.enum(['TOP', 'MIDDLE', 'BOTTOM']).optional().describe('Vertical alignment'),
|
|
184
|
+
wrapStrategy: z.enum(['OVERFLOW_CELL', 'LEGACY_WRAP', 'CLIP', 'WRAP']).optional().describe('Text wrap strategy'),
|
|
185
|
+
formatJson: z.string().optional().describe('Escape hatch: raw CellFormat JSON. When provided, named flags above are ignored and formatJson is sent as-is.'),
|
|
186
|
+
formatFields: z.string().optional().describe('Comma-separated field mask (e.g. textFormat.bold,backgroundColor). Required when using formatJson; auto-computed when using named flags.'),
|
|
145
187
|
account: accountParam,
|
|
146
188
|
},
|
|
147
|
-
}, async (
|
|
148
|
-
const
|
|
189
|
+
}, async (rawArgs) => {
|
|
190
|
+
const a = rawArgs as {
|
|
191
|
+
spreadsheetId: string;
|
|
192
|
+
range: string;
|
|
193
|
+
bold?: boolean;
|
|
194
|
+
italic?: boolean;
|
|
195
|
+
underline?: boolean;
|
|
196
|
+
strikethrough?: boolean;
|
|
197
|
+
fontSize?: number;
|
|
198
|
+
fontFamily?: string;
|
|
199
|
+
textColor?: string;
|
|
200
|
+
backgroundColor?: string;
|
|
201
|
+
horizontalAlignment?: string;
|
|
202
|
+
verticalAlignment?: string;
|
|
203
|
+
wrapStrategy?: string;
|
|
204
|
+
formatJson?: string;
|
|
205
|
+
formatFields?: string;
|
|
206
|
+
account?: string;
|
|
207
|
+
};
|
|
208
|
+
let formatJson = a.formatJson;
|
|
209
|
+
let formatFields = a.formatFields;
|
|
210
|
+
// If the caller didn't pass raw JSON, compose it from the named flags.
|
|
211
|
+
if (!formatJson) {
|
|
212
|
+
const cellFormat: Record<string, unknown> = {};
|
|
213
|
+
const textFormat: Record<string, unknown> = {};
|
|
214
|
+
const fields: string[] = [];
|
|
215
|
+
if (a.bold !== undefined) { textFormat.bold = a.bold; fields.push('textFormat.bold'); }
|
|
216
|
+
if (a.italic !== undefined) { textFormat.italic = a.italic; fields.push('textFormat.italic'); }
|
|
217
|
+
if (a.underline !== undefined) { textFormat.underline = a.underline; fields.push('textFormat.underline'); }
|
|
218
|
+
if (a.strikethrough !== undefined) { textFormat.strikethrough = a.strikethrough; fields.push('textFormat.strikethrough'); }
|
|
219
|
+
if (a.fontSize !== undefined) { textFormat.fontSize = a.fontSize; fields.push('textFormat.fontSize'); }
|
|
220
|
+
if (a.fontFamily !== undefined) { textFormat.fontFamily = a.fontFamily; fields.push('textFormat.fontFamily'); }
|
|
221
|
+
if (a.textColor !== undefined) {
|
|
222
|
+
const rgb = hexToRgb(a.textColor);
|
|
223
|
+
if (!rgb) throw new Error(`Invalid textColor: ${a.textColor} (expected #RRGGBB or #RGB)`);
|
|
224
|
+
textFormat.foregroundColor = rgb;
|
|
225
|
+
fields.push('textFormat.foregroundColor');
|
|
226
|
+
}
|
|
227
|
+
if (Object.keys(textFormat).length > 0) cellFormat.textFormat = textFormat;
|
|
228
|
+
if (a.backgroundColor !== undefined) {
|
|
229
|
+
const rgb = hexToRgb(a.backgroundColor);
|
|
230
|
+
if (!rgb) throw new Error(`Invalid backgroundColor: ${a.backgroundColor} (expected #RRGGBB or #RGB)`);
|
|
231
|
+
cellFormat.backgroundColor = rgb;
|
|
232
|
+
fields.push('backgroundColor');
|
|
233
|
+
}
|
|
234
|
+
if (a.horizontalAlignment !== undefined) {
|
|
235
|
+
cellFormat.horizontalAlignment = a.horizontalAlignment;
|
|
236
|
+
fields.push('horizontalAlignment');
|
|
237
|
+
}
|
|
238
|
+
if (a.verticalAlignment !== undefined) {
|
|
239
|
+
cellFormat.verticalAlignment = a.verticalAlignment;
|
|
240
|
+
fields.push('verticalAlignment');
|
|
241
|
+
}
|
|
242
|
+
if (a.wrapStrategy !== undefined) {
|
|
243
|
+
cellFormat.wrapStrategy = a.wrapStrategy;
|
|
244
|
+
fields.push('wrapStrategy');
|
|
245
|
+
}
|
|
246
|
+
if (Object.keys(cellFormat).length === 0) {
|
|
247
|
+
throw new Error('gog_sheets_format requires at least one named flag or a formatJson value');
|
|
248
|
+
}
|
|
249
|
+
formatJson = JSON.stringify(cellFormat);
|
|
250
|
+
if (!formatFields) formatFields = fields.join(',');
|
|
251
|
+
}
|
|
252
|
+
const args = ['sheets', 'format', a.spreadsheetId, a.range, `--format-json=${formatJson}`];
|
|
149
253
|
if (formatFields) args.push(`--format-fields=${formatFields}`);
|
|
150
|
-
return runOrDiagnose(args, { account });
|
|
254
|
+
return runOrDiagnose(args, { account: a.account });
|
|
151
255
|
});
|
|
152
256
|
|
|
153
257
|
server.registerTool('gog_sheets_number_format', {
|
|
@@ -170,19 +170,132 @@ describe('gog_sheets_unmerge', () => {
|
|
|
170
170
|
|
|
171
171
|
// 10. format
|
|
172
172
|
describe('gog_sheets_format', () => {
|
|
173
|
-
it('
|
|
173
|
+
it('passes raw formatJson + formatFields through unchanged (escape hatch)', async () => {
|
|
174
174
|
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
175
175
|
const handlers = setupHandlers();
|
|
176
176
|
const fj = '{"textFormat":{"bold":true}}';
|
|
177
|
-
await handlers.get('gog_sheets_format')!({ spreadsheetId: 'sid', range: 'A1:B2', formatJson: fj });
|
|
178
|
-
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
177
|
+
await handlers.get('gog_sheets_format')!({ spreadsheetId: 'sid', range: 'A1:B2', formatJson: fj, formatFields: 'textFormat.bold' });
|
|
178
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
179
|
+
['sheets', 'format', 'sid', 'A1:B2', `--format-json=${fj}`, '--format-fields=textFormat.bold'],
|
|
180
|
+
{ account: undefined },
|
|
181
|
+
);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('passes raw formatJson without formatFields', async () => {
|
|
185
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
186
|
+
const handlers = setupHandlers();
|
|
187
|
+
await handlers.get('gog_sheets_format')!({ spreadsheetId: 'sid', range: 'A1', formatJson: '{}' });
|
|
188
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
189
|
+
['sheets', 'format', 'sid', 'A1', '--format-json={}'],
|
|
190
|
+
{ account: undefined },
|
|
191
|
+
);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it('composes named flags into CellFormat JSON + auto-computed fields', async () => {
|
|
195
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
196
|
+
const handlers = setupHandlers();
|
|
197
|
+
await handlers.get('gog_sheets_format')!({
|
|
198
|
+
spreadsheetId: 'sid', range: 'A1:C3',
|
|
199
|
+
bold: true,
|
|
200
|
+
backgroundColor: '#FFF5D9',
|
|
201
|
+
wrapStrategy: 'WRAP',
|
|
202
|
+
verticalAlignment: 'TOP',
|
|
203
|
+
});
|
|
204
|
+
const call = vi.mocked(lib.runOrDiagnose).mock.calls[0]!;
|
|
205
|
+
const argv = call[0];
|
|
206
|
+
expect(argv[0]).toBe('sheets');
|
|
207
|
+
expect(argv[1]).toBe('format');
|
|
208
|
+
expect(argv[2]).toBe('sid');
|
|
209
|
+
expect(argv[3]).toBe('A1:C3');
|
|
210
|
+
const fmtArg = (argv[4] as string).replace(/^--format-json=/, '');
|
|
211
|
+
const parsed = JSON.parse(fmtArg);
|
|
212
|
+
expect(parsed.textFormat.bold).toBe(true);
|
|
213
|
+
expect(parsed.backgroundColor.red).toBeCloseTo(1.0);
|
|
214
|
+
expect(parsed.backgroundColor.green).toBeCloseTo(245 / 255);
|
|
215
|
+
expect(parsed.backgroundColor.blue).toBeCloseTo(217 / 255);
|
|
216
|
+
expect(parsed.wrapStrategy).toBe('WRAP');
|
|
217
|
+
expect(parsed.verticalAlignment).toBe('TOP');
|
|
218
|
+
const fieldsArg = argv[5] as string;
|
|
219
|
+
expect(fieldsArg).toMatch(/^--format-fields=/);
|
|
220
|
+
expect(fieldsArg).toContain('textFormat.bold');
|
|
221
|
+
expect(fieldsArg).toContain('backgroundColor');
|
|
222
|
+
expect(fieldsArg).toContain('wrapStrategy');
|
|
223
|
+
expect(fieldsArg).toContain('verticalAlignment');
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it('composes all named flags including textColor + alignment', async () => {
|
|
227
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
228
|
+
const handlers = setupHandlers();
|
|
229
|
+
await handlers.get('gog_sheets_format')!({
|
|
230
|
+
spreadsheetId: 'sid', range: 'A1',
|
|
231
|
+
italic: true,
|
|
232
|
+
underline: true,
|
|
233
|
+
strikethrough: true,
|
|
234
|
+
fontSize: 14,
|
|
235
|
+
fontFamily: 'Inter',
|
|
236
|
+
textColor: '#FFF',
|
|
237
|
+
horizontalAlignment: 'CENTER',
|
|
238
|
+
});
|
|
239
|
+
const call = vi.mocked(lib.runOrDiagnose).mock.calls[0]!;
|
|
240
|
+
const fmtArg = (call[0][4] as string).replace(/^--format-json=/, '');
|
|
241
|
+
const parsed = JSON.parse(fmtArg);
|
|
242
|
+
expect(parsed.textFormat.italic).toBe(true);
|
|
243
|
+
expect(parsed.textFormat.underline).toBe(true);
|
|
244
|
+
expect(parsed.textFormat.strikethrough).toBe(true);
|
|
245
|
+
expect(parsed.textFormat.fontSize).toBe(14);
|
|
246
|
+
expect(parsed.textFormat.fontFamily).toBe('Inter');
|
|
247
|
+
expect(parsed.textFormat.foregroundColor.red).toBeCloseTo(1);
|
|
248
|
+
expect(parsed.textFormat.foregroundColor.green).toBeCloseTo(1);
|
|
249
|
+
expect(parsed.textFormat.foregroundColor.blue).toBeCloseTo(1);
|
|
250
|
+
expect(parsed.horizontalAlignment).toBe('CENTER');
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it('rejects bad hex in textColor', async () => {
|
|
254
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
255
|
+
const handlers = setupHandlers();
|
|
256
|
+
await expect(
|
|
257
|
+
handlers.get('gog_sheets_format')!({ spreadsheetId: 'sid', range: 'A1', textColor: 'not-a-hex' }),
|
|
258
|
+
).rejects.toThrow(/Invalid textColor/);
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it('rejects bad hex in backgroundColor', async () => {
|
|
262
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
263
|
+
const handlers = setupHandlers();
|
|
264
|
+
await expect(
|
|
265
|
+
handlers.get('gog_sheets_format')!({ spreadsheetId: 'sid', range: 'A1', backgroundColor: 'orange' }),
|
|
266
|
+
).rejects.toThrow(/Invalid backgroundColor/);
|
|
179
267
|
});
|
|
180
268
|
|
|
181
|
-
it('
|
|
269
|
+
it('rejects no-op calls with neither flags nor formatJson', async () => {
|
|
270
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
271
|
+
const handlers = setupHandlers();
|
|
272
|
+
await expect(
|
|
273
|
+
handlers.get('gog_sheets_format')!({ spreadsheetId: 'sid', range: 'A1' }),
|
|
274
|
+
).rejects.toThrow(/requires at least one named flag/);
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
it('honors caller-provided formatFields when also using named flags', async () => {
|
|
278
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
279
|
+
const handlers = setupHandlers();
|
|
280
|
+
await handlers.get('gog_sheets_format')!({
|
|
281
|
+
spreadsheetId: 'sid', range: 'A1',
|
|
282
|
+
bold: true,
|
|
283
|
+
formatFields: 'textFormat.bold,textFormat.italic',
|
|
284
|
+
});
|
|
285
|
+
const call = vi.mocked(lib.runOrDiagnose).mock.calls[0]!;
|
|
286
|
+
expect(call[0][5]).toBe('--format-fields=textFormat.bold,textFormat.italic');
|
|
287
|
+
});
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
describe('gog_sheets_list_tabs', () => {
|
|
291
|
+
it('uses metadata with a --select projection', async () => {
|
|
182
292
|
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
183
293
|
const handlers = setupHandlers();
|
|
184
|
-
await handlers.get('
|
|
185
|
-
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
294
|
+
await handlers.get('gog_sheets_list_tabs')!({ spreadsheetId: 'sid' });
|
|
295
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
296
|
+
['sheets', 'metadata', 'sid', '--select=sheets.properties.sheetId,sheets.properties.title,sheets.properties.index,sheets.properties.gridProperties'],
|
|
297
|
+
{ account: undefined },
|
|
298
|
+
);
|
|
186
299
|
});
|
|
187
300
|
});
|
|
188
301
|
|