- name
- pptx-official
- description
- Use this skill whenever a Microsoft PowerPoint (.pptx) file is being produced, opened, transformed, or read. That includes: authoring slide decks, pitch decks, executive readouts, training material, or any presentation deliverable; extracting text or structure from an existing .pptx; filling a .pptx template with values; converting a deck to PDF or images; splitting or merging decks; inspecting slides, layouts, masters, tables, images, charts, speaker notes, or comments. Trigger on words like 'deck', 'slides', 'presentation', 'pitch deck', 'keynote' (when a .pptx is expected as output), or any filename ending in .pptx. Do NOT trigger when the primary deliverable is a Word document, spreadsheet, PDF report, HTML site, or Google Slides API call, even if presentation-shaped content appears along the way.
- license
- Apache-2.0 — see LICENSE for terms and third-party attributions
# PPTX Skill
An Apache-2.0 toolkit for producing, editing, and reading Microsoft PowerPoint
(`.pptx`) files. Written from scratch against the public
[ECMA-376 / ISO/IEC 29500 (PresentationML)](https://www.ecma-international.org/publications-and-standards/standards/ecma-376/)
specification and built on permissively-licensed tooling (`python-pptx` MIT,
`pptxgenjs` MIT, `lxml` BSD-3-Clause, `Pillow` MIT-CMU, optional external
binaries `soffice` MPL 2.0 and `pdftoppm` GPL) so it can be reused in
commercial projects without restriction.
## Decision matrix
| Situation | Path | Read first |
|-----------|------|------------|
| Build a deck from a prompt or dataset — no source file to start from | Author with `python-pptx` (structured / repeatable) or PptxGenJS (design-heavy, JS) | [`create.md`](create.md) |
| You have a `.pptx` template to fill in — keep its master, layouts, look | Placeholder replacement via `python-pptx` | [`edit.md`](edit.md) → *Template fill* |
| Deep structural edits — reorder slides, splice XML, add unusual objects | Explode → edit XML parts → assemble | [`edit.md`](edit.md) → *Raw XML workflow* |
| Only need the text / speaker notes / structure out of a `.pptx` | Extraction pipeline | [`read.md`](read.md) |
| Turn a deck into PDF or PNG images (visual QA, publishing) | `scripts/render_pdf.py` / `scripts/render_slides.py` via LibreOffice | see *QA* below |
| Grid of slide thumbnails for previewing a template | `scripts/contact_sheet.py` (Pillow) | [`read.md`](read.md) → *Thumbnails* |
If the task mixes several of these, do them in this order:
**read → plan slide-by-slide → edit/create → validate → visual QA.**
## One-time environment setup
> **Bundled runtime:** when the `MIMO_PYTHON` environment variable is set, skip `uv`/pip installs — run every Python command below with `uv run` replaced by `"$MIMO_PYTHON"` (python-pptx/Pillow/lxml preinstalled; pip console scripts unavailable, use `"$MIMO_PYTHON" -m <module>`). A bundled LibreOffice is exposed as `MIMO_SOFFICE` (picked up automatically by `soffice_bridge.py`/`render_pdf.py`), and slide rasterisation automatically falls back to pypdfium2 in that interpreter when Poppler (`pdftoppm`) is absent. For the PptxGenJS authoring path, bundled Node.js is exposed as `MIMO_NODE`, with pptxgenjs/react/react-dom/sharp/react-icons/mathjax-full preinstalled in `MIMO_NODE_MODULES` — run scripts as `NODE_PATH="$MIMO_NODE_MODULES" "$MIMO_NODE" <script.js>` instead of `npm install`.
### Prerequisites
If `uv` or `bun` are not yet installed:
```bash
# Install uv (Python package/project manager)
# macOS / Linux
curl -LsSf https://astral.sh/uv/install.sh | sh
# Windows: powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
# Install bun (TypeScript runtime, replaces Node.js for this workflow)
# macOS / Linux
curl -fsSL https://bun.sh/install | bash
# Windows: powershell -c "irm bun.sh/install.ps1|iex"
```
Only if the user explicitly refuses `uv` / `bun`, substitute `pip` (in a venv you manage yourself) for `uv`, and `npm`/`pnpm` + `npx tsx` for `bun` — everything else in this skill stays the same.
### Python (uv)
Python dependencies are managed by `uv`. Do not use `pip` directly.
```bash
# Initialize project (if no pyproject.toml exists)
uv init -p 3.12
# Add dependencies
uv add python-pptx lxml Pillow
uv add defusedxml # safe XML parsing (recommended for manual XML edits)
```
**Rules:**
- Never use `pip` — always `uv add` for packages.
- Never run `python scripts/...` directly — always `uv run scripts/...`.
- Don't manually manage environments with `python -m venv` or `source .venv/bin/activate`.
### TypeScript (bun)
For PptxGenJS creation, use `bun` (project-local, not global installs):
```bash
# Initialize (if no package.json exists)
bun init -y
# Add dependencies
bun add pptxgenjs # core PPTX creation library
bun add react react-dom sharp # rasterization (icons + formulas)
bun add react-icons # icon library (FA, MD, etc.)
bun add mathjax-full # LaTeX formula rendering
# Type definitions (including Bun runtime types)
bun add -d @types/bun @types/react @types/react-dom
```
Create a `tsconfig.json` if one doesn't exist:
```json
{
"compilerOptions": {
"lib": ["ESNext"],
"target": "ESNext",
"module": "Preserve",
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"noEmit": true,
"jsx": "react-jsx",
"strict": true,
"skipLibCheck": true,
"types": ["bun"]
}
}
```
Run scripts directly as TypeScript — no transpilation needed:
```bash
bun run create-ppt.ts
```
**Always run type checking after writing or modifying TS code:**
```bash
bun tsc --noEmit
```
Models may inadvertently use outdated PptxGenJS API signatures or
deprecated syntax without realizing it. A type check catches these
mismatches before runtime.
### System dependencies (PDF/PNG rendering)
```bash
# macOS
brew install --cask libreoffice
brew install poppler
# Debian/Ubuntu
sudo apt-get install -y libreoffice poppler-utils
```
Every script under `scripts/` uses only the standard library plus
`python-pptx`, `lxml`, and `Pillow`. No proprietary dependencies. External
binaries (`soffice`, `pdftoppm`) are invoked as subprocesses; nothing is
bundled or statically linked.
## Common commands
```bash
# 1. Extract text from every slide (title, body, notes) — the "what does it say?" query
uv run scripts/dump_text.py input.pptx --notes > input.txt
# 2. Convert a deck to PDF for review
uv run scripts/render_pdf.py input.pptx # writes input.pdf next to it
# 3. Convert every slide to a PNG (visual QA)
uv run scripts/render_slides.py input.pptx --out slides/ # writes slides/slide-1.png, ...
# 4. Grid thumbnail preview (planning which template slide to reuse)
uv run scripts/contact_sheet.py input.pptx --cols 3 # writes input.contact-sheet.jpg
# 5. Explode a .pptx into readable XML for surgical edits
uv run scripts/explode.py input.pptx unpacked/
# 6. Reassemble an exploded tree
uv run scripts/assemble.py unpacked/ output.pptx
# 7. Drop orphaned slides and unused media before reassembly
uv run scripts/prune.py unpacked/
# 8. Duplicate slide 3, or spin up a new slide from layout 5
uv run scripts/insert_slide.py unpacked/ --clone slide3.xml
uv run scripts/insert_slide.py unpacked/ --blank-from slideLayout5.xml
# 9. Well-formedness check (ZIP + XML + python-pptx round-trip)
uv run scripts/diagnose.py output.pptx
```
Every script is a small Python CLI. Some scripts (e.g. `render_pdf.py`,
`render_slides.py`, `contact_sheet.py`) import from a shared helper
(`soffice_bridge.py`) in the same directory — copy them together. Read the top
of each file for its full CLI options.
## Live preview
A live preview server is available for real-time slide feedback.
**Not started by default.** When multi-slide work begins, ask the user
if they want live preview enabled.
**Only offer this in a pure command-line environment.** This server is
for the MiMoCode CLI. If you are running inside a host that embeds
MiMoCode via the SDK — a web UI, a desktop app, an IDE plugin, etc. —
that host almost certainly has its own native preview / file-open
mechanism; use it instead and do NOT start this server.
If yes, and this is a CLI environment:
```bash
# `scripts/preview.ts` lives in this skill's bundle directory, not in
# your cwd. Prefix it with the absolute path shown in this skill's
# location header (the folder that contains SKILL.md) — refer to it
# as <SKILL_DIR> below.
# Start (spawns background server, prints URL, exits immediately)
bun run <SKILL_DIR>/scripts/preview.ts /path/to/output.pptx
bun run <SKILL_DIR>/scripts/preview.ts /path/to/output.pptx --port 5000
# Stop
bun run <SKILL_DIR>/scripts/preview.ts --stop /path/to/output.pptx
```
If the server is already running, `preview.ts` detects this via PID file
and prints the existing URL instead of spawning a duplicate.
The background server watches the `.pptx` file, debounces (800ms),
converts to PDF via LibreOffice, and pushes a WebSocket reload to the
browser. The browser's native PDF viewer provides scroll, thumbnails,
zoom, and search. Preview output goes to `.pptx-preview/` (separate
from `qa/`, no conflict with other scripts).
Give the user the printed URL to open in their browser.
**Live preview is for humans only — it does NOT replace visual QA.**
The preview server lets the user watch progress. You must still run
the visual QA subagent (render PNGs to `qa/`, spawn a vision model to
inspect them) as described in the "Visual QA execution model" section.
These are independent workflows:
- Preview server → user sees live updates in browser
- Visual QA subagent → automated inspection, catches issues you can't
**When to regenerate the .pptx during multi-slide work:** If the user
has the preview server running, regenerate the `.pptx` (re-run the
creation script) after completing each logical module — e.g. after
finishing a section's slides, not after every single shape placement.
This gives the user meaningful visual checkpoints without excessive
intermediate renders.
## Authoring principles
Slides are a **visual surface**. Users read the deck at 40 feet from the back
of a room, or in a browser tab three inches wide on a phone. Both have to
work. Keep these in mind:
1. **One idea per slide.** If you can't summarize the slide in a five-word
title, split it in two. Long-form reasoning belongs in the doc that
accompanies the deck, not on the slide itself.
2. **Title, not label.** The title is the thesis of the slide. "Revenue" is a
label. "Revenue grew 34% on 22% headcount" is a title. Titles carry the
argument; bodies carry the evidence.
3. **Every slide earns its visuals.** A slide without a chart, image, icon,
or shape is usually a bullet dump. Turn it into a comparison, a stat
callout, a diagram, or delete it. **A "visual" is not necessarily a
picture** — a stat callout, a comparison shape, or a well-typeset quote
is already a visual. When you do need a picture, see *Image sourcing*
below.
4. **Layouts, not per-slide geometry.** For anything reused (section
dividers, content pages, quote slides), define a `slide_layout` once and
apply it. This makes swapping the theme a one-line change instead of a
fifty-slide sweep.
5. **Aspect ratio matches the target.** 16:9 (default) for laptops and
projectors; 4:3 only when explicitly asked (still common in academia and
some corporate templates); 16:10 for older widescreen.
6. **Speaker notes carry the words.** Put the full narration into
`slide.notes_slide.notes_text_frame` so the presenter can rehearse from
the deck itself. On the slide, keep it to the phrase they can hold in
their head.
7. **Bullets are not the default.** Bullet lists are the failure mode of
most decks — comparisons, tables, icons-with-labels, and stat callouts
almost always land better.
## Image sourcing (choose the right channel)
"Every slide earns its visuals" does **not** mean "generate an image for
every slide." Choose the source based on what the image does. Four channels,
ordered by preference (lower cost + higher stability first):
**L1 · Draw in code.** Icons via react-icons / iconify. Charts via
matplotlib / plotly / echarts. Flowcharts, comparisons, org charts via
shapes + lines. Anything that is a data or concept visualization —
never fetch or generate an image for this.
**L2 · Search a stock library, then download the bytes.** When a slide
needs a **generic real photo** (city skyline, office desk, team
collaboration, nature, stock imagery), use `websearch` with a
`site:unsplash.com` / `site:pexels.com` / `site:pixabay.com` query to find
a real URL, then **download the image to a local file** and pass that path
to `add_picture` (see *Downloading* below). Do NOT try to pass an HTTP URL
directly to `add_picture` — python-pptx's `add_picture` only accepts a
local path or a file-like object; it will NOT fetch a URL for you.
**L3 · Search a specific source.** For **specific real things** (a
particular company's logo, a specific product's official screenshot, a
named person's photo), use `websearch` with a targeted query (e.g.
`"Acme Inc" logo site:acme.com`, or `<product name> screenshot`) instead of
a stock-library query, then download the bytes the same way as L2. Never
call `image_gen` here — a generated logo will not look like the real logo.
Note: `webfetch` returns text/markdown/html only and cannot deliver binary
image bytes — use `bash` + `curl` or python `urllib` to download.
**L4 · Generate with `image_gen`.** Only for **stylized visuals** — cover
art, hero backgrounds, illustration-style concept images, brand-mood
imagery. Budget: **at most 1–2 `image_gen` calls per deck**, typically for
cover or section dividers. Not for every content slide.
### Downloading a URL for `add_picture`
Two working patterns. Both keep the file local so `add_picture` can read
the bytes:
```python
# Pattern A: download to a temp file, then pass the path
from urllib.request import Request, urlopen
from pathlib import Path
def download_image(url: str, dest: Path) -> Path:
req = Request(url, headers={"User-Agent": "Mozilla/5.0"}) # some CDNs 403 an empty UA
with urlopen(req, timeout=15) as r:
dest.write_bytes(r.read())
return dest
path = download_image(hit_url, Path("assets/hero.jpg"))
slide.shapes.add_picture(str(path), Inches(1), Inches(1.5), width=Inches(11))
```
```python
# Pattern B: in-memory via BytesIO — no temp file, but same request headers
from io import BytesIO
from urllib.request import Request, urlopen
req = Request(hit_url, headers={"User-Agent": "Mozilla/5.0"})
with urlopen(req, timeout=15) as r:
slide.shapes.add_picture(BytesIO(r.read()), Inches(1), Inches(1.5), width=Inches(11))
```
Or from a bash step:
```bash
curl -sSL -A "Mozilla/5.0" -o assets/hero.jpg "$URL"
```
Wrap any download in a try/except: on failure fall through to the next
channel (L3 → L4) or a shape+text fallback — never leave a slide blank.
### Decision (run per slide)
1. Does this slide actually need a picture? Often the answer is no — a
large stat callout, a comparison shape, a well-typeset quote, or a
diagram *is* a visual. Skip pictures when layout + typography carries
the message.
2. If yes, pick the cheapest channel that works:
- Data / concept / flow / icon → **L1** (draw in code)
- Generic real photo → **L2** (stock library search + download)
- Specific brand / logo / product / person → **L3** (targeted web search + download)
- Stylized cover / hero / illustration → **L4** (`image_gen`)
3. If a fetch fails: L2 → L3 → L4 → shape + text fallback. Never leave a
slide blank because an image failed to load.
### Anti-patterns
- **Calling `image_gen` on every content slide.** Slow, expensive,
style-inconsistent, visually noisy. A 20-slide deck with 20 generated
images is a red flag, not a success.
- **Passing an HTTP URL to `add_picture`.** python-pptx will raise
`FileNotFoundError` — always download the bytes first (see *Downloading*).
- **Using `webfetch` to grab image bytes.** `webfetch` returns text only.
Use `bash` + `curl` or python `urllib` for binaries.
- **Making up a stock-library URL from memory.** Unsplash / Pexels CDN
paths are opaque hashes — you cannot reliably recall a URL that both
exists AND matches the described content. Always search first, then
download.
- **Generating a logo, celebrity, or product screenshot with `image_gen`.**
The output will not resemble the real thing. Use targeted web search (L3).
- **Executive / status / weekly decks in illustration style.** Work
reporting is data + icons + hierarchy, not concept art. Reserve L4 for
launch, brand, or hero visuals.
- **Leaving a slide blank because an image fetch failed.** Always have a
shape + text fallback; a well-formatted stat callout is a better slide
than an empty one anyway.
### Scene defaults (rough mix per deck type)
L2/L3 both cost about one `websearch` + one download per image — cheap
عرض على GitHub