Imported from petobens/ai-harness (
skills/google-slides/SKILL.md). Install upstream withnpx skills add petobens/ai-harness --skill google-slides. Copyright stays with the author.
Google Slides
You interact with Google Slides through gws: reading and inspecting decks,
editing text and styling, adding, duplicating, and deleting slides, and
rendering slides to check their fit. Reads and edits work on any deck. When you
create or add a slide, do it template-first — start from a slide in the Muttdata
template deck and preserve its visual design, rather than building a layout from
scratch.
The work is visual execution, not content strategy. Content — storyline, titles, chips, bullets, template recommendations — may already be decided upstream. Do not invent the argument; render the provided content by changing only the text needed. When the content names a recommended template slide, treat that as a visual hint, not a content instruction.
Muttdata template deck:
1_dE4_JqjIfj-aL30WJvxpV0YCgoPbJsQHOr8z1gJHCo
(https://docs.google.com/presentation/d/1_dE4_JqjIfj-aL30WJvxpV0YCgoPbJsQHOr8z1gJHCo/edit).
Role boundaries
- These boundaries govern how you create and edit slides; plain reads and inspections have no such limits. When editing, you own visual execution, not content strategy — assume titles, chips, and bullets may already be final.
- Do not rewrite content unless necessary for visual fit. Prefer layout adaptation over wording changes.
- If content is too dense, preserve readability and suggest splitting the slide rather than shrinking typography aggressively.
- If substantial net-new content would be needed to complete a slide, say a content pass is needed rather than expanding the argument yourself. At most make minimal wording fixes for fit, grammar, truncation, or parallelism.
Rules
- Confirm before creating a presentation, copying a slide into a deck, or trashing a presentation. Do not ask for extra permission before normal text edits to an existing deck.
- Do not ask for task-level permission before safe local prep, such as
inspecting a deck, building request JSON in
/tmp, or running--dry-run. If the environment requires sandbox approval, request it as a tool permission only. - Google Slides reads and writes require network access. In restricted
sandboxes, if a
gwscommand fails with a DNS, discovery, or other network-access error, rerun the same command with escalated tool permissions; do not treat it as a presentation or API-shape failure. - When reading a deck, keep the content in agent context and return only a brief confirmation with the title, ID, URL, and inspected slides by default. Do not paste slide text, a full extraction, or a summary unless the user explicitly asks for one.
- Always inspect the target presentation before editing, and review every edited or created slide for visual fit before reporting done.
- Make the smallest safe change that achieves the goal, and preserve consistency across the whole deck.
- The first delivered version of each slide must already be visually clean — no known overflow, clipping, excessive line breaks, or poor fit left for a later round.
- Accept Slides URLs or IDs; always pass the bare presentation ID (the segment
after
/presentation/d/) togws. - Use
gws schema slides.presentations.METHODorgws slides --helpwhen unsure about params or request bodies. - Report final title, ID, URL, and operations performed.
Files (create, find, copy, rename, trash)
Use the google-drive skill for these. A presentation's mimeType is
application/vnd.google-apps.presentation; pass it when creating, and confirm
the target matches it before copy, rename, or trash. (Note: "copy" here means
copying a whole presentation file in Drive, a different operation from copying
a single template slide between decks (see Copying a template slide across
decks).)
# Create a blank presentation
gws drive files create \
--params '{"fields":"id,name,webViewLink","supportsAllDrives":true}' \
--json '{"name":"Presentation title","mimeType":"application/vnd.google-apps.presentation","parents":["root"]}'
Reading and inspecting
Reading and inspecting are different intents. Read when you need the deck's
content — to show it or answer a question about it. Inspect when you are preparing
an edit and need element objectIds, placeholder types, text run indices,
geometry, and styling. Both save the response to /tmp first, then parse the
local JSON, which avoids losing the read to terminal output truncation or a
transient network error, and verify it is a presentation payload, not an API
error object:
jq -e '(has("error") | not) and (.slides | type == "array")' /tmp/slides-read.json
If it contains an error with DNS, discovery, connection, or temporary lookup
wording, rerun the same gws command with escalated network permissions. Do not
pipe a failed response into jq as if it were a deck.
Reading content
For a read, fetch the full payload — omit the fields mask so nothing is left
out, including text inside grouped elements and table cells:
gws slides presentations get \
--params '{"presentationId":"PRES_ID"}' \
2> /dev/null > /tmp/slides-read.json
Use the compact slide index for a read confirmation; extract full slide text only when the user explicitly asks. The extractor recurses through grouped children and table cells:
jq -r '
def texts:
[
.shape?.text?.textElements[]?.textRun?.content,
(.elementGroup?.children[]? | texts),
.table?.tableRows[]?.tableCells[]?.text?.textElements[]?.textRun?.content
] | flatten | map(select(. != null));
"TITLE\t" + (.title // ""),
(.slides | to_entries[] |
["SLIDE", (.key + 1 | tostring), .value.objectId,
([.value.pageElements[]? | texts[]]
| map(gsub("\u000b"; " ")
| gsub("\n"; " ")
| gsub(" +"; " ")
| gsub("^ +| +$"; ""))
| map(select(length > 0))
| .[0:4]
| join(" | "))
] | @tsv)
' /tmp/slides-read.json
Text baked into images, charts, or other raster assets is not API-native; render a thumbnail (see Verifying) and inspect visually when that content matters.
Inspecting for edits
For edit prep, add a field mask to surface the objectIds, placeholder types, run
indices, geometry, and styling you need while keeping the response manageable; it
also reaches text inside grouped elements and table cells:
fields="title,pageSize,slides(objectId,\
slideProperties.layoutObjectId,pageElements(objectId,size,transform,\
shape(shapeType,placeholder,text(textElements(startIndex,endIndex,\
textRun(content,style),paragraphMarker(bullet,style)))),\
elementGroup(children(objectId,size,transform,\
shape(shapeType,placeholder,text(textElements(startIndex,endIndex,\
textRun(content,style),paragraphMarker(bullet,style)))),image,\
table(tableRows(tableCells(text(textElements(startIndex,endIndex,\
textRun(content,style),paragraphMarker(bullet,style)))))))),image,\
table(tableRows(tableCells(text(textElements(startIndex,endIndex,\
textRun(content,style),paragraphMarker(bullet,style)))))))))"
params="$(jq -nc \
--arg fields "$fields" \
'{presentationId:"PRES_ID",fields:$fields}')"
gws slides presentations get \
--params "$params" \
2> /dev/null > /tmp/slides-read.json
From the output, note for each shape you intend to edit: its objectId, the
placeholder.type (e.g. TITLE, SUBTITLE, BODY), the text run
startIndex/endIndex (zero-based, end exclusive — these are the ranges you
target for styling), and its position/size. Element order is z-order.
When post-processing this output programmatically, account for two quirks:
startIndex is omitted entirely when it is 0 (read it as .get("startIndex", 0), never index directly), and gws prints a Using keyring backend banner to
stderr. Pipe with 2>/dev/null when feeding stdout to a JSON parser; never
2>&1, which merges that banner into the JSON and breaks the parse.
Editing an existing deck
Text and styling changes on slides already in the deck. Creating new slides is template-first and covered below.
Text and style edits
Every edit below is one gws slides presentations batchUpdate call (requests
apply atomically; one invalid request rolls back the whole batch). Validate
layout-sensitive bodies with --dry-run first. For non-trivial JSON, write
/tmp/slides.json and pass --json "$(cat /tmp/slides.json)".
gws slides presentations batchUpdate --dry-run \
--params '{"presentationId":"PRES_ID"}' \
--json "$(cat /tmp/slides.json)"
Replace text (the common case). replaceAllText is the simplest way to
swap template placeholder text. Scope it to one slide with pageObjectIds.
{
"requests": [
{
"replaceAllText": {
"containsText": {
"text": "{{TITLE}}",
"matchCase": true
},
"replaceText": "Q3 Revenue",
"pageObjectIds": [
"SLIDE_OBJECT_ID"
]
}
}
]
}
Replace a single shape's text while preserving its style. Read the shape's first text-run style and paragraph style from the inspect step, then delete all text, insert the new text, and reapply the captured styles over the full range:
{
"requests": [
{
"deleteText": {
"objectId": "SHAPE_ID",
"textRange": {
"type": "ALL"
}
}
},
{
"insertText": {
"objectId": "SHAPE_ID",
"insertionIndex": 0,
"text": "New copy"
}
},
{
"updateTextStyle": {
"objectId": "SHAPE_ID",
"textRange": {
"type": "ALL"
},
"style": {
"bold": true,
"fontSize": {
"magnitude": 18,
"unit": "PT"
}
},
"fields": "bold,fontSize"
}
}
]
}
Restyle existing text over an explicit range (indices from inspect; omit the
range or use {"type":"ALL"} for the whole shape):
{
"requests": [
{
"updateTextStyle": {
"objectId": "SHAPE_ID",
"textRange": {
"type": "FIXED_RANGE",
"startIndex": 0,
"endIndex": 12
},
"style": {
"bold": true,
"foregroundColor": {
"opaqueColor": {
"rgbColor": {
"red": 0.0,
"green": 0.06,
"blue": 1.0
}
}
}
},
"fields": "bold,foregroundColor"
}
}
]
}
Supported style fields mirror the Slides API: bold, italic, underline,
strikethrough, smallCaps, foregroundColor (RGB floats 0..1), fontFamily,
fontSize ({magnitude, unit:"PT"}), and link ({url}). Always set fields
to exactly the keys you changed.
Slide-level operations (each a single request in a batchUpdate):
- Create a blank slide:
createSlidewith optionalinsertionIndex(zero-based) andslideLayoutReference.predefinedLayout. Prefer copying a template slide overcreateSlide. - Duplicate a slide:
duplicateObjectwith{objectId:"SLIDE_ID"}; reposition the copy with a follow-upupdateSlidesPosition({slideObjectIds:["NEW_ID"],insertionIndex:N}). - Delete a slide:
deleteObjectwith{objectId:"SLIDE_ID"}.
For anything the above do not cover — shapes, images, tables, geometry,
recoloring — build the requests directly against the Slides batchUpdate API.
Text replacement and fitting
- Change text content but preserve formatting. Take particular care not to flip bold to regular or regular to bold, and preserve font family, size, color, emphasis, alignment, and text-box structure unless a small change is required for fit.
- A
**Chip:**field is a short pill-style context label (e.g. "Overall Outlook"), not a subtitle or second title. Render it in an existing chip/badge/pill/tag element; if the copied template has none, choose a template slide that does rather than adding a subtitle box. Keep chip text short (one to three words) and preserve its fill, corner radius, typography, alignment, and position. - Treat each text box or shape as a hard bounding box: text must fit fully inside without overflow, clipping, or collision. Use the template's original text as the practical maximum density.
- Let the container wrap text naturally. Do not add manual line breaks, paragraph breaks, or blank lines to force wrapping unless they are already in the template. Preserve the original paragraph structure when possible.
- If replacement text overflows, first remove unnecessary manual breaks or excess paragraph spacing. If it still does not fit, prefer a better template slide or split the content rather than forcing denser copy or shrinking type.
- Make geometry adjustments only to prevent overflow, overlap, or clipping — never restyle, recolor, reshape, or stretch elements otherwise.
- Before finishing, review every edited text box for overflow, clipped text, empty lines, unnatural wrapping, and over-dense copy.
Creating slides (template-first)
- Every new slide must start from an existing slide in the template deck. To add a slide, copy the best structural match from the template deck (see Copying a template slide across decks), then edit only the text content needed.
- Do not recreate template slides manually unless copying is impossible.
- Treat the copied slide as the source of truth for layout, spacing, typography, shapes, containers, alignment, emphasis, and footer behavior.
- Treat template slide titles as structural labels (the layout/content pattern), not as content to reproduce; use them as a selection signal.
- If an existing target slide is a poor fit, prefer replacing it with a copied template slide rather than redesigning it by hand.
- Keep all original visual styling unless a minimal adjustment is required to prevent a layout defect.
Template slide selection
Before adding a slide, review the template deck and pick the slide whose structure best matches the content. Match by layout, not superficial text similarity. Common structures: title, section divider, slide with a top-right chip/badge, single statement, 2-column comparison, 3-column framework, card grid, timeline, quote/highlight, image + text, metrics/KPI, process/flow.
Prefer the template slide whose number of text fields, grouping, hierarchy, chip placement, and density most closely match the target content.
Selection priority:
- Exact match on a recommended template slide title, when available and structurally sound.
- Best structural fit for the content.
- Preservation of visual quality and readability.
- Recommended slide number, when provided.
- Variety across the deck.
- Minimal editing effort.
Variety guardrail: maximize template variety across the deck while preserving coherence, but never choose a worse-fitting slide just for variety. When several are equally suitable, prefer one not yet used (or used less) in the deck. Reuse the exact same template slide only when it is clearly the best fit or when consistency across a repeated sequence is desirable. When in doubt, choose the more restrained option and stay as close to the original template as possible.
Copying a template slide across decks
The Slides API cannot copy a slide between presentations while preserving its
design, so this goes through the Muttdata Apps Script web app. Its deployed
source is copy-slides-webapp.gs; edit that
copy and redeploy when the web app changes. Prefer a source slide objectId
(from inspecting the template deck); sourceSlideIndex (1-based) is the
fallback. insertionIndex sets the destination position; omit to append.
url="$(pass show gcloud/appscript/copy-slides/webapp-url)"
secret="$(pass show gcloud/appscript/copy-slides/webapp-secret)"
jq -nc \
--arg secret "$secret" \
--arg src 1_dE4_JqjIfj-aL30WJvxpV0YCgoPbJsQHOr8z1gJHCo \
--arg dst DEST_PRES_ID \
--arg slide SOURCE_SLIDE_OBJECT_ID \
'{secret:$secret, sourcePresentationId:$src, destinationPresentationId:$dst, sourceSlideObjectId:$slide}' |
curl -sSL -H 'Content-Type: application/json' -d @- "$url"
A success response is {"ok":true,"newSlideObjectId":"..."}; failure is
{"ok":false,"error":"..."} or an HTTP status ≥ 400. After copying, inspect the
new slide and replace only the text content needed.
Verifying
The deck JSON cannot reveal whether text overflows its box, collides, or wraps badly — only a render can. For each slide you created or edited, render it and look at the result before reporting done:
# Returns a PNG URL for one rendered slide
url="$(gws slides presentations pages getThumbnail \
--params '{
"presentationId": "PRES_ID",
"pageObjectId": "SLIDE_OBJECT_ID",
"thumbnailProperties.thumbnailSize": "LARGE"
}' \
2> /dev/null | python3 -c \
'import json,sys; print(json.load(sys.stdin)["contentUrl"])')"
curl -sSL "$url" -o /tmp/slide.png
Then open /tmp/slide.png with the Read tool and inspect it for overflow,
clipping, collisions, awkward wrapping, and overall fit. Fix any defect and
re-render before finishing.
getThumbnail is an expensive read request for quota, so use it as a final
visual check per created or edited slide, not after every intermediate edit.
Structured read-back (text runs, indices, styles) stays the right tool for
verifying content and styling; the render is specifically for visual fit.
What to avoid
- Creating slides from scratch when a template slide could be copied.
- Using screenshots, deck-style inference, palette invention, or new visual systems as primary guidance.
- Reformatting copied slides unnecessarily, or carelessly changing bolding, emphasis, shape geometry, or layout rhythm.
- Overlapping text, icons, or shapes; text overflow, clipping, truncated paragraphs, hidden lines, or body copy spilling outside its container.
- Adding unnecessary line breaks, blank lines, or manual wraps that make text taller than needed.
- Exceeding the text capacity suggested by the template's original content.
- Broken geometry or misalignment introduced during text replacement.
- Leaving any slide with unresolved overlap, overflow, clipping, or accidental formatting drift.