gogcli-mcp-docs 2.4.1 → 2.6.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 +121 -10
- package/manifest.json +13 -1
- package/package.json +1 -1
- package/server.json +2 -2
- package/src/tools/docs-extra.ts +121 -10
- package/tests/tools/docs-extra.test.ts +175 -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.6.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.6.0",
|
|
19
19
|
"author": {
|
|
20
20
|
"name": "Chris Hall"
|
|
21
21
|
},
|
package/dist/index.js
CHANGED
|
@@ -31105,6 +31105,11 @@ var paginationParams = {
|
|
|
31105
31105
|
page: external_exports.string().optional().describe("Page token"),
|
|
31106
31106
|
all: external_exports.boolean().optional().describe("Fetch all pages")
|
|
31107
31107
|
};
|
|
31108
|
+
function pushPaginationFlags(args, p) {
|
|
31109
|
+
if (p.max !== void 0) args.push(`--max=${p.max}`);
|
|
31110
|
+
if (p.page) args.push(`--page=${p.page}`);
|
|
31111
|
+
if (p.all) args.push("--all");
|
|
31112
|
+
}
|
|
31108
31113
|
function registerRunTool(server2, options) {
|
|
31109
31114
|
const { service, examples, omitAccount = false, note } = options;
|
|
31110
31115
|
const baseDescription = `Run any gog ${service} subcommand not covered by the other tools. Run \`gog ${service} --help\` for the full list of subcommands, or \`gog ${service} <subcommand> --help\` for flags on a specific subcommand.`;
|
|
@@ -31313,7 +31318,7 @@ var failIfNotEmptyParam = external_exports.boolean().optional().describe(
|
|
|
31313
31318
|
);
|
|
31314
31319
|
|
|
31315
31320
|
// ../gogcli-mcp/src/server.ts
|
|
31316
|
-
var VERSION = true ? "2.
|
|
31321
|
+
var VERSION = true ? "2.6.0" : "0.0.0";
|
|
31317
31322
|
function createServer(options) {
|
|
31318
31323
|
return new McpServer({
|
|
31319
31324
|
name: options?.name ?? "gogcli",
|
|
@@ -31341,13 +31346,22 @@ function registerExtraDocsTools(server2) {
|
|
|
31341
31346
|
annotations: { destructiveHint: true },
|
|
31342
31347
|
inputSchema: {
|
|
31343
31348
|
docId: external_exports.string().describe("Doc ID (from the URL)"),
|
|
31344
|
-
start: external_exports.number().describe("Start index (character position, 1-based)"),
|
|
31345
|
-
end: external_exports.number().describe("End index (character position, exclusive)"),
|
|
31349
|
+
start: external_exports.number().optional().describe("Start index (character position, 1-based). Required unless `at` is set."),
|
|
31350
|
+
end: external_exports.number().optional().describe("End index (character position, exclusive). Required unless `at` is set."),
|
|
31351
|
+
at: external_exports.string().optional().describe("Anchor by literal text and delete that matched range, instead of supplying start/end indices."),
|
|
31352
|
+
occurrence: external_exports.number().int().optional().describe("Use the Nth `at` match (1-based; required when `at` is ambiguous)"),
|
|
31353
|
+
matchCase: external_exports.boolean().optional().describe("Case-sensitive `at` matching"),
|
|
31346
31354
|
tabId: external_exports.string().optional().describe("Tab ID to delete content from (for multi-tab docs)"),
|
|
31347
31355
|
account: accountParam
|
|
31348
31356
|
}
|
|
31349
|
-
}, async ({ docId, start, end, tabId, account }) => {
|
|
31350
|
-
const args = ["docs", "delete"
|
|
31357
|
+
}, async ({ docId, start, end, at, occurrence, matchCase, tabId, account }) => {
|
|
31358
|
+
const args = ["docs", "delete"];
|
|
31359
|
+
if (start !== void 0) args.push(`--start=${start}`);
|
|
31360
|
+
if (end !== void 0) args.push(`--end=${end}`);
|
|
31361
|
+
args.push(docId);
|
|
31362
|
+
if (at) args.push(`--at=${at}`);
|
|
31363
|
+
if (occurrence !== void 0) args.push(`--occurrence=${occurrence}`);
|
|
31364
|
+
if (matchCase) args.push("--match-case");
|
|
31351
31365
|
if (tabId) args.push(`--tab-id=${tabId}`);
|
|
31352
31366
|
return runOrDiagnose(args, { account });
|
|
31353
31367
|
});
|
|
@@ -31418,6 +31432,9 @@ function registerExtraDocsTools(server2) {
|
|
|
31418
31432
|
noUnderline: external_exports.boolean().optional().describe("Clear underline"),
|
|
31419
31433
|
strikethrough: external_exports.boolean().optional().describe("Set strikethrough"),
|
|
31420
31434
|
noStrikethrough: external_exports.boolean().optional().describe("Clear strikethrough"),
|
|
31435
|
+
code: external_exports.boolean().optional().describe("Apply code style (Courier New monospace + grey background)"),
|
|
31436
|
+
link: external_exports.string().optional().describe("Set a hyperlink on the matched text. Accepts http://, https://, mailto:, #bookmarkId, or #heading-slug."),
|
|
31437
|
+
noLink: external_exports.boolean().optional().describe("Clear any hyperlink on the matched text"),
|
|
31421
31438
|
alignment: external_exports.enum(["left", "center", "right", "justify", "start", "end", "justified"]).optional().describe("Paragraph alignment"),
|
|
31422
31439
|
lineSpacing: external_exports.number().optional().describe("Line spacing percentage (e.g. 100 for single, 150 for 1.5x, 200 for double)"),
|
|
31423
31440
|
headingLevel: external_exports.number().int().optional().describe("Set paragraph named style to HEADING_1..HEADING_6 (shortcut for namedStyle=HEADING_N)"),
|
|
@@ -31443,6 +31460,9 @@ function registerExtraDocsTools(server2) {
|
|
|
31443
31460
|
if (a.noUnderline) argv.push("--no-underline");
|
|
31444
31461
|
if (a.strikethrough) argv.push("--strikethrough");
|
|
31445
31462
|
if (a.noStrikethrough) argv.push("--no-strikethrough");
|
|
31463
|
+
if (a.code) argv.push("--code");
|
|
31464
|
+
if (a.link) argv.push(`--link=${a.link}`);
|
|
31465
|
+
if (a.noLink) argv.push("--no-link");
|
|
31446
31466
|
if (a.alignment) argv.push(`--alignment=${a.alignment}`);
|
|
31447
31467
|
if (a.lineSpacing !== void 0) argv.push(`--line-spacing=${a.lineSpacing}`);
|
|
31448
31468
|
if (a.headingLevel !== void 0) argv.push(`--heading-level=${a.headingLevel}`);
|
|
@@ -31472,14 +31492,20 @@ function registerExtraDocsTools(server2) {
|
|
|
31472
31492
|
content: external_exports.string().optional().describe("Text content to insert"),
|
|
31473
31493
|
index: external_exports.number().optional().describe("Character index to insert at (1-based; default: 1 = start of doc). Prefer gog_docs_append when you want to add at the end."),
|
|
31474
31494
|
file: external_exports.string().optional().describe("Path to a file whose content to insert"),
|
|
31495
|
+
at: external_exports.string().optional().describe("Anchor by literal text and insert at the start of the matched range, instead of computing an index."),
|
|
31496
|
+
occurrence: external_exports.number().int().optional().describe("Use the Nth `at` match (1-based; required when `at` is ambiguous)"),
|
|
31497
|
+
matchCase: external_exports.boolean().optional().describe("Case-sensitive `at` matching"),
|
|
31475
31498
|
tabId: external_exports.string().optional().describe("Tab ID to insert into (for multi-tab docs)"),
|
|
31476
31499
|
account: accountParam
|
|
31477
31500
|
}
|
|
31478
|
-
}, async ({ docId, content, index, file: file2, tabId, account }) => {
|
|
31501
|
+
}, async ({ docId, content, index, file: file2, at, occurrence, matchCase, tabId, account }) => {
|
|
31479
31502
|
const args = ["docs", "insert", docId];
|
|
31480
31503
|
if (content) args.push(content);
|
|
31481
31504
|
if (index !== void 0) args.push(`--index=${index}`);
|
|
31482
31505
|
if (file2) args.push(`--file=${file2}`);
|
|
31506
|
+
if (at) args.push(`--at=${at}`);
|
|
31507
|
+
if (occurrence !== void 0) args.push(`--occurrence=${occurrence}`);
|
|
31508
|
+
if (matchCase) args.push("--match-case");
|
|
31483
31509
|
if (tabId) args.push(`--tab-id=${tabId}`);
|
|
31484
31510
|
return runOrDiagnose(args, { account });
|
|
31485
31511
|
});
|
|
@@ -31545,17 +31571,23 @@ function registerExtraDocsTools(server2) {
|
|
|
31545
31571
|
index: external_exports.number().optional().describe("Character index to insert at"),
|
|
31546
31572
|
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
31573
|
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."),
|
|
31574
|
+
at: external_exports.string().optional().describe("Anchor by literal text and replace that matched range, instead of supplying an index or replaceRange."),
|
|
31575
|
+
occurrence: external_exports.number().int().optional().describe("Use the Nth `at` match (1-based; required when `at` is ambiguous)"),
|
|
31576
|
+
matchCase: external_exports.boolean().optional().describe("Case-sensitive `at` matching"),
|
|
31548
31577
|
tabId: external_exports.string().optional().describe("Tab ID for multi-tab docs"),
|
|
31549
31578
|
pageless: external_exports.boolean().optional().describe("Set document to pageless format"),
|
|
31550
31579
|
account: accountParam
|
|
31551
31580
|
}
|
|
31552
|
-
}, async ({ docId, text, file: file2, index, replaceRange, markdown, tabId, pageless, account }) => {
|
|
31581
|
+
}, async ({ docId, text, file: file2, index, replaceRange, markdown, at, occurrence, matchCase, tabId, pageless, account }) => {
|
|
31553
31582
|
const args = ["docs", "update", docId];
|
|
31554
31583
|
if (text) args.push(`--text=${text}`);
|
|
31555
31584
|
if (file2) args.push(`--file=${file2}`);
|
|
31556
31585
|
if (index !== void 0) args.push(`--index=${index}`);
|
|
31557
31586
|
if (replaceRange) args.push(`--replace-range=${replaceRange}`);
|
|
31558
31587
|
if (markdown) args.push("--markdown");
|
|
31588
|
+
if (at) args.push(`--at=${at}`);
|
|
31589
|
+
if (occurrence !== void 0) args.push(`--occurrence=${occurrence}`);
|
|
31590
|
+
if (matchCase) args.push("--match-case");
|
|
31559
31591
|
if (tabId) args.push(`--tab-id=${tabId}`);
|
|
31560
31592
|
if (pageless) args.push("--pageless");
|
|
31561
31593
|
return runOrDiagnose(args, { account });
|
|
@@ -31566,11 +31598,15 @@ function registerExtraDocsTools(server2) {
|
|
|
31566
31598
|
inputSchema: {
|
|
31567
31599
|
docId: external_exports.string().describe("Doc ID (from the URL)"),
|
|
31568
31600
|
includeResolved: external_exports.boolean().optional().describe("Include resolved comments (default: false, open only)"),
|
|
31601
|
+
since: external_exports.string().optional().describe("Only return comments modified at or after this RFC3339 timestamp (e.g. 2026-06-01T00:00:00Z)"),
|
|
31602
|
+
...paginationParams,
|
|
31569
31603
|
account: accountParam
|
|
31570
31604
|
}
|
|
31571
|
-
}, async ({ docId, includeResolved, account }) => {
|
|
31605
|
+
}, async ({ docId, includeResolved, since, max, page, all, account }) => {
|
|
31572
31606
|
const args = ["docs", "comments", "list", docId];
|
|
31573
31607
|
if (includeResolved) args.push("--include-resolved");
|
|
31608
|
+
if (since) args.push(`--since=${since}`);
|
|
31609
|
+
pushPaginationFlags(args, { max, page, all });
|
|
31574
31610
|
return runOrDiagnose(args, { account });
|
|
31575
31611
|
});
|
|
31576
31612
|
server2.registerTool("gog_docs_comments_get", {
|
|
@@ -31644,6 +31680,69 @@ function registerExtraDocsTools(server2) {
|
|
|
31644
31680
|
}, async ({ docId, commentId, account }) => {
|
|
31645
31681
|
return runOrDiagnose(["docs", "comments", "reopen", docId, commentId], { account });
|
|
31646
31682
|
});
|
|
31683
|
+
server2.registerTool("gog_docs_comments_locate", {
|
|
31684
|
+
description: "Locate a comment's anchor in a Google Doc \u2014 resolves the comment's quoted text to its current Docs API index range, or reports the comment as orphaned if the quote can no longer be found (e.g. the anchored text was edited away). Read-only; useful before an index-based edit near a comment.",
|
|
31685
|
+
annotations: { readOnlyHint: true },
|
|
31686
|
+
inputSchema: {
|
|
31687
|
+
docId: external_exports.string().describe("Doc ID (from the URL)"),
|
|
31688
|
+
commentId: external_exports.string().describe("Comment ID to locate"),
|
|
31689
|
+
matchCase: external_exports.boolean().optional().describe("Case-sensitive matching of the comment quote"),
|
|
31690
|
+
normalizeWhitespace: external_exports.boolean().optional().describe("Collapse whitespace while matching the comment quote"),
|
|
31691
|
+
tab: external_exports.string().optional().describe("Target a specific tab by title or ID"),
|
|
31692
|
+
account: accountParam
|
|
31693
|
+
}
|
|
31694
|
+
}, async ({ docId, commentId, matchCase, normalizeWhitespace, tab, account }) => {
|
|
31695
|
+
const args = ["docs", "comments", "locate", docId, commentId];
|
|
31696
|
+
if (matchCase) args.push("--match-case");
|
|
31697
|
+
if (normalizeWhitespace) args.push("--normalize-whitespace");
|
|
31698
|
+
if (tab) args.push(`--tab=${tab}`);
|
|
31699
|
+
return runOrDiagnose(args, { account });
|
|
31700
|
+
});
|
|
31701
|
+
server2.registerTool("gog_docs_find_range", {
|
|
31702
|
+
description: "Map literal text in a Google Doc to its Docs API UTF-16 index range(s). Read-only helper for computing the start/end indices that index-based tools (gog_docs_delete, gog_docs_update --replace-range) need. Returns the first match by default; use occurrence to pick a specific one or all to return every match.",
|
|
31703
|
+
annotations: { readOnlyHint: true },
|
|
31704
|
+
inputSchema: {
|
|
31705
|
+
docId: external_exports.string().describe("Doc ID (from the URL)"),
|
|
31706
|
+
text: external_exports.string().describe("Literal text to locate"),
|
|
31707
|
+
occurrence: external_exports.number().int().optional().describe("Return the Nth occurrence (1-based; default: first)"),
|
|
31708
|
+
matchCase: external_exports.boolean().optional().describe("Case-sensitive matching"),
|
|
31709
|
+
normalizeWhitespace: external_exports.boolean().optional().describe("Collapse whitespace while matching"),
|
|
31710
|
+
all: external_exports.boolean().optional().describe("Return all matches instead of just one"),
|
|
31711
|
+
failEmpty: external_exports.boolean().optional().describe("Treat no matches as an error instead of returning an empty result"),
|
|
31712
|
+
tab: external_exports.string().optional().describe("Target a specific tab by title or ID"),
|
|
31713
|
+
account: accountParam
|
|
31714
|
+
}
|
|
31715
|
+
}, async ({ docId, text, occurrence, matchCase, normalizeWhitespace, all, failEmpty, tab, account }) => {
|
|
31716
|
+
const args = ["docs", "find-range", docId, text];
|
|
31717
|
+
if (occurrence !== void 0) args.push(`--occurrence=${occurrence}`);
|
|
31718
|
+
if (matchCase) args.push("--match-case");
|
|
31719
|
+
if (normalizeWhitespace) args.push("--normalize-whitespace");
|
|
31720
|
+
if (all) args.push("--all");
|
|
31721
|
+
if (failEmpty) args.push("--fail-empty");
|
|
31722
|
+
if (tab) args.push(`--tab=${tab}`);
|
|
31723
|
+
return runOrDiagnose(args, { account });
|
|
31724
|
+
});
|
|
31725
|
+
server2.registerTool("gog_docs_table_column_width", {
|
|
31726
|
+
description: "Set a fixed width (in points) for a table column, or reset columns to Docs-managed even distribution. Target the table by 1-based index in document order (negative counts from the end) and the column by 1-based number. Pass evenlyDistributed without col to reset every column in the table.",
|
|
31727
|
+
annotations: { destructiveHint: true },
|
|
31728
|
+
inputSchema: {
|
|
31729
|
+
docId: external_exports.string().describe("Doc ID (from the URL)"),
|
|
31730
|
+
col: external_exports.number().int().optional().describe("1-based column number. Omit with evenlyDistributed to reset all columns."),
|
|
31731
|
+
width: external_exports.number().optional().describe("Fixed column width in points (minimum 5pt). Mutually exclusive with evenlyDistributed."),
|
|
31732
|
+
evenlyDistributed: external_exports.boolean().optional().describe("Reset the selected column (or all columns when col is omitted) to Docs-managed equal width."),
|
|
31733
|
+
tableIndex: external_exports.number().int().optional().describe("1-based table index in document order; negative counts from the end (default: 1)"),
|
|
31734
|
+
tab: external_exports.string().optional().describe("Target tab title or ID"),
|
|
31735
|
+
account: accountParam
|
|
31736
|
+
}
|
|
31737
|
+
}, async ({ docId, col, width, evenlyDistributed, tableIndex, tab, account }) => {
|
|
31738
|
+
const args = ["docs", "table-column-width", docId];
|
|
31739
|
+
if (col !== void 0) args.push(`--col=${col}`);
|
|
31740
|
+
if (width !== void 0) args.push(`--width=${width}`);
|
|
31741
|
+
if (evenlyDistributed) args.push("--evenly-distributed");
|
|
31742
|
+
if (tableIndex !== void 0) args.push(`--table-index=${tableIndex}`);
|
|
31743
|
+
if (tab) args.push(`--tab=${tab}`);
|
|
31744
|
+
return runOrDiagnose(args, { account });
|
|
31745
|
+
});
|
|
31647
31746
|
server2.registerTool("gog_docs_insert_page_break", {
|
|
31648
31747
|
description: "Insert a Google Docs page break via InsertPageBreakRequest \u2014 the only path for multi-page deliverables (markdown has no page-break construct). Specify `index` for a precise character position, or `atEnd` for end-of-doc.",
|
|
31649
31748
|
annotations: { destructiveHint: true },
|
|
@@ -31651,13 +31750,19 @@ function registerExtraDocsTools(server2) {
|
|
|
31651
31750
|
docId: external_exports.string().describe("Doc ID (from the URL)"),
|
|
31652
31751
|
index: external_exports.number().int().optional().describe("Character index to insert at (1 = beginning). Omit or use atEnd for end-of-doc."),
|
|
31653
31752
|
atEnd: external_exports.boolean().optional().describe("Insert at end-of-doc/tab (mutually exclusive with index)"),
|
|
31753
|
+
at: external_exports.string().optional().describe("Anchor by literal text and insert the page break at the start of the matched range, instead of an index."),
|
|
31754
|
+
occurrence: external_exports.number().int().optional().describe("Use the Nth `at` match (1-based; required when `at` is ambiguous)"),
|
|
31755
|
+
matchCase: external_exports.boolean().optional().describe("Case-sensitive `at` matching"),
|
|
31654
31756
|
tab: external_exports.string().optional().describe("Target a specific tab by title or ID"),
|
|
31655
31757
|
account: accountParam
|
|
31656
31758
|
}
|
|
31657
|
-
}, async ({ docId, index, atEnd, tab, account }) => {
|
|
31759
|
+
}, async ({ docId, index, atEnd, at, occurrence, matchCase, tab, account }) => {
|
|
31658
31760
|
const args = ["docs", "insert-page-break", docId];
|
|
31659
31761
|
if (index !== void 0) args.push(`--index=${index}`);
|
|
31660
31762
|
if (atEnd) args.push("--at-end");
|
|
31763
|
+
if (at) args.push(`--at=${at}`);
|
|
31764
|
+
if (occurrence !== void 0) args.push(`--occurrence=${occurrence}`);
|
|
31765
|
+
if (matchCase) args.push("--match-case");
|
|
31661
31766
|
if (tab) args.push(`--tab=${tab}`);
|
|
31662
31767
|
return runOrDiagnose(args, { account });
|
|
31663
31768
|
});
|
|
@@ -31799,13 +31904,19 @@ function registerExtraDocsTools(server2) {
|
|
|
31799
31904
|
email: external_exports.string().describe("Email address for the person chip"),
|
|
31800
31905
|
index: external_exports.number().int().optional().describe("Character index to insert at. Omit or use atEnd for end-of-doc."),
|
|
31801
31906
|
atEnd: external_exports.boolean().optional().describe("Insert at end-of-doc/tab (mutually exclusive with index)"),
|
|
31907
|
+
at: external_exports.string().optional().describe("Anchor by literal text, delete the match, and insert the person chip there, instead of an index."),
|
|
31908
|
+
occurrence: external_exports.number().int().optional().describe("Use the Nth `at` match (1-based; required when `at` is ambiguous)"),
|
|
31909
|
+
matchCase: external_exports.boolean().optional().describe("Case-sensitive `at` matching"),
|
|
31802
31910
|
tab: external_exports.string().optional().describe("Target a specific tab by title or ID"),
|
|
31803
31911
|
account: accountParam
|
|
31804
31912
|
}
|
|
31805
|
-
}, async ({ docId, email: email3, index, atEnd, tab, account }) => {
|
|
31913
|
+
}, async ({ docId, email: email3, index, atEnd, at, occurrence, matchCase, tab, account }) => {
|
|
31806
31914
|
const args = ["docs", "insert-person", docId, `--email=${email3}`];
|
|
31807
31915
|
if (index !== void 0) args.push(`--index=${index}`);
|
|
31808
31916
|
if (atEnd) args.push("--at-end");
|
|
31917
|
+
if (at) args.push(`--at=${at}`);
|
|
31918
|
+
if (occurrence !== void 0) args.push(`--occurrence=${occurrence}`);
|
|
31919
|
+
if (matchCase) args.push("--match-case");
|
|
31809
31920
|
if (tab) args.push(`--tab=${tab}`);
|
|
31810
31921
|
return runOrDiagnose(args, { account });
|
|
31811
31922
|
});
|
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.6.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",
|
|
@@ -195,6 +195,18 @@
|
|
|
195
195
|
"name": "gog_docs_comments_reopen",
|
|
196
196
|
"description": "Reopen a previously resolved comment."
|
|
197
197
|
},
|
|
198
|
+
{
|
|
199
|
+
"name": "gog_docs_comments_locate",
|
|
200
|
+
"description": "Resolve a comment's quoted anchor to its current Docs API index range, or report it as orphaned."
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
"name": "gog_docs_find_range",
|
|
204
|
+
"description": "Map literal text to its Docs API UTF-16 index range(s) for index-based edits."
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
"name": "gog_docs_table_column_width",
|
|
208
|
+
"description": "Set a fixed table column width in points, or reset columns to Docs-managed even distribution."
|
|
209
|
+
},
|
|
198
210
|
{
|
|
199
211
|
"name": "gog_docs_insert_page_break",
|
|
200
212
|
"description": "Insert a Google Docs page break at a given index or at end-of-doc — the only path for multi-page deliverables."
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gogcli-mcp-docs",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.6.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.6.0",
|
|
11
11
|
"packages": [
|
|
12
12
|
{
|
|
13
13
|
"registryType": "npm",
|
|
14
14
|
"identifier": "gogcli-mcp-docs",
|
|
15
|
-
"version": "2.
|
|
15
|
+
"version": "2.6.0",
|
|
16
16
|
"transport": {
|
|
17
17
|
"type": "stdio"
|
|
18
18
|
},
|
package/src/tools/docs-extra.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
2
|
import { z } from 'zod';
|
|
3
|
-
import { accountParam, runOrDiagnose } from '../../../gogcli-mcp/src/lib.js';
|
|
3
|
+
import { accountParam, runOrDiagnose, paginationParams, pushPaginationFlags } from '../../../gogcli-mcp/src/lib.js';
|
|
4
4
|
|
|
5
5
|
export function registerExtraDocsTools(server: McpServer): void {
|
|
6
6
|
server.registerTool('gog_docs_copy', {
|
|
@@ -22,13 +22,22 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
22
22
|
annotations: { destructiveHint: true },
|
|
23
23
|
inputSchema: {
|
|
24
24
|
docId: z.string().describe('Doc ID (from the URL)'),
|
|
25
|
-
start: z.number().describe('Start index (character position, 1-based)'),
|
|
26
|
-
end: z.number().describe('End index (character position, exclusive)'),
|
|
25
|
+
start: z.number().optional().describe('Start index (character position, 1-based). Required unless `at` is set.'),
|
|
26
|
+
end: z.number().optional().describe('End index (character position, exclusive). Required unless `at` is set.'),
|
|
27
|
+
at: z.string().optional().describe('Anchor by literal text and delete that matched range, instead of supplying start/end indices.'),
|
|
28
|
+
occurrence: z.number().int().optional().describe('Use the Nth `at` match (1-based; required when `at` is ambiguous)'),
|
|
29
|
+
matchCase: z.boolean().optional().describe('Case-sensitive `at` matching'),
|
|
27
30
|
tabId: z.string().optional().describe('Tab ID to delete content from (for multi-tab docs)'),
|
|
28
31
|
account: accountParam,
|
|
29
32
|
},
|
|
30
|
-
}, async ({ docId, start, end, tabId, account }) => {
|
|
31
|
-
const args = ['docs', 'delete'
|
|
33
|
+
}, async ({ docId, start, end, at, occurrence, matchCase, tabId, account }) => {
|
|
34
|
+
const args = ['docs', 'delete'];
|
|
35
|
+
if (start !== undefined) args.push(`--start=${start}`);
|
|
36
|
+
if (end !== undefined) args.push(`--end=${end}`);
|
|
37
|
+
args.push(docId);
|
|
38
|
+
if (at) args.push(`--at=${at}`);
|
|
39
|
+
if (occurrence !== undefined) args.push(`--occurrence=${occurrence}`);
|
|
40
|
+
if (matchCase) args.push('--match-case');
|
|
32
41
|
if (tabId) args.push(`--tab-id=${tabId}`);
|
|
33
42
|
return runOrDiagnose(args, { account });
|
|
34
43
|
});
|
|
@@ -103,6 +112,9 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
103
112
|
noUnderline: z.boolean().optional().describe('Clear underline'),
|
|
104
113
|
strikethrough: z.boolean().optional().describe('Set strikethrough'),
|
|
105
114
|
noStrikethrough: z.boolean().optional().describe('Clear strikethrough'),
|
|
115
|
+
code: z.boolean().optional().describe('Apply code style (Courier New monospace + grey background)'),
|
|
116
|
+
link: z.string().optional().describe('Set a hyperlink on the matched text. Accepts http://, https://, mailto:, #bookmarkId, or #heading-slug.'),
|
|
117
|
+
noLink: z.boolean().optional().describe('Clear any hyperlink on the matched text'),
|
|
106
118
|
alignment: z.enum(['left', 'center', 'right', 'justify', 'start', 'end', 'justified']).optional().describe('Paragraph alignment'),
|
|
107
119
|
lineSpacing: z.number().optional().describe('Line spacing percentage (e.g. 100 for single, 150 for 1.5x, 200 for double)'),
|
|
108
120
|
headingLevel: z.number().int().optional().describe('Set paragraph named style to HEADING_1..HEADING_6 (shortcut for namedStyle=HEADING_N)'),
|
|
@@ -124,6 +136,8 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
124
136
|
italic?: boolean; noItalic?: boolean;
|
|
125
137
|
underline?: boolean; noUnderline?: boolean;
|
|
126
138
|
strikethrough?: boolean; noStrikethrough?: boolean;
|
|
139
|
+
code?: boolean;
|
|
140
|
+
link?: string; noLink?: boolean;
|
|
127
141
|
alignment?: string;
|
|
128
142
|
lineSpacing?: number;
|
|
129
143
|
headingLevel?: number;
|
|
@@ -147,6 +161,9 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
147
161
|
if (a.noUnderline) argv.push('--no-underline');
|
|
148
162
|
if (a.strikethrough) argv.push('--strikethrough');
|
|
149
163
|
if (a.noStrikethrough) argv.push('--no-strikethrough');
|
|
164
|
+
if (a.code) argv.push('--code');
|
|
165
|
+
if (a.link) argv.push(`--link=${a.link}`);
|
|
166
|
+
if (a.noLink) argv.push('--no-link');
|
|
150
167
|
if (a.alignment) argv.push(`--alignment=${a.alignment}`);
|
|
151
168
|
if (a.lineSpacing !== undefined) argv.push(`--line-spacing=${a.lineSpacing}`);
|
|
152
169
|
if (a.headingLevel !== undefined) argv.push(`--heading-level=${a.headingLevel}`);
|
|
@@ -178,14 +195,20 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
178
195
|
content: z.string().optional().describe('Text content to insert'),
|
|
179
196
|
index: z.number().optional().describe('Character index to insert at (1-based; default: 1 = start of doc). Prefer gog_docs_append when you want to add at the end.'),
|
|
180
197
|
file: z.string().optional().describe('Path to a file whose content to insert'),
|
|
198
|
+
at: z.string().optional().describe('Anchor by literal text and insert at the start of the matched range, instead of computing an index.'),
|
|
199
|
+
occurrence: z.number().int().optional().describe('Use the Nth `at` match (1-based; required when `at` is ambiguous)'),
|
|
200
|
+
matchCase: z.boolean().optional().describe('Case-sensitive `at` matching'),
|
|
181
201
|
tabId: z.string().optional().describe('Tab ID to insert into (for multi-tab docs)'),
|
|
182
202
|
account: accountParam,
|
|
183
203
|
},
|
|
184
|
-
}, async ({ docId, content, index, file, tabId, account }) => {
|
|
204
|
+
}, async ({ docId, content, index, file, at, occurrence, matchCase, tabId, account }) => {
|
|
185
205
|
const args = ['docs', 'insert', docId];
|
|
186
206
|
if (content) args.push(content);
|
|
187
207
|
if (index !== undefined) args.push(`--index=${index}`);
|
|
188
208
|
if (file) args.push(`--file=${file}`);
|
|
209
|
+
if (at) args.push(`--at=${at}`);
|
|
210
|
+
if (occurrence !== undefined) args.push(`--occurrence=${occurrence}`);
|
|
211
|
+
if (matchCase) args.push('--match-case');
|
|
189
212
|
if (tabId) args.push(`--tab-id=${tabId}`);
|
|
190
213
|
return runOrDiagnose(args, { account });
|
|
191
214
|
});
|
|
@@ -255,17 +278,23 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
255
278
|
index: z.number().optional().describe('Character index to insert at'),
|
|
256
279
|
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
280
|
markdown: z.boolean().optional().describe('Convert markdown in the text/file to Google Docs formatting (headings, bold, lists, etc.) instead of inserting it literally.'),
|
|
281
|
+
at: z.string().optional().describe('Anchor by literal text and replace that matched range, instead of supplying an index or replaceRange.'),
|
|
282
|
+
occurrence: z.number().int().optional().describe('Use the Nth `at` match (1-based; required when `at` is ambiguous)'),
|
|
283
|
+
matchCase: z.boolean().optional().describe('Case-sensitive `at` matching'),
|
|
258
284
|
tabId: z.string().optional().describe('Tab ID for multi-tab docs'),
|
|
259
285
|
pageless: z.boolean().optional().describe('Set document to pageless format'),
|
|
260
286
|
account: accountParam,
|
|
261
287
|
},
|
|
262
|
-
}, async ({ docId, text, file, index, replaceRange, markdown, tabId, pageless, account }) => {
|
|
288
|
+
}, async ({ docId, text, file, index, replaceRange, markdown, at, occurrence, matchCase, tabId, pageless, account }) => {
|
|
263
289
|
const args = ['docs', 'update', docId];
|
|
264
290
|
if (text) args.push(`--text=${text}`);
|
|
265
291
|
if (file) args.push(`--file=${file}`);
|
|
266
292
|
if (index !== undefined) args.push(`--index=${index}`);
|
|
267
293
|
if (replaceRange) args.push(`--replace-range=${replaceRange}`);
|
|
268
294
|
if (markdown) args.push('--markdown');
|
|
295
|
+
if (at) args.push(`--at=${at}`);
|
|
296
|
+
if (occurrence !== undefined) args.push(`--occurrence=${occurrence}`);
|
|
297
|
+
if (matchCase) args.push('--match-case');
|
|
269
298
|
if (tabId) args.push(`--tab-id=${tabId}`);
|
|
270
299
|
if (pageless) args.push('--pageless');
|
|
271
300
|
return runOrDiagnose(args, { account });
|
|
@@ -279,11 +308,15 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
279
308
|
inputSchema: {
|
|
280
309
|
docId: z.string().describe('Doc ID (from the URL)'),
|
|
281
310
|
includeResolved: z.boolean().optional().describe('Include resolved comments (default: false, open only)'),
|
|
311
|
+
since: z.string().optional().describe('Only return comments modified at or after this RFC3339 timestamp (e.g. 2026-06-01T00:00:00Z)'),
|
|
312
|
+
...paginationParams,
|
|
282
313
|
account: accountParam,
|
|
283
314
|
},
|
|
284
|
-
}, async ({ docId, includeResolved, account }) => {
|
|
315
|
+
}, async ({ docId, includeResolved, since, max, page, all, account }) => {
|
|
285
316
|
const args = ['docs', 'comments', 'list', docId];
|
|
286
317
|
if (includeResolved) args.push('--include-resolved');
|
|
318
|
+
if (since) args.push(`--since=${since}`);
|
|
319
|
+
pushPaginationFlags(args, { max, page, all });
|
|
287
320
|
return runOrDiagnose(args, { account });
|
|
288
321
|
});
|
|
289
322
|
|
|
@@ -365,6 +398,72 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
365
398
|
return runOrDiagnose(['docs', 'comments', 'reopen', docId, commentId], { account });
|
|
366
399
|
});
|
|
367
400
|
|
|
401
|
+
server.registerTool('gog_docs_comments_locate', {
|
|
402
|
+
description: 'Locate a comment\'s anchor in a Google Doc — resolves the comment\'s quoted text to its current Docs API index range, or reports the comment as orphaned if the quote can no longer be found (e.g. the anchored text was edited away). Read-only; useful before an index-based edit near a comment.',
|
|
403
|
+
annotations: { readOnlyHint: true },
|
|
404
|
+
inputSchema: {
|
|
405
|
+
docId: z.string().describe('Doc ID (from the URL)'),
|
|
406
|
+
commentId: z.string().describe('Comment ID to locate'),
|
|
407
|
+
matchCase: z.boolean().optional().describe('Case-sensitive matching of the comment quote'),
|
|
408
|
+
normalizeWhitespace: z.boolean().optional().describe('Collapse whitespace while matching the comment quote'),
|
|
409
|
+
tab: z.string().optional().describe('Target a specific tab by title or ID'),
|
|
410
|
+
account: accountParam,
|
|
411
|
+
},
|
|
412
|
+
}, async ({ docId, commentId, matchCase, normalizeWhitespace, tab, account }) => {
|
|
413
|
+
const args = ['docs', 'comments', 'locate', docId, commentId];
|
|
414
|
+
if (matchCase) args.push('--match-case');
|
|
415
|
+
if (normalizeWhitespace) args.push('--normalize-whitespace');
|
|
416
|
+
if (tab) args.push(`--tab=${tab}`);
|
|
417
|
+
return runOrDiagnose(args, { account });
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
server.registerTool('gog_docs_find_range', {
|
|
421
|
+
description: 'Map literal text in a Google Doc to its Docs API UTF-16 index range(s). Read-only helper for computing the start/end indices that index-based tools (gog_docs_delete, gog_docs_update --replace-range) need. Returns the first match by default; use occurrence to pick a specific one or all to return every match.',
|
|
422
|
+
annotations: { readOnlyHint: true },
|
|
423
|
+
inputSchema: {
|
|
424
|
+
docId: z.string().describe('Doc ID (from the URL)'),
|
|
425
|
+
text: z.string().describe('Literal text to locate'),
|
|
426
|
+
occurrence: z.number().int().optional().describe('Return the Nth occurrence (1-based; default: first)'),
|
|
427
|
+
matchCase: z.boolean().optional().describe('Case-sensitive matching'),
|
|
428
|
+
normalizeWhitespace: z.boolean().optional().describe('Collapse whitespace while matching'),
|
|
429
|
+
all: z.boolean().optional().describe('Return all matches instead of just one'),
|
|
430
|
+
failEmpty: z.boolean().optional().describe('Treat no matches as an error instead of returning an empty result'),
|
|
431
|
+
tab: z.string().optional().describe('Target a specific tab by title or ID'),
|
|
432
|
+
account: accountParam,
|
|
433
|
+
},
|
|
434
|
+
}, async ({ docId, text, occurrence, matchCase, normalizeWhitespace, all, failEmpty, tab, account }) => {
|
|
435
|
+
const args = ['docs', 'find-range', docId, text];
|
|
436
|
+
if (occurrence !== undefined) args.push(`--occurrence=${occurrence}`);
|
|
437
|
+
if (matchCase) args.push('--match-case');
|
|
438
|
+
if (normalizeWhitespace) args.push('--normalize-whitespace');
|
|
439
|
+
if (all) args.push('--all');
|
|
440
|
+
if (failEmpty) args.push('--fail-empty');
|
|
441
|
+
if (tab) args.push(`--tab=${tab}`);
|
|
442
|
+
return runOrDiagnose(args, { account });
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
server.registerTool('gog_docs_table_column_width', {
|
|
446
|
+
description: 'Set a fixed width (in points) for a table column, or reset columns to Docs-managed even distribution. Target the table by 1-based index in document order (negative counts from the end) and the column by 1-based number. Pass evenlyDistributed without col to reset every column in the table.',
|
|
447
|
+
annotations: { destructiveHint: true },
|
|
448
|
+
inputSchema: {
|
|
449
|
+
docId: z.string().describe('Doc ID (from the URL)'),
|
|
450
|
+
col: z.number().int().optional().describe('1-based column number. Omit with evenlyDistributed to reset all columns.'),
|
|
451
|
+
width: z.number().optional().describe('Fixed column width in points (minimum 5pt). Mutually exclusive with evenlyDistributed.'),
|
|
452
|
+
evenlyDistributed: z.boolean().optional().describe('Reset the selected column (or all columns when col is omitted) to Docs-managed equal width.'),
|
|
453
|
+
tableIndex: z.number().int().optional().describe('1-based table index in document order; negative counts from the end (default: 1)'),
|
|
454
|
+
tab: z.string().optional().describe('Target tab title or ID'),
|
|
455
|
+
account: accountParam,
|
|
456
|
+
},
|
|
457
|
+
}, async ({ docId, col, width, evenlyDistributed, tableIndex, tab, account }) => {
|
|
458
|
+
const args = ['docs', 'table-column-width', docId];
|
|
459
|
+
if (col !== undefined) args.push(`--col=${col}`);
|
|
460
|
+
if (width !== undefined) args.push(`--width=${width}`);
|
|
461
|
+
if (evenlyDistributed) args.push('--evenly-distributed');
|
|
462
|
+
if (tableIndex !== undefined) args.push(`--table-index=${tableIndex}`);
|
|
463
|
+
if (tab) args.push(`--tab=${tab}`);
|
|
464
|
+
return runOrDiagnose(args, { account });
|
|
465
|
+
});
|
|
466
|
+
|
|
368
467
|
server.registerTool('gog_docs_insert_page_break', {
|
|
369
468
|
description: 'Insert a Google Docs page break via InsertPageBreakRequest — the only path for multi-page deliverables (markdown has no page-break construct). Specify `index` for a precise character position, or `atEnd` for end-of-doc.',
|
|
370
469
|
annotations: { destructiveHint: true },
|
|
@@ -372,13 +471,19 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
372
471
|
docId: z.string().describe('Doc ID (from the URL)'),
|
|
373
472
|
index: z.number().int().optional().describe('Character index to insert at (1 = beginning). Omit or use atEnd for end-of-doc.'),
|
|
374
473
|
atEnd: z.boolean().optional().describe('Insert at end-of-doc/tab (mutually exclusive with index)'),
|
|
474
|
+
at: z.string().optional().describe('Anchor by literal text and insert the page break at the start of the matched range, instead of an index.'),
|
|
475
|
+
occurrence: z.number().int().optional().describe('Use the Nth `at` match (1-based; required when `at` is ambiguous)'),
|
|
476
|
+
matchCase: z.boolean().optional().describe('Case-sensitive `at` matching'),
|
|
375
477
|
tab: z.string().optional().describe('Target a specific tab by title or ID'),
|
|
376
478
|
account: accountParam,
|
|
377
479
|
},
|
|
378
|
-
}, async ({ docId, index, atEnd, tab, account }) => {
|
|
480
|
+
}, async ({ docId, index, atEnd, at, occurrence, matchCase, tab, account }) => {
|
|
379
481
|
const args = ['docs', 'insert-page-break', docId];
|
|
380
482
|
if (index !== undefined) args.push(`--index=${index}`);
|
|
381
483
|
if (atEnd) args.push('--at-end');
|
|
484
|
+
if (at) args.push(`--at=${at}`);
|
|
485
|
+
if (occurrence !== undefined) args.push(`--occurrence=${occurrence}`);
|
|
486
|
+
if (matchCase) args.push('--match-case');
|
|
382
487
|
if (tab) args.push(`--tab=${tab}`);
|
|
383
488
|
return runOrDiagnose(args, { account });
|
|
384
489
|
});
|
|
@@ -526,13 +631,19 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
526
631
|
email: z.string().describe('Email address for the person chip'),
|
|
527
632
|
index: z.number().int().optional().describe('Character index to insert at. Omit or use atEnd for end-of-doc.'),
|
|
528
633
|
atEnd: z.boolean().optional().describe('Insert at end-of-doc/tab (mutually exclusive with index)'),
|
|
634
|
+
at: z.string().optional().describe('Anchor by literal text, delete the match, and insert the person chip there, instead of an index.'),
|
|
635
|
+
occurrence: z.number().int().optional().describe('Use the Nth `at` match (1-based; required when `at` is ambiguous)'),
|
|
636
|
+
matchCase: z.boolean().optional().describe('Case-sensitive `at` matching'),
|
|
529
637
|
tab: z.string().optional().describe('Target a specific tab by title or ID'),
|
|
530
638
|
account: accountParam,
|
|
531
639
|
},
|
|
532
|
-
}, async ({ docId, email, index, atEnd, tab, account }) => {
|
|
640
|
+
}, async ({ docId, email, index, atEnd, at, occurrence, matchCase, tab, account }) => {
|
|
533
641
|
const args = ['docs', 'insert-person', docId, `--email=${email}`];
|
|
534
642
|
if (index !== undefined) args.push(`--index=${index}`);
|
|
535
643
|
if (atEnd) args.push('--at-end');
|
|
644
|
+
if (at) args.push(`--at=${at}`);
|
|
645
|
+
if (occurrence !== undefined) args.push(`--occurrence=${occurrence}`);
|
|
646
|
+
if (matchCase) args.push('--match-case');
|
|
536
647
|
if (tab) args.push(`--tab=${tab}`);
|
|
537
648
|
return runOrDiagnose(args, { account });
|
|
538
649
|
});
|
|
@@ -63,6 +63,17 @@ describe('gog_docs_delete', () => {
|
|
|
63
63
|
);
|
|
64
64
|
});
|
|
65
65
|
|
|
66
|
+
// gog 0.23.0
|
|
67
|
+
it('anchors by text with --at/--occurrence/--match-case', async () => {
|
|
68
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
69
|
+
const handlers = setupHandlers();
|
|
70
|
+
await handlers.get('gog_docs_delete')!({ docId: 'abc', at: 'TODO', occurrence: 2, matchCase: true });
|
|
71
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
72
|
+
['docs', 'delete', 'abc', '--at=TODO', '--occurrence=2', '--match-case'],
|
|
73
|
+
{ account: undefined },
|
|
74
|
+
);
|
|
75
|
+
});
|
|
76
|
+
|
|
66
77
|
it('includes --tab-id when provided', async () => {
|
|
67
78
|
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
68
79
|
const handlers = setupHandlers();
|
|
@@ -179,6 +190,17 @@ describe('gog_docs_insert', () => {
|
|
|
179
190
|
);
|
|
180
191
|
});
|
|
181
192
|
|
|
193
|
+
// gog 0.23.0
|
|
194
|
+
it('anchors by text with --at/--occurrence/--match-case', async () => {
|
|
195
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
196
|
+
const handlers = setupHandlers();
|
|
197
|
+
await handlers.get('gog_docs_insert')!({ docId: 'abc', content: 'X', at: 'HERE', occurrence: 3, matchCase: true });
|
|
198
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
199
|
+
['docs', 'insert', 'abc', 'X', '--at=HERE', '--occurrence=3', '--match-case'],
|
|
200
|
+
{ account: undefined },
|
|
201
|
+
);
|
|
202
|
+
});
|
|
203
|
+
|
|
182
204
|
it('includes --index when provided', async () => {
|
|
183
205
|
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
184
206
|
const handlers = setupHandlers();
|
|
@@ -312,6 +334,17 @@ describe('gog_docs_update', () => {
|
|
|
312
334
|
);
|
|
313
335
|
});
|
|
314
336
|
|
|
337
|
+
// gog 0.23.0
|
|
338
|
+
it('anchors by text with --at/--occurrence/--match-case', async () => {
|
|
339
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
340
|
+
const handlers = setupHandlers();
|
|
341
|
+
await handlers.get('gog_docs_update')!({ docId: 'abc', text: 'New', at: 'OLD', occurrence: 1, matchCase: true });
|
|
342
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
343
|
+
['docs', 'update', 'abc', '--text=New', '--at=OLD', '--occurrence=1', '--match-case'],
|
|
344
|
+
{ account: undefined },
|
|
345
|
+
);
|
|
346
|
+
});
|
|
347
|
+
|
|
315
348
|
it('includes --file when provided', async () => {
|
|
316
349
|
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
317
350
|
const handlers = setupHandlers();
|
|
@@ -446,6 +479,94 @@ describe('gog_docs_comments_list', () => {
|
|
|
446
479
|
{ account: 'other@gmail.com' },
|
|
447
480
|
);
|
|
448
481
|
});
|
|
482
|
+
|
|
483
|
+
// gog 0.22.0 adds --since; pagination flags were already supported by gog.
|
|
484
|
+
it('includes --since and pagination flags when set', async () => {
|
|
485
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
486
|
+
const handlers = setupHandlers();
|
|
487
|
+
await handlers.get('gog_docs_comments_list')!({
|
|
488
|
+
docId: 'abc', includeResolved: true, since: '2026-06-01T00:00:00Z', max: 10, page: 'tok', all: true,
|
|
489
|
+
});
|
|
490
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
491
|
+
['docs', 'comments', 'list', 'abc', '--include-resolved', '--since=2026-06-01T00:00:00Z', '--max=10', '--page=tok', '--all'],
|
|
492
|
+
{ account: undefined },
|
|
493
|
+
);
|
|
494
|
+
});
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
describe('gog_docs_table_column_width', () => {
|
|
498
|
+
it('sets a fixed column width', async () => {
|
|
499
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
500
|
+
const handlers = setupHandlers();
|
|
501
|
+
await handlers.get('gog_docs_table_column_width')!({
|
|
502
|
+
docId: 'd1', col: 2, width: 120, tableIndex: 1, tab: 'Body',
|
|
503
|
+
});
|
|
504
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
505
|
+
['docs', 'table-column-width', 'd1', '--col=2', '--width=120', '--table-index=1', '--tab=Body'],
|
|
506
|
+
{ account: undefined },
|
|
507
|
+
);
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
it('resets all columns to even distribution', async () => {
|
|
511
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
512
|
+
const handlers = setupHandlers();
|
|
513
|
+
await handlers.get('gog_docs_table_column_width')!({ docId: 'd1', evenlyDistributed: true });
|
|
514
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
515
|
+
['docs', 'table-column-width', 'd1', '--evenly-distributed'],
|
|
516
|
+
{ account: undefined },
|
|
517
|
+
);
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
it('forwards account override', async () => {
|
|
521
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
522
|
+
const handlers = setupHandlers();
|
|
523
|
+
await handlers.get('gog_docs_table_column_width')!({ docId: 'd1', col: 1, width: 80, account: 'other@gmail.com' });
|
|
524
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
525
|
+
['docs', 'table-column-width', 'd1', '--col=1', '--width=80'],
|
|
526
|
+
{ account: 'other@gmail.com' },
|
|
527
|
+
);
|
|
528
|
+
});
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
// gog 0.23.0
|
|
532
|
+
describe('gog_docs_find_range', () => {
|
|
533
|
+
it('maps matched text to index ranges', async () => {
|
|
534
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
535
|
+
const handlers = setupHandlers();
|
|
536
|
+
await handlers.get('gog_docs_find_range')!({ docId: 'd1', text: 'hello' });
|
|
537
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['docs', 'find-range', 'd1', 'hello'], { account: undefined });
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
it('passes all match flags', async () => {
|
|
541
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
542
|
+
const handlers = setupHandlers();
|
|
543
|
+
await handlers.get('gog_docs_find_range')!({
|
|
544
|
+
docId: 'd1', text: 'hello', occurrence: 2, matchCase: true, normalizeWhitespace: true, all: true, failEmpty: true, tab: 'T',
|
|
545
|
+
});
|
|
546
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
547
|
+
['docs', 'find-range', 'd1', 'hello', '--occurrence=2', '--match-case', '--normalize-whitespace', '--all', '--fail-empty', '--tab=T'],
|
|
548
|
+
{ account: undefined },
|
|
549
|
+
);
|
|
550
|
+
});
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
describe('gog_docs_comments_locate', () => {
|
|
554
|
+
it('locates a comment by id', async () => {
|
|
555
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
556
|
+
const handlers = setupHandlers();
|
|
557
|
+
await handlers.get('gog_docs_comments_locate')!({ docId: 'd1', commentId: 'c1' });
|
|
558
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['docs', 'comments', 'locate', 'd1', 'c1'], { account: undefined });
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
it('passes match flags', async () => {
|
|
562
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
563
|
+
const handlers = setupHandlers();
|
|
564
|
+
await handlers.get('gog_docs_comments_locate')!({ docId: 'd1', commentId: 'c1', matchCase: true, normalizeWhitespace: true, tab: 'T' });
|
|
565
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
566
|
+
['docs', 'comments', 'locate', 'd1', 'c1', '--match-case', '--normalize-whitespace', '--tab=T'],
|
|
567
|
+
{ account: undefined },
|
|
568
|
+
);
|
|
569
|
+
});
|
|
449
570
|
});
|
|
450
571
|
|
|
451
572
|
describe('gog_docs_comments_get', () => {
|
|
@@ -718,6 +839,38 @@ describe('gog_docs_format', () => {
|
|
|
718
839
|
{ account: undefined },
|
|
719
840
|
);
|
|
720
841
|
});
|
|
842
|
+
|
|
843
|
+
// gog 0.22.0
|
|
844
|
+
it('passes --code when code is true', async () => {
|
|
845
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
846
|
+
const handlers = setupHandlers();
|
|
847
|
+
await handlers.get('gog_docs_format')!({ docId: 'd1', match: 'snippet', code: true });
|
|
848
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
849
|
+
['docs', 'format', 'd1', '--match=snippet', '--code'],
|
|
850
|
+
{ account: undefined },
|
|
851
|
+
);
|
|
852
|
+
});
|
|
853
|
+
|
|
854
|
+
// gog 0.23.0
|
|
855
|
+
it('sets a hyperlink with --link', async () => {
|
|
856
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
857
|
+
const handlers = setupHandlers();
|
|
858
|
+
await handlers.get('gog_docs_format')!({ docId: 'd1', match: 'see docs', link: 'https://example.com' });
|
|
859
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
860
|
+
['docs', 'format', 'd1', '--match=see docs', '--link=https://example.com'],
|
|
861
|
+
{ account: undefined },
|
|
862
|
+
);
|
|
863
|
+
});
|
|
864
|
+
|
|
865
|
+
it('clears a hyperlink with --no-link', async () => {
|
|
866
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
867
|
+
const handlers = setupHandlers();
|
|
868
|
+
await handlers.get('gog_docs_format')!({ docId: 'd1', match: 'linked', noLink: true });
|
|
869
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
870
|
+
['docs', 'format', 'd1', '--match=linked', '--no-link'],
|
|
871
|
+
{ account: undefined },
|
|
872
|
+
);
|
|
873
|
+
});
|
|
721
874
|
});
|
|
722
875
|
|
|
723
876
|
// --- gog 0.18.0 new tools ---
|
|
@@ -733,6 +886,17 @@ describe('gog_docs_insert_page_break', () => {
|
|
|
733
886
|
);
|
|
734
887
|
});
|
|
735
888
|
|
|
889
|
+
// gog 0.23.0
|
|
890
|
+
it('anchors by text with --at/--occurrence/--match-case', async () => {
|
|
891
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
892
|
+
const handlers = setupHandlers();
|
|
893
|
+
await handlers.get('gog_docs_insert_page_break')!({ docId: 'd1', at: 'Chapter 2', occurrence: 1, matchCase: true });
|
|
894
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
895
|
+
['docs', 'insert-page-break', 'd1', '--at=Chapter 2', '--occurrence=1', '--match-case'],
|
|
896
|
+
{ account: undefined },
|
|
897
|
+
);
|
|
898
|
+
});
|
|
899
|
+
|
|
736
900
|
it('uses --at-end when atEnd is true', async () => {
|
|
737
901
|
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
738
902
|
const handlers = setupHandlers();
|
|
@@ -897,6 +1061,17 @@ describe('gog_docs_insert_person', () => {
|
|
|
897
1061
|
);
|
|
898
1062
|
});
|
|
899
1063
|
|
|
1064
|
+
// gog 0.23.0
|
|
1065
|
+
it('anchors by text with --at/--occurrence/--match-case', async () => {
|
|
1066
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
1067
|
+
const handlers = setupHandlers();
|
|
1068
|
+
await handlers.get('gog_docs_insert_person')!({ docId: 'd1', email: 'a@b.com', at: '@alice', occurrence: 1, matchCase: true });
|
|
1069
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
1070
|
+
['docs', 'insert-person', 'd1', '--email=a@b.com', '--at=@alice', '--occurrence=1', '--match-case'],
|
|
1071
|
+
{ account: undefined },
|
|
1072
|
+
);
|
|
1073
|
+
});
|
|
1074
|
+
|
|
900
1075
|
it('passes --index, --at-end and --tab', async () => {
|
|
901
1076
|
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
902
1077
|
const handlers = setupHandlers();
|