gogcli-mcp-docs 2.5.0 → 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 +88 -9
- package/manifest.json +9 -1
- package/package.json +1 -1
- package/server.json +2 -2
- package/src/tools/docs-extra.ts +90 -8
- package/tests/tools/docs-extra.test.ts +117 -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
|
@@ -31318,7 +31318,7 @@ var failIfNotEmptyParam = external_exports.boolean().optional().describe(
|
|
|
31318
31318
|
);
|
|
31319
31319
|
|
|
31320
31320
|
// ../gogcli-mcp/src/server.ts
|
|
31321
|
-
var VERSION = true ? "2.
|
|
31321
|
+
var VERSION = true ? "2.6.0" : "0.0.0";
|
|
31322
31322
|
function createServer(options) {
|
|
31323
31323
|
return new McpServer({
|
|
31324
31324
|
name: options?.name ?? "gogcli",
|
|
@@ -31346,13 +31346,22 @@ function registerExtraDocsTools(server2) {
|
|
|
31346
31346
|
annotations: { destructiveHint: true },
|
|
31347
31347
|
inputSchema: {
|
|
31348
31348
|
docId: external_exports.string().describe("Doc ID (from the URL)"),
|
|
31349
|
-
start: external_exports.number().describe("Start index (character position, 1-based)"),
|
|
31350
|
-
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"),
|
|
31351
31354
|
tabId: external_exports.string().optional().describe("Tab ID to delete content from (for multi-tab docs)"),
|
|
31352
31355
|
account: accountParam
|
|
31353
31356
|
}
|
|
31354
|
-
}, async ({ docId, start, end, tabId, account }) => {
|
|
31355
|
-
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");
|
|
31356
31365
|
if (tabId) args.push(`--tab-id=${tabId}`);
|
|
31357
31366
|
return runOrDiagnose(args, { account });
|
|
31358
31367
|
});
|
|
@@ -31424,6 +31433,8 @@ function registerExtraDocsTools(server2) {
|
|
|
31424
31433
|
strikethrough: external_exports.boolean().optional().describe("Set strikethrough"),
|
|
31425
31434
|
noStrikethrough: external_exports.boolean().optional().describe("Clear strikethrough"),
|
|
31426
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"),
|
|
31427
31438
|
alignment: external_exports.enum(["left", "center", "right", "justify", "start", "end", "justified"]).optional().describe("Paragraph alignment"),
|
|
31428
31439
|
lineSpacing: external_exports.number().optional().describe("Line spacing percentage (e.g. 100 for single, 150 for 1.5x, 200 for double)"),
|
|
31429
31440
|
headingLevel: external_exports.number().int().optional().describe("Set paragraph named style to HEADING_1..HEADING_6 (shortcut for namedStyle=HEADING_N)"),
|
|
@@ -31450,6 +31461,8 @@ function registerExtraDocsTools(server2) {
|
|
|
31450
31461
|
if (a.strikethrough) argv.push("--strikethrough");
|
|
31451
31462
|
if (a.noStrikethrough) argv.push("--no-strikethrough");
|
|
31452
31463
|
if (a.code) argv.push("--code");
|
|
31464
|
+
if (a.link) argv.push(`--link=${a.link}`);
|
|
31465
|
+
if (a.noLink) argv.push("--no-link");
|
|
31453
31466
|
if (a.alignment) argv.push(`--alignment=${a.alignment}`);
|
|
31454
31467
|
if (a.lineSpacing !== void 0) argv.push(`--line-spacing=${a.lineSpacing}`);
|
|
31455
31468
|
if (a.headingLevel !== void 0) argv.push(`--heading-level=${a.headingLevel}`);
|
|
@@ -31479,14 +31492,20 @@ function registerExtraDocsTools(server2) {
|
|
|
31479
31492
|
content: external_exports.string().optional().describe("Text content to insert"),
|
|
31480
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."),
|
|
31481
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"),
|
|
31482
31498
|
tabId: external_exports.string().optional().describe("Tab ID to insert into (for multi-tab docs)"),
|
|
31483
31499
|
account: accountParam
|
|
31484
31500
|
}
|
|
31485
|
-
}, async ({ docId, content, index, file: file2, tabId, account }) => {
|
|
31501
|
+
}, async ({ docId, content, index, file: file2, at, occurrence, matchCase, tabId, account }) => {
|
|
31486
31502
|
const args = ["docs", "insert", docId];
|
|
31487
31503
|
if (content) args.push(content);
|
|
31488
31504
|
if (index !== void 0) args.push(`--index=${index}`);
|
|
31489
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");
|
|
31490
31509
|
if (tabId) args.push(`--tab-id=${tabId}`);
|
|
31491
31510
|
return runOrDiagnose(args, { account });
|
|
31492
31511
|
});
|
|
@@ -31552,17 +31571,23 @@ function registerExtraDocsTools(server2) {
|
|
|
31552
31571
|
index: external_exports.number().optional().describe("Character index to insert at"),
|
|
31553
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.'),
|
|
31554
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"),
|
|
31555
31577
|
tabId: external_exports.string().optional().describe("Tab ID for multi-tab docs"),
|
|
31556
31578
|
pageless: external_exports.boolean().optional().describe("Set document to pageless format"),
|
|
31557
31579
|
account: accountParam
|
|
31558
31580
|
}
|
|
31559
|
-
}, 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 }) => {
|
|
31560
31582
|
const args = ["docs", "update", docId];
|
|
31561
31583
|
if (text) args.push(`--text=${text}`);
|
|
31562
31584
|
if (file2) args.push(`--file=${file2}`);
|
|
31563
31585
|
if (index !== void 0) args.push(`--index=${index}`);
|
|
31564
31586
|
if (replaceRange) args.push(`--replace-range=${replaceRange}`);
|
|
31565
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");
|
|
31566
31591
|
if (tabId) args.push(`--tab-id=${tabId}`);
|
|
31567
31592
|
if (pageless) args.push("--pageless");
|
|
31568
31593
|
return runOrDiagnose(args, { account });
|
|
@@ -31655,6 +31680,48 @@ function registerExtraDocsTools(server2) {
|
|
|
31655
31680
|
}, async ({ docId, commentId, account }) => {
|
|
31656
31681
|
return runOrDiagnose(["docs", "comments", "reopen", docId, commentId], { account });
|
|
31657
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
|
+
});
|
|
31658
31725
|
server2.registerTool("gog_docs_table_column_width", {
|
|
31659
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.",
|
|
31660
31727
|
annotations: { destructiveHint: true },
|
|
@@ -31683,13 +31750,19 @@ function registerExtraDocsTools(server2) {
|
|
|
31683
31750
|
docId: external_exports.string().describe("Doc ID (from the URL)"),
|
|
31684
31751
|
index: external_exports.number().int().optional().describe("Character index to insert at (1 = beginning). Omit or use atEnd for end-of-doc."),
|
|
31685
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"),
|
|
31686
31756
|
tab: external_exports.string().optional().describe("Target a specific tab by title or ID"),
|
|
31687
31757
|
account: accountParam
|
|
31688
31758
|
}
|
|
31689
|
-
}, async ({ docId, index, atEnd, tab, account }) => {
|
|
31759
|
+
}, async ({ docId, index, atEnd, at, occurrence, matchCase, tab, account }) => {
|
|
31690
31760
|
const args = ["docs", "insert-page-break", docId];
|
|
31691
31761
|
if (index !== void 0) args.push(`--index=${index}`);
|
|
31692
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");
|
|
31693
31766
|
if (tab) args.push(`--tab=${tab}`);
|
|
31694
31767
|
return runOrDiagnose(args, { account });
|
|
31695
31768
|
});
|
|
@@ -31831,13 +31904,19 @@ function registerExtraDocsTools(server2) {
|
|
|
31831
31904
|
email: external_exports.string().describe("Email address for the person chip"),
|
|
31832
31905
|
index: external_exports.number().int().optional().describe("Character index to insert at. Omit or use atEnd for end-of-doc."),
|
|
31833
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"),
|
|
31834
31910
|
tab: external_exports.string().optional().describe("Target a specific tab by title or ID"),
|
|
31835
31911
|
account: accountParam
|
|
31836
31912
|
}
|
|
31837
|
-
}, async ({ docId, email: email3, index, atEnd, tab, account }) => {
|
|
31913
|
+
}, async ({ docId, email: email3, index, atEnd, at, occurrence, matchCase, tab, account }) => {
|
|
31838
31914
|
const args = ["docs", "insert-person", docId, `--email=${email3}`];
|
|
31839
31915
|
if (index !== void 0) args.push(`--index=${index}`);
|
|
31840
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");
|
|
31841
31920
|
if (tab) args.push(`--tab=${tab}`);
|
|
31842
31921
|
return runOrDiagnose(args, { account });
|
|
31843
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,14 @@
|
|
|
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
|
+
},
|
|
198
206
|
{
|
|
199
207
|
"name": "gog_docs_table_column_width",
|
|
200
208
|
"description": "Set a fixed table column width in points, or reset columns to Docs-managed even distribution."
|
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
|
@@ -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
|
});
|
|
@@ -104,6 +113,8 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
104
113
|
strikethrough: z.boolean().optional().describe('Set strikethrough'),
|
|
105
114
|
noStrikethrough: z.boolean().optional().describe('Clear strikethrough'),
|
|
106
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'),
|
|
107
118
|
alignment: z.enum(['left', 'center', 'right', 'justify', 'start', 'end', 'justified']).optional().describe('Paragraph alignment'),
|
|
108
119
|
lineSpacing: z.number().optional().describe('Line spacing percentage (e.g. 100 for single, 150 for 1.5x, 200 for double)'),
|
|
109
120
|
headingLevel: z.number().int().optional().describe('Set paragraph named style to HEADING_1..HEADING_6 (shortcut for namedStyle=HEADING_N)'),
|
|
@@ -126,6 +137,7 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
126
137
|
underline?: boolean; noUnderline?: boolean;
|
|
127
138
|
strikethrough?: boolean; noStrikethrough?: boolean;
|
|
128
139
|
code?: boolean;
|
|
140
|
+
link?: string; noLink?: boolean;
|
|
129
141
|
alignment?: string;
|
|
130
142
|
lineSpacing?: number;
|
|
131
143
|
headingLevel?: number;
|
|
@@ -150,6 +162,8 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
150
162
|
if (a.strikethrough) argv.push('--strikethrough');
|
|
151
163
|
if (a.noStrikethrough) argv.push('--no-strikethrough');
|
|
152
164
|
if (a.code) argv.push('--code');
|
|
165
|
+
if (a.link) argv.push(`--link=${a.link}`);
|
|
166
|
+
if (a.noLink) argv.push('--no-link');
|
|
153
167
|
if (a.alignment) argv.push(`--alignment=${a.alignment}`);
|
|
154
168
|
if (a.lineSpacing !== undefined) argv.push(`--line-spacing=${a.lineSpacing}`);
|
|
155
169
|
if (a.headingLevel !== undefined) argv.push(`--heading-level=${a.headingLevel}`);
|
|
@@ -181,14 +195,20 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
181
195
|
content: z.string().optional().describe('Text content to insert'),
|
|
182
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.'),
|
|
183
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'),
|
|
184
201
|
tabId: z.string().optional().describe('Tab ID to insert into (for multi-tab docs)'),
|
|
185
202
|
account: accountParam,
|
|
186
203
|
},
|
|
187
|
-
}, async ({ docId, content, index, file, tabId, account }) => {
|
|
204
|
+
}, async ({ docId, content, index, file, at, occurrence, matchCase, tabId, account }) => {
|
|
188
205
|
const args = ['docs', 'insert', docId];
|
|
189
206
|
if (content) args.push(content);
|
|
190
207
|
if (index !== undefined) args.push(`--index=${index}`);
|
|
191
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');
|
|
192
212
|
if (tabId) args.push(`--tab-id=${tabId}`);
|
|
193
213
|
return runOrDiagnose(args, { account });
|
|
194
214
|
});
|
|
@@ -258,17 +278,23 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
258
278
|
index: z.number().optional().describe('Character index to insert at'),
|
|
259
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.'),
|
|
260
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'),
|
|
261
284
|
tabId: z.string().optional().describe('Tab ID for multi-tab docs'),
|
|
262
285
|
pageless: z.boolean().optional().describe('Set document to pageless format'),
|
|
263
286
|
account: accountParam,
|
|
264
287
|
},
|
|
265
|
-
}, async ({ docId, text, file, index, replaceRange, markdown, tabId, pageless, account }) => {
|
|
288
|
+
}, async ({ docId, text, file, index, replaceRange, markdown, at, occurrence, matchCase, tabId, pageless, account }) => {
|
|
266
289
|
const args = ['docs', 'update', docId];
|
|
267
290
|
if (text) args.push(`--text=${text}`);
|
|
268
291
|
if (file) args.push(`--file=${file}`);
|
|
269
292
|
if (index !== undefined) args.push(`--index=${index}`);
|
|
270
293
|
if (replaceRange) args.push(`--replace-range=${replaceRange}`);
|
|
271
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');
|
|
272
298
|
if (tabId) args.push(`--tab-id=${tabId}`);
|
|
273
299
|
if (pageless) args.push('--pageless');
|
|
274
300
|
return runOrDiagnose(args, { account });
|
|
@@ -372,6 +398,50 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
372
398
|
return runOrDiagnose(['docs', 'comments', 'reopen', docId, commentId], { account });
|
|
373
399
|
});
|
|
374
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
|
+
|
|
375
445
|
server.registerTool('gog_docs_table_column_width', {
|
|
376
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.',
|
|
377
447
|
annotations: { destructiveHint: true },
|
|
@@ -401,13 +471,19 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
401
471
|
docId: z.string().describe('Doc ID (from the URL)'),
|
|
402
472
|
index: z.number().int().optional().describe('Character index to insert at (1 = beginning). Omit or use atEnd for end-of-doc.'),
|
|
403
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'),
|
|
404
477
|
tab: z.string().optional().describe('Target a specific tab by title or ID'),
|
|
405
478
|
account: accountParam,
|
|
406
479
|
},
|
|
407
|
-
}, async ({ docId, index, atEnd, tab, account }) => {
|
|
480
|
+
}, async ({ docId, index, atEnd, at, occurrence, matchCase, tab, account }) => {
|
|
408
481
|
const args = ['docs', 'insert-page-break', docId];
|
|
409
482
|
if (index !== undefined) args.push(`--index=${index}`);
|
|
410
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');
|
|
411
487
|
if (tab) args.push(`--tab=${tab}`);
|
|
412
488
|
return runOrDiagnose(args, { account });
|
|
413
489
|
});
|
|
@@ -555,13 +631,19 @@ export function registerExtraDocsTools(server: McpServer): void {
|
|
|
555
631
|
email: z.string().describe('Email address for the person chip'),
|
|
556
632
|
index: z.number().int().optional().describe('Character index to insert at. Omit or use atEnd for end-of-doc.'),
|
|
557
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'),
|
|
558
637
|
tab: z.string().optional().describe('Target a specific tab by title or ID'),
|
|
559
638
|
account: accountParam,
|
|
560
639
|
},
|
|
561
|
-
}, async ({ docId, email, index, atEnd, tab, account }) => {
|
|
640
|
+
}, async ({ docId, email, index, atEnd, at, occurrence, matchCase, tab, account }) => {
|
|
562
641
|
const args = ['docs', 'insert-person', docId, `--email=${email}`];
|
|
563
642
|
if (index !== undefined) args.push(`--index=${index}`);
|
|
564
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');
|
|
565
647
|
if (tab) args.push(`--tab=${tab}`);
|
|
566
648
|
return runOrDiagnose(args, { account });
|
|
567
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();
|
|
@@ -495,6 +528,47 @@ describe('gog_docs_table_column_width', () => {
|
|
|
495
528
|
});
|
|
496
529
|
});
|
|
497
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
|
+
});
|
|
570
|
+
});
|
|
571
|
+
|
|
498
572
|
describe('gog_docs_comments_get', () => {
|
|
499
573
|
it('calls runOrDiagnose with correct args', async () => {
|
|
500
574
|
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
@@ -776,6 +850,27 @@ describe('gog_docs_format', () => {
|
|
|
776
850
|
{ account: undefined },
|
|
777
851
|
);
|
|
778
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
|
+
});
|
|
779
874
|
});
|
|
780
875
|
|
|
781
876
|
// --- gog 0.18.0 new tools ---
|
|
@@ -791,6 +886,17 @@ describe('gog_docs_insert_page_break', () => {
|
|
|
791
886
|
);
|
|
792
887
|
});
|
|
793
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
|
+
|
|
794
900
|
it('uses --at-end when atEnd is true', async () => {
|
|
795
901
|
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
796
902
|
const handlers = setupHandlers();
|
|
@@ -955,6 +1061,17 @@ describe('gog_docs_insert_person', () => {
|
|
|
955
1061
|
);
|
|
956
1062
|
});
|
|
957
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
|
+
|
|
958
1075
|
it('passes --index, --at-end and --tab', async () => {
|
|
959
1076
|
vi.mocked(lib.runOrDiagnose).mockResolvedValue(toText('{}'));
|
|
960
1077
|
const handlers = setupHandlers();
|