google-tools-mcp 1.2.11 → 2.0.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/LICENSE +21 -21
- package/README.md +555 -276
- package/dist/cachedToolsList.js +52 -52
- package/dist/googleDocsApiHelpers.js +76 -12
- package/dist/helpers.js +572 -252
- package/dist/httpAuth.js +296 -0
- package/dist/index.js +162 -11
- package/dist/logger.js +87 -87
- package/dist/markdown-transformer/docsToMarkdown.js +222 -21
- package/dist/markdown-transformer/index.js +19 -9
- package/dist/markdown-transformer/markdownToDocs.js +458 -28
- package/dist/readTracker.js +136 -17
- package/dist/sessionContext.js +37 -0
- package/dist/setup.js +187 -9
- package/dist/tools/calendar/getBusy.js +64 -64
- package/dist/tools/calendar/getEvents.js +141 -141
- package/dist/tools/calendar/getFree.js +225 -225
- package/dist/tools/calendar/index.js +19 -19
- package/dist/tools/calendar/listCalendars.js +38 -38
- package/dist/tools/calendar/listRecurringInstances.js +83 -83
- package/dist/tools/calendar/manageCalendar.js +121 -121
- package/dist/tools/calendar/manageEvent.js +258 -258
- package/dist/tools/calendar/moveEvent.js +66 -66
- package/dist/tools/docs/addTab.js +10 -12
- package/dist/tools/docs/appendToGoogleDoc.js +9 -4
- package/dist/tools/docs/deleteRange.js +9 -4
- package/dist/tools/docs/findAndReplace.js +9 -4
- package/dist/tools/docs/formatting/applyParagraphStyle.js +4 -1
- package/dist/tools/docs/formatting/getFormatting.js +171 -171
- package/dist/tools/docs/insertImage.js +15 -2
- package/dist/tools/docs/insertPageBreak.js +4 -1
- package/dist/tools/docs/insertTable.js +4 -1
- package/dist/tools/docs/insertTableWithData.js +4 -1
- package/dist/tools/docs/modifyText.js +14 -4
- package/dist/tools/docs/modifyText.test.js +84 -84
- package/dist/tools/docs/readGoogleDoc.js +71 -11
- package/dist/tools/docs/renameTab.js +12 -14
- package/dist/tools/drafts.js +165 -165
- package/dist/tools/drive/createDocument.js +13 -1
- package/dist/tools/drive/downloadFile.js +268 -268
- package/dist/tools/drive/getFileInfo.js +48 -48
- package/dist/tools/drive/getFilePath.js +50 -50
- package/dist/tools/drive/listDriveFiles.js +112 -112
- package/dist/tools/drive/listSharedDrives.js +52 -52
- package/dist/tools/drive/listSharedWithMe.js +97 -97
- package/dist/tools/drive/uploadFile.js +111 -111
- package/dist/tools/extras/index.js +9 -9
- package/dist/tools/extras/readDriveFile.js +212 -206
- package/dist/tools/extras/readFile.js +84 -84
- package/dist/tools/extras/searchFileContents.js +81 -81
- package/dist/tools/forms/batchUpdateForm.js +81 -81
- package/dist/tools/forms/createForm.js +54 -54
- package/dist/tools/forms/getForm.js +118 -118
- package/dist/tools/forms/getFormResponse.js +45 -45
- package/dist/tools/forms/index.js +15 -15
- package/dist/tools/forms/listFormResponses.js +56 -56
- package/dist/tools/forms/setPublishSettings.js +59 -59
- package/dist/tools/gmail/drafts.js +165 -165
- package/dist/tools/gmail/labels.js +54 -83
- package/dist/tools/gmail/messages.js +444 -441
- package/dist/tools/gmail/settings.js +358 -528
- package/dist/tools/gmail/threads.js +286 -164
- package/dist/tools/index.js +511 -473
- package/dist/tools/labels.js +103 -103
- package/dist/tools/legacyAliases.js +426 -0
- package/dist/tools/maps/directions.js +72 -0
- package/dist/tools/maps/geocode.js +16 -0
- package/dist/tools/maps/index.js +15 -0
- package/dist/tools/maps/mapsClient.js +83 -0
- package/dist/tools/maps/placeDetails.js +16 -0
- package/dist/tools/maps/reverseGeocode.js +19 -0
- package/dist/tools/maps/searchNearby.js +54 -0
- package/dist/tools/maps/searchPlaces.js +24 -0
- package/dist/tools/messages.js +448 -448
- package/dist/tools/settings.js +528 -528
- package/dist/tools/slides/createPresentation.js +104 -104
- package/dist/tools/slides/createShape.js +92 -92
- package/dist/tools/slides/createTextBox.js +82 -82
- package/dist/tools/slides/deleteSlide.js +30 -30
- package/dist/tools/slides/duplicateSlide.js +37 -37
- package/dist/tools/slides/exportThumbnail.js +42 -42
- package/dist/tools/slides/formatParagraph.js +72 -72
- package/dist/tools/slides/formatText.js +84 -84
- package/dist/tools/slides/getPresentation.js +87 -87
- package/dist/tools/slides/index.js +33 -33
- package/dist/tools/slides/reorderSlides.js +41 -41
- package/dist/tools/slides/replaceAllText.js +46 -46
- package/dist/tools/slides/setBackground.js +58 -58
- package/dist/tools/slides/speakerNotes.js +102 -102
- package/dist/tools/slides/styleShape.js +111 -111
- package/dist/tools/slides/updatePresentation.js +128 -128
- package/dist/tools/threads.js +145 -145
- package/dist/tools/utils/appendMarkdownToGoogleDoc.js +31 -7
- package/dist/tools/utils/replaceDocumentWithMarkdown.js +72 -9
- package/dist/updateCheck.js +202 -0
- package/dist/workspace.js +122 -0
- package/package.json +82 -80
|
@@ -1,111 +1,111 @@
|
|
|
1
|
-
import { UserError } from 'fastmcp';
|
|
2
|
-
import { z } from 'zod';
|
|
3
|
-
import { getSlidesClient } from '../../clients.js';
|
|
4
|
-
|
|
5
|
-
export function register(server) {
|
|
6
|
-
server.addTool({
|
|
7
|
-
name: 'styleSlidesShape',
|
|
8
|
-
description:
|
|
9
|
-
'Style a shape in Google Slides: set background color, outline color/weight/dash style.',
|
|
10
|
-
parameters: z.object({
|
|
11
|
-
presentationId: z.string().describe('Presentation ID'),
|
|
12
|
-
objectId: z.string().describe('Shape object ID'),
|
|
13
|
-
backgroundColor: z
|
|
14
|
-
.object({
|
|
15
|
-
red: z.number().min(0).max(1).optional(),
|
|
16
|
-
green: z.number().min(0).max(1).optional(),
|
|
17
|
-
blue: z.number().min(0).max(1).optional(),
|
|
18
|
-
alpha: z.number().min(0).max(1).optional(),
|
|
19
|
-
})
|
|
20
|
-
.optional()
|
|
21
|
-
.describe('Fill color (RGBA 0-1)'),
|
|
22
|
-
outlineColor: z
|
|
23
|
-
.object({
|
|
24
|
-
red: z.number().min(0).max(1).optional(),
|
|
25
|
-
green: z.number().min(0).max(1).optional(),
|
|
26
|
-
blue: z.number().min(0).max(1).optional(),
|
|
27
|
-
})
|
|
28
|
-
.optional()
|
|
29
|
-
.describe('Outline color (RGB 0-1)'),
|
|
30
|
-
outlineWeight: z.number().optional().describe('Outline thickness in points'),
|
|
31
|
-
outlineDashStyle: z
|
|
32
|
-
.enum(['SOLID', 'DOT', 'DASH', 'DASH_DOT', 'LONG_DASH', 'LONG_DASH_DOT'])
|
|
33
|
-
.optional()
|
|
34
|
-
.describe('Outline dash style'),
|
|
35
|
-
}),
|
|
36
|
-
execute: async (args, { log }) => {
|
|
37
|
-
const slidesApi = await getSlidesClient();
|
|
38
|
-
const shapeProperties = {};
|
|
39
|
-
const fields = [];
|
|
40
|
-
|
|
41
|
-
if (args.backgroundColor) {
|
|
42
|
-
shapeProperties.shapeBackgroundFill = {
|
|
43
|
-
solidFill: {
|
|
44
|
-
color: {
|
|
45
|
-
rgbColor: {
|
|
46
|
-
red: args.backgroundColor.red || 0,
|
|
47
|
-
green: args.backgroundColor.green || 0,
|
|
48
|
-
blue: args.backgroundColor.blue || 0,
|
|
49
|
-
},
|
|
50
|
-
},
|
|
51
|
-
alpha: args.backgroundColor.alpha ?? 1,
|
|
52
|
-
},
|
|
53
|
-
};
|
|
54
|
-
fields.push('shapeBackgroundFill');
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const outline = {};
|
|
58
|
-
let hasOutline = false;
|
|
59
|
-
|
|
60
|
-
if (args.outlineColor) {
|
|
61
|
-
outline.outlineFill = {
|
|
62
|
-
solidFill: {
|
|
63
|
-
color: {
|
|
64
|
-
rgbColor: {
|
|
65
|
-
red: args.outlineColor.red || 0,
|
|
66
|
-
green: args.outlineColor.green || 0,
|
|
67
|
-
blue: args.outlineColor.blue || 0,
|
|
68
|
-
},
|
|
69
|
-
},
|
|
70
|
-
},
|
|
71
|
-
};
|
|
72
|
-
hasOutline = true;
|
|
73
|
-
}
|
|
74
|
-
if (args.outlineWeight !== undefined) {
|
|
75
|
-
outline.weight = { magnitude: args.outlineWeight, unit: 'PT' };
|
|
76
|
-
hasOutline = true;
|
|
77
|
-
}
|
|
78
|
-
if (args.outlineDashStyle) {
|
|
79
|
-
outline.dashStyle = args.outlineDashStyle;
|
|
80
|
-
hasOutline = true;
|
|
81
|
-
}
|
|
82
|
-
if (hasOutline) {
|
|
83
|
-
shapeProperties.outline = outline;
|
|
84
|
-
fields.push('outline');
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
if (fields.length === 0) throw new UserError('No styling options specified');
|
|
88
|
-
|
|
89
|
-
try {
|
|
90
|
-
await slidesApi.presentations.batchUpdate({
|
|
91
|
-
presentationId: args.presentationId,
|
|
92
|
-
requestBody: {
|
|
93
|
-
requests: [
|
|
94
|
-
{
|
|
95
|
-
updateShapeProperties: {
|
|
96
|
-
objectId: args.objectId,
|
|
97
|
-
shapeProperties,
|
|
98
|
-
fields: fields.join(','),
|
|
99
|
-
},
|
|
100
|
-
},
|
|
101
|
-
],
|
|
102
|
-
},
|
|
103
|
-
});
|
|
104
|
-
return JSON.stringify({ success: true, message: `Applied styling to shape ${args.objectId}` });
|
|
105
|
-
} catch (error) {
|
|
106
|
-
log.error(`Error styling shape: ${error.message || error}`);
|
|
107
|
-
throw new UserError(`Failed to style shape: ${error.message || 'Unknown error'}`);
|
|
108
|
-
}
|
|
109
|
-
},
|
|
110
|
-
});
|
|
111
|
-
}
|
|
1
|
+
import { UserError } from 'fastmcp';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { getSlidesClient } from '../../clients.js';
|
|
4
|
+
|
|
5
|
+
export function register(server) {
|
|
6
|
+
server.addTool({
|
|
7
|
+
name: 'styleSlidesShape',
|
|
8
|
+
description:
|
|
9
|
+
'Style a shape in Google Slides: set background color, outline color/weight/dash style.',
|
|
10
|
+
parameters: z.object({
|
|
11
|
+
presentationId: z.string().describe('Presentation ID'),
|
|
12
|
+
objectId: z.string().describe('Shape object ID'),
|
|
13
|
+
backgroundColor: z
|
|
14
|
+
.object({
|
|
15
|
+
red: z.number().min(0).max(1).optional(),
|
|
16
|
+
green: z.number().min(0).max(1).optional(),
|
|
17
|
+
blue: z.number().min(0).max(1).optional(),
|
|
18
|
+
alpha: z.number().min(0).max(1).optional(),
|
|
19
|
+
})
|
|
20
|
+
.optional()
|
|
21
|
+
.describe('Fill color (RGBA 0-1)'),
|
|
22
|
+
outlineColor: z
|
|
23
|
+
.object({
|
|
24
|
+
red: z.number().min(0).max(1).optional(),
|
|
25
|
+
green: z.number().min(0).max(1).optional(),
|
|
26
|
+
blue: z.number().min(0).max(1).optional(),
|
|
27
|
+
})
|
|
28
|
+
.optional()
|
|
29
|
+
.describe('Outline color (RGB 0-1)'),
|
|
30
|
+
outlineWeight: z.number().optional().describe('Outline thickness in points'),
|
|
31
|
+
outlineDashStyle: z
|
|
32
|
+
.enum(['SOLID', 'DOT', 'DASH', 'DASH_DOT', 'LONG_DASH', 'LONG_DASH_DOT'])
|
|
33
|
+
.optional()
|
|
34
|
+
.describe('Outline dash style'),
|
|
35
|
+
}),
|
|
36
|
+
execute: async (args, { log }) => {
|
|
37
|
+
const slidesApi = await getSlidesClient();
|
|
38
|
+
const shapeProperties = {};
|
|
39
|
+
const fields = [];
|
|
40
|
+
|
|
41
|
+
if (args.backgroundColor) {
|
|
42
|
+
shapeProperties.shapeBackgroundFill = {
|
|
43
|
+
solidFill: {
|
|
44
|
+
color: {
|
|
45
|
+
rgbColor: {
|
|
46
|
+
red: args.backgroundColor.red || 0,
|
|
47
|
+
green: args.backgroundColor.green || 0,
|
|
48
|
+
blue: args.backgroundColor.blue || 0,
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
alpha: args.backgroundColor.alpha ?? 1,
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
fields.push('shapeBackgroundFill');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const outline = {};
|
|
58
|
+
let hasOutline = false;
|
|
59
|
+
|
|
60
|
+
if (args.outlineColor) {
|
|
61
|
+
outline.outlineFill = {
|
|
62
|
+
solidFill: {
|
|
63
|
+
color: {
|
|
64
|
+
rgbColor: {
|
|
65
|
+
red: args.outlineColor.red || 0,
|
|
66
|
+
green: args.outlineColor.green || 0,
|
|
67
|
+
blue: args.outlineColor.blue || 0,
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
hasOutline = true;
|
|
73
|
+
}
|
|
74
|
+
if (args.outlineWeight !== undefined) {
|
|
75
|
+
outline.weight = { magnitude: args.outlineWeight, unit: 'PT' };
|
|
76
|
+
hasOutline = true;
|
|
77
|
+
}
|
|
78
|
+
if (args.outlineDashStyle) {
|
|
79
|
+
outline.dashStyle = args.outlineDashStyle;
|
|
80
|
+
hasOutline = true;
|
|
81
|
+
}
|
|
82
|
+
if (hasOutline) {
|
|
83
|
+
shapeProperties.outline = outline;
|
|
84
|
+
fields.push('outline');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (fields.length === 0) throw new UserError('No styling options specified');
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
await slidesApi.presentations.batchUpdate({
|
|
91
|
+
presentationId: args.presentationId,
|
|
92
|
+
requestBody: {
|
|
93
|
+
requests: [
|
|
94
|
+
{
|
|
95
|
+
updateShapeProperties: {
|
|
96
|
+
objectId: args.objectId,
|
|
97
|
+
shapeProperties,
|
|
98
|
+
fields: fields.join(','),
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
return JSON.stringify({ success: true, message: `Applied styling to shape ${args.objectId}` });
|
|
105
|
+
} catch (error) {
|
|
106
|
+
log.error(`Error styling shape: ${error.message || error}`);
|
|
107
|
+
throw new UserError(`Failed to style shape: ${error.message || 'Unknown error'}`);
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
}
|
|
@@ -1,128 +1,128 @@
|
|
|
1
|
-
import { UserError } from 'fastmcp';
|
|
2
|
-
import { z } from 'zod';
|
|
3
|
-
import { getSlidesClient } from '../../clients.js';
|
|
4
|
-
|
|
5
|
-
export function register(server) {
|
|
6
|
-
server.addTool({
|
|
7
|
-
name: 'updatePresentation',
|
|
8
|
-
description:
|
|
9
|
-
'Replace all slides in an existing Google Slides presentation with new content. Deletes existing slides and creates new ones with the provided title/content pairs.',
|
|
10
|
-
parameters: z.object({
|
|
11
|
-
presentationId: z.string().describe('Presentation ID'),
|
|
12
|
-
slides: z
|
|
13
|
-
.array(
|
|
14
|
-
z.object({
|
|
15
|
-
title: z.string().describe('Slide title'),
|
|
16
|
-
content: z.string().describe('Slide body content'),
|
|
17
|
-
}),
|
|
18
|
-
)
|
|
19
|
-
.min(1)
|
|
20
|
-
.describe('Array of slide objects to replace existing slides'),
|
|
21
|
-
}),
|
|
22
|
-
execute: async (args, { log }) => {
|
|
23
|
-
const slidesApi = await getSlidesClient();
|
|
24
|
-
log.info(`Updating presentation: ${args.presentationId}`);
|
|
25
|
-
|
|
26
|
-
try {
|
|
27
|
-
const currentPresentation = await slidesApi.presentations.get({
|
|
28
|
-
presentationId: args.presentationId,
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
if (!currentPresentation.data.slides) {
|
|
32
|
-
throw new UserError('No slides found in presentation');
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// Delete all slides except the first, then clear and rewrite the first
|
|
36
|
-
const slideIdsToDelete = currentPresentation.data.slides
|
|
37
|
-
.slice(1)
|
|
38
|
-
.map((s) => s.objectId)
|
|
39
|
-
.filter(Boolean);
|
|
40
|
-
|
|
41
|
-
const requests = [];
|
|
42
|
-
|
|
43
|
-
// Delete extra slides
|
|
44
|
-
for (const id of slideIdsToDelete) {
|
|
45
|
-
requests.push({ deleteObject: { objectId: id } });
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// Clear text from the first slide's text elements
|
|
49
|
-
const firstSlide = currentPresentation.data.slides[0];
|
|
50
|
-
if (firstSlide?.pageElements) {
|
|
51
|
-
for (const el of firstSlide.pageElements) {
|
|
52
|
-
if (el.objectId && el.shape?.text) {
|
|
53
|
-
requests.push({ deleteText: { objectId: el.objectId, textRange: { type: 'ALL' } } });
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// Insert new content into first slide
|
|
59
|
-
const firstContent = args.slides[0];
|
|
60
|
-
if (firstSlide?.pageElements) {
|
|
61
|
-
let titleId, bodyId;
|
|
62
|
-
for (const el of firstSlide.pageElements) {
|
|
63
|
-
const pt = el.shape?.placeholder?.type;
|
|
64
|
-
if (pt === 'TITLE' || pt === 'CENTERED_TITLE') titleId = el.objectId;
|
|
65
|
-
else if (pt === 'BODY' || pt === 'SUBTITLE') bodyId = el.objectId;
|
|
66
|
-
}
|
|
67
|
-
if (titleId) requests.push({ insertText: { objectId: titleId, text: firstContent.title, insertionIndex: 0 } });
|
|
68
|
-
if (bodyId) requests.push({ insertText: { objectId: bodyId, text: firstContent.content, insertionIndex: 0 } });
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// Create additional slides
|
|
72
|
-
for (let i = 1; i < args.slides.length; i++) {
|
|
73
|
-
requests.push({
|
|
74
|
-
createSlide: {
|
|
75
|
-
objectId: `slide_${Date.now()}_${i}`,
|
|
76
|
-
slideLayoutReference: { predefinedLayout: 'TITLE_AND_BODY' },
|
|
77
|
-
},
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
await slidesApi.presentations.batchUpdate({
|
|
82
|
-
presentationId: args.presentationId,
|
|
83
|
-
requestBody: { requests },
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
// Populate new slides (index 1+)
|
|
87
|
-
if (args.slides.length > 1) {
|
|
88
|
-
const updated = await slidesApi.presentations.get({ presentationId: args.presentationId });
|
|
89
|
-
const contentRequests = [];
|
|
90
|
-
for (let i = 1; i < args.slides.length && updated.data.slides; i++) {
|
|
91
|
-
const slide = args.slides[i];
|
|
92
|
-
const pageSlide = updated.data.slides[i];
|
|
93
|
-
if (!pageSlide?.pageElements) continue;
|
|
94
|
-
for (const el of pageSlide.pageElements) {
|
|
95
|
-
if (!el.objectId) continue;
|
|
96
|
-
const pt = el.shape?.placeholder?.type;
|
|
97
|
-
if (pt === 'TITLE' || pt === 'CENTERED_TITLE') {
|
|
98
|
-
contentRequests.push({ insertText: { objectId: el.objectId, text: slide.title, insertionIndex: 0 } });
|
|
99
|
-
} else if (pt === 'BODY' || pt === 'SUBTITLE') {
|
|
100
|
-
contentRequests.push({ insertText: { objectId: el.objectId, text: slide.content, insertionIndex: 0 } });
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
if (contentRequests.length > 0) {
|
|
105
|
-
await slidesApi.presentations.batchUpdate({
|
|
106
|
-
presentationId: args.presentationId,
|
|
107
|
-
requestBody: { requests: contentRequests },
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
return JSON.stringify(
|
|
113
|
-
{
|
|
114
|
-
presentationId: args.presentationId,
|
|
115
|
-
slidesUpdated: args.slides.length,
|
|
116
|
-
link: `https://docs.google.com/presentation/d/${args.presentationId}`,
|
|
117
|
-
},
|
|
118
|
-
null,
|
|
119
|
-
2,
|
|
120
|
-
);
|
|
121
|
-
} catch (error) {
|
|
122
|
-
if (error instanceof UserError) throw error;
|
|
123
|
-
log.error(`Error updating presentation: ${error.message || error}`);
|
|
124
|
-
throw new UserError(`Failed to update presentation: ${error.message || 'Unknown error'}`);
|
|
125
|
-
}
|
|
126
|
-
},
|
|
127
|
-
});
|
|
128
|
-
}
|
|
1
|
+
import { UserError } from 'fastmcp';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { getSlidesClient } from '../../clients.js';
|
|
4
|
+
|
|
5
|
+
export function register(server) {
|
|
6
|
+
server.addTool({
|
|
7
|
+
name: 'updatePresentation',
|
|
8
|
+
description:
|
|
9
|
+
'Replace all slides in an existing Google Slides presentation with new content. Deletes existing slides and creates new ones with the provided title/content pairs.',
|
|
10
|
+
parameters: z.object({
|
|
11
|
+
presentationId: z.string().describe('Presentation ID'),
|
|
12
|
+
slides: z
|
|
13
|
+
.array(
|
|
14
|
+
z.object({
|
|
15
|
+
title: z.string().describe('Slide title'),
|
|
16
|
+
content: z.string().describe('Slide body content'),
|
|
17
|
+
}),
|
|
18
|
+
)
|
|
19
|
+
.min(1)
|
|
20
|
+
.describe('Array of slide objects to replace existing slides'),
|
|
21
|
+
}),
|
|
22
|
+
execute: async (args, { log }) => {
|
|
23
|
+
const slidesApi = await getSlidesClient();
|
|
24
|
+
log.info(`Updating presentation: ${args.presentationId}`);
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
const currentPresentation = await slidesApi.presentations.get({
|
|
28
|
+
presentationId: args.presentationId,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
if (!currentPresentation.data.slides) {
|
|
32
|
+
throw new UserError('No slides found in presentation');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Delete all slides except the first, then clear and rewrite the first
|
|
36
|
+
const slideIdsToDelete = currentPresentation.data.slides
|
|
37
|
+
.slice(1)
|
|
38
|
+
.map((s) => s.objectId)
|
|
39
|
+
.filter(Boolean);
|
|
40
|
+
|
|
41
|
+
const requests = [];
|
|
42
|
+
|
|
43
|
+
// Delete extra slides
|
|
44
|
+
for (const id of slideIdsToDelete) {
|
|
45
|
+
requests.push({ deleteObject: { objectId: id } });
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Clear text from the first slide's text elements
|
|
49
|
+
const firstSlide = currentPresentation.data.slides[0];
|
|
50
|
+
if (firstSlide?.pageElements) {
|
|
51
|
+
for (const el of firstSlide.pageElements) {
|
|
52
|
+
if (el.objectId && el.shape?.text) {
|
|
53
|
+
requests.push({ deleteText: { objectId: el.objectId, textRange: { type: 'ALL' } } });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Insert new content into first slide
|
|
59
|
+
const firstContent = args.slides[0];
|
|
60
|
+
if (firstSlide?.pageElements) {
|
|
61
|
+
let titleId, bodyId;
|
|
62
|
+
for (const el of firstSlide.pageElements) {
|
|
63
|
+
const pt = el.shape?.placeholder?.type;
|
|
64
|
+
if (pt === 'TITLE' || pt === 'CENTERED_TITLE') titleId = el.objectId;
|
|
65
|
+
else if (pt === 'BODY' || pt === 'SUBTITLE') bodyId = el.objectId;
|
|
66
|
+
}
|
|
67
|
+
if (titleId) requests.push({ insertText: { objectId: titleId, text: firstContent.title, insertionIndex: 0 } });
|
|
68
|
+
if (bodyId) requests.push({ insertText: { objectId: bodyId, text: firstContent.content, insertionIndex: 0 } });
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Create additional slides
|
|
72
|
+
for (let i = 1; i < args.slides.length; i++) {
|
|
73
|
+
requests.push({
|
|
74
|
+
createSlide: {
|
|
75
|
+
objectId: `slide_${Date.now()}_${i}`,
|
|
76
|
+
slideLayoutReference: { predefinedLayout: 'TITLE_AND_BODY' },
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
await slidesApi.presentations.batchUpdate({
|
|
82
|
+
presentationId: args.presentationId,
|
|
83
|
+
requestBody: { requests },
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// Populate new slides (index 1+)
|
|
87
|
+
if (args.slides.length > 1) {
|
|
88
|
+
const updated = await slidesApi.presentations.get({ presentationId: args.presentationId });
|
|
89
|
+
const contentRequests = [];
|
|
90
|
+
for (let i = 1; i < args.slides.length && updated.data.slides; i++) {
|
|
91
|
+
const slide = args.slides[i];
|
|
92
|
+
const pageSlide = updated.data.slides[i];
|
|
93
|
+
if (!pageSlide?.pageElements) continue;
|
|
94
|
+
for (const el of pageSlide.pageElements) {
|
|
95
|
+
if (!el.objectId) continue;
|
|
96
|
+
const pt = el.shape?.placeholder?.type;
|
|
97
|
+
if (pt === 'TITLE' || pt === 'CENTERED_TITLE') {
|
|
98
|
+
contentRequests.push({ insertText: { objectId: el.objectId, text: slide.title, insertionIndex: 0 } });
|
|
99
|
+
} else if (pt === 'BODY' || pt === 'SUBTITLE') {
|
|
100
|
+
contentRequests.push({ insertText: { objectId: el.objectId, text: slide.content, insertionIndex: 0 } });
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
if (contentRequests.length > 0) {
|
|
105
|
+
await slidesApi.presentations.batchUpdate({
|
|
106
|
+
presentationId: args.presentationId,
|
|
107
|
+
requestBody: { requests: contentRequests },
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return JSON.stringify(
|
|
113
|
+
{
|
|
114
|
+
presentationId: args.presentationId,
|
|
115
|
+
slidesUpdated: args.slides.length,
|
|
116
|
+
link: `https://docs.google.com/presentation/d/${args.presentationId}`,
|
|
117
|
+
},
|
|
118
|
+
null,
|
|
119
|
+
2,
|
|
120
|
+
);
|
|
121
|
+
} catch (error) {
|
|
122
|
+
if (error instanceof UserError) throw error;
|
|
123
|
+
log.error(`Error updating presentation: ${error.message || error}`);
|
|
124
|
+
throw new UserError(`Failed to update presentation: ${error.message || 'Unknown error'}`);
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
}
|