backend/onyx/skills/builtin/pptx/SKILL.md
Path convention: All commands run from the session workspace (your working directory). Never
cdinto the skill directory. Prefix all skill scripts with.opencode/skills/pptx/. All generated files (unpacked dirs, output presentations, thumbnails, PDFs, images) go inoutputs/.
| Task | Guide |
|---|---|
| Read/analyze content | python -m markitdown presentation.pptx |
| Edit or create from template | Read editing.md |
| Create from scratch | Read components.md — tested layout library (default); pptxgenjs.md for raw layouts it doesn't cover |
| Charts from real data | Read charts.md — deck-styled matplotlib PNGs via scripts/chart.py |
| Icons | node .opencode/skills/pptx/scripts/icon.js <name> --color <hex> — see pptxgenjs.md |
| Lint layout (QA step 0) | python .opencode/skills/pptx/scripts/lint.py outputs/output.pptx |
A complete, text-viable deck must exist in outputs/ before any asset
enhancement. The worst outcome is a turn that ends with no deck at all.
outputs/<name>.pptx. On the template-editing path
this means an EARLY first clean+pack (see editing.md): edits
stranded in outputs/unpacked/ deliver nothing.If time or context is running short at any point, stop enhancing and ship the current saved deck. Never spend the start of a turn generating or inspecting assets for a deck that doesn't exist yet.
Read attached/source documents as text (python -m markitdown file.pdf).
Never view_image source pages or figure crops — image reads exhaust the
model context in a handful of pages and the turn dies before the deck is
built. To reuse figures from a source PDF, extract the embedded images
directly — no rendering, no cropping, no visual inspection:
mkdir -p outputs/figs/
pdfimages -png -p source.pdf outputs/figs/fig # fig-<page>-<n>.png per image
python -c "from PIL import Image; import glob; [print(p, Image.open(p).size) for p in sorted(glob.glob('outputs/figs/*.png'))]"
Pick figures by page number and pixel size (real figures are large; icons and logos are small), place them directly, and verify them in the rendered-slide QA pass at the end — never by viewing each extraction.
Starting from a well-designed template produces far better decks than building from scratch. There are two distinct sources of templates — check both:
# User-uploaded templates (the user's own / brand decks) — list these first
ls user_library/*.pptx attachments/*.pptx 2>/dev/null
# Built-in templates (generic starting points provided by Onyx)
ls /workspace/templates/pptx/*.pptx 2>/dev/null
user_library/ and attachments/) and "Built-in templates" (from /workspace/templates/pptx/) — listing the user's own templates first. Briefly describe each (use thumbnail.py to preview if helpful), then ask which one to use — or whether to build from scratch — and wait for their answer before building. Never silently pick one: a user's own brand deck and a generic built-in are not interchangeable, and only the user knows which they want. This is one of the few cases where you should pause and ask rather than act autonomously..pptx for a more polished, on-brand result next time.Once a template is chosen, follow editing.md. Use the from-scratch path only when the user has no template or explicitly opts for it — default to the tested layout library (components.md), and fall back to raw pptxgenjs.md for layouts it doesn't cover.
# Text extraction
python -m markitdown presentation.pptx
# Visual overview
python .opencode/skills/pptx/scripts/thumbnail.py presentation.pptx
# Raw XML
python .opencode/skills/pptx/scripts/office/unpack.py presentation.pptx outputs/unpacked/
Read editing.md for full details.
thumbnail.pyRead pptxgenjs.md for full details.
Use only when no template is available or the user explicitly chooses to build from scratch (see Choosing a Template).
These ideas are for building from scratch. When developing on a chosen template, inherit the template's existing palette, fonts, and layout language — match it, don't redesign it. Skip the palette/font suggestions below and reuse what the template already defines.
Don't create boring slides. Plain bullets on a white background won't impress anyone. Consider ideas from this list for each slide.
Choose colors that match your topic — don't default to generic blue. Use these palettes as inspiration:
| Theme | Primary | Secondary | Accent |
|---|---|---|---|
| Midnight Executive | 1E2761 (navy) | CADCFC (ice blue) | FFFFFF (white) |
| Forest & Moss | 2C5F2D (forest) | 97BC62 (moss) | F5F5F5 (cream) |
| Coral Energy | F96167 (coral) | F9E795 (gold) | 2F3C7E (navy) |
| Warm Terracotta | B85042 (terracotta) | E7E8D1 (sand) | A7BEAE (sage) |
| Ocean Gradient | 065A82 (deep blue) | 1C7293 (teal) | 21295C (midnight) |
| Charcoal Minimal | 36454F (charcoal) | F2F2F2 (off-white) | 212121 (black) |
| Teal Trust | 028090 (teal) | 00A896 (seafoam) | 02C39A (mint) |
| Berry & Cream | 6D2E46 (berry) | A26769 (dusty rose) | ECE2D0 (cream) |
| Sage Calm | 84B59F (sage) | 69A297 (eucalyptus) | 50808E (slate) |
| Cherry Bold | 990011 (cherry) | FCF6F5 (off-white) | 2F3C7E (navy) |
Every slide needs a visual element — image, chart, icon, or shape. Text-only slides are forgettable. Render deck-styled charts from real data with charts.md; render icons zero-setup with scripts/icon.js (see pptxgenjs.md).
Layout options:
Data display:
Visual polish:
Choose an interesting font pairing — don't default to Arial. Pick a header font with personality and pair it with a clean body font.
When building from scratch, use only the fonts below — any other font name silently falls back and renders wrong. Available: Inter, Montserrat, Lato, EB Garamond and Fira Code (monospace), plus Calibri, Cambria, Arial, Times New Roman (rendered via metric-compatible substitutes — Carlito/Caladea/Liberation).
When editing a template, this list does not apply — keep the template's own fonts. A template may use fonts not listed here (e.g. Kabel, Garet); these are typically embedded in the file and render correctly even though they aren't installed system-wide. Do not swap them for an installed font — that destroys the template's design. clean.py/pack.py preserve embedded fonts (ppt/fonts/), so they survive the edit cycle.
| Header Font | Body Font | Feel |
|---|---|---|
| Montserrat | Inter | Modern, geometric — strong default for a contemporary deck |
| Inter | Inter | Minimal; lean on weight (Bold/Light) for hierarchy |
| EB Garamond | Inter | Editorial — elegant serif title over a clean sans body |
| Lato | Lato | Professional, corporate-safe |
| Cambria | Calibri | Classic Office serif/sans pairing |
| Arial Black | Inter | Bold, high-impact headlines |
Use Fira Code for code samples or dense numeric/stat blocks.
| Element | Size |
|---|---|
| Slide title | 36-44pt bold |
| Section header | 20-24pt bold |
| Body text | 14-16pt |
| Captions | 10-12pt muted |
margin: 0 on the text box or offset the shape to account for paddingAssume there are problems. Your job is to find them.
Your first render is almost never correct. Approach QA as a bug hunt, not a confirmation step. If you found zero issues on first inspection, you weren't looking hard enough.
Before any render or vision pass, run the deterministic layout linter — it catches the mechanical defects (off-slide shapes, sub-margin text, text overflow, overlapping text frames, low-contrast explicit colors, unknown fonts) with exact slide/shape references:
python .opencode/skills/pptx/scripts/lint.py outputs/output.pptx
LINT_CLEAN means no findings; nonzero means ERRORs exist.--profile dense (relaxes the margin warning from 0.5" to 0.25"); error-level checks are unchanged. Tight margins on a dense analytical slide are a deliberate style, not a defect — don't strip density to silence standard-profile MARGIN warnings.python -m markitdown output.pptx
Check for missing content, typos, wrong order.
When using templates, check for leftover placeholder text:
python -m markitdown output.pptx | grep -iE "xxxx|lorem|ipsum|this.*(page|slide).*layout"
If grep returns results, fix them before declaring success.
⚠️ USE SUBAGENTS — even for 2-3 slides. You've been staring at the code and will see what you expect, not what's there. Subagents have fresh eyes.
Convert slides to images (see Converting to Images), then use this prompt:
Visually inspect these slides. Assume there are issues — find them.
Look for:
- Decorative lines positioned for single-line text but title wrapped to two lines
- Source citations or footers colliding with content above
- Elements too close (< 0.3" gaps) or cards/sections nearly touching
- Uneven gaps (large empty area in one place, cramped in another)
- Columns or similar elements not aligned consistently
- Low-contrast text over images or gradients (the linter can't check these)
- Low-contrast icons (e.g., dark icons on dark backgrounds without a contrasting circle)
- Text boxes too narrow causing excessive wrapping
- Inconsistent styling across slides (fonts, colors, motifs drifting)
- Leftover placeholder content
For each slide, list issues or areas of concern, even if minor.
Read and analyze these images:
1. /path/to/slide-01.jpg (Expected: [brief description])
2. /path/to/slide-02.jpg (Expected: [brief description])
Report ALL issues found, including minor ones.
lint.py → fix every ERROR (and unjustified WARNs)Do not declare success until you've completed at least one fix-and-verify cycle.
Convert presentations to individual slide images for visual inspection:
python .opencode/skills/pptx/scripts/office/soffice.py --headless --convert-to pdf outputs/output.pptx
pdftoppm -jpeg -r 150 outputs/output.pdf outputs/slide
This creates slide-01.jpg, slide-02.jpg, etc.
To re-render specific slides after fixes:
pdftoppm -jpeg -r 150 -f N -l N outputs/output.pdf outputs/slide-fixed
pip install "markitdown[pptx]" - text extractionpip install Pillow - thumbnail gridsnpm install -g pptxgenjs - creating from scratchsoffice) - PDF conversion (auto-configured for sandboxed environments via .opencode/skills/pptx/scripts/office/soffice.py)pdftoppm) - PDF to images