SuperAPI AI Directory

p-md-to-slides

Office & productivity

View repository

Updated 10-15 22:39

About this skill

p-md-to-slides

Convert a Markdown deck source into a styled Google Slides presentation, with a chosen design theme and speaker notes pushed from the source.

When to use

  • The user has a slide.md (or similar) with ## Slide N — title sections and wants Google Slides
  • The user is preparing a talk / LT / conference presentation and has drafted the content in Markdown
  • The user asks to convert / build / generate slides from a Markdown source

Not for activity-driven decks (see o-tailor-allhands-deck for those) — this is for hand-authored Markdown → Google Slides.

Prerequisites

  • gws CLI installed and authenticated (gws auth login if needed)
  • Python 3 on PATH (for one-shot heredoc snippets)
  • Network access to Google Slides API

Input format

See references/md-format.md for the full spec. Quick summary:

# <deck title>

## Slide N — <subtitle>

**話すこと:**
<speaker notes — pushed to the slide's notes BODY>

**スライド要素:**
- <visual element hint 1>
- <visual element hint 2>

Both **話すこと:** and **スライド要素:** are optional per slide.

Workflow

The skill is intentionally lightweight — no shipped scripts. Every step is a concrete shell or Python heredoc that Claude executes inline, with patterns referenced from references/.

Step 1: Read the source

Use the Read tool on the user's slide.md. Confirm:

  • The file exists and parses (look for at least one ## Slide N — line)
  • The slide count matches what the user expects
  • Identify which slides have **話すこと:** and which don't

If the user is starting from scratch, offer to scaffold a slide.md from a brief.

Step 2: Pick a theme

Read the relevant theme reference file based on user preference (default tailor if not specified):

  • references/themes/tailor.md — Tailor brand (navy/cream/blue, DM Sans + Roboto Mono, alternating rhythm)
  • references/themes/minimal.md — All-cream, navy text
  • references/themes/dark.md — All-navy, cream text, electric blue accents

If the user wants something custom, ask for brand colors + display font and adapt inline.

Step 3: Parse the markdown

A short Python heredoc handles the parsing. The format is small enough that this beats a separate script.

python3 - <<'PY' > /tmp/p-md-to-slides/parsed.json
import json, re, sys
from pathlib import Path

SRC = "/path/to/slide.md"  # ← fill in
SLIDE_HEADER = re.compile(r"^##\s*Slide\s*(\d+)\s*[—–-]\s*(.+?)\s*$")
BLOCK_MARKER = re.compile(r"^\*\*([^*]+):\*\*\s*$")
BULLET = re.compile(r"^\s*[-*]\s+(.*)$")

text = Path(SRC).read_text(encoding="utf-8")
lines = text.splitlines()

title = next((l[2:].strip() for l in lines if l.startswith("# ") and not l.startswith("## ")), "")
starts = [i for i, l in enumerate(lines) if SLIDE_HEADER.match(l)]
slides = []
for idx, start in enumerate(starts):
    end = starts[idx+1] if idx+1 < len(starts) else len(lines)
    m = SLIDE_HEADER.match(lines[start])
    blocks = {}
    code_blocks = []
    current = None
    in_code = False
    code_buf = []
    for line in lines[start+1:end]:
        if line.lstrip().startswith("```"):
            if in_code:
                code_blocks.append("\n".join(code_buf)); code_buf = []; in_code = False
            else:
                current = None; in_code = True
            continue
        if in_code: code_buf.append(line); continue
        mm = BLOCK_MARKER.match(line)
        if mm:
            current = mm.group(1).strip()
            blocks.setdefault(current, []); continue
        if line.strip() == "---":
            current = None; continue
        if current is not None:
            blocks[current].append(line)
    notes = "\n".join(blocks.get("話すこと", [])).strip()
    elements_raw = "\n".join(blocks.get("スライド要素", [])).strip()
    elements = [BULLET.match(l).group(1).strip() for l in elements_raw.splitlines() if BULLET.match(l)]
    slides.append({"index": idx, "number": int(m.group(1)), "subtitle": m.group(2).strip(),
                    "notes": notes, "elements": elements, "elements_raw": elements_raw,
                    "code_blocks": code_blocks})
print(json.dumps({"title": title, "slides": slides}, ensure_ascii=False, indent=2))
PY

Inspect the JSON briefly (slide count, notes lengths) before continuing.

Step 4: Create the presentation shell

gws slides presentations create --json '{"title":"<deck title>"}' \
  | python3 -c "import json,sys; d=json.load(sys.stdin); print('PRES_ID=' + d['presentationId']); print('BLANK=' + next(l['objectId'] for l in d.get('layouts',[]) if l.get('layoutProperties',{}).get('displayName')=='Blank')); print('DEFAULT=' + d['slides'][0]['objectId'])"

Capture PRES_ID, BLANK (Blank layout ID), and DEFAULT (default slide ID — must delete later).

Step 5: Build the deck

Write a one-off /tmp/p-md-to-slides/<deck-name>/build.py that emits batchUpdate JSON. Read references/slides-helpers.md for the helper code pattern (page constants, color blending, Slide builder class) — copy the helpers into your build script, then add per-slide content.

The base recipe per slide:

  1. create_slide(slide_id, index, BLANK, bg=theme_bg_for_index) — bg cycles per theme rhythm
  2. top_bar(...) — small uppercase labels (e.g. "05 · 工夫① ...")
  3. Heading — 22–24pt w700 for content slides, 40pt w600 bottom-anchored for the title slide
  4. Body — 16–18pt w600–700 bullets from elements, or fall back to a clean prose dump

Projection-readable defaults. Sizes are calibrated for talks projected on a 3–4m screen viewed from 5–10m back. A deck that "looks balanced" in your laptop preview is usually too small in the room. Default heavy and big; trim only when content actually overflows. See the typography table in the chosen theme file for the full role-by-role scale.

At the end, delete(DEFAULT) to remove the auto-created blank slide, then push the batch:

BODY=$(cat /tmp/p-md-to-slides/<deck-name>/batch.json)
gws slides presentations batchUpdate \
  --params '{"presentationId":"<PRES_ID>"}' \
  --json "$BODY"

batchUpdate is transactional — if anything fails, nothing applies. Inspect the error response carefully.

Step 6: Push speaker notes

Discover the notes BODY shape per slide, then insertText:

gws slides presentations get \
  --params '{"presentationId":"<PRES_ID>","fields":"slides(objectId,slideProperties.notesPage.pageElements(objectId,shape.placeholder))"}' \
  | python3 -c "
import json, sys
d = json.load(sys.stdin)
for s in d['slides']:
    sid = s['objectId']
    for el in s.get('slideProperties', {}).get('notesPage', {}).get('pageElements', []):
        if el.get('shape', {}).get('placeholder', {}).get('type') == 'BODY':
            print(f'{sid}|{el[\"objectId\"]}')
"

For each slide, build an insertText request against the notes BODY objectId with the slide's notes text. Skip slides without notes. Don't use deleteText on empty notes — see gotcha in references/slides-api-patterns.md.

Step 7: Verify with thumbnails

mkdir -p /tmp/p-md-to-slides/<deck-name>/thumbs
gws slides presentations get --params '{"presentationId":"<PRES_ID>","fields":"slides.objectId"}' \
  | python3 -c "import json,sys; [print(s['objectId']) for s in json.load(sys.stdin)['slides']]" \
  | nl | while read i sid; do
    url=$(gws slides presentations pages getThumbnail \
      --params "{\"presentationId\":\"<PRES_ID>\",\"pageObjectId\":\"$sid\",\"thumbnailProperties.mimeType\":\"PNG\",\"thumbnailProperties.thumbnailSize\":\"LARGE\"}" \
      | python3 -c "import json,sys;print(json.load(sys.stdin)['contentUrl'])")
    curl -s -o "/tmp/p-md-to-slides/<deck-name>/thumbs/$(printf %02d $i)_${sid}.png" "$url"
  done

Then Read each PNG to inspect. The slide is only 405pt tall — content past y≈395 is invisible. The thumbnail is the only catch.

Step 8: Iterate on visual polish

The base deck has heading + body only. Editorial polish is content-aware and applied as a per-deck upgrade.py. Common moves (all using the same Slide builder pattern from references/slides-helpers.md):

  • Decorative anchor character (top-right): a single oversized Roboto Mono glyph in dim color. Numbers ("01" / "02" / "03") for ordered section slides, but also "?" on question slides, "/" on transitions, "※" on caveats. The anchor labels the slide without adding reading load.
  • Subhead (tagline) under heading: 12–14pt muted single line at y≈95. Names the slide's thesis — the "why am I looking at this?" — not just the topic. Particularly effective on section / content slides where the heading alone is the topic name.
  • "Before / After" or "Journey" slides: colored rectangles with arrow between, accent badge for the "why"
  • Flow diagrams: colored boxes per stage, contrasting fills for branches
  • Title slide: small accent bar (theme accent) above the headline
  • Summary slides (simple): 2×2 cards with light fill and left accent bar
  • Summary slides (story-structured): narrative-numbered cards (01 課題 / 02 解決 / 03 品質 / 04 未来). Use when the recap has a sequence, not just 4 independent points — the numbers tell the audience this is a story arc.
  • Dark footer band (cream summary closer): navy band at the bottom of a cream summary slide holding CTA / contact info. Acts as a visual period — physically separates "what we covered" from "thank you / find me here."

Apply by writing a fresh batchUpdate, re-fetch thumbnails, iterate.

Step 9: Deliver

https://docs.google.com/presentation/d/<PRES_ID>/edit

Key gotchas (full list: references/slides-api-patterns.md)

  • Page height is 405pt — not 540. Anything past y≈395 is off-canvas.
  • Object IDs must be unique across the entire presentation. Don't reuse.
  • Empty notes: startIndex==endIndex==0; use insertText only, never deleteText.
  • batchUpdate is transactional: one failure rolls back the whole batch.
  • z-order is creation order: backgrounds must be SEND_TO_BACK explicitly.
  • DM Sans + Japanese: falls back, may render slightly oblique. Acceptable; verify with thumbnails.

Reference files

  • references/md-format.md — input format spec
  • references/slides-api-patterns.md — gotchas, color math, request shapes
  • references/slides-helpers.md — reusable Python helper class (copy into build.py)
  • references/themes/{tailor,minimal,dark}.md — design tokens per theme