@triffon/google-docs-mcp 1.11.3-triffon
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/LICENSE +7 -0
- package/README.md +567 -0
- package/dist/auth.js +327 -0
- package/dist/cachedToolsList.js +38 -0
- package/dist/clients.js +180 -0
- package/dist/config.js +9 -0
- package/dist/downloadProxy.js +82 -0
- package/dist/driveQueryUtils.js +7 -0
- package/dist/firestoreTokenStorage.js +32 -0
- package/dist/googleDocsApiHelpers.js +1257 -0
- package/dist/googleSheetsApiHelpers.js +849 -0
- package/dist/index.js +240 -0
- package/dist/landingPage.js +103 -0
- package/dist/logger.js +58 -0
- package/dist/markdown-transformer/docsToMarkdown.js +259 -0
- package/dist/markdown-transformer/index.js +128 -0
- package/dist/markdown-transformer/markdownToDocs.js +834 -0
- package/dist/remoteWrapper.js +72 -0
- package/dist/tools/calendar/createEvent.js +89 -0
- package/dist/tools/calendar/deleteEvent.js +49 -0
- package/dist/tools/calendar/helpers.js +19 -0
- package/dist/tools/calendar/index.js +12 -0
- package/dist/tools/calendar/listEvents.js +84 -0
- package/dist/tools/calendar/quickAddEvent.js +55 -0
- package/dist/tools/calendar/updateEvent.js +84 -0
- package/dist/tools/docs/addTab.js +84 -0
- package/dist/tools/docs/appendTableRows.js +89 -0
- package/dist/tools/docs/appendToGoogleDoc.js +85 -0
- package/dist/tools/docs/cloneTable.js +159 -0
- package/dist/tools/docs/comments/addComment.js +83 -0
- package/dist/tools/docs/comments/deleteComment.js +30 -0
- package/dist/tools/docs/comments/getComment.js +45 -0
- package/dist/tools/docs/comments/index.js +14 -0
- package/dist/tools/docs/comments/listComments.js +43 -0
- package/dist/tools/docs/comments/replyToComment.js +35 -0
- package/dist/tools/docs/comments/resolveComment.js +55 -0
- package/dist/tools/docs/deleteRange.js +61 -0
- package/dist/tools/docs/deleteTableRows.js +62 -0
- package/dist/tools/docs/findAndReplace.js +54 -0
- package/dist/tools/docs/findElement.js +43 -0
- package/dist/tools/docs/findSectionsByHeading.js +46 -0
- package/dist/tools/docs/formatting/applyParagraphStyle.js +83 -0
- package/dist/tools/docs/formatting/applyTextStyle.js +49 -0
- package/dist/tools/docs/formatting/batchApplyTextStyle.js +86 -0
- package/dist/tools/docs/formatting/index.js +16 -0
- package/dist/tools/docs/formatting/updateTableBorders.js +81 -0
- package/dist/tools/docs/formatting/updateTableCellStyle.js +85 -0
- package/dist/tools/docs/formatting/updateTableColumnWidth.js +51 -0
- package/dist/tools/docs/formatting/updateTableRowStyle.js +74 -0
- package/dist/tools/docs/getTableStructure.js +48 -0
- package/dist/tools/docs/index.js +66 -0
- package/dist/tools/docs/insertDateChip.js +82 -0
- package/dist/tools/docs/insertImage.js +112 -0
- package/dist/tools/docs/insertPageBreak.js +47 -0
- package/dist/tools/docs/insertPerson.js +53 -0
- package/dist/tools/docs/insertRichLink.js +58 -0
- package/dist/tools/docs/insertSectionBreak.js +60 -0
- package/dist/tools/docs/insertTable.js +42 -0
- package/dist/tools/docs/insertTableWithData.js +125 -0
- package/dist/tools/docs/insertText.js +49 -0
- package/dist/tools/docs/listDocumentTables.js +47 -0
- package/dist/tools/docs/listDocumentTabs.js +59 -0
- package/dist/tools/docs/listSmartChips.js +41 -0
- package/dist/tools/docs/modifyText.js +147 -0
- package/dist/tools/docs/readGoogleDoc.js +164 -0
- package/dist/tools/docs/renameTab.js +47 -0
- package/dist/tools/docs/replaceTableRowData.js +55 -0
- package/dist/tools/docs/smartChipHelpers.js +71 -0
- package/dist/tools/docs/structureHelpers.js +250 -0
- package/dist/tools/docs/tabFieldMasks.js +48 -0
- package/dist/tools/docs/tableRowDataHelpers.js +53 -0
- package/dist/tools/docs/updateSectionStyle.js +148 -0
- package/dist/tools/drive/copyFile.js +63 -0
- package/dist/tools/drive/createDocument.js +105 -0
- package/dist/tools/drive/createFolder.js +48 -0
- package/dist/tools/drive/createFromTemplate.js +82 -0
- package/dist/tools/drive/deleteFile.js +72 -0
- package/dist/tools/drive/downloadFile.js +266 -0
- package/dist/tools/drive/getDocumentInfo.js +48 -0
- package/dist/tools/drive/getFolderInfo.js +48 -0
- package/dist/tools/drive/index.js +34 -0
- package/dist/tools/drive/listDriveFiles.js +129 -0
- package/dist/tools/drive/listFolderContents.js +83 -0
- package/dist/tools/drive/listGoogleDocs.js +70 -0
- package/dist/tools/drive/moveFile.js +54 -0
- package/dist/tools/drive/renameFile.js +39 -0
- package/dist/tools/drive/savePathGuard.js +86 -0
- package/dist/tools/drive/searchDriveFiles.js +148 -0
- package/dist/tools/drive/searchGoogleDocs.js +77 -0
- package/dist/tools/drive/setFilePermission.js +68 -0
- package/dist/tools/gmail/createDraft.js +58 -0
- package/dist/tools/gmail/deleteDraft.js +37 -0
- package/dist/tools/gmail/getDraft.js +52 -0
- package/dist/tools/gmail/getMessage.js +92 -0
- package/dist/tools/gmail/helpers.js +113 -0
- package/dist/tools/gmail/index.js +28 -0
- package/dist/tools/gmail/listDrafts.js +74 -0
- package/dist/tools/gmail/listLabels.js +31 -0
- package/dist/tools/gmail/listMessages.js +87 -0
- package/dist/tools/gmail/modifyMessageLabels.js +54 -0
- package/dist/tools/gmail/sendDraft.js +42 -0
- package/dist/tools/gmail/sendEmail.js +56 -0
- package/dist/tools/gmail/trashMessage.js +37 -0
- package/dist/tools/gmail/triageInbox.js +147 -0
- package/dist/tools/gmail/updateDraft.js +60 -0
- package/dist/tools/index.js +64 -0
- package/dist/tools/script/appsScriptShared.js +96 -0
- package/dist/tools/script/createAppsScriptProject.js +74 -0
- package/dist/tools/script/getAppsScriptContent.js +56 -0
- package/dist/tools/script/index.js +14 -0
- package/dist/tools/script/updateAppsScriptContent.js +56 -0
- package/dist/tools/sheets/addConditionalFormatting.js +143 -0
- package/dist/tools/sheets/addSpreadsheetSheet.js +34 -0
- package/dist/tools/sheets/appendSpreadsheetRows.js +44 -0
- package/dist/tools/sheets/appendTableRows.js +51 -0
- package/dist/tools/sheets/autoResizeColumns.js +67 -0
- package/dist/tools/sheets/autoResizeRows.js +63 -0
- package/dist/tools/sheets/batchWrite.js +61 -0
- package/dist/tools/sheets/clearSpreadsheetRange.js +31 -0
- package/dist/tools/sheets/comments/commentAnchor.js +95 -0
- package/dist/tools/sheets/comments/createSheetsCellNote.js +33 -0
- package/dist/tools/sheets/comments/createSheetsComment.js +168 -0
- package/dist/tools/sheets/comments/deleteSheetsComment.js +32 -0
- package/dist/tools/sheets/comments/getSheetsComment.js +51 -0
- package/dist/tools/sheets/comments/index.js +16 -0
- package/dist/tools/sheets/comments/listSheetsComments.js +177 -0
- package/dist/tools/sheets/comments/replyToSheetsComment.js +37 -0
- package/dist/tools/sheets/comments/resolveSheetsComment.js +53 -0
- package/dist/tools/sheets/copyFormatting.js +59 -0
- package/dist/tools/sheets/copySheetTo.js +36 -0
- package/dist/tools/sheets/createSpreadsheet.js +72 -0
- package/dist/tools/sheets/createTable.js +120 -0
- package/dist/tools/sheets/deleteChart.js +41 -0
- package/dist/tools/sheets/deleteConditionalFormatting.js +46 -0
- package/dist/tools/sheets/deleteSheet.js +43 -0
- package/dist/tools/sheets/deleteTable.js +56 -0
- package/dist/tools/sheets/duplicateSheet.js +53 -0
- package/dist/tools/sheets/formatCells.js +122 -0
- package/dist/tools/sheets/freezeRowsAndColumns.js +58 -0
- package/dist/tools/sheets/getConditionalFormatting.js +98 -0
- package/dist/tools/sheets/getSpreadsheetInfo.js +44 -0
- package/dist/tools/sheets/getTable.js +48 -0
- package/dist/tools/sheets/groupRows.js +62 -0
- package/dist/tools/sheets/index.js +84 -0
- package/dist/tools/sheets/insertChart.js +225 -0
- package/dist/tools/sheets/listGoogleSheets.js +66 -0
- package/dist/tools/sheets/listTables.js +55 -0
- package/dist/tools/sheets/protectRange.js +59 -0
- package/dist/tools/sheets/readCellFormat.js +143 -0
- package/dist/tools/sheets/readSpreadsheet.js +36 -0
- package/dist/tools/sheets/renameSheet.js +48 -0
- package/dist/tools/sheets/setCellBorders.js +94 -0
- package/dist/tools/sheets/setColumnWidths.js +43 -0
- package/dist/tools/sheets/setDropdownValidation.js +51 -0
- package/dist/tools/sheets/setRowHeights.js +63 -0
- package/dist/tools/sheets/ungroupAllRows.js +66 -0
- package/dist/tools/sheets/updateTableRange.js +51 -0
- package/dist/tools/sheets/writeSpreadsheet.js +45 -0
- package/dist/tools/utils/appendMarkdownToGoogleDoc.js +95 -0
- package/dist/tools/utils/index.js +8 -0
- package/dist/tools/utils/replaceDocumentWithMarkdown.js +161 -0
- package/dist/tools/utils/replaceRangeWithMarkdown.js +72 -0
- package/dist/types.js +208 -0
- package/dist/upstreamAuth.js +62 -0
- package/package.json +51 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { UserError } from 'fastmcp';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { getSheetsClient } from '../../clients.js';
|
|
4
|
+
import * as SheetsHelpers from '../../googleSheetsApiHelpers.js';
|
|
5
|
+
export function register(server) {
|
|
6
|
+
server.addTool({
|
|
7
|
+
name: 'listTables',
|
|
8
|
+
description: 'Lists all tables in a spreadsheet or specific sheet. Use this to discover table names and IDs before performing table operations.',
|
|
9
|
+
parameters: z.strictObject({
|
|
10
|
+
spreadsheetId: z
|
|
11
|
+
.string()
|
|
12
|
+
.describe('The spreadsheet ID — the long string between /d/ and /edit in a Google Sheets URL.'),
|
|
13
|
+
sheetName: z
|
|
14
|
+
.string()
|
|
15
|
+
.optional()
|
|
16
|
+
.describe('Optional: filter tables to only those on this specific sheet.'),
|
|
17
|
+
}),
|
|
18
|
+
execute: async (args, { log }) => {
|
|
19
|
+
const sheets = await getSheetsClient();
|
|
20
|
+
log.info(`Listing tables for spreadsheet: ${args.spreadsheetId}`);
|
|
21
|
+
try {
|
|
22
|
+
const tables = await SheetsHelpers.listAllTables(sheets, args.spreadsheetId, args.sheetName);
|
|
23
|
+
if (tables.length === 0) {
|
|
24
|
+
return JSON.stringify({
|
|
25
|
+
spreadsheetId: args.spreadsheetId,
|
|
26
|
+
sheetFilter: args.sheetName || 'All sheets',
|
|
27
|
+
tables: [],
|
|
28
|
+
message: 'No tables found. Use createTable to create a table.',
|
|
29
|
+
}, null, 2);
|
|
30
|
+
}
|
|
31
|
+
const tableList = tables.map((item) => ({
|
|
32
|
+
tableId: item.table.tableId,
|
|
33
|
+
name: item.table.name,
|
|
34
|
+
sheetName: item.sheetName,
|
|
35
|
+
columnCount: item.table.columnProperties?.length || 0,
|
|
36
|
+
range: item.table.range
|
|
37
|
+
? `${item.sheetName}!${SheetsHelpers.rowColToA1(item.table.range.startRowIndex || 0, item.table.range.startColumnIndex || 0)}:${SheetsHelpers.rowColToA1((item.table.range.endRowIndex || 1) - 1, (item.table.range.endColumnIndex || 1) - 1)}`
|
|
38
|
+
: 'Unknown',
|
|
39
|
+
}));
|
|
40
|
+
return JSON.stringify({
|
|
41
|
+
spreadsheetId: args.spreadsheetId,
|
|
42
|
+
sheetFilter: args.sheetName || 'All sheets',
|
|
43
|
+
count: tableList.length,
|
|
44
|
+
tables: tableList,
|
|
45
|
+
}, null, 2);
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
log.error(`Error listing tables: ${error.message || error}`);
|
|
49
|
+
if (error instanceof UserError)
|
|
50
|
+
throw error;
|
|
51
|
+
throw new UserError(`Failed to list tables: ${error.message || 'Unknown error'}`);
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { UserError } from 'fastmcp';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { getSheetsClient } from '../../clients.js';
|
|
4
|
+
import * as SheetsHelpers from '../../googleSheetsApiHelpers.js';
|
|
5
|
+
export function register(server) {
|
|
6
|
+
server.addTool({
|
|
7
|
+
name: 'protectRange',
|
|
8
|
+
description: "Locks (protects) a range or an entire sheet to prevent accidental edits. Optionally specify a description. The protection applies to the authenticated user's account.",
|
|
9
|
+
parameters: z.strictObject({
|
|
10
|
+
spreadsheetId: z
|
|
11
|
+
.string()
|
|
12
|
+
.describe('The spreadsheet ID — the long string between /d/ and /edit in a Google Sheets URL.'),
|
|
13
|
+
sheetName: z
|
|
14
|
+
.string()
|
|
15
|
+
.optional()
|
|
16
|
+
.describe('Name of the sheet/tab. Defaults to the first sheet if not provided.'),
|
|
17
|
+
range: z
|
|
18
|
+
.string()
|
|
19
|
+
.optional()
|
|
20
|
+
.describe('A1 notation range to protect (e.g., "A1:D10", "1:1"). Omit to protect the entire sheet.'),
|
|
21
|
+
description: z
|
|
22
|
+
.string()
|
|
23
|
+
.optional()
|
|
24
|
+
.describe('Human-readable description for this protection (e.g., "Header row — do not edit").'),
|
|
25
|
+
warningOnly: z
|
|
26
|
+
.boolean()
|
|
27
|
+
.optional()
|
|
28
|
+
.describe('If true, shows a warning when editing but does not block it. Defaults to false (fully locked).'),
|
|
29
|
+
}),
|
|
30
|
+
execute: async (args, { log }) => {
|
|
31
|
+
const sheets = await getSheetsClient();
|
|
32
|
+
log.info(`Protecting range in spreadsheet ${args.spreadsheetId}`);
|
|
33
|
+
try {
|
|
34
|
+
const sheetId = await SheetsHelpers.resolveSheetId(sheets, args.spreadsheetId, args.sheetName);
|
|
35
|
+
const protectedRange = {
|
|
36
|
+
description: args.description ?? '',
|
|
37
|
+
warningOnly: args.warningOnly ?? false,
|
|
38
|
+
range: args.range ? SheetsHelpers.parseA1ToGridRange(args.range, sheetId) : { sheetId },
|
|
39
|
+
};
|
|
40
|
+
const response = await sheets.spreadsheets.batchUpdate({
|
|
41
|
+
spreadsheetId: args.spreadsheetId,
|
|
42
|
+
requestBody: {
|
|
43
|
+
requests: [{ addProtectedRange: { protectedRange } }],
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
const result = response.data.replies?.[0]?.addProtectedRange?.protectedRange;
|
|
47
|
+
const target = args.range ? `range "${args.range}"` : 'entire sheet';
|
|
48
|
+
const mode = args.warningOnly ? 'warning-only' : 'fully locked';
|
|
49
|
+
return `Successfully protected ${target} (Protection ID: ${result?.protectedRangeId}, mode: ${mode}).`;
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
log.error(`Error protecting range: ${error.message || error}`);
|
|
53
|
+
if (error instanceof UserError)
|
|
54
|
+
throw error;
|
|
55
|
+
throw new UserError(`Failed to protect range: ${error.message || 'Unknown error'}`);
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { UserError } from 'fastmcp';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { getSheetsClient } from '../../clients.js';
|
|
4
|
+
import { rowColToA1 } from '../../googleSheetsApiHelpers.js';
|
|
5
|
+
/**
|
|
6
|
+
* Converts a Google Sheets RGBA color object (0-1 range) to a hex string.
|
|
7
|
+
* Returns null if the color is undefined or has no meaningful channels.
|
|
8
|
+
*/
|
|
9
|
+
function rgbaToHex(color) {
|
|
10
|
+
if (!color)
|
|
11
|
+
return null;
|
|
12
|
+
const r = Math.round((color.red ?? 0) * 255);
|
|
13
|
+
const g = Math.round((color.green ?? 0) * 255);
|
|
14
|
+
const b = Math.round((color.blue ?? 0) * 255);
|
|
15
|
+
return `#${r.toString(16).padStart(2, '0').toUpperCase()}${g.toString(16).padStart(2, '0').toUpperCase()}${b.toString(16).padStart(2, '0').toUpperCase()}`;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Extracts a simplified formatting summary from a Google Sheets CellFormat object.
|
|
19
|
+
* Only includes properties that are explicitly set (non-default).
|
|
20
|
+
*/
|
|
21
|
+
function simplifyFormat(fmt) {
|
|
22
|
+
if (!fmt)
|
|
23
|
+
return null;
|
|
24
|
+
const result = {};
|
|
25
|
+
// Text formatting
|
|
26
|
+
if (fmt.textFormat) {
|
|
27
|
+
const tf = {};
|
|
28
|
+
if (fmt.textFormat.bold)
|
|
29
|
+
tf.bold = true;
|
|
30
|
+
if (fmt.textFormat.italic)
|
|
31
|
+
tf.italic = true;
|
|
32
|
+
if (fmt.textFormat.strikethrough)
|
|
33
|
+
tf.strikethrough = true;
|
|
34
|
+
if (fmt.textFormat.underline)
|
|
35
|
+
tf.underline = true;
|
|
36
|
+
if (fmt.textFormat.fontSize != null)
|
|
37
|
+
tf.fontSize = fmt.textFormat.fontSize;
|
|
38
|
+
if (fmt.textFormat.fontFamily)
|
|
39
|
+
tf.fontFamily = fmt.textFormat.fontFamily;
|
|
40
|
+
if (fmt.textFormat.foregroundColorStyle?.rgbColor) {
|
|
41
|
+
tf.foregroundColor = rgbaToHex(fmt.textFormat.foregroundColorStyle.rgbColor);
|
|
42
|
+
}
|
|
43
|
+
else if (fmt.textFormat.foregroundColor) {
|
|
44
|
+
tf.foregroundColor = rgbaToHex(fmt.textFormat.foregroundColor);
|
|
45
|
+
}
|
|
46
|
+
if (Object.keys(tf).length > 0)
|
|
47
|
+
result.textFormat = tf;
|
|
48
|
+
}
|
|
49
|
+
// Background color
|
|
50
|
+
if (fmt.backgroundColorStyle?.rgbColor) {
|
|
51
|
+
result.backgroundColor = rgbaToHex(fmt.backgroundColorStyle.rgbColor);
|
|
52
|
+
}
|
|
53
|
+
else if (fmt.backgroundColor) {
|
|
54
|
+
result.backgroundColor = rgbaToHex(fmt.backgroundColor);
|
|
55
|
+
}
|
|
56
|
+
// Alignment
|
|
57
|
+
if (fmt.horizontalAlignment)
|
|
58
|
+
result.horizontalAlignment = fmt.horizontalAlignment;
|
|
59
|
+
if (fmt.verticalAlignment)
|
|
60
|
+
result.verticalAlignment = fmt.verticalAlignment;
|
|
61
|
+
// Number format
|
|
62
|
+
if (fmt.numberFormat) {
|
|
63
|
+
result.numberFormat = {
|
|
64
|
+
type: fmt.numberFormat.type,
|
|
65
|
+
pattern: fmt.numberFormat.pattern,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
// Borders
|
|
69
|
+
if (fmt.borders) {
|
|
70
|
+
const borders = {};
|
|
71
|
+
for (const side of ['top', 'bottom', 'left', 'right']) {
|
|
72
|
+
if (fmt.borders[side]) {
|
|
73
|
+
borders[side] = {
|
|
74
|
+
style: fmt.borders[side].style,
|
|
75
|
+
...(fmt.borders[side].colorStyle?.rgbColor
|
|
76
|
+
? { color: rgbaToHex(fmt.borders[side].colorStyle.rgbColor) }
|
|
77
|
+
: fmt.borders[side].color
|
|
78
|
+
? { color: rgbaToHex(fmt.borders[side].color) }
|
|
79
|
+
: {}),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (Object.keys(borders).length > 0)
|
|
84
|
+
result.borders = borders;
|
|
85
|
+
}
|
|
86
|
+
// Wrap strategy
|
|
87
|
+
if (fmt.wrapStrategy)
|
|
88
|
+
result.wrapStrategy = fmt.wrapStrategy;
|
|
89
|
+
return Object.keys(result).length > 0 ? result : null;
|
|
90
|
+
}
|
|
91
|
+
export function register(server) {
|
|
92
|
+
server.addTool({
|
|
93
|
+
name: 'readCellFormat',
|
|
94
|
+
description: 'Reads the formatting/style of cells in a given range. Returns formatting details like bold, italic, fontSize, fontFamily, colors, alignment, borders, and number format per cell.',
|
|
95
|
+
parameters: z.strictObject({
|
|
96
|
+
spreadsheetId: z
|
|
97
|
+
.string()
|
|
98
|
+
.describe('The spreadsheet ID — the long string between /d/ and /edit in a Google Sheets URL.'),
|
|
99
|
+
range: z
|
|
100
|
+
.string()
|
|
101
|
+
.describe('A1 notation range to read formatting from (e.g., "Sheet1!A1:D5" or "A1:B2").'),
|
|
102
|
+
}),
|
|
103
|
+
execute: async (args, { log }) => {
|
|
104
|
+
const sheets = await getSheetsClient();
|
|
105
|
+
log.info(`Reading cell format for range "${args.range}" in spreadsheet ${args.spreadsheetId}`);
|
|
106
|
+
try {
|
|
107
|
+
const response = await sheets.spreadsheets.get({
|
|
108
|
+
spreadsheetId: args.spreadsheetId,
|
|
109
|
+
ranges: [args.range],
|
|
110
|
+
includeGridData: true,
|
|
111
|
+
fields: 'sheets.data.rowData.values.userEnteredFormat,sheets.data.startRow,sheets.data.startColumn',
|
|
112
|
+
});
|
|
113
|
+
const sheetData = response.data.sheets?.[0]?.data?.[0];
|
|
114
|
+
if (!sheetData?.rowData) {
|
|
115
|
+
return JSON.stringify({ range: args.range, cells: [] }, null, 2);
|
|
116
|
+
}
|
|
117
|
+
const startRow = sheetData.startRow ?? 0;
|
|
118
|
+
const startCol = sheetData.startColumn ?? 0;
|
|
119
|
+
const cells = [];
|
|
120
|
+
for (let rowIdx = 0; rowIdx < sheetData.rowData.length; rowIdx++) {
|
|
121
|
+
const row = sheetData.rowData[rowIdx];
|
|
122
|
+
if (!row.values)
|
|
123
|
+
continue;
|
|
124
|
+
for (let colIdx = 0; colIdx < row.values.length; colIdx++) {
|
|
125
|
+
const cellData = row.values[colIdx];
|
|
126
|
+
const fmt = simplifyFormat(cellData?.userEnteredFormat);
|
|
127
|
+
if (fmt) {
|
|
128
|
+
const cellRef = rowColToA1(startRow + rowIdx, startCol + colIdx);
|
|
129
|
+
cells.push({ cell: cellRef, format: fmt });
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return JSON.stringify({ range: args.range, cells }, null, 2);
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
log.error(`Error reading cell format for spreadsheet ${args.spreadsheetId}: ${error.message || error}`);
|
|
137
|
+
if (error instanceof UserError)
|
|
138
|
+
throw error;
|
|
139
|
+
throw new UserError(`Failed to read cell format: ${error.message || 'Unknown error'}`);
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { UserError } from 'fastmcp';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { getSheetsClient } from '../../clients.js';
|
|
4
|
+
import * as SheetsHelpers from '../../googleSheetsApiHelpers.js';
|
|
5
|
+
export function register(server) {
|
|
6
|
+
server.addTool({
|
|
7
|
+
name: 'readSpreadsheet',
|
|
8
|
+
description: 'Reads data from a range in a spreadsheet. Returns rows as arrays. Use A1 notation for the range (e.g., "Sheet1!A1:C10").',
|
|
9
|
+
parameters: z.strictObject({
|
|
10
|
+
spreadsheetId: z
|
|
11
|
+
.string()
|
|
12
|
+
.describe('The spreadsheet ID — the long string between /d/ and /edit in a Google Sheets URL.'),
|
|
13
|
+
range: z.string().describe('A1 notation range to read (e.g., "A1:B10" or "Sheet1!A1:B10").'),
|
|
14
|
+
valueRenderOption: z
|
|
15
|
+
.enum(['FORMATTED_VALUE', 'UNFORMATTED_VALUE', 'FORMULA'])
|
|
16
|
+
.optional()
|
|
17
|
+
.default('FORMATTED_VALUE')
|
|
18
|
+
.describe('How values should be rendered in the output.'),
|
|
19
|
+
}),
|
|
20
|
+
execute: async (args, { log }) => {
|
|
21
|
+
const sheets = await getSheetsClient();
|
|
22
|
+
log.info(`Reading spreadsheet ${args.spreadsheetId}, range: ${args.range}`);
|
|
23
|
+
try {
|
|
24
|
+
const response = await SheetsHelpers.readRange(sheets, args.spreadsheetId, args.range, args.valueRenderOption);
|
|
25
|
+
const values = response.values || [];
|
|
26
|
+
return JSON.stringify({ range: args.range, values }, null, 2);
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
log.error(`Error reading spreadsheet ${args.spreadsheetId}: ${error.message || error}`);
|
|
30
|
+
if (error instanceof UserError)
|
|
31
|
+
throw error;
|
|
32
|
+
throw new UserError(`Failed to read spreadsheet: ${error.message || 'Unknown error'}`);
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { UserError } from 'fastmcp';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { getSheetsClient } from '../../clients.js';
|
|
4
|
+
export function register(server) {
|
|
5
|
+
server.addTool({
|
|
6
|
+
name: 'renameSheet',
|
|
7
|
+
description: 'Renames a sheet (tab) in a spreadsheet. Use getSpreadsheetInfo to find the numeric sheet ID.',
|
|
8
|
+
parameters: z.strictObject({
|
|
9
|
+
spreadsheetId: z
|
|
10
|
+
.string()
|
|
11
|
+
.describe('The spreadsheet ID — the long string between /d/ and /edit in a Google Sheets URL.'),
|
|
12
|
+
sheetId: z
|
|
13
|
+
.number()
|
|
14
|
+
.int()
|
|
15
|
+
.describe('The numeric sheet ID to rename. Use getSpreadsheetInfo to find sheet IDs.'),
|
|
16
|
+
newName: z.string().min(1).describe('The new name for the sheet.'),
|
|
17
|
+
}),
|
|
18
|
+
execute: async (args, { log }) => {
|
|
19
|
+
const sheets = await getSheetsClient();
|
|
20
|
+
log.info(`Renaming sheet ID ${args.sheetId} to "${args.newName}" in spreadsheet ${args.spreadsheetId}`);
|
|
21
|
+
try {
|
|
22
|
+
await sheets.spreadsheets.batchUpdate({
|
|
23
|
+
spreadsheetId: args.spreadsheetId,
|
|
24
|
+
requestBody: {
|
|
25
|
+
requests: [
|
|
26
|
+
{
|
|
27
|
+
updateSheetProperties: {
|
|
28
|
+
properties: {
|
|
29
|
+
sheetId: args.sheetId,
|
|
30
|
+
title: args.newName,
|
|
31
|
+
},
|
|
32
|
+
fields: 'title',
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
return `Successfully renamed sheet (ID: ${args.sheetId}) to "${args.newName}".`;
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
log.error(`Error renaming sheet in spreadsheet ${args.spreadsheetId}: ${error.message || error}`);
|
|
42
|
+
if (error instanceof UserError)
|
|
43
|
+
throw error;
|
|
44
|
+
throw new UserError(`Failed to rename sheet: ${error.message || 'Unknown error'}`);
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { UserError } from 'fastmcp';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { getSheetsClient } from '../../clients.js';
|
|
4
|
+
import * as SheetsHelpers from '../../googleSheetsApiHelpers.js';
|
|
5
|
+
const borderStyleEnum = z.enum([
|
|
6
|
+
'SOLID',
|
|
7
|
+
'SOLID_MEDIUM',
|
|
8
|
+
'SOLID_THICK',
|
|
9
|
+
'DOTTED',
|
|
10
|
+
'DASHED',
|
|
11
|
+
'DOUBLE',
|
|
12
|
+
'NONE',
|
|
13
|
+
]);
|
|
14
|
+
const createBorderSchema = () => z
|
|
15
|
+
.object({
|
|
16
|
+
style: borderStyleEnum.describe('Border line style.'),
|
|
17
|
+
color: z
|
|
18
|
+
.string()
|
|
19
|
+
.optional()
|
|
20
|
+
.describe('Border color as hex (e.g., "#000000"). Defaults to black.'),
|
|
21
|
+
})
|
|
22
|
+
.optional();
|
|
23
|
+
export function register(server) {
|
|
24
|
+
server.addTool({
|
|
25
|
+
name: 'setCellBorders',
|
|
26
|
+
description: 'Sets borders on a range of cells. Each side (top, bottom, left, right, innerHorizontal, innerVertical) can be configured independently with a style and color. Use style "NONE" to remove a border.',
|
|
27
|
+
parameters: z
|
|
28
|
+
.object({
|
|
29
|
+
spreadsheetId: z
|
|
30
|
+
.string()
|
|
31
|
+
.describe('The spreadsheet ID — the long string between /d/ and /edit in a Google Sheets URL.'),
|
|
32
|
+
range: z.string().describe('A1 notation range (e.g., "Sheet1!A1:D10", "1:1", "A:A").'),
|
|
33
|
+
top: createBorderSchema().describe('Top border of the range.'),
|
|
34
|
+
bottom: createBorderSchema().describe('Bottom border of the range.'),
|
|
35
|
+
left: createBorderSchema().describe('Left border of the range.'),
|
|
36
|
+
right: createBorderSchema().describe('Right border of the range.'),
|
|
37
|
+
innerHorizontal: createBorderSchema().describe('Horizontal borders between rows inside the range.'),
|
|
38
|
+
innerVertical: createBorderSchema().describe('Vertical borders between columns inside the range.'),
|
|
39
|
+
})
|
|
40
|
+
.refine((d) => d.top !== undefined ||
|
|
41
|
+
d.bottom !== undefined ||
|
|
42
|
+
d.left !== undefined ||
|
|
43
|
+
d.right !== undefined ||
|
|
44
|
+
d.innerHorizontal !== undefined ||
|
|
45
|
+
d.innerVertical !== undefined, { message: 'At least one border side must be specified.' }),
|
|
46
|
+
execute: async (args, { log }) => {
|
|
47
|
+
const sheets = await getSheetsClient();
|
|
48
|
+
log.info(`Setting borders on range "${args.range}" in spreadsheet ${args.spreadsheetId}`);
|
|
49
|
+
try {
|
|
50
|
+
const { sheetName, a1Range } = SheetsHelpers.parseRange(args.range);
|
|
51
|
+
const sheetId = await SheetsHelpers.resolveSheetId(sheets, args.spreadsheetId, sheetName);
|
|
52
|
+
const gridRange = SheetsHelpers.parseA1ToGridRange(a1Range, sheetId);
|
|
53
|
+
const buildBorder = (b) => {
|
|
54
|
+
if (!b)
|
|
55
|
+
return undefined;
|
|
56
|
+
const border = { style: b.style };
|
|
57
|
+
if (b.color) {
|
|
58
|
+
const rgb = SheetsHelpers.hexToRgb(b.color);
|
|
59
|
+
if (!rgb)
|
|
60
|
+
throw new UserError(`Invalid border color: "${b.color}".`);
|
|
61
|
+
border.colorStyle = { rgbColor: rgb };
|
|
62
|
+
}
|
|
63
|
+
return border;
|
|
64
|
+
};
|
|
65
|
+
const borders = {};
|
|
66
|
+
if (args.top !== undefined)
|
|
67
|
+
borders.top = buildBorder(args.top);
|
|
68
|
+
if (args.bottom !== undefined)
|
|
69
|
+
borders.bottom = buildBorder(args.bottom);
|
|
70
|
+
if (args.left !== undefined)
|
|
71
|
+
borders.left = buildBorder(args.left);
|
|
72
|
+
if (args.right !== undefined)
|
|
73
|
+
borders.right = buildBorder(args.right);
|
|
74
|
+
if (args.innerHorizontal !== undefined)
|
|
75
|
+
borders.innerHorizontal = buildBorder(args.innerHorizontal);
|
|
76
|
+
if (args.innerVertical !== undefined)
|
|
77
|
+
borders.innerVertical = buildBorder(args.innerVertical);
|
|
78
|
+
await sheets.spreadsheets.batchUpdate({
|
|
79
|
+
spreadsheetId: args.spreadsheetId,
|
|
80
|
+
requestBody: {
|
|
81
|
+
requests: [{ updateBorders: { range: gridRange, ...borders } }],
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
return `Successfully set borders on range "${args.range}".`;
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
log.error(`Error setting borders: ${error.message || error}`);
|
|
88
|
+
if (error instanceof UserError)
|
|
89
|
+
throw error;
|
|
90
|
+
throw new UserError(`Failed to set borders: ${error.message || 'Unknown error'}`);
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { UserError } from 'fastmcp';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { getSheetsClient } from '../../clients.js';
|
|
4
|
+
import * as SheetsHelpers from '../../googleSheetsApiHelpers.js';
|
|
5
|
+
export function register(server) {
|
|
6
|
+
server.addTool({
|
|
7
|
+
name: 'setColumnWidths',
|
|
8
|
+
description: 'Sets the width (in pixels) of one or more columns in a spreadsheet. Accepts multiple column specs in a single call, each targeting a single column or a contiguous range (e.g., "A", "B:D").',
|
|
9
|
+
parameters: z.strictObject({
|
|
10
|
+
spreadsheetId: z
|
|
11
|
+
.string()
|
|
12
|
+
.describe('The spreadsheet ID — the long string between /d/ and /edit in a Google Sheets URL.'),
|
|
13
|
+
sheetName: z
|
|
14
|
+
.string()
|
|
15
|
+
.optional()
|
|
16
|
+
.describe('Name of the sheet/tab. Defaults to the first sheet if not provided.'),
|
|
17
|
+
columnWidths: z
|
|
18
|
+
.array(z.strictObject({
|
|
19
|
+
column: z
|
|
20
|
+
.string()
|
|
21
|
+
.describe('Column or column range in A1 notation (e.g., "A", "B:D").'),
|
|
22
|
+
width: z.number().int().min(0).describe('Width in pixels. Use 0 to hide the column.'),
|
|
23
|
+
}))
|
|
24
|
+
.min(1)
|
|
25
|
+
.describe('List of column width specifications to apply.'),
|
|
26
|
+
}),
|
|
27
|
+
execute: async (args, { log }) => {
|
|
28
|
+
const sheets = await getSheetsClient();
|
|
29
|
+
log.info(`Setting column widths in spreadsheet ${args.spreadsheetId}`);
|
|
30
|
+
try {
|
|
31
|
+
await SheetsHelpers.setColumnWidths(sheets, args.spreadsheetId, args.sheetName, args.columnWidths);
|
|
32
|
+
const summary = args.columnWidths.map((cw) => `${cw.column}=${cw.width}px`).join(', ');
|
|
33
|
+
return `Successfully set column widths: ${summary}.`;
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
log.error(`Error setting column widths: ${error.message || error}`);
|
|
37
|
+
if (error instanceof UserError)
|
|
38
|
+
throw error;
|
|
39
|
+
throw new UserError(`Failed to set column widths: ${error.message || 'Unknown error'}`);
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { UserError } from 'fastmcp';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { getSheetsClient } from '../../clients.js';
|
|
4
|
+
import * as SheetsHelpers from '../../googleSheetsApiHelpers.js';
|
|
5
|
+
export function register(server) {
|
|
6
|
+
server.addTool({
|
|
7
|
+
name: 'setDropdownValidation',
|
|
8
|
+
description: 'Adds or removes a dropdown list on a range of cells. Provide values to create a dropdown restricting input to those options. Omit values to remove dropdown validation from the range.',
|
|
9
|
+
parameters: z.strictObject({
|
|
10
|
+
spreadsheetId: z
|
|
11
|
+
.string()
|
|
12
|
+
.describe('The spreadsheet ID — the long string between /d/ and /edit in a Google Sheets URL.'),
|
|
13
|
+
range: z
|
|
14
|
+
.string()
|
|
15
|
+
.describe('A1 notation range to apply the dropdown to (e.g., "Sheet1!B2:B100" or "C2:C50").'),
|
|
16
|
+
values: z
|
|
17
|
+
.array(z.string())
|
|
18
|
+
.optional()
|
|
19
|
+
.describe('The allowed dropdown options (e.g., ["Open", "In Progress", "Done"]). Omit to remove existing dropdown validation from the range.'),
|
|
20
|
+
strict: z
|
|
21
|
+
.boolean()
|
|
22
|
+
.optional()
|
|
23
|
+
.default(true)
|
|
24
|
+
.describe('If true, reject input that does not match one of the dropdown values.'),
|
|
25
|
+
inputMessage: z
|
|
26
|
+
.string()
|
|
27
|
+
.optional()
|
|
28
|
+
.describe('Help text shown when a cell with the dropdown is selected.'),
|
|
29
|
+
}),
|
|
30
|
+
execute: async (args, { log }) => {
|
|
31
|
+
const sheets = await getSheetsClient();
|
|
32
|
+
const isClearing = !args.values || args.values.length === 0;
|
|
33
|
+
log.info(isClearing
|
|
34
|
+
? `Clearing dropdown validation on "${args.range}" in spreadsheet ${args.spreadsheetId}`
|
|
35
|
+
: `Setting dropdown validation on "${args.range}" with ${args.values.length} options in spreadsheet ${args.spreadsheetId}`);
|
|
36
|
+
try {
|
|
37
|
+
await SheetsHelpers.setDropdownValidation(sheets, args.spreadsheetId, args.range, args.values, args.strict, args.inputMessage);
|
|
38
|
+
if (isClearing) {
|
|
39
|
+
return `Successfully removed dropdown validation from range "${args.range}".`;
|
|
40
|
+
}
|
|
41
|
+
return `Successfully added dropdown validation to range "${args.range}" with ${args.values.length} options: ${args.values.join(', ')}.`;
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
log.error(`Error setting dropdown validation: ${error.message || error}`);
|
|
45
|
+
if (error instanceof UserError)
|
|
46
|
+
throw error;
|
|
47
|
+
throw new UserError(`Failed to set dropdown validation: ${error.message || 'Unknown error'}`);
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { UserError } from 'fastmcp';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { getSheetsClient } from '../../clients.js';
|
|
4
|
+
import * as SheetsHelpers from '../../googleSheetsApiHelpers.js';
|
|
5
|
+
export function register(server) {
|
|
6
|
+
server.addTool({
|
|
7
|
+
name: 'setRowHeights',
|
|
8
|
+
description: 'Sets a fixed pixel height for a range of rows in a spreadsheet. Useful for ensuring rows are tall enough to display wrapped text.',
|
|
9
|
+
parameters: z
|
|
10
|
+
.object({
|
|
11
|
+
spreadsheetId: z
|
|
12
|
+
.string()
|
|
13
|
+
.describe('The spreadsheet ID — the long string between /d/ and /edit in a Google Sheets URL.'),
|
|
14
|
+
sheetName: z
|
|
15
|
+
.string()
|
|
16
|
+
.optional()
|
|
17
|
+
.describe('Name of the sheet/tab. Defaults to the first sheet if not provided.'),
|
|
18
|
+
startRow: z.number().int().min(1).describe('1-based start row index (inclusive).'),
|
|
19
|
+
endRow: z.number().int().min(1).describe('1-based end row index (inclusive).'),
|
|
20
|
+
pixelSize: z
|
|
21
|
+
.number()
|
|
22
|
+
.int()
|
|
23
|
+
.min(2)
|
|
24
|
+
.describe('Height in pixels (e.g., 40 for comfortable single-line, 80 for two-line wrapped text).'),
|
|
25
|
+
})
|
|
26
|
+
.refine((d) => d.endRow >= d.startRow, {
|
|
27
|
+
message: 'endRow must be greater than or equal to startRow.',
|
|
28
|
+
}),
|
|
29
|
+
execute: async (args, { log }) => {
|
|
30
|
+
const sheets = await getSheetsClient();
|
|
31
|
+
log.info(`Setting row heights in spreadsheet ${args.spreadsheetId}`);
|
|
32
|
+
try {
|
|
33
|
+
const sheetId = await SheetsHelpers.resolveSheetId(sheets, args.spreadsheetId, args.sheetName);
|
|
34
|
+
await sheets.spreadsheets.batchUpdate({
|
|
35
|
+
spreadsheetId: args.spreadsheetId,
|
|
36
|
+
requestBody: {
|
|
37
|
+
requests: [
|
|
38
|
+
{
|
|
39
|
+
updateDimensionProperties: {
|
|
40
|
+
range: {
|
|
41
|
+
sheetId,
|
|
42
|
+
dimension: 'ROWS',
|
|
43
|
+
startIndex: args.startRow - 1,
|
|
44
|
+
endIndex: args.endRow,
|
|
45
|
+
},
|
|
46
|
+
properties: { pixelSize: args.pixelSize },
|
|
47
|
+
fields: 'pixelSize',
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
],
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
return `Successfully set rows ${args.startRow}–${args.endRow} to ${args.pixelSize}px height.`;
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
log.error(`Error setting row heights: ${error.message || error}`);
|
|
57
|
+
if (error instanceof UserError)
|
|
58
|
+
throw error;
|
|
59
|
+
throw new UserError(`Failed to set row heights: ${error.message || 'Unknown error'}`);
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { UserError } from 'fastmcp';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { getSheetsClient } from '../../clients.js';
|
|
4
|
+
import * as SheetsHelpers from '../../googleSheetsApiHelpers.js';
|
|
5
|
+
export function register(server) {
|
|
6
|
+
server.addTool({
|
|
7
|
+
name: 'ungroupAllRows',
|
|
8
|
+
description: 'Removes all row groupings from a sheet by deleting the entire row dimension group. Use before re-running groupRows to prevent duplicate collapse widgets from accumulating across report refreshes.',
|
|
9
|
+
parameters: z.strictObject({
|
|
10
|
+
spreadsheetId: z
|
|
11
|
+
.string()
|
|
12
|
+
.describe('The spreadsheet ID — the long string between /d/ and /edit in a Google Sheets URL.'),
|
|
13
|
+
sheetName: z
|
|
14
|
+
.string()
|
|
15
|
+
.optional()
|
|
16
|
+
.describe('Name of the sheet/tab. Defaults to the first sheet if not provided.'),
|
|
17
|
+
totalRows: z
|
|
18
|
+
.number()
|
|
19
|
+
.int()
|
|
20
|
+
.optional()
|
|
21
|
+
.default(500)
|
|
22
|
+
.describe('Number of rows to clear groups from (default: 500). Set higher if the sheet has more rows.'),
|
|
23
|
+
}),
|
|
24
|
+
execute: async (args, { log }) => {
|
|
25
|
+
const sheets = await getSheetsClient();
|
|
26
|
+
log.info(`Removing all row groups from spreadsheet ${args.spreadsheetId}`);
|
|
27
|
+
try {
|
|
28
|
+
const sheetId = await SheetsHelpers.resolveSheetId(sheets, args.spreadsheetId, args.sheetName);
|
|
29
|
+
const totalRows = args.totalRows ?? 500;
|
|
30
|
+
const request = {
|
|
31
|
+
deleteDimensionGroup: {
|
|
32
|
+
range: {
|
|
33
|
+
sheetId,
|
|
34
|
+
dimension: 'ROWS',
|
|
35
|
+
startIndex: 0,
|
|
36
|
+
endIndex: totalRows,
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
// deleteDimensionGroup removes one level at a time; call repeatedly until no groups remain
|
|
41
|
+
let removed = 0;
|
|
42
|
+
// eslint-disable-next-line no-constant-condition
|
|
43
|
+
while (true) {
|
|
44
|
+
try {
|
|
45
|
+
await sheets.spreadsheets.batchUpdate({
|
|
46
|
+
spreadsheetId: args.spreadsheetId,
|
|
47
|
+
requestBody: { requests: [request] },
|
|
48
|
+
});
|
|
49
|
+
removed++;
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
// When no groups remain the API returns an error — treat as done
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return `Successfully removed all row groups (${removed} level(s) cleared).`;
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
log.error(`Error removing row groups: ${error.message || error}`);
|
|
60
|
+
if (error instanceof UserError)
|
|
61
|
+
throw error;
|
|
62
|
+
throw new UserError(`Failed to remove row groups: ${error.message || 'Unknown error'}`);
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
}
|