gdocs-mcp 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auth/google-auth.d.ts +1 -0
- package/dist/auth/google-auth.js +21 -2
- package/dist/index.js +20 -1
- package/dist/tools/add-comment.d.ts +14 -0
- package/dist/tools/add-comment.js +36 -0
- package/dist/tools/copy-document.d.ts +12 -0
- package/dist/tools/copy-document.js +28 -0
- package/dist/tools/delete-document.d.ts +12 -0
- package/dist/tools/delete-document.js +36 -0
- package/dist/tools/export-document.d.ts +26 -0
- package/dist/tools/export-document.js +43 -0
- package/dist/tools/get-comments.d.ts +25 -0
- package/dist/tools/get-comments.js +44 -0
- package/dist/tools/insert-image.d.ts +14 -0
- package/dist/tools/insert-image.js +43 -0
- package/dist/tools/insert-link.d.ts +14 -0
- package/dist/tools/insert-link.js +38 -0
- package/dist/tools/insert-page-break.d.ts +10 -0
- package/dist/tools/insert-page-break.js +30 -0
- package/dist/tools/insert-table.d.ts +14 -0
- package/dist/tools/insert-table.js +36 -0
- package/package.json +1 -1
|
@@ -3,6 +3,7 @@ declare const CONFIG_DIR: string;
|
|
|
3
3
|
declare const TOKEN_PATH: string;
|
|
4
4
|
declare const CREDENTIALS_PATH_DEFAULT: string;
|
|
5
5
|
export declare function getAuthClient(): OAuth2Client;
|
|
6
|
+
export declare function isReadOnlyMode(): boolean;
|
|
6
7
|
export declare function getConfigDir(): string;
|
|
7
8
|
export declare function getTokenPath(): string;
|
|
8
9
|
export declare function getScopes(): string[];
|
package/dist/auth/google-auth.js
CHANGED
|
@@ -4,7 +4,13 @@ import path from 'path';
|
|
|
4
4
|
const CONFIG_DIR = path.join(process.env.HOME || '~', '.gdocs-mcp');
|
|
5
5
|
const TOKEN_PATH = path.join(CONFIG_DIR, 'token.json');
|
|
6
6
|
const CREDENTIALS_PATH_DEFAULT = path.join(CONFIG_DIR, 'credentials.json');
|
|
7
|
-
const
|
|
7
|
+
const READONLY = process.env.GDOCS_MCP_READONLY === '1';
|
|
8
|
+
const FULL_SCOPES = [
|
|
9
|
+
'https://www.googleapis.com/auth/documents',
|
|
10
|
+
'https://www.googleapis.com/auth/spreadsheets.readonly',
|
|
11
|
+
'https://www.googleapis.com/auth/drive',
|
|
12
|
+
];
|
|
13
|
+
const READONLY_SCOPES = [
|
|
8
14
|
'https://www.googleapis.com/auth/documents',
|
|
9
15
|
'https://www.googleapis.com/auth/spreadsheets.readonly',
|
|
10
16
|
'https://www.googleapis.com/auth/drive.readonly',
|
|
@@ -31,6 +37,16 @@ export function getAuthClient() {
|
|
|
31
37
|
throw new Error('Auth required. Run: npx gdocs-mcp auth');
|
|
32
38
|
}
|
|
33
39
|
const token = JSON.parse(fs.readFileSync(TOKEN_PATH, 'utf8'));
|
|
40
|
+
// Check if token has sufficient scopes for full mode
|
|
41
|
+
if (!READONLY && token.scope) {
|
|
42
|
+
const grantedScopes = token.scope.split(' ');
|
|
43
|
+
const hasDriveWrite = grantedScopes.some((s) => s === 'https://www.googleapis.com/auth/drive');
|
|
44
|
+
if (!hasDriveWrite) {
|
|
45
|
+
throw new Error('Auth scopes insufficient. v0.4 requires expanded Drive access for export, copy, delete, and comments.\n' +
|
|
46
|
+
'Run: npx gdocs-mcp auth\n' +
|
|
47
|
+
'Or set GDOCS_MCP_READONLY=1 to use read-only mode with the original scope.');
|
|
48
|
+
}
|
|
49
|
+
}
|
|
34
50
|
oAuth2Client.setCredentials(token);
|
|
35
51
|
oAuth2Client.on('tokens', (newTokens) => {
|
|
36
52
|
const existing = JSON.parse(fs.readFileSync(TOKEN_PATH, 'utf8'));
|
|
@@ -39,6 +55,9 @@ export function getAuthClient() {
|
|
|
39
55
|
});
|
|
40
56
|
return oAuth2Client;
|
|
41
57
|
}
|
|
58
|
+
export function isReadOnlyMode() {
|
|
59
|
+
return READONLY;
|
|
60
|
+
}
|
|
42
61
|
export function getConfigDir() {
|
|
43
62
|
return CONFIG_DIR;
|
|
44
63
|
}
|
|
@@ -46,6 +65,6 @@ export function getTokenPath() {
|
|
|
46
65
|
return TOKEN_PATH;
|
|
47
66
|
}
|
|
48
67
|
export function getScopes() {
|
|
49
|
-
return
|
|
68
|
+
return READONLY ? READONLY_SCOPES : FULL_SCOPES;
|
|
50
69
|
}
|
|
51
70
|
export { CONFIG_DIR, TOKEN_PATH, CREDENTIALS_PATH_DEFAULT };
|
package/dist/index.js
CHANGED
|
@@ -20,7 +20,16 @@ import { DeleteStylePresetSchema, deleteStylePreset } from './tools/delete-style
|
|
|
20
20
|
import { UpdateHeaderFooterSchema, updateHeaderFooter } from './tools/update-header-footer.js';
|
|
21
21
|
import { FormatListSchema, formatList } from './tools/format-list.js';
|
|
22
22
|
import { ExecuteScriptSchema, executeScript } from './tools/execute-script.js';
|
|
23
|
-
|
|
23
|
+
import { InsertImageSchema, insertImage } from './tools/insert-image.js';
|
|
24
|
+
import { InsertTableSchema, insertTable } from './tools/insert-table.js';
|
|
25
|
+
import { InsertPageBreakSchema, insertPageBreak } from './tools/insert-page-break.js';
|
|
26
|
+
import { InsertLinkSchema, insertLink } from './tools/insert-link.js';
|
|
27
|
+
import { ExportDocumentSchema, exportDocument } from './tools/export-document.js';
|
|
28
|
+
import { CopyDocumentSchema, copyDocument } from './tools/copy-document.js';
|
|
29
|
+
import { DeleteDocumentSchema, deleteDocument } from './tools/delete-document.js';
|
|
30
|
+
import { GetCommentsSchema, getComments } from './tools/get-comments.js';
|
|
31
|
+
import { AddCommentSchema, addComment } from './tools/add-comment.js';
|
|
32
|
+
const VERSION = '0.4.0';
|
|
24
33
|
const QUIET = process.env.GDOCS_MCP_QUIET === '1';
|
|
25
34
|
const server = new McpServer({
|
|
26
35
|
name: 'gdocs-mcp',
|
|
@@ -106,6 +115,16 @@ registerTool('delete_style_preset', 'Delete a user-defined style preset. Cannot
|
|
|
106
115
|
registerTool('update_header_footer', 'Create or update header/footer content and styling. Supports page number insertion.', UpdateHeaderFooterSchema, updateHeaderFooter);
|
|
107
116
|
registerTool('format_list', 'Apply bullet, numbered, or remove list formatting on a paragraph range. Supports 6 glyph presets.', FormatListSchema, formatList);
|
|
108
117
|
registerTool('execute_script', 'Execute a function in a deployed Google Apps Script project. Returns JSON result.', ExecuteScriptSchema, executeScript);
|
|
118
|
+
// v0.4 content tools
|
|
119
|
+
registerTool('insert_image', 'Insert an image from a URL at a specific position. Supports optional width/height sizing in PT.', InsertImageSchema, insertImage);
|
|
120
|
+
registerTool('insert_table', 'Create a table with specified rows (1-20) and columns (1-20) at a position.', InsertTableSchema, insertTable);
|
|
121
|
+
registerTool('insert_page_break', 'Insert a page break at a specific position.', InsertPageBreakSchema, insertPageBreak);
|
|
122
|
+
registerTool('insert_link', 'Add a hyperlink to an existing text range. The text must already exist in the document.', InsertLinkSchema, insertLink);
|
|
123
|
+
registerTool('export_document', 'Export a Google Doc as PDF, DOCX, TXT, or HTML. Saves to file or returns base64.', ExportDocumentSchema, exportDocument);
|
|
124
|
+
registerTool('copy_document', 'Create a copy of a Google Doc with a new title. Inherits content and formatting.', CopyDocumentSchema, copyDocument);
|
|
125
|
+
registerTool('delete_document', 'Move a Google Doc to trash. Requires confirmTitle matching the exact document title.', DeleteDocumentSchema, deleteDocument);
|
|
126
|
+
registerTool('get_comments', 'List comments on a document with author, content, resolved status, and replies.', GetCommentsSchema, getComments);
|
|
127
|
+
registerTool('add_comment', 'Add a comment anchored to specific text in the document. Anchors to first occurrence of quotedText.', AddCommentSchema, addComment);
|
|
109
128
|
async function main() {
|
|
110
129
|
const transport = new StdioServerTransport();
|
|
111
130
|
await server.connect(transport);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const AddCommentSchema: z.ZodObject<{
|
|
3
|
+
documentId: z.ZodString;
|
|
4
|
+
content: z.ZodString;
|
|
5
|
+
quotedText: z.ZodString;
|
|
6
|
+
}, z.core.$strip>;
|
|
7
|
+
export declare function addComment(args: z.infer<typeof AddCommentSchema>): Promise<{
|
|
8
|
+
documentId: string;
|
|
9
|
+
commentId: string | null | undefined;
|
|
10
|
+
content: string | null | undefined;
|
|
11
|
+
quotedText: string | undefined;
|
|
12
|
+
createdTime: string | null | undefined;
|
|
13
|
+
_apiMs: number;
|
|
14
|
+
}>;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { google } from 'googleapis';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { getAuthClient, isReadOnlyMode } from '../auth/google-auth.js';
|
|
4
|
+
export const AddCommentSchema = z.object({
|
|
5
|
+
documentId: z.string().describe('The Google Doc document ID'),
|
|
6
|
+
content: z.string().describe('The comment text'),
|
|
7
|
+
quotedText: z.string().describe('Text in the document to anchor the comment to. Google matches the first occurrence.'),
|
|
8
|
+
});
|
|
9
|
+
export async function addComment(args) {
|
|
10
|
+
if (isReadOnlyMode()) {
|
|
11
|
+
throw new Error('add_comment is not available in read-only mode. Remove GDOCS_MCP_READONLY=1 and re-auth.');
|
|
12
|
+
}
|
|
13
|
+
const auth = getAuthClient();
|
|
14
|
+
const drive = google.drive({ version: 'v3', auth });
|
|
15
|
+
const apiStart = performance.now();
|
|
16
|
+
const res = await drive.comments.create({
|
|
17
|
+
fileId: args.documentId,
|
|
18
|
+
fields: 'id,author(displayName),content,quotedFileContent,createdTime',
|
|
19
|
+
requestBody: {
|
|
20
|
+
content: args.content,
|
|
21
|
+
quotedFileContent: {
|
|
22
|
+
value: args.quotedText,
|
|
23
|
+
mimeType: 'text/html',
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
const apiMs = Math.round(performance.now() - apiStart);
|
|
28
|
+
return {
|
|
29
|
+
documentId: args.documentId,
|
|
30
|
+
commentId: res.data.id,
|
|
31
|
+
content: res.data.content,
|
|
32
|
+
quotedText: res.data.quotedFileContent?.value,
|
|
33
|
+
createdTime: res.data.createdTime,
|
|
34
|
+
_apiMs: apiMs,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const CopyDocumentSchema: z.ZodObject<{
|
|
3
|
+
documentId: z.ZodString;
|
|
4
|
+
newTitle: z.ZodString;
|
|
5
|
+
}, z.core.$strip>;
|
|
6
|
+
export declare function copyDocument(args: z.infer<typeof CopyDocumentSchema>): Promise<{
|
|
7
|
+
originalDocumentId: string;
|
|
8
|
+
newDocumentId: string | null | undefined;
|
|
9
|
+
title: string | null | undefined;
|
|
10
|
+
url: string | null | undefined;
|
|
11
|
+
_apiMs: number;
|
|
12
|
+
}>;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { google } from 'googleapis';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { getAuthClient, isReadOnlyMode } from '../auth/google-auth.js';
|
|
4
|
+
export const CopyDocumentSchema = z.object({
|
|
5
|
+
documentId: z.string().describe('The Google Doc document ID to copy'),
|
|
6
|
+
newTitle: z.string().describe('Title for the copy'),
|
|
7
|
+
});
|
|
8
|
+
export async function copyDocument(args) {
|
|
9
|
+
if (isReadOnlyMode()) {
|
|
10
|
+
throw new Error('copy_document is not available in read-only mode. Remove GDOCS_MCP_READONLY=1 and re-auth.');
|
|
11
|
+
}
|
|
12
|
+
const auth = getAuthClient();
|
|
13
|
+
const drive = google.drive({ version: 'v3', auth });
|
|
14
|
+
const apiStart = performance.now();
|
|
15
|
+
const res = await drive.files.copy({
|
|
16
|
+
fileId: args.documentId,
|
|
17
|
+
requestBody: { name: args.newTitle },
|
|
18
|
+
fields: 'id, name, webViewLink',
|
|
19
|
+
});
|
|
20
|
+
const apiMs = Math.round(performance.now() - apiStart);
|
|
21
|
+
return {
|
|
22
|
+
originalDocumentId: args.documentId,
|
|
23
|
+
newDocumentId: res.data.id,
|
|
24
|
+
title: res.data.name,
|
|
25
|
+
url: res.data.webViewLink,
|
|
26
|
+
_apiMs: apiMs,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const DeleteDocumentSchema: z.ZodObject<{
|
|
3
|
+
documentId: z.ZodString;
|
|
4
|
+
confirmTitle: z.ZodString;
|
|
5
|
+
}, z.core.$strip>;
|
|
6
|
+
export declare function deleteDocument(args: z.infer<typeof DeleteDocumentSchema>): Promise<{
|
|
7
|
+
documentId: string;
|
|
8
|
+
title: string;
|
|
9
|
+
trashed: boolean;
|
|
10
|
+
message: string;
|
|
11
|
+
_apiMs: number;
|
|
12
|
+
}>;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { google } from 'googleapis';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { getAuthClient, isReadOnlyMode } from '../auth/google-auth.js';
|
|
4
|
+
export const DeleteDocumentSchema = z.object({
|
|
5
|
+
documentId: z.string().describe('The Google Doc document ID to trash'),
|
|
6
|
+
confirmTitle: z.string().describe('Must match the exact document title to confirm deletion. This prevents accidental trashing.'),
|
|
7
|
+
});
|
|
8
|
+
export async function deleteDocument(args) {
|
|
9
|
+
if (isReadOnlyMode()) {
|
|
10
|
+
throw new Error('delete_document is not available in read-only mode. Remove GDOCS_MCP_READONLY=1 and re-auth.');
|
|
11
|
+
}
|
|
12
|
+
const auth = getAuthClient();
|
|
13
|
+
const drive = google.drive({ version: 'v3', auth });
|
|
14
|
+
const apiStart = performance.now();
|
|
15
|
+
// Get the actual document title
|
|
16
|
+
const file = await drive.files.get({
|
|
17
|
+
fileId: args.documentId,
|
|
18
|
+
fields: 'name',
|
|
19
|
+
});
|
|
20
|
+
const actualTitle = file.data.name;
|
|
21
|
+
if (args.confirmTitle !== actualTitle) {
|
|
22
|
+
throw new Error(`Confirmation failed. To trash this document, pass confirmTitle: "${actualTitle}"`);
|
|
23
|
+
}
|
|
24
|
+
await drive.files.update({
|
|
25
|
+
fileId: args.documentId,
|
|
26
|
+
requestBody: { trashed: true },
|
|
27
|
+
});
|
|
28
|
+
const apiMs = Math.round(performance.now() - apiStart);
|
|
29
|
+
return {
|
|
30
|
+
documentId: args.documentId,
|
|
31
|
+
title: actualTitle,
|
|
32
|
+
trashed: true,
|
|
33
|
+
message: 'Document moved to trash. It can be recovered from Google Drive trash.',
|
|
34
|
+
_apiMs: apiMs,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const ExportDocumentSchema: z.ZodObject<{
|
|
3
|
+
documentId: z.ZodString;
|
|
4
|
+
format: z.ZodEnum<{
|
|
5
|
+
pdf: "pdf";
|
|
6
|
+
docx: "docx";
|
|
7
|
+
txt: "txt";
|
|
8
|
+
html: "html";
|
|
9
|
+
}>;
|
|
10
|
+
outputPath: z.ZodOptional<z.ZodString>;
|
|
11
|
+
}, z.core.$strip>;
|
|
12
|
+
export declare function exportDocument(args: z.infer<typeof ExportDocumentSchema>): Promise<{
|
|
13
|
+
documentId: string;
|
|
14
|
+
format: "pdf" | "docx" | "txt" | "html";
|
|
15
|
+
savedTo: string;
|
|
16
|
+
sizeBytes: number;
|
|
17
|
+
_apiMs: number;
|
|
18
|
+
content?: undefined;
|
|
19
|
+
} | {
|
|
20
|
+
documentId: string;
|
|
21
|
+
format: "pdf" | "docx" | "txt" | "html";
|
|
22
|
+
content: string;
|
|
23
|
+
sizeBytes: number;
|
|
24
|
+
_apiMs: number;
|
|
25
|
+
savedTo?: undefined;
|
|
26
|
+
}>;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { google } from 'googleapis';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import { getAuthClient, isReadOnlyMode } from '../auth/google-auth.js';
|
|
5
|
+
const MIME_TYPES = {
|
|
6
|
+
pdf: 'application/pdf',
|
|
7
|
+
docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
8
|
+
txt: 'text/plain',
|
|
9
|
+
html: 'text/html',
|
|
10
|
+
};
|
|
11
|
+
export const ExportDocumentSchema = z.object({
|
|
12
|
+
documentId: z.string().describe('The Google Doc document ID'),
|
|
13
|
+
format: z.enum(['pdf', 'docx', 'txt', 'html']).describe('Export format: pdf, docx, txt, or html'),
|
|
14
|
+
outputPath: z.string().optional().describe('Local file path to save the export. If omitted, returns base64-encoded content.'),
|
|
15
|
+
});
|
|
16
|
+
export async function exportDocument(args) {
|
|
17
|
+
if (isReadOnlyMode()) {
|
|
18
|
+
throw new Error('export_document is not available in read-only mode. Remove GDOCS_MCP_READONLY=1 and re-auth.');
|
|
19
|
+
}
|
|
20
|
+
const auth = getAuthClient();
|
|
21
|
+
const drive = google.drive({ version: 'v3', auth });
|
|
22
|
+
const apiStart = performance.now();
|
|
23
|
+
const res = await drive.files.export({ fileId: args.documentId, mimeType: MIME_TYPES[args.format] }, { responseType: 'arraybuffer' });
|
|
24
|
+
const buffer = Buffer.from(res.data);
|
|
25
|
+
const apiMs = Math.round(performance.now() - apiStart);
|
|
26
|
+
if (args.outputPath) {
|
|
27
|
+
fs.writeFileSync(args.outputPath, buffer);
|
|
28
|
+
return {
|
|
29
|
+
documentId: args.documentId,
|
|
30
|
+
format: args.format,
|
|
31
|
+
savedTo: args.outputPath,
|
|
32
|
+
sizeBytes: buffer.length,
|
|
33
|
+
_apiMs: apiMs,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
return {
|
|
37
|
+
documentId: args.documentId,
|
|
38
|
+
format: args.format,
|
|
39
|
+
content: buffer.toString('base64'),
|
|
40
|
+
sizeBytes: buffer.length,
|
|
41
|
+
_apiMs: apiMs,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const GetCommentsSchema: z.ZodObject<{
|
|
3
|
+
documentId: z.ZodString;
|
|
4
|
+
includeResolved: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
5
|
+
}, z.core.$strip>;
|
|
6
|
+
export declare function getComments(args: z.infer<typeof GetCommentsSchema>): Promise<{
|
|
7
|
+
documentId: string;
|
|
8
|
+
comments: {
|
|
9
|
+
id: string | null | undefined;
|
|
10
|
+
author: string;
|
|
11
|
+
authorEmail: string | null | undefined;
|
|
12
|
+
content: string | null | undefined;
|
|
13
|
+
quotedText: string | null;
|
|
14
|
+
resolved: boolean;
|
|
15
|
+
createdTime: string | null | undefined;
|
|
16
|
+
modifiedTime: string | null | undefined;
|
|
17
|
+
replies: {
|
|
18
|
+
author: string;
|
|
19
|
+
content: string | null | undefined;
|
|
20
|
+
createdTime: string | null | undefined;
|
|
21
|
+
}[];
|
|
22
|
+
}[];
|
|
23
|
+
count: number;
|
|
24
|
+
_apiMs: number;
|
|
25
|
+
}>;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { google } from 'googleapis';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { getAuthClient, isReadOnlyMode } from '../auth/google-auth.js';
|
|
4
|
+
export const GetCommentsSchema = z.object({
|
|
5
|
+
documentId: z.string().describe('The Google Doc document ID'),
|
|
6
|
+
includeResolved: z.boolean().optional().default(false).describe('Include resolved comments (default: false, only open comments)'),
|
|
7
|
+
});
|
|
8
|
+
export async function getComments(args) {
|
|
9
|
+
if (isReadOnlyMode()) {
|
|
10
|
+
throw new Error('get_comments is not available in read-only mode. Remove GDOCS_MCP_READONLY=1 and re-auth.');
|
|
11
|
+
}
|
|
12
|
+
const auth = getAuthClient();
|
|
13
|
+
const drive = google.drive({ version: 'v3', auth });
|
|
14
|
+
const apiStart = performance.now();
|
|
15
|
+
const res = await drive.comments.list({
|
|
16
|
+
fileId: args.documentId,
|
|
17
|
+
fields: 'comments(id,author(displayName,emailAddress),content,quotedFileContent,resolved,createdTime,modifiedTime,replies(author(displayName),content,createdTime))',
|
|
18
|
+
pageSize: 100,
|
|
19
|
+
});
|
|
20
|
+
const comments = (res.data.comments || [])
|
|
21
|
+
.filter(c => args.includeResolved || !c.resolved)
|
|
22
|
+
.map(c => ({
|
|
23
|
+
id: c.id,
|
|
24
|
+
author: c.author?.displayName || 'Unknown',
|
|
25
|
+
authorEmail: c.author?.emailAddress,
|
|
26
|
+
content: c.content,
|
|
27
|
+
quotedText: c.quotedFileContent?.value || null,
|
|
28
|
+
resolved: c.resolved || false,
|
|
29
|
+
createdTime: c.createdTime,
|
|
30
|
+
modifiedTime: c.modifiedTime,
|
|
31
|
+
replies: (c.replies || []).map(r => ({
|
|
32
|
+
author: r.author?.displayName || 'Unknown',
|
|
33
|
+
content: r.content,
|
|
34
|
+
createdTime: r.createdTime,
|
|
35
|
+
})),
|
|
36
|
+
}));
|
|
37
|
+
const apiMs = Math.round(performance.now() - apiStart);
|
|
38
|
+
return {
|
|
39
|
+
documentId: args.documentId,
|
|
40
|
+
comments,
|
|
41
|
+
count: comments.length,
|
|
42
|
+
_apiMs: apiMs,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const InsertImageSchema: z.ZodObject<{
|
|
3
|
+
documentId: z.ZodString;
|
|
4
|
+
uri: z.ZodString;
|
|
5
|
+
index: z.ZodNumber;
|
|
6
|
+
width: z.ZodOptional<z.ZodNumber>;
|
|
7
|
+
height: z.ZodOptional<z.ZodNumber>;
|
|
8
|
+
}, z.core.$strip>;
|
|
9
|
+
export declare function insertImage(args: z.infer<typeof InsertImageSchema>): Promise<{
|
|
10
|
+
documentId: string;
|
|
11
|
+
inlineObjectId: string | null | undefined;
|
|
12
|
+
index: number;
|
|
13
|
+
_apiMs: number;
|
|
14
|
+
}>;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { google } from 'googleapis';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { getAuthClient } from '../auth/google-auth.js';
|
|
4
|
+
export const InsertImageSchema = z.object({
|
|
5
|
+
documentId: z.string().describe('The Google Doc document ID'),
|
|
6
|
+
uri: z.string().url().describe('Public URL of the image to insert'),
|
|
7
|
+
index: z.number().describe('Position in the document to insert the image'),
|
|
8
|
+
width: z.number().optional().describe('Image width in PT. If only width is set, height scales proportionally.'),
|
|
9
|
+
height: z.number().optional().describe('Image height in PT. If only height is set, width scales proportionally.'),
|
|
10
|
+
});
|
|
11
|
+
export async function insertImage(args) {
|
|
12
|
+
const auth = getAuthClient();
|
|
13
|
+
const docs = google.docs({ version: 'v1', auth });
|
|
14
|
+
const apiStart = performance.now();
|
|
15
|
+
const objectSize = {};
|
|
16
|
+
if (args.width !== undefined) {
|
|
17
|
+
objectSize.width = { magnitude: args.width, unit: 'PT' };
|
|
18
|
+
}
|
|
19
|
+
if (args.height !== undefined) {
|
|
20
|
+
objectSize.height = { magnitude: args.height, unit: 'PT' };
|
|
21
|
+
}
|
|
22
|
+
const request = {
|
|
23
|
+
insertInlineImage: {
|
|
24
|
+
location: { index: args.index },
|
|
25
|
+
uri: args.uri,
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
if (Object.keys(objectSize).length > 0) {
|
|
29
|
+
request.insertInlineImage.objectSize = objectSize;
|
|
30
|
+
}
|
|
31
|
+
const res = await docs.documents.batchUpdate({
|
|
32
|
+
documentId: args.documentId,
|
|
33
|
+
requestBody: { requests: [request] },
|
|
34
|
+
});
|
|
35
|
+
const inlineObjectId = res.data.replies?.[0]?.insertInlineImage?.objectId;
|
|
36
|
+
const apiMs = Math.round(performance.now() - apiStart);
|
|
37
|
+
return {
|
|
38
|
+
documentId: args.documentId,
|
|
39
|
+
inlineObjectId,
|
|
40
|
+
index: args.index,
|
|
41
|
+
_apiMs: apiMs,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const InsertLinkSchema: z.ZodObject<{
|
|
3
|
+
documentId: z.ZodString;
|
|
4
|
+
url: z.ZodString;
|
|
5
|
+
startIndex: z.ZodNumber;
|
|
6
|
+
endIndex: z.ZodNumber;
|
|
7
|
+
}, z.core.$strip>;
|
|
8
|
+
export declare function insertLink(args: z.infer<typeof InsertLinkSchema>): Promise<{
|
|
9
|
+
documentId: string;
|
|
10
|
+
url: string;
|
|
11
|
+
startIndex: number;
|
|
12
|
+
endIndex: number;
|
|
13
|
+
_apiMs: number;
|
|
14
|
+
}>;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { google } from 'googleapis';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { getAuthClient } from '../auth/google-auth.js';
|
|
4
|
+
export const InsertLinkSchema = z.object({
|
|
5
|
+
documentId: z.string().describe('The Google Doc document ID'),
|
|
6
|
+
url: z.string().url().describe('The URL to link to'),
|
|
7
|
+
startIndex: z.number().describe('Start index of the text range to make into a link'),
|
|
8
|
+
endIndex: z.number().describe('End index of the text range to make into a link'),
|
|
9
|
+
});
|
|
10
|
+
export async function insertLink(args) {
|
|
11
|
+
const auth = getAuthClient();
|
|
12
|
+
const docs = google.docs({ version: 'v1', auth });
|
|
13
|
+
const apiStart = performance.now();
|
|
14
|
+
await docs.documents.batchUpdate({
|
|
15
|
+
documentId: args.documentId,
|
|
16
|
+
requestBody: {
|
|
17
|
+
requests: [
|
|
18
|
+
{
|
|
19
|
+
updateTextStyle: {
|
|
20
|
+
range: { startIndex: args.startIndex, endIndex: args.endIndex },
|
|
21
|
+
textStyle: {
|
|
22
|
+
link: { url: args.url },
|
|
23
|
+
},
|
|
24
|
+
fields: 'link',
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
const apiMs = Math.round(performance.now() - apiStart);
|
|
31
|
+
return {
|
|
32
|
+
documentId: args.documentId,
|
|
33
|
+
url: args.url,
|
|
34
|
+
startIndex: args.startIndex,
|
|
35
|
+
endIndex: args.endIndex,
|
|
36
|
+
_apiMs: apiMs,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const InsertPageBreakSchema: z.ZodObject<{
|
|
3
|
+
documentId: z.ZodString;
|
|
4
|
+
index: z.ZodNumber;
|
|
5
|
+
}, z.core.$strip>;
|
|
6
|
+
export declare function insertPageBreak(args: z.infer<typeof InsertPageBreakSchema>): Promise<{
|
|
7
|
+
documentId: string;
|
|
8
|
+
index: number;
|
|
9
|
+
_apiMs: number;
|
|
10
|
+
}>;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { google } from 'googleapis';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { getAuthClient } from '../auth/google-auth.js';
|
|
4
|
+
export const InsertPageBreakSchema = z.object({
|
|
5
|
+
documentId: z.string().describe('The Google Doc document ID'),
|
|
6
|
+
index: z.number().describe('Position in the document to insert the page break'),
|
|
7
|
+
});
|
|
8
|
+
export async function insertPageBreak(args) {
|
|
9
|
+
const auth = getAuthClient();
|
|
10
|
+
const docs = google.docs({ version: 'v1', auth });
|
|
11
|
+
const apiStart = performance.now();
|
|
12
|
+
await docs.documents.batchUpdate({
|
|
13
|
+
documentId: args.documentId,
|
|
14
|
+
requestBody: {
|
|
15
|
+
requests: [
|
|
16
|
+
{
|
|
17
|
+
insertPageBreak: {
|
|
18
|
+
location: { index: args.index },
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
],
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
const apiMs = Math.round(performance.now() - apiStart);
|
|
25
|
+
return {
|
|
26
|
+
documentId: args.documentId,
|
|
27
|
+
index: args.index,
|
|
28
|
+
_apiMs: apiMs,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const InsertTableSchema: z.ZodObject<{
|
|
3
|
+
documentId: z.ZodString;
|
|
4
|
+
rows: z.ZodNumber;
|
|
5
|
+
columns: z.ZodNumber;
|
|
6
|
+
index: z.ZodNumber;
|
|
7
|
+
}, z.core.$strip>;
|
|
8
|
+
export declare function insertTable(args: z.infer<typeof InsertTableSchema>): Promise<{
|
|
9
|
+
documentId: string;
|
|
10
|
+
rows: number;
|
|
11
|
+
columns: number;
|
|
12
|
+
index: number;
|
|
13
|
+
_apiMs: number;
|
|
14
|
+
}>;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { google } from 'googleapis';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { getAuthClient } from '../auth/google-auth.js';
|
|
4
|
+
export const InsertTableSchema = z.object({
|
|
5
|
+
documentId: z.string().describe('The Google Doc document ID'),
|
|
6
|
+
rows: z.number().min(1).max(20).describe('Number of rows (1-20)'),
|
|
7
|
+
columns: z.number().min(1).max(20).describe('Number of columns (1-20)'),
|
|
8
|
+
index: z.number().describe('Position in the document to insert the table'),
|
|
9
|
+
});
|
|
10
|
+
export async function insertTable(args) {
|
|
11
|
+
const auth = getAuthClient();
|
|
12
|
+
const docs = google.docs({ version: 'v1', auth });
|
|
13
|
+
const apiStart = performance.now();
|
|
14
|
+
await docs.documents.batchUpdate({
|
|
15
|
+
documentId: args.documentId,
|
|
16
|
+
requestBody: {
|
|
17
|
+
requests: [
|
|
18
|
+
{
|
|
19
|
+
insertTable: {
|
|
20
|
+
location: { index: args.index },
|
|
21
|
+
rows: args.rows,
|
|
22
|
+
columns: args.columns,
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
],
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
const apiMs = Math.round(performance.now() - apiStart);
|
|
29
|
+
return {
|
|
30
|
+
documentId: args.documentId,
|
|
31
|
+
rows: args.rows,
|
|
32
|
+
columns: args.columns,
|
|
33
|
+
index: args.index,
|
|
34
|
+
_apiMs: apiMs,
|
|
35
|
+
};
|
|
36
|
+
}
|
package/package.json
CHANGED