$35
This skill supports Claude Code and Codex as equal targets.
Generates professional branded presentations in Google Slides using the BinPar dark template. Presentations are visual-first: the agent autonomously selects layouts, generates SVG visuals via parallel subagents, and assembles a polished deck.
IMPORTANT: All gws commands MUST be prefixed with CI=true to disable the TUI and get plain JSON output. Example: CI=true gws slides presentations get ...
Reference files (read as needed during execution):
references/template-structure.md — Complete layout catalog with element positions and sizesreferences/gws-slides-commands.md — All Slides + Drive CLI commands with examplesreferences/svg-generation-guide.md — SVG subagent instructions, brand kit, and prompt templatesreferences/image-pipeline.md — SVG→PNG conversion, upload, insertion, and cleanup1930-cBpaLoe7F-sRoWy_XB5Xj2wEwXWc8MYvAOvuAxArgb(34, 32, 51) — dark navy/purplergb(253, 157, 0) — BinPar orangergb(255, 255, 255) — whiteSlides are NOT documents. Follow these principles:
CI=true gws drive files list --params '{"pageSize": 1}'
gws is not found → tell the user: "Ask me to 'Set up BinPar tools' to install and configure it." Then stop.CI=true gws auth login, extract the URL from the output, and display it as text in the chat so the user can click it.Check which SVG→PNG converter is available (run once, remember the result):
python3 -c "import cairosvg; print('cairosvg')" 2>/dev/null \
|| (which inkscape 2>/dev/null && echo 'inkscape') \
|| (ls '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome' 2>/dev/null && echo 'chrome') \
|| echo 'pillow-fallback'
Store the result. If pillow-fallback, subagents will generate PNGs directly with Pillow instead of SVGs. See references/image-pipeline.md for conversion commands per tool.
Parse the user's request to extract:
| Field | Required | How to infer | If not inferable |
|---|---|---|---|
| Topic / title | Yes | From conversation | AskQuestionTool |
| Key content points | Yes | Listed in request | AskQuestionTool if none provided |
| Language | Yes | User's language | Default Spanish |
| Audience | No | Context clues | Infer "general" |
| Contact person | Yes | Never assume | Always ask — name, role, phone, email |
| Speaker notes | No | Default: no | Ask once via AskQuestionTool |
| Destination folder | Yes | URL or name in request | AskQuestionTool |
Rules:
https://drive.google.com/drive/folders/{FOLDER_ID}CI=true gws drive files list --params '{"q": "name = '\''Folder Name'\'' and mimeType = '\''application/vnd.google-apps.folder'\''", "pageSize": 5}'
Based on the gathered requirements, plan the complete slide deck. This is done autonomously — do not ask the user to approve the slide plan. Use your judgment to create a well-structured, professional presentation.
Typical range: 8–16 slides. Base the count on:
Every deck has this fixed structure:
| Position | Slide | Layout | Notes |
|---|---|---|---|
| First | Title / Cover | TITLE (template slide 1) | Always present |
| Second | Table of Contents | BLANK custom (template slide 2) | Always present, auto-generated last |
| Middle | Content slides | Varies per slide | 4–12 content slides typically |
| Penultimate | Contact | Contact custom (template slide 13) | Always present |
| Last | Closing | BLANK custom (template slide 14) | Always present — do not modify |
For each content slide, select from the layout catalog based on content type:
| Content Type | Recommended Layout | Template Slide # | Images Needed |
|---|---|---|---|
| Section divider / transition | SECTION_HEADER | Use createSlide with layout p3 | 0 |
| Text explanation with optional quote | Plain Text + Quote | 3 | 0 |
| Two parallel topics | Two Columns | 4 | 0 |
| Four short concepts (2×2 grid) | Four Blocks | 5 | 0 |
| Content + single supporting visual | 2 Cols + Image | 6 | 1 |
| Two concepts + two supporting visuals | 2 Blocks + Images | 7 | 2 |
| Three-image showcase | 3 Img Composition | 8 | 3 |
| Full visual emphasis | Full Image | 9 | 1 |
| Feature list / steps / highlights | Special List | 10 | 0 |
| Card comparison / team / tiers | Cards Group | 11 | 0 |
| Concept definitions / glossary | Table of Concepts | 12 | 0 |
| Key metric or number | BIG_NUMBER | Use createSlide with layout p11 | 0 |
| Key statement | MAIN_POINT | Use createSlide with layout p8 | 0 |
Layout variety rule: Do not use the same layout for two consecutive content slides. Alternate between text-only and visual layouts.
For each slide that uses an image layout, decide what SVG to generate:
See references/svg-generation-guide.md for category details and prompt templates.
Build an internal plan (not shown to user) mapping each slide to:
CI=true gws drive files copy \
--params '{"fileId": "1930-cBpaLoe7F-sRoWy_XB5Xj2wEwXWc8MYvAOvuAxA"}' \
--json '{"name": "PRESENTATION_TITLE", "parents": ["FOLDER_ID"]}'
Extract the new presentation id from the response. If the user doesn't care about the folder, omit parents.
CI=true gws slides presentations get --params '{"presentationId": "NEW_PRES_ID"}'
Parse the response to build a map of:
slideProperties.layoutObjectId)Cross-reference with references/template-structure.md to identify each element's role (title, subtitle, body, image, etc.) by matching objectIds.
Build a single batchUpdate with three types of operations, in this order:
For each content slide in the plan that uses a template slide (slides 1–14), issue a duplicateObject request:
{"duplicateObject": {"objectId": "TEMPLATE_SLIDE_OBJECT_ID"}}
For slides using master layouts not in the template (SECTION_HEADER, BIG_NUMBER, MAIN_POINT), use createSlide:
{"createSlide": {"slideLayoutReference": {"predefinedLayout": "SECTION_HEADER"}}}
After all duplications, use updateSlidesPosition to arrange slides in the planned order:
{
"updateSlidesPosition": {
"slideObjectIds": ["NEW_SLIDE_1", "NEW_SLIDE_2", "..."],
"insertionIndex": 0
}
}
Delete all 14 original template slides:
{"deleteObject": {"objectId": "ORIGINAL_SLIDE_1"}},
{"deleteObject": {"objectId": "ORIGINAL_SLIDE_2"}},
...
Critical ordering: Duplications first, then reorder, then deletions. All can go in one batchUpdate if ordered correctly.
Execute:
CI=true gws slides presentations batchUpdate \
--params '{"presentationId": "PRES_ID"}' \
--json '{"requests": [...]}'
After structural mutations, re-read to get fresh, authoritative objectIds:
CI=true gws slides presentations get --params '{"presentationId": "PRES_ID"}'
Build a new map of slide objectIds and their elements. The duplicated slides have system-generated IDs — identify elements by their placeholder type (TITLE, SUBTITLE, BODY), shape type, or position to match them to your content plan.
mkdir -p ./slide_assets
For every slide that needs an image, spawn a subagent in parallel (no cap on concurrency). Each subagent receives:
references/svg-generation-guide.md)references/template-structure.md, multiplied by 2 for retina)./slide_assets/slide_N_img_M.svgUse the subagent prompt template from references/svg-generation-guide.md.
If using Pillow fallback (no SVG converter detected in Step 0.2): Modify the subagent prompt to generate PNGs directly using Pillow instead of SVGs. See references/image-pipeline.md Option 4.
| Layout | Image Slot | SVG viewBox (2x retina) |
|---|---|---|
| 2 Cols + Image | 1 | 612 × 560 |
| 2 Blocks + Images | 1 (left) | 230 × 210 |
| 2 Blocks + Images | 2 (right) | 230 × 210 |
| 3 Img Composition | 1 (left) | 402 × 460 |
| 3 Img Composition | 2 (center) | 402 × 460 |
| 3 Img Composition | 3 (right) | 402 × 460 |
| Full Image | 1 | 604 × 810 |
Using the converter detected in Step 0.2, convert each SVG to PNG. See references/image-pipeline.md Step 3 for the specific command per converter.
Generate at the SVG's native viewBox dimensions (already 2x for retina).
If conversion fails for any image, try the next converter in the priority chain. If all fail, fall back to Pillow direct generation.
CI=true gws drive files create \
--upload ./slide_assets/slide_N_img_M.png \
--json '{"name": "slide_N_img_M.png", "mimeType": "image/png"}' \
--params '{"uploadType": "multipart"}'
CI=true gws drive permissions create \
--params '{"fileId": "UPLOADED_FILE_ID"}' \
--json '{"role": "reader", "type": "anyone"}'
Build a batchUpdate that:
{"deleteObject": {"objectId": "EXISTING_SAMPLE_IMAGE_ID"}},
{
"createImage": {
"url": "https://drive.google.com/uc?export=download&id=UPLOADED_FILE_ID",
"elementProperties": {
"pageObjectId": "SLIDE_OBJECT_ID",
"size": {
"width": {"magnitude": WIDTH_PT, "unit": "PT"},
"height": {"magnitude": HEIGHT_PT, "unit": "PT"}
},
"transform": {
"scaleX": 1, "scaleY": 1,
"translateX": X_PT, "translateY": Y_PT,
"unit": "PT"
}
}
}
}
Use the exact position values from references/template-structure.md § Image Insertion Reference:
| Layout | Img | x PT | y PT | w PT | h PT |
|---|---|---|---|---|---|
| 2 Cols + Image | 1 | 382.0 | 95.6 | 306.2 | 280.0 |
| 2 Blocks + Images | 1 | 124.4 | 98.9 | 115.0 | 105.2 |
| 2 Blocks + Images | 2 | 480.5 | 98.9 | 115.0 | 105.2 |
| 3 Img Composition | 1 | 25.7 | 95.6 | 201.1 | 230.0 |
| 3 Img Composition | 2 | 259.9 | 95.6 | 201.1 | 230.0 |
| 3 Img Composition | 3 | 494.1 | 95.6 | 201.1 | 230.0 |
| Full Image | 1 | 418.0 | 0.0 | 302.0 | 405.0 |
After all images are inserted, you MUST verify every slide with images visually. This is not optional — SVG generation is error-prone and issues like clipping, white borders, or cut-off content are only detectable visually.
For each slide that contains generated images, get a rendered thumbnail:
CI=true gws slides presentations.pages getThumbnail \
--params '{"presentationId": "PRES_ID", "pageObjectId": "SLIDE_OBJECT_ID", "thumbnailProperties.thumbnailSize": "LARGE"}'
curl -sL "THUMBNAIL_CONTENT_URL" -o /tmp/slide_N_thumb.png
Then use the Read tool on the downloaded PNG file to visually inspect it. The Read tool supports images and will show you the rendered slide.
For each slide, confirm:
If any issue is found, regenerate the SVG with fixes (add padding for clipping, fix background for borders, simplify for quality), reconvert, re-upload, re-insert, and re-verify.
See references/image-pipeline.md Step 8 for detailed recovery procedures per issue type.
For each slide, replace the template placeholder text with generated content. Use one of two strategies:
For each text element on the slide:
{"deleteText": {"objectId": "SHAPE_ID", "textRange": {"type": "ALL"}}},
{"insertText": {"objectId": "SHAPE_ID", "insertionIndex": 0, "text": "New content"}}
This clears all text and inserts new content. Text styling is inherited from the template layout.
When a slide has unique placeholder text:
{
"replaceAllText": {
"containsText": {"text": "PLACEHOLDER", "matchCase": true},
"replaceText": "Actual content",
"pageObjectIds": ["SLIDE_OBJECT_ID"]
}
}
Always scope with pageObjectIds to avoid replacing text on other slides.
Most layouts follow this pattern (element roles from references/template-structure.md):
| Placeholder Type | Role | Content Guidelines |
|---|---|---|
| SUBTITLE (top, ~y=22pt) | Section label | Short category label, 2-4 words |
| TITLE (~y=39pt) | Slide heading | Clear, concise title, max 8 words |
| BODY (upper area) | Intro or full-width text | 1-2 sentences max |
| SUBTITLE (mid area) | Article/column title | Column heading, 3-6 words |
| BODY (lower/column area) | Column content | Bullet points or short paragraphs |
For each slide, find the notes page BODY element:
From the presentation read, access slides[N].slideProperties.notesPage.pageElements and find the element with placeholder.type = "BODY". Then:
{"insertText": {"objectId": "NOTES_BODY_ELEMENT_ID", "insertionIndex": 0, "text": "Speaker notes content..."}}
Speaker notes should contain the detailed explanation behind the slide — the prose that would be in a document. Keep slide text concise and put depth in the notes.
Contact slide: Replace text in the contact card group children:
Closing slide: Do NOT modify. The "GRACIAS" text, BinPar logo, and decorative elements stay as-is.
Keep batchUpdate requests under ~20 operations per call. Process all text operations for a few slides per batch.
The TOC must be generated LAST, after all other slides have their final titles.
CI=true gws slides presentations get --params '{"presentationId": "PRES_ID"}'
For each slide (excluding Title, TOC, Contact, and Closing), find the TITLE placeholder element and extract its text content.
● First Section Title ........................................ pág. 3
● Second Section Title ........................................ pág. 4
● Third Section Title ........................................ pág. 5
Pad with dots to align page numbers. Page numbers are 1-indexed slide positions.
Replace the TOC description text (second text box) with a brief description of the presentation:
{"deleteText": {"objectId": "TOC_DESCRIPTION_ID", "textRange": {"type": "ALL"}}},
{"insertText": {"objectId": "TOC_DESCRIPTION_ID", "insertionIndex": 0, "text": "En las siguientes diapositivas encontrarás un desglose detallado de [presentation topic]."}}
Replace the index entries (third text box) with the generated TOC:
{"deleteText": {"objectId": "TOC_INDEX_ID", "textRange": {"type": "ALL"}}},
{"insertText": {"objectId": "TOC_INDEX_ID", "insertionIndex": 0, "text": "● Section Title ........................................ pág. 3\n● Section Title ........................................ pág. 4"}}
CI=true gws drive files delete --params '{"fileId": "UPLOADED_FILE_ID_1"}'
CI=true gws drive files delete --params '{"fileId": "UPLOADED_FILE_ID_2"}'
# ... for each uploaded image
rm -rf ./slide_assets/
Non-critical failures here (404 on delete) can be logged and ignored.
CI=true gws drive files get --params '{"fileId": "PRES_ID", "fields": "id,name,webViewLink"}'
Display the following as text in the chat message (not just in tool output):
IMPORTANT: Always show the URL as plain text in the chat message so the user can see and click it.
| Error | Cause | Solution |
|---|---|---|
gws not found | CLI not installed | Tell user: "Ask me to 'Set up BinPar tools'" |
| 401 Unauthorized | Token expired | Run CI=true gws auth login, show URL in chat |
| Template not accessible | Sharing permissions | Verify template ID 1930-cBpaLoe7F-sRoWy_XB5Xj2wEwXWc8MYvAOvuAxA is shared |
batchUpdate fails | Invalid objectId | Re-read presentation (Step 6) for fresh IDs, rebuild requests, retry |
| SVG generation fails | Subagent error | Re-spawn subagent with simpler instructions |
| SVG conversion fails | No converter available | Try next in chain: cairosvg → inkscape → chrome → pillow |
| Image upload fails | Drive quota/permissions | Retry once; check auth status |
createImage returns 400 | URL not publicly accessible | Verify permission was set in Step 9.2 |
createImage invalid image | Corrupt PNG | Reconvert from SVG or regenerate |
| Rate limit (429) | API quota exceeded | Wait 2-5 seconds, retry the failed call |
| Large batch timeout | Too many operations | Split batchUpdate into smaller batches (~20 requests) |
| Cleanup delete fails | File already deleted | Non-critical — log and continue |
If a batchUpdate fails, always re-read the presentation structure before retrying — objectIds and slide positions may have changed.