gogcli-mcp-docs 2.3.0 → 2.4.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.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/dist/index.js +142 -3
- package/manifest.json +21 -1
- package/package.json +1 -1
- package/server.json +2 -2
- package/src/tools/docs-extra.ts +146 -2
- package/tests/tools/docs-extra.test.ts +184 -0
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
},
|
|
8
8
|
"metadata": {
|
|
9
9
|
"description": "Extended Google Docs for Claude via gogcli — auth + full Docs and comments support",
|
|
10
|
-
"version": "2.
|
|
10
|
+
"version": "2.4.0"
|
|
11
11
|
},
|
|
12
12
|
"plugins": [
|
|
13
13
|
{
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"displayName": "gogcli (Docs)",
|
|
16
16
|
"source": "./",
|
|
17
17
|
"description": "Extended Google Docs for Claude via gogcli — auth + full Docs and comments support",
|
|
18
|
-
"version": "2.
|
|
18
|
+
"version": "2.4.0",
|
|
19
19
|
"author": {
|
|
20
20
|
"name": "Chris Hall"
|
|
21
21
|
},
|
package/dist/index.js
CHANGED
|
@@ -31313,7 +31313,7 @@ var failIfNotEmptyParam = external_exports.boolean().optional().describe(
|
|
|
31313
31313
|
);
|
|
31314
31314
|
|
|
31315
31315
|
// ../gogcli-mcp/src/server.ts
|
|
31316
|
-
var VERSION = true ? "2.
|
|
31316
|
+
var VERSION = true ? "2.4.0" : "0.0.0";
|
|
31317
31317
|
function createServer(options) {
|
|
31318
31318
|
return new McpServer({
|
|
31319
31319
|
name: options?.name ?? "gogcli",
|
|
@@ -31543,15 +31543,19 @@ function registerExtraDocsTools(server2) {
|
|
|
31543
31543
|
text: external_exports.string().optional().describe("Text content to write"),
|
|
31544
31544
|
file: external_exports.string().optional().describe("Path to a file whose content to use"),
|
|
31545
31545
|
index: external_exports.number().optional().describe("Character index to insert at"),
|
|
31546
|
+
replaceRange: external_exports.string().optional().describe('Replace a UTF-16 Docs API range START:END (e.g. "25:40") instead of inserting at index. The replacement text/file content overwrites the range.'),
|
|
31547
|
+
markdown: external_exports.boolean().optional().describe("Convert markdown in the text/file to Google Docs formatting (headings, bold, lists, etc.) instead of inserting it literally."),
|
|
31546
31548
|
tabId: external_exports.string().optional().describe("Tab ID for multi-tab docs"),
|
|
31547
31549
|
pageless: external_exports.boolean().optional().describe("Set document to pageless format"),
|
|
31548
31550
|
account: accountParam
|
|
31549
31551
|
}
|
|
31550
|
-
}, async ({ docId, text, file: file2, index, tabId, pageless, account }) => {
|
|
31552
|
+
}, async ({ docId, text, file: file2, index, replaceRange, markdown, tabId, pageless, account }) => {
|
|
31551
31553
|
const args = ["docs", "update", docId];
|
|
31552
31554
|
if (text) args.push(`--text=${text}`);
|
|
31553
31555
|
if (file2) args.push(`--file=${file2}`);
|
|
31554
31556
|
if (index !== void 0) args.push(`--index=${index}`);
|
|
31557
|
+
if (replaceRange) args.push(`--replace-range=${replaceRange}`);
|
|
31558
|
+
if (markdown) args.push("--markdown");
|
|
31555
31559
|
if (tabId) args.push(`--tab-id=${tabId}`);
|
|
31556
31560
|
if (pageless) args.push("--pageless");
|
|
31557
31561
|
return runOrDiagnose(args, { account });
|
|
@@ -31663,11 +31667,25 @@ function registerExtraDocsTools(server2) {
|
|
|
31663
31667
|
inputSchema: {
|
|
31664
31668
|
docId: external_exports.string().describe("Doc ID (from the URL)"),
|
|
31665
31669
|
layout: external_exports.enum(["pageless", "pages"]).optional().describe("Page layout (default: pageless)"),
|
|
31670
|
+
pageSize: external_exports.enum(["A4", "A5", "Letter", "Legal", "Tabloid"]).optional().describe("Named page size preset (only applies in paged layout)"),
|
|
31671
|
+
pageWidth: external_exports.string().optional().describe('Page width (points by default; supports pt, in, cm, mm \u2014 e.g. "8.5in")'),
|
|
31672
|
+
pageHeight: external_exports.string().optional().describe("Page height (points by default; supports pt, in, cm, mm)"),
|
|
31673
|
+
marginTop: external_exports.string().optional().describe("Top page margin (points by default; supports pt, in, cm, mm)"),
|
|
31674
|
+
marginBottom: external_exports.string().optional().describe("Bottom page margin (points by default; supports pt, in, cm, mm)"),
|
|
31675
|
+
marginLeft: external_exports.string().optional().describe("Left page margin (points by default; supports pt, in, cm, mm)"),
|
|
31676
|
+
marginRight: external_exports.string().optional().describe("Right page margin (points by default; supports pt, in, cm, mm)"),
|
|
31666
31677
|
account: accountParam
|
|
31667
31678
|
}
|
|
31668
|
-
}, async ({ docId, layout, account }) => {
|
|
31679
|
+
}, async ({ docId, layout, pageSize, pageWidth, pageHeight, marginTop, marginBottom, marginLeft, marginRight, account }) => {
|
|
31669
31680
|
const args = ["docs", "page-layout", docId];
|
|
31670
31681
|
if (layout) args.push(`--layout=${layout}`);
|
|
31682
|
+
if (pageSize) args.push(`--page-size=${pageSize}`);
|
|
31683
|
+
if (pageWidth) args.push(`--page-width=${pageWidth}`);
|
|
31684
|
+
if (pageHeight) args.push(`--page-height=${pageHeight}`);
|
|
31685
|
+
if (marginTop) args.push(`--margin-top=${marginTop}`);
|
|
31686
|
+
if (marginBottom) args.push(`--margin-bottom=${marginBottom}`);
|
|
31687
|
+
if (marginLeft) args.push(`--margin-left=${marginLeft}`);
|
|
31688
|
+
if (marginRight) args.push(`--margin-right=${marginRight}`);
|
|
31671
31689
|
return runOrDiagnose(args, { account });
|
|
31672
31690
|
});
|
|
31673
31691
|
server2.registerTool("gog_docs_insert_table", {
|
|
@@ -31691,6 +31709,127 @@ function registerExtraDocsTools(server2) {
|
|
|
31691
31709
|
if (tab) args.push(`--tab=${tab}`);
|
|
31692
31710
|
return runOrDiagnose(args, { account });
|
|
31693
31711
|
});
|
|
31712
|
+
server2.registerTool("gog_docs_cell_update", {
|
|
31713
|
+
description: "Replace (or append to) the content of a single table cell, addressed by table / row / column \u2014 non-destructive to the rest of the table, unlike index-based edits that shift on every change. Provide content inline or read it from contentFile. Note: row/col/tableIndex are 1-based here; the sibling gog_docs_cell_style uses 0-based addressing.",
|
|
31714
|
+
annotations: { destructiveHint: true },
|
|
31715
|
+
inputSchema: {
|
|
31716
|
+
docId: external_exports.string().describe("Doc ID (from the URL)"),
|
|
31717
|
+
row: external_exports.number().int().describe("1-based row number"),
|
|
31718
|
+
col: external_exports.number().int().describe("1-based column number"),
|
|
31719
|
+
content: external_exports.string().optional().describe("Replacement content (omit when using contentFile)"),
|
|
31720
|
+
contentFile: external_exports.string().optional().describe("Read replacement content from a file instead of content"),
|
|
31721
|
+
append: external_exports.boolean().optional().describe("Append inside the cell instead of replacing existing cell content"),
|
|
31722
|
+
format: external_exports.enum(["markdown", "plain"]).optional().describe("Content format (default: markdown)"),
|
|
31723
|
+
tableIndex: external_exports.number().int().optional().describe("1-based table index in document order; negative counts from the end (default: 1)"),
|
|
31724
|
+
tab: external_exports.string().optional().describe("Target a specific tab by title or ID"),
|
|
31725
|
+
account: accountParam
|
|
31726
|
+
}
|
|
31727
|
+
}, async ({ docId, row, col, content, contentFile, append, format, tableIndex, tab, account }) => {
|
|
31728
|
+
const args = ["docs", "cell-update", docId, `--row=${row}`, `--col=${col}`];
|
|
31729
|
+
if (content !== void 0) args.push(`--content=${content}`);
|
|
31730
|
+
if (contentFile) args.push(`--content-file=${contentFile}`);
|
|
31731
|
+
if (append) args.push("--append");
|
|
31732
|
+
if (format) args.push(`--format=${format}`);
|
|
31733
|
+
if (tableIndex !== void 0) args.push(`--table-index=${tableIndex}`);
|
|
31734
|
+
if (tab) args.push(`--tab=${tab}`);
|
|
31735
|
+
return runOrDiagnose(args, { account });
|
|
31736
|
+
});
|
|
31737
|
+
server2.registerTool("gog_docs_cell_style", {
|
|
31738
|
+
description: "Apply background color and/or inline text styling (bold, italic, underline, colors) to one or more table cells, addressed by 0-based row/column with optional spans. Sibling to gog_docs_cell_update, which changes cell content rather than styling. Note: row/col/tableIndex are 0-based here; gog_docs_cell_update uses 1-based addressing.",
|
|
31739
|
+
annotations: { destructiveHint: true },
|
|
31740
|
+
inputSchema: {
|
|
31741
|
+
docId: external_exports.string().describe("Doc ID (from the URL)"),
|
|
31742
|
+
row: external_exports.number().int().describe("0-based row number"),
|
|
31743
|
+
col: external_exports.number().int().describe("0-based column number"),
|
|
31744
|
+
rowSpan: external_exports.number().int().optional().describe("Number of rows to style (default: 1)"),
|
|
31745
|
+
colSpan: external_exports.number().int().optional().describe("Number of columns to style (default: 1)"),
|
|
31746
|
+
backgroundColor: external_exports.string().optional().describe("Cell background color as #RRGGBB or #RGB"),
|
|
31747
|
+
textColor: external_exports.string().optional().describe("Text color as #RRGGBB or #RGB"),
|
|
31748
|
+
bold: external_exports.boolean().optional().describe("Set cell text bold"),
|
|
31749
|
+
italic: external_exports.boolean().optional().describe("Set cell text italic"),
|
|
31750
|
+
underline: external_exports.boolean().optional().describe("Set cell text underline"),
|
|
31751
|
+
tableIndex: external_exports.number().int().optional().describe("0-based table index in document order (default: 0)"),
|
|
31752
|
+
tab: external_exports.string().optional().describe("Target a specific tab by title or ID"),
|
|
31753
|
+
account: accountParam
|
|
31754
|
+
}
|
|
31755
|
+
}, async ({ docId, row, col, rowSpan, colSpan, backgroundColor, textColor, bold, italic, underline, tableIndex, tab, account }) => {
|
|
31756
|
+
const args = ["docs", "cell-style", docId, `--row=${row}`, `--col=${col}`];
|
|
31757
|
+
if (rowSpan !== void 0) args.push(`--row-span=${rowSpan}`);
|
|
31758
|
+
if (colSpan !== void 0) args.push(`--col-span=${colSpan}`);
|
|
31759
|
+
if (backgroundColor) args.push(`--background-color=${backgroundColor}`);
|
|
31760
|
+
if (textColor) args.push(`--text-color=${textColor}`);
|
|
31761
|
+
if (bold) args.push("--bold");
|
|
31762
|
+
if (italic) args.push("--italic");
|
|
31763
|
+
if (underline) args.push("--underline");
|
|
31764
|
+
if (tableIndex !== void 0) args.push(`--table-index=${tableIndex}`);
|
|
31765
|
+
if (tab) args.push(`--tab=${tab}`);
|
|
31766
|
+
return runOrDiagnose(args, { account });
|
|
31767
|
+
});
|
|
31768
|
+
server2.registerTool("gog_docs_insert_image", {
|
|
31769
|
+
description: "Upload a local image (PNG, JPEG, or GIF) and insert it into a Google Doc. The image is uploaded to Drive, temporarily shared so Docs can fetch it, inserted, then the public permission is revoked. Replaces placeholder text (at) or appends at end-of-doc.",
|
|
31770
|
+
annotations: { destructiveHint: true },
|
|
31771
|
+
inputSchema: {
|
|
31772
|
+
docId: external_exports.string().describe("Doc ID (from the URL)"),
|
|
31773
|
+
file: external_exports.string().describe("Local PNG, JPEG, or GIF image to upload and insert"),
|
|
31774
|
+
at: external_exports.string().optional().describe('Placeholder text to replace, or "end" to append (default: end)'),
|
|
31775
|
+
width: external_exports.number().optional().describe("Image width in points (default: 468)"),
|
|
31776
|
+
height: external_exports.number().optional().describe("Image height in points (optional; width-only preserves aspect ratio)"),
|
|
31777
|
+
name: external_exports.string().optional().describe("Override the uploaded Drive filename"),
|
|
31778
|
+
parent: external_exports.string().optional().describe("Drive folder ID for the uploaded image"),
|
|
31779
|
+
onRestricted: external_exports.enum(["error", "link"]).optional().describe("If public sharing is blocked: error out, or fall back to a linked image (default: error)"),
|
|
31780
|
+
tab: external_exports.string().optional().describe("Target a specific tab by title or ID"),
|
|
31781
|
+
account: accountParam
|
|
31782
|
+
}
|
|
31783
|
+
}, async ({ docId, file: file2, at, width, height, name, parent, onRestricted, tab, account }) => {
|
|
31784
|
+
const args = ["docs", "insert-image", docId, `--file=${file2}`];
|
|
31785
|
+
if (at) args.push(`--at=${at}`);
|
|
31786
|
+
if (width !== void 0) args.push(`--width=${width}`);
|
|
31787
|
+
if (height !== void 0) args.push(`--height=${height}`);
|
|
31788
|
+
if (name) args.push(`--name=${name}`);
|
|
31789
|
+
if (parent) args.push(`--parent=${parent}`);
|
|
31790
|
+
if (onRestricted) args.push(`--on-restricted=${onRestricted}`);
|
|
31791
|
+
if (tab) args.push(`--tab=${tab}`);
|
|
31792
|
+
return runOrDiagnose(args, { account });
|
|
31793
|
+
});
|
|
31794
|
+
server2.registerTool("gog_docs_insert_person", {
|
|
31795
|
+
description: "Insert a native Google Docs person smart chip (the interactive @-mention chip) for an email address, at a character index or end-of-doc.",
|
|
31796
|
+
annotations: { destructiveHint: true },
|
|
31797
|
+
inputSchema: {
|
|
31798
|
+
docId: external_exports.string().describe("Doc ID (from the URL)"),
|
|
31799
|
+
email: external_exports.string().describe("Email address for the person chip"),
|
|
31800
|
+
index: external_exports.number().int().optional().describe("Character index to insert at. Omit or use atEnd for end-of-doc."),
|
|
31801
|
+
atEnd: external_exports.boolean().optional().describe("Insert at end-of-doc/tab (mutually exclusive with index)"),
|
|
31802
|
+
tab: external_exports.string().optional().describe("Target a specific tab by title or ID"),
|
|
31803
|
+
account: accountParam
|
|
31804
|
+
}
|
|
31805
|
+
}, async ({ docId, email: email3, index, atEnd, tab, account }) => {
|
|
31806
|
+
const args = ["docs", "insert-person", docId, `--email=${email3}`];
|
|
31807
|
+
if (index !== void 0) args.push(`--index=${index}`);
|
|
31808
|
+
if (atEnd) args.push("--at-end");
|
|
31809
|
+
if (tab) args.push(`--tab=${tab}`);
|
|
31810
|
+
return runOrDiagnose(args, { account });
|
|
31811
|
+
});
|
|
31812
|
+
server2.registerTool("gog_docs_insert_date_chip", {
|
|
31813
|
+
description: "Insert a native Google Docs date smart chip, at a character index or end-of-doc.",
|
|
31814
|
+
annotations: { destructiveHint: true },
|
|
31815
|
+
inputSchema: {
|
|
31816
|
+
docId: external_exports.string().describe("Doc ID (from the URL)"),
|
|
31817
|
+
date: external_exports.string().optional().describe("Date to insert as YYYY-MM-DD (default: today)"),
|
|
31818
|
+
format: external_exports.enum(["abbreviated", "full", "iso"]).optional().describe("Date display format (default: abbreviated)"),
|
|
31819
|
+
index: external_exports.number().int().optional().describe("Character index to insert at. Omit or use atEnd for end-of-doc."),
|
|
31820
|
+
atEnd: external_exports.boolean().optional().describe("Insert at end-of-doc/tab (mutually exclusive with index)"),
|
|
31821
|
+
tab: external_exports.string().optional().describe("Target a specific tab by title or ID"),
|
|
31822
|
+
account: accountParam
|
|
31823
|
+
}
|
|
31824
|
+
}, async ({ docId, date: date5, format, index, atEnd, tab, account }) => {
|
|
31825
|
+
const args = ["docs", "insert-date-chip", docId];
|
|
31826
|
+
if (date5) args.push(`--date=${date5}`);
|
|
31827
|
+
if (format) args.push(`--format=${format}`);
|
|
31828
|
+
if (index !== void 0) args.push(`--index=${index}`);
|
|
31829
|
+
if (atEnd) args.push("--at-end");
|
|
31830
|
+
if (tab) args.push(`--tab=${tab}`);
|
|
31831
|
+
return runOrDiagnose(args, { account });
|
|
31832
|
+
});
|
|
31694
31833
|
server2.registerTool("gog_docs_add_tab", {
|
|
31695
31834
|
description: "Add a tab to a Google Doc. Tabs partition a doc into independently-addressable sections (multi-tab docs). Optionally set the title, zero-based position, parent tab (for nesting), and an emoji icon.",
|
|
31696
31835
|
inputSchema: {
|
package/manifest.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"manifest_version": "0.3",
|
|
4
4
|
"name": "gogcli-mcp-docs",
|
|
5
5
|
"display_name": "gogcli (Docs)",
|
|
6
|
-
"version": "2.
|
|
6
|
+
"version": "2.4.0",
|
|
7
7
|
"description": "Extended Google Docs for Claude via gogcli — auth + full Docs and comments support",
|
|
8
8
|
"author": {
|
|
9
9
|
"name": "Chris Hall",
|
|
@@ -206,6 +206,26 @@
|
|
|
206
206
|
{
|
|
207
207
|
"name": "gog_docs_insert_table",
|
|
208
208
|
"description": "Insert a native Google Docs table with given dimensions, optionally populated from a JSON 2D array."
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
"name": "gog_docs_cell_update",
|
|
212
|
+
"description": "Replace or append the content of a single table cell, addressed by table/row/column."
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
"name": "gog_docs_cell_style",
|
|
216
|
+
"description": "Apply background color and inline text styling to one or more table cells."
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
"name": "gog_docs_insert_image",
|
|
220
|
+
"description": "Upload a local image and insert it into a Google Doc."
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
"name": "gog_docs_insert_person",
|
|
224
|
+
"description": "Insert a native Google Docs person smart chip for an email address."
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
"name": "gog_docs_insert_date_chip",
|
|
228
|
+
"description": "Insert a native Google Docs date smart chip."
|
|
209
229
|
}
|
|
210
230
|
],
|
|
211
231
|
"compatibility": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gogcli-mcp-docs",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.0",
|
|
4
4
|
"mcpName": "io.github.chrischall/gogcli-mcp-docs",
|
|
5
5
|
"description": "Extended Google Docs MCP server via gogcli — all base tools plus full Docs 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-docs"
|
|
9
9
|
},
|
|
10
|
-
"version": "2.
|
|
10
|
+
"version": "2.4.0",
|
|
11
11
|
"packages": [
|
|
12
12
|
{
|
|
13
13
|
"registryType": "npm",
|
|
14
14
|
"identifier": "gogcli-mcp-docs",
|
|
15
|
-
"version": "2.
|
|
15
|
+
"version": "2.4.0",
|
|
16
16
|
"transport": {
|
|
17
17
|
"type": "stdio"
|
|
18
18
|
},
|
package/src/tools/docs-extra.ts
CHANGED
|
@@ -253,15 +253,19 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
253
253
|
text: z.string().optional().describe('Text content to write'),
|
|
254
254
|
file: z.string().optional().describe('Path to a file whose content to use'),
|
|
255
255
|
index: z.number().optional().describe('Character index to insert at'),
|
|
256
|
+
replaceRange: z.string().optional().describe('Replace a UTF-16 Docs API range START:END (e.g. "25:40") instead of inserting at index. The replacement text/file content overwrites the range.'),
|
|
257
|
+
markdown: z.boolean().optional().describe('Convert markdown in the text/file to Google Docs formatting (headings, bold, lists, etc.) instead of inserting it literally.'),
|
|
256
258
|
tabId: z.string().optional().describe('Tab ID for multi-tab docs'),
|
|
257
259
|
pageless: z.boolean().optional().describe('Set document to pageless format'),
|
|
258
260
|
account: accountParam,
|
|
259
261
|
},
|
|
260
|
-
}, async ({ docId, text, file, index, tabId, pageless, account }) => {
|
|
262
|
+
}, async ({ docId, text, file, index, replaceRange, markdown, tabId, pageless, account }) => {
|
|
261
263
|
const args = ['docs', 'update', docId];
|
|
262
264
|
if (text) args.push(`--text=${text}`);
|
|
263
265
|
if (file) args.push(`--file=${file}`);
|
|
264
266
|
if (index !== undefined) args.push(`--index=${index}`);
|
|
267
|
+
if (replaceRange) args.push(`--replace-range=${replaceRange}`);
|
|
268
|
+
if (markdown) args.push('--markdown');
|
|
265
269
|
if (tabId) args.push(`--tab-id=${tabId}`);
|
|
266
270
|
if (pageless) args.push('--pageless');
|
|
267
271
|
return runOrDiagnose(args, { account });
|
|
@@ -385,11 +389,25 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
385
389
|
inputSchema: {
|
|
386
390
|
docId: z.string().describe('Doc ID (from the URL)'),
|
|
387
391
|
layout: z.enum(['pageless', 'pages']).optional().describe('Page layout (default: pageless)'),
|
|
392
|
+
pageSize: z.enum(['A4', 'A5', 'Letter', 'Legal', 'Tabloid']).optional().describe('Named page size preset (only applies in paged layout)'),
|
|
393
|
+
pageWidth: z.string().optional().describe('Page width (points by default; supports pt, in, cm, mm — e.g. "8.5in")'),
|
|
394
|
+
pageHeight: z.string().optional().describe('Page height (points by default; supports pt, in, cm, mm)'),
|
|
395
|
+
marginTop: z.string().optional().describe('Top page margin (points by default; supports pt, in, cm, mm)'),
|
|
396
|
+
marginBottom: z.string().optional().describe('Bottom page margin (points by default; supports pt, in, cm, mm)'),
|
|
397
|
+
marginLeft: z.string().optional().describe('Left page margin (points by default; supports pt, in, cm, mm)'),
|
|
398
|
+
marginRight: z.string().optional().describe('Right page margin (points by default; supports pt, in, cm, mm)'),
|
|
388
399
|
account: accountParam,
|
|
389
400
|
},
|
|
390
|
-
}, async ({ docId, layout, account }) => {
|
|
401
|
+
}, async ({ docId, layout, pageSize, pageWidth, pageHeight, marginTop, marginBottom, marginLeft, marginRight, account }) => {
|
|
391
402
|
const args = ['docs', 'page-layout', docId];
|
|
392
403
|
if (layout) args.push(`--layout=${layout}`);
|
|
404
|
+
if (pageSize) args.push(`--page-size=${pageSize}`);
|
|
405
|
+
if (pageWidth) args.push(`--page-width=${pageWidth}`);
|
|
406
|
+
if (pageHeight) args.push(`--page-height=${pageHeight}`);
|
|
407
|
+
if (marginTop) args.push(`--margin-top=${marginTop}`);
|
|
408
|
+
if (marginBottom) args.push(`--margin-bottom=${marginBottom}`);
|
|
409
|
+
if (marginLeft) args.push(`--margin-left=${marginLeft}`);
|
|
410
|
+
if (marginRight) args.push(`--margin-right=${marginRight}`);
|
|
393
411
|
return runOrDiagnose(args, { account });
|
|
394
412
|
});
|
|
395
413
|
|
|
@@ -415,6 +433,132 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
415
433
|
return runOrDiagnose(args, { account });
|
|
416
434
|
});
|
|
417
435
|
|
|
436
|
+
server.registerTool('gog_docs_cell_update', {
|
|
437
|
+
description: 'Replace (or append to) the content of a single table cell, addressed by table / row / column — non-destructive to the rest of the table, unlike index-based edits that shift on every change. Provide content inline or read it from contentFile. Note: row/col/tableIndex are 1-based here; the sibling gog_docs_cell_style uses 0-based addressing.',
|
|
438
|
+
annotations: { destructiveHint: true },
|
|
439
|
+
inputSchema: {
|
|
440
|
+
docId: z.string().describe('Doc ID (from the URL)'),
|
|
441
|
+
row: z.number().int().describe('1-based row number'),
|
|
442
|
+
col: z.number().int().describe('1-based column number'),
|
|
443
|
+
content: z.string().optional().describe('Replacement content (omit when using contentFile)'),
|
|
444
|
+
contentFile: z.string().optional().describe('Read replacement content from a file instead of content'),
|
|
445
|
+
append: z.boolean().optional().describe('Append inside the cell instead of replacing existing cell content'),
|
|
446
|
+
format: z.enum(['markdown', 'plain']).optional().describe('Content format (default: markdown)'),
|
|
447
|
+
tableIndex: z.number().int().optional().describe('1-based table index in document order; negative counts from the end (default: 1)'),
|
|
448
|
+
tab: z.string().optional().describe('Target a specific tab by title or ID'),
|
|
449
|
+
account: accountParam,
|
|
450
|
+
},
|
|
451
|
+
}, async ({ docId, row, col, content, contentFile, append, format, tableIndex, tab, account }) => {
|
|
452
|
+
const args = ['docs', 'cell-update', docId, `--row=${row}`, `--col=${col}`];
|
|
453
|
+
if (content !== undefined) args.push(`--content=${content}`);
|
|
454
|
+
if (contentFile) args.push(`--content-file=${contentFile}`);
|
|
455
|
+
if (append) args.push('--append');
|
|
456
|
+
if (format) args.push(`--format=${format}`);
|
|
457
|
+
if (tableIndex !== undefined) args.push(`--table-index=${tableIndex}`);
|
|
458
|
+
if (tab) args.push(`--tab=${tab}`);
|
|
459
|
+
return runOrDiagnose(args, { account });
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
server.registerTool('gog_docs_cell_style', {
|
|
463
|
+
description: 'Apply background color and/or inline text styling (bold, italic, underline, colors) to one or more table cells, addressed by 0-based row/column with optional spans. Sibling to gog_docs_cell_update, which changes cell content rather than styling. Note: row/col/tableIndex are 0-based here; gog_docs_cell_update uses 1-based addressing.',
|
|
464
|
+
annotations: { destructiveHint: true },
|
|
465
|
+
inputSchema: {
|
|
466
|
+
docId: z.string().describe('Doc ID (from the URL)'),
|
|
467
|
+
row: z.number().int().describe('0-based row number'),
|
|
468
|
+
col: z.number().int().describe('0-based column number'),
|
|
469
|
+
rowSpan: z.number().int().optional().describe('Number of rows to style (default: 1)'),
|
|
470
|
+
colSpan: z.number().int().optional().describe('Number of columns to style (default: 1)'),
|
|
471
|
+
backgroundColor: z.string().optional().describe('Cell background color as #RRGGBB or #RGB'),
|
|
472
|
+
textColor: z.string().optional().describe('Text color as #RRGGBB or #RGB'),
|
|
473
|
+
bold: z.boolean().optional().describe('Set cell text bold'),
|
|
474
|
+
italic: z.boolean().optional().describe('Set cell text italic'),
|
|
475
|
+
underline: z.boolean().optional().describe('Set cell text underline'),
|
|
476
|
+
tableIndex: z.number().int().optional().describe('0-based table index in document order (default: 0)'),
|
|
477
|
+
tab: z.string().optional().describe('Target a specific tab by title or ID'),
|
|
478
|
+
account: accountParam,
|
|
479
|
+
},
|
|
480
|
+
}, async ({ docId, row, col, rowSpan, colSpan, backgroundColor, textColor, bold, italic, underline, tableIndex, tab, account }) => {
|
|
481
|
+
const args = ['docs', 'cell-style', docId, `--row=${row}`, `--col=${col}`];
|
|
482
|
+
if (rowSpan !== undefined) args.push(`--row-span=${rowSpan}`);
|
|
483
|
+
if (colSpan !== undefined) args.push(`--col-span=${colSpan}`);
|
|
484
|
+
if (backgroundColor) args.push(`--background-color=${backgroundColor}`);
|
|
485
|
+
if (textColor) args.push(`--text-color=${textColor}`);
|
|
486
|
+
if (bold) args.push('--bold');
|
|
487
|
+
if (italic) args.push('--italic');
|
|
488
|
+
if (underline) args.push('--underline');
|
|
489
|
+
if (tableIndex !== undefined) args.push(`--table-index=${tableIndex}`);
|
|
490
|
+
if (tab) args.push(`--tab=${tab}`);
|
|
491
|
+
return runOrDiagnose(args, { account });
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
server.registerTool('gog_docs_insert_image', {
|
|
495
|
+
description: 'Upload a local image (PNG, JPEG, or GIF) and insert it into a Google Doc. The image is uploaded to Drive, temporarily shared so Docs can fetch it, inserted, then the public permission is revoked. Replaces placeholder text (at) or appends at end-of-doc.',
|
|
496
|
+
annotations: { destructiveHint: true },
|
|
497
|
+
inputSchema: {
|
|
498
|
+
docId: z.string().describe('Doc ID (from the URL)'),
|
|
499
|
+
file: z.string().describe('Local PNG, JPEG, or GIF image to upload and insert'),
|
|
500
|
+
at: z.string().optional().describe('Placeholder text to replace, or "end" to append (default: end)'),
|
|
501
|
+
width: z.number().optional().describe('Image width in points (default: 468)'),
|
|
502
|
+
height: z.number().optional().describe('Image height in points (optional; width-only preserves aspect ratio)'),
|
|
503
|
+
name: z.string().optional().describe('Override the uploaded Drive filename'),
|
|
504
|
+
parent: z.string().optional().describe('Drive folder ID for the uploaded image'),
|
|
505
|
+
onRestricted: z.enum(['error', 'link']).optional().describe('If public sharing is blocked: error out, or fall back to a linked image (default: error)'),
|
|
506
|
+
tab: z.string().optional().describe('Target a specific tab by title or ID'),
|
|
507
|
+
account: accountParam,
|
|
508
|
+
},
|
|
509
|
+
}, async ({ docId, file, at, width, height, name, parent, onRestricted, tab, account }) => {
|
|
510
|
+
const args = ['docs', 'insert-image', docId, `--file=${file}`];
|
|
511
|
+
if (at) args.push(`--at=${at}`);
|
|
512
|
+
if (width !== undefined) args.push(`--width=${width}`);
|
|
513
|
+
if (height !== undefined) args.push(`--height=${height}`);
|
|
514
|
+
if (name) args.push(`--name=${name}`);
|
|
515
|
+
if (parent) args.push(`--parent=${parent}`);
|
|
516
|
+
if (onRestricted) args.push(`--on-restricted=${onRestricted}`);
|
|
517
|
+
if (tab) args.push(`--tab=${tab}`);
|
|
518
|
+
return runOrDiagnose(args, { account });
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
server.registerTool('gog_docs_insert_person', {
|
|
522
|
+
description: 'Insert a native Google Docs person smart chip (the interactive @-mention chip) for an email address, at a character index or end-of-doc.',
|
|
523
|
+
annotations: { destructiveHint: true },
|
|
524
|
+
inputSchema: {
|
|
525
|
+
docId: z.string().describe('Doc ID (from the URL)'),
|
|
526
|
+
email: z.string().describe('Email address for the person chip'),
|
|
527
|
+
index: z.number().int().optional().describe('Character index to insert at. Omit or use atEnd for end-of-doc.'),
|
|
528
|
+
atEnd: z.boolean().optional().describe('Insert at end-of-doc/tab (mutually exclusive with index)'),
|
|
529
|
+
tab: z.string().optional().describe('Target a specific tab by title or ID'),
|
|
530
|
+
account: accountParam,
|
|
531
|
+
},
|
|
532
|
+
}, async ({ docId, email, index, atEnd, tab, account }) => {
|
|
533
|
+
const args = ['docs', 'insert-person', docId, `--email=${email}`];
|
|
534
|
+
if (index !== undefined) args.push(`--index=${index}`);
|
|
535
|
+
if (atEnd) args.push('--at-end');
|
|
536
|
+
if (tab) args.push(`--tab=${tab}`);
|
|
537
|
+
return runOrDiagnose(args, { account });
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
server.registerTool('gog_docs_insert_date_chip', {
|
|
541
|
+
description: 'Insert a native Google Docs date smart chip, at a character index or end-of-doc.',
|
|
542
|
+
annotations: { destructiveHint: true },
|
|
543
|
+
inputSchema: {
|
|
544
|
+
docId: z.string().describe('Doc ID (from the URL)'),
|
|
545
|
+
date: z.string().optional().describe('Date to insert as YYYY-MM-DD (default: today)'),
|
|
546
|
+
format: z.enum(['abbreviated', 'full', 'iso']).optional().describe('Date display format (default: abbreviated)'),
|
|
547
|
+
index: z.number().int().optional().describe('Character index to insert at. Omit or use atEnd for end-of-doc.'),
|
|
548
|
+
atEnd: z.boolean().optional().describe('Insert at end-of-doc/tab (mutually exclusive with index)'),
|
|
549
|
+
tab: z.string().optional().describe('Target a specific tab by title or ID'),
|
|
550
|
+
account: accountParam,
|
|
551
|
+
},
|
|
552
|
+
}, async ({ docId, date, format, index, atEnd, tab, account }) => {
|
|
553
|
+
const args = ['docs', 'insert-date-chip', docId];
|
|
554
|
+
if (date) args.push(`--date=${date}`);
|
|
555
|
+
if (format) args.push(`--format=${format}`);
|
|
556
|
+
if (index !== undefined) args.push(`--index=${index}`);
|
|
557
|
+
if (atEnd) args.push('--at-end');
|
|
558
|
+
if (tab) args.push(`--tab=${tab}`);
|
|
559
|
+
return runOrDiagnose(args, { account });
|
|
560
|
+
});
|
|
561
|
+
|
|
418
562
|
server.registerTool('gog_docs_add_tab', {
|
|
419
563
|
description: 'Add a tab to a Google Doc. Tabs partition a doc into independently-addressable sections (multi-tab docs). Optionally set the title, zero-based position, parent tab (for nesting), and an emoji icon.',
|
|
420
564
|
inputSchema: {
|
|
@@ -378,6 +378,36 @@ describe('gog_docs_update', () => {
|
|
|
378
378
|
await handlers.get('gog_docs_update')!({ docId: 'abc' });
|
|
379
379
|
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['docs', 'update', 'abc'], { account: undefined });
|
|
380
380
|
});
|
|
381
|
+
|
|
382
|
+
it('includes --replace-range when provided', async () => {
|
|
383
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
384
|
+
const handlers = setupHandlers();
|
|
385
|
+
await handlers.get('gog_docs_update')!({ docId: 'abc', text: 'new', replaceRange: '25:40' });
|
|
386
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
387
|
+
['docs', 'update', 'abc', '--text=new', '--replace-range=25:40'],
|
|
388
|
+
{ account: undefined },
|
|
389
|
+
);
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
it('includes --markdown when true', async () => {
|
|
393
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
394
|
+
const handlers = setupHandlers();
|
|
395
|
+
await handlers.get('gog_docs_update')!({ docId: 'abc', text: '# Heading', markdown: true });
|
|
396
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
397
|
+
['docs', 'update', 'abc', '--text=# Heading', '--markdown'],
|
|
398
|
+
{ account: undefined },
|
|
399
|
+
);
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
it('omits --markdown when false', async () => {
|
|
403
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
404
|
+
const handlers = setupHandlers();
|
|
405
|
+
await handlers.get('gog_docs_update')!({ docId: 'abc', text: 'hi', markdown: false });
|
|
406
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
407
|
+
['docs', 'update', 'abc', '--text=hi'],
|
|
408
|
+
{ account: undefined },
|
|
409
|
+
);
|
|
410
|
+
});
|
|
381
411
|
});
|
|
382
412
|
|
|
383
413
|
// --- Dedicated comment tools ---
|
|
@@ -741,6 +771,160 @@ describe('gog_docs_page_layout', () => {
|
|
|
741
771
|
await handlers.get('gog_docs_page_layout')!({ docId: 'd1' });
|
|
742
772
|
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['docs', 'page-layout', 'd1'], { account: undefined });
|
|
743
773
|
});
|
|
774
|
+
|
|
775
|
+
it('passes --page-size, page dimensions and all margins', async () => {
|
|
776
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
777
|
+
const handlers = setupHandlers();
|
|
778
|
+
await handlers.get('gog_docs_page_layout')!({
|
|
779
|
+
docId: 'd1', layout: 'pages', pageSize: 'A4', pageWidth: '8.5in', pageHeight: '11in',
|
|
780
|
+
marginTop: '1in', marginBottom: '1in', marginLeft: '0.75in', marginRight: '0.75in',
|
|
781
|
+
});
|
|
782
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
783
|
+
['docs', 'page-layout', 'd1', '--layout=pages', '--page-size=A4', '--page-width=8.5in',
|
|
784
|
+
'--page-height=11in', '--margin-top=1in', '--margin-bottom=1in', '--margin-left=0.75in', '--margin-right=0.75in'],
|
|
785
|
+
{ account: undefined },
|
|
786
|
+
);
|
|
787
|
+
});
|
|
788
|
+
});
|
|
789
|
+
|
|
790
|
+
describe('gog_docs_cell_update', () => {
|
|
791
|
+
it('passes required --row and --col', async () => {
|
|
792
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
793
|
+
const handlers = setupHandlers();
|
|
794
|
+
await handlers.get('gog_docs_cell_update')!({ docId: 'd1', row: 2, col: 3, content: 'hi' });
|
|
795
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
796
|
+
['docs', 'cell-update', 'd1', '--row=2', '--col=3', '--content=hi'],
|
|
797
|
+
{ account: undefined },
|
|
798
|
+
);
|
|
799
|
+
});
|
|
800
|
+
|
|
801
|
+
it('supports empty-string content, --content-file, --append, --format, --table-index and --tab', async () => {
|
|
802
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
803
|
+
const handlers = setupHandlers();
|
|
804
|
+
await handlers.get('gog_docs_cell_update')!({
|
|
805
|
+
docId: 'd1', row: 1, col: 1, content: '', contentFile: '/tmp/c.md', append: true,
|
|
806
|
+
format: 'plain', tableIndex: -1, tab: 'Tab2',
|
|
807
|
+
});
|
|
808
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
809
|
+
['docs', 'cell-update', 'd1', '--row=1', '--col=1', '--content=', '--content-file=/tmp/c.md',
|
|
810
|
+
'--append', '--format=plain', '--table-index=-1', '--tab=Tab2'],
|
|
811
|
+
{ account: undefined },
|
|
812
|
+
);
|
|
813
|
+
});
|
|
814
|
+
|
|
815
|
+
it('omits content when not provided and forwards account', async () => {
|
|
816
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
817
|
+
const handlers = setupHandlers();
|
|
818
|
+
await handlers.get('gog_docs_cell_update')!({ docId: 'd1', row: 1, col: 2, account: 'a@b.com' });
|
|
819
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
820
|
+
['docs', 'cell-update', 'd1', '--row=1', '--col=2'],
|
|
821
|
+
{ account: 'a@b.com' },
|
|
822
|
+
);
|
|
823
|
+
});
|
|
824
|
+
});
|
|
825
|
+
|
|
826
|
+
describe('gog_docs_cell_style', () => {
|
|
827
|
+
it('passes required --row and --col', async () => {
|
|
828
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
829
|
+
const handlers = setupHandlers();
|
|
830
|
+
await handlers.get('gog_docs_cell_style')!({ docId: 'd1', row: 0, col: 0 });
|
|
831
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
832
|
+
['docs', 'cell-style', 'd1', '--row=0', '--col=0'],
|
|
833
|
+
{ account: undefined },
|
|
834
|
+
);
|
|
835
|
+
});
|
|
836
|
+
|
|
837
|
+
it('passes spans, colors and text styling', async () => {
|
|
838
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
839
|
+
const handlers = setupHandlers();
|
|
840
|
+
await handlers.get('gog_docs_cell_style')!({
|
|
841
|
+
docId: 'd1', row: 1, col: 2, rowSpan: 2, colSpan: 3, backgroundColor: '#eee',
|
|
842
|
+
textColor: '#111', bold: true, italic: true, underline: true, tableIndex: 1, tab: 'T',
|
|
843
|
+
});
|
|
844
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
845
|
+
['docs', 'cell-style', 'd1', '--row=1', '--col=2', '--row-span=2', '--col-span=3',
|
|
846
|
+
'--background-color=#eee', '--text-color=#111', '--bold', '--italic', '--underline',
|
|
847
|
+
'--table-index=1', '--tab=T'],
|
|
848
|
+
{ account: undefined },
|
|
849
|
+
);
|
|
850
|
+
});
|
|
851
|
+
|
|
852
|
+
it('omits text styling flags when false', async () => {
|
|
853
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
854
|
+
const handlers = setupHandlers();
|
|
855
|
+
await handlers.get('gog_docs_cell_style')!({ docId: 'd1', row: 0, col: 0, bold: false, italic: false, underline: false });
|
|
856
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
857
|
+
['docs', 'cell-style', 'd1', '--row=0', '--col=0'],
|
|
858
|
+
{ account: undefined },
|
|
859
|
+
);
|
|
860
|
+
});
|
|
861
|
+
});
|
|
862
|
+
|
|
863
|
+
describe('gog_docs_insert_image', () => {
|
|
864
|
+
it('passes required --file and defaults', async () => {
|
|
865
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
866
|
+
const handlers = setupHandlers();
|
|
867
|
+
await handlers.get('gog_docs_insert_image')!({ docId: 'd1', file: '/tmp/pic.png' });
|
|
868
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
869
|
+
['docs', 'insert-image', 'd1', '--file=/tmp/pic.png'],
|
|
870
|
+
{ account: undefined },
|
|
871
|
+
);
|
|
872
|
+
});
|
|
873
|
+
|
|
874
|
+
it('passes placement, sizing, naming and restriction handling', async () => {
|
|
875
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
876
|
+
const handlers = setupHandlers();
|
|
877
|
+
await handlers.get('gog_docs_insert_image')!({
|
|
878
|
+
docId: 'd1', file: '/tmp/pic.png', at: '{{logo}}', width: 200, height: 100,
|
|
879
|
+
name: 'logo.png', parent: 'folder1', onRestricted: 'link', tab: 'T',
|
|
880
|
+
});
|
|
881
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
882
|
+
['docs', 'insert-image', 'd1', '--file=/tmp/pic.png', '--at={{logo}}', '--width=200',
|
|
883
|
+
'--height=100', '--name=logo.png', '--parent=folder1', '--on-restricted=link', '--tab=T'],
|
|
884
|
+
{ account: undefined },
|
|
885
|
+
);
|
|
886
|
+
});
|
|
887
|
+
});
|
|
888
|
+
|
|
889
|
+
describe('gog_docs_insert_person', () => {
|
|
890
|
+
it('passes required --email', async () => {
|
|
891
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
892
|
+
const handlers = setupHandlers();
|
|
893
|
+
await handlers.get('gog_docs_insert_person')!({ docId: 'd1', email: 'a@b.com' });
|
|
894
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
895
|
+
['docs', 'insert-person', 'd1', '--email=a@b.com'],
|
|
896
|
+
{ account: undefined },
|
|
897
|
+
);
|
|
898
|
+
});
|
|
899
|
+
|
|
900
|
+
it('passes --index, --at-end and --tab', async () => {
|
|
901
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
902
|
+
const handlers = setupHandlers();
|
|
903
|
+
await handlers.get('gog_docs_insert_person')!({ docId: 'd1', email: 'a@b.com', index: 0, atEnd: true, tab: 'T' });
|
|
904
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
905
|
+
['docs', 'insert-person', 'd1', '--email=a@b.com', '--index=0', '--at-end', '--tab=T'],
|
|
906
|
+
{ account: undefined },
|
|
907
|
+
);
|
|
908
|
+
});
|
|
909
|
+
});
|
|
910
|
+
|
|
911
|
+
describe('gog_docs_insert_date_chip', () => {
|
|
912
|
+
it('omits all optional flags by default', async () => {
|
|
913
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
914
|
+
const handlers = setupHandlers();
|
|
915
|
+
await handlers.get('gog_docs_insert_date_chip')!({ docId: 'd1' });
|
|
916
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['docs', 'insert-date-chip', 'd1'], { account: undefined });
|
|
917
|
+
});
|
|
918
|
+
|
|
919
|
+
it('passes --date, --format, --index and --at-end', async () => {
|
|
920
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
921
|
+
const handlers = setupHandlers();
|
|
922
|
+
await handlers.get('gog_docs_insert_date_chip')!({ docId: 'd1', date: '2026-06-01', format: 'iso', index: 5, atEnd: true });
|
|
923
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
924
|
+
['docs', 'insert-date-chip', 'd1', '--date=2026-06-01', '--format=iso', '--index=5', '--at-end'],
|
|
925
|
+
{ account: undefined },
|
|
926
|
+
);
|
|
927
|
+
});
|
|
744
928
|
});
|
|
745
929
|
|
|
746
930
|
describe('gog_docs_insert_table', () => {
|