Transform ideas into stunning presentations in seconds. Write your content, pick a style, and let AI handle the rest β no design skills required.
- Save Hours β What used to take hours now takes minutes
- Professional Results β Every slide looks polished and cohesive
- Zero Learning Curve β If you can write markdown, you can create presentations
- Iterate Fast β Generate multiple variants instantly and pick your favorite
- π Plan β A skill writes the deck plan from your idea and references, with no API calls
- π¨ Two-Tone Style Plates β Dark curtain plates (cover/transition/ending) + light teaching plates (content), filled from a design brief
- πΌοΈ Slide References β Attach slide-specific photos or image globs in the plan; they pass through to the script
- π§ Role-Aware Render β Routes each slide to the matching style plates by role (cover / transition / content / ending)
- π Multiple Variants β Generate several design options per slide
- π― Selective Regeneration β Redo specific slides without starting over
- β‘ Parallel Generation β Concurrent API calls with configurable concurrency
- π½οΈ Auto PPTX β One 16:9 deck: slide images plus
[Speech:]notes in presenter view - π¬ Narrated Video β TTS + ffmpeg turns slides and
[Speech:]tags into MP4 - π Multi-Provider β OpenRouter (text + images) and Volcengine/Doubao Seedream (images)
pip install -r requirements.txt
cp .env.example .env # add your API keysFour stages β plan β write β design β render. Present is optional.
# 1. Plan (no API): idea.md + references β facts.md, plan_N.md, design_brief.md
# Use the plan-deck skill, then check the plan:
python3 -m src.plan.cli --plan work/plan_16.md
# 2. Write: plan_N.md + facts.md (required) β one text call per slide β script_N.md
python3 -m src.write.cli --work work --plan work/plan_16.md
# 3. Design: image calls β work/style/*.png
python3 -m src.design.cli --work work --script work/script_16.md
# 4. Render: one image call per slide β image dir + PPTX
python3 -m src.render.cli --work work --script work/script_16.md
# Optional: slide PNGs + [Speech:] tags β narrated MP4 (requires ffmpeg)
python3 -m src.present.cli --output work/image_YYYYMMDD_HHMMSSThen curate (delete variants you don't love) and polish (--page to regenerate individual slides).
π‘ Plan writes
facts.md(one- **F1 Name.** fact β citationbullet per fact),plan_N.md(slides cite fact IDs inEvidence:), anddesign_brief.md;src.plan.clialso checks thatSection: k/totallines match the roadmap and that every cited ID exists in thefacts.mdbeside the plan. Write requiresfacts.md, sends each slide its cited facts plus the most related others, expands each slide in parallel, saves prompts underwork/write_prompts/, and then checks the script for structure, page numbers (Slide number: Non content slides only, never a total), text/visual balance (content slides within a 0.85-1.35 text-to-visual ratio; cover, roadmap, and ending slides more visual than text), and speech length (1-2 minutes on content slides, about 30 seconds on the rest). Design writes five plates underwork/style/(style_base_noncontent.png,style_base_content.png,style_cover.png,style_transition.png,style_content.png), each with a*_prompt.txtsidecar. Render routes plates by slide role on the full deck, then applies--page, and writes images towork/image_YYYYMMDD_HHMMSS/plus a PPTX inwork/. Present writespresentation_video_YYYYMMDD_HHMMSS.mp4(install ffmpeg first).
# Design: one plate per stage, or several candidates to pick from
python3 -m src.design.cli --work work --script work/script_16.md
python3 -m src.design.cli --work work --script work/script_16.md --candidates 4 --pick 1,1,2,1,3
# Write: redo a few slides
python3 -m src.write.cli --work work --plan work/plan_16.md --page "3,7-9"
# Render: multiple variants per slide
python3 -m src.render.cli --work work --script work/script_16.md --copy 4
# Render: selective regeneration
python3 -m src.render.cli --work work --script work/script_36.md --page "1,3,5-7"
# Render: rebuild PPTX after curating (no API calls)
python3 -m src.render.cli --work work --pptx-only --output work/image_20260520_220006
python3 -m src.render.cli --work work --pptx-only --output work/image_20260520_220006 --variant 1
# Present: TTS + video from curated slides (MiniMax TTS)
python3 -m src.present.cli --output work/image_20260520_220006 --variant 1
# Present: clone your voice from a short reference recording (clean WAV/MP3)
python3 -m src.present.cli --output work/image_20260520_220006 \
--reference-audio work/voice_sample.wav
# Present: remux only after TTS MP3s already exist (no API calls)
python3 -m src.present.cli --output work/image_20260520_220006 --mux-only| Option | Description |
|---|---|
--plan |
Deck plan to validate (default: work/plan_16.md); also checks Evidence: IDs against facts.md in the same folder |
| Option | Description |
|---|---|
--work |
Work directory (default: work/); must contain facts.md, optional idea.md |
--plan |
Deck plan (plan_N.md), required |
--page |
Slides to rewrite, e.g. 3,7-9 (merges into an existing script) |
--txt-model |
Text model override |
--api-key |
OpenRouter API key override |
--proxy |
HTTP/HTTPS proxy for text calls |
| Option | Description |
|---|---|
--work |
Work directory (default: work/) |
--script |
Script file (default: work/script_16.md) |
--brief |
Design brief (default: WORK/design_brief.md) |
--style |
Style directory (default: WORK/style/) |
--candidates |
Candidates per style image (default: 1, max: 12). 1 skips picking |
--pick |
Pre-select indices: base_noncontent,base_content,cover,transition,content |
--api-key |
API key override |
--proxy |
HTTP/HTTPS proxy (OpenRouter only) |
--provider |
openrouter or volcengine |
| Option | Description |
|---|---|
--work |
Work directory (default: work/) |
--script |
Script file (default: work/script_16.md) |
--style |
Directory holding the style images (default: WORK/style when it exists, else style/) |
--copy |
Variants per slide (default: 1) |
--output |
Output dir (default: WORK/image_YYYYMMDD_HHMMSS/); PPTX goes to --work |
--api-key |
API key override |
--page |
Pages to generate, e.g. 1,3,5-7 |
--proxy |
HTTP/HTTPS proxy (OpenRouter only) |
--provider |
openrouter or volcengine |
--balance-only |
Print OpenRouter credits and exit |
--no-balance |
Skip credits line after run |
--pptx-only |
Rebuild PPTX from existing PNGs in --output dir (no API calls); speaker notes from --script [Speech:] tags |
--variant |
With --pptx-only: variant filter for the PPTX, e.g. 1 or 1,2 |
Credits: After each OpenRouter run, remaining credits are printed unless
--no-balance. Requires an OpenRouter Management API key β setOPENROUTER_MANAGEMENT_API_KEYor[openrouter] management_api_keyin.env.
| Option | Description |
|---|---|
--work |
Work directory (default: work/) |
--script |
Script with [Speech:] tags (default: work/script_16.md). A named missing file is an error. When omitted and the default is missing, uses the newest script_*.md snapshot in --output |
--output |
Render image directory with slide_p##_v##.png (required) |
--page |
Slides to include, e.g. 1,3,5-7 |
--variant |
Variant filter, e.g. 1 or 1,2 |
--api-key |
MiniMax API key override |
--tts-model |
MiniMax TTS model (default: speech-2.8-hd) |
--voice |
TTS voice (default: Chinese (Mandarin)_Lyrical_Voice) |
--reference-audio |
WAV/MP3/FLAC of your voice for MiniMax voice cloning |
--voice-id |
Saved MiniMax voice ID (alternative to --reference-audio) |
--mux-only |
Skip TTS; mux existing slide_p##_v##.mp3 files (no API calls) |
--silent-seconds |
Duration for slides without speech (default: 3.0) |
Prerequisite: ffmpeg and
ffprobemust be on your PATH.Voice clone:
--reference-audiouploads reference audio and clones voice dynamically via MiniMax.
work/
βββ idea.md # seed: title, audience, language
βββ facts.md # verified facts with citations
βββ source.md # optional extra source material
βββ design_brief.md # motif, palette language, on-plate labels
βββ plan_16.md # deck plan (content-slide count in the name)
βββ script_16.md # full slide script
βββ write_prompts/ # one prompt file per slide
βββ style/ # five style plates for this deck
β βββ style_base_noncontent.png
β βββ style_base_content.png
β βββ style_cover.png
β βββ style_transition.png
β βββ style_content.png
β βββ style_*_prompt.txt
β βββ style_candidates/
βββ image_YYYYMMDD_HHMMSS/ # render output (slide PNGs)
βββ slides_YYYYMMDD.pptx # slide images + speaker notes
βββ presentation_video_YYYYMMDD_HHMMSS.mp4 # narrated slide video
# PPT Outline: My Presentation
---
## Slide 1: Introduction
- **Key point:** Why this matters
- Core insight: One takeaway the audience should remember
[Style: style_content.png, style_base_content.png]
[Reference: photos/founder.png]
[Visual: Split-screen hero; light content background. Slide number: 1]
[Speech: Conversational presenter narration for this slide]
---
## Appendix: Global Visual Requirements
- **Theme:** Two-tone deck β white/light content slides; dark curtain for cover, transitions, ending. Accents: Primary #2563EB, Accent #10B981
- **Fonts:** Title / body families and sizes| Tag | Purpose |
|---|---|
[Visual: ...] |
Layout, composition, diagrams, icons, motifs |
[Speech: ...] |
Presenter narration (used by present + PPTX speaker notes) |
[Style: ...] |
Style plates for this slide β filenames only, looked up in --style; omit to inherit the plate for the slide's role |
[Reference: ...] |
Slide-specific image refs such as headshots or charts (place immediately before [Visual:]; leading @ accepted) |
## Appendix: ... |
Deck-wide text constraints only (theme/hex, fonts/sizes; state two-tone backgrounds) |
Include 3β6 transition slides (titles prefixed Roadmap:) with progress bar k/total section markers. Content slides show Slide number: N β the page number only, never the total. Without a [Style: ...] tag, render attaches dark plates to cover/transition/ending and light plates to content slides:
| Slide role | Plates attached |
|---|---|
| cover, ending | style_cover.png, style_base_noncontent.png |
| transition | style_transition.png, style_base_noncontent.png |
| content | style_content.png, style_base_content.png |
work/
βββ image_YYYYMMDD_HHMMSS/
β βββ script_16.md # script snapshot used for this run
β βββ slide_p01_prompt.txt # exact prompt used for slide 1
β βββ slide_p01_v01.png # Slide 1, variant 1
β βββ slide_p01_v01.mp3 # TTS audio (after present)
β βββ slide_p01_v02.png # Slide 1, variant 2
β βββ slide_p02_v01.png # Slide 2, variant 1
βββ slides_YYYYMMDD.pptx # all slides + speaker notes in one deck
βββ presentation_video_YYYYMMDD_HHMMSS.mp4 # narrated MP4
Copy .env.example β .env and fill in your keys. INI-style sections:
| Section | Used by | Key settings |
|---|---|---|
| (preamble) | All | provider, max_concurrent, proxy |
[openrouter] |
Write / Render (when provider = openrouter) |
api_key, management_api_key, img_model, txt_model, use_proxy |
[volcengine] |
Design / Render (when provider = volcengine) |
api_key, img_model, txt_model, use_proxy |
[minimax] |
Present (+ text/image when it is the active provider) | api_key, img_model, txt_model, use_proxy, optional tts_model, tts_voice |
- Write β
txt_modelfrom the activeprovidersection (falls back across[openrouter],[volcengine],[minimax]) - Design / Render β
img_modelfrom the activeprovider(src.design.cli+src.render.cli); OpenRouter image calls go through the unified Image API (POST /v1/images), so image-only models such asopenai/gpt-image-2.5-sunburstwork alongside chat-style ones likegoogle/gemini-3-pro-image-preview - Present β MiniMax TTS via
[minimax]tts_model/tts_voiceor--tts-model/--voice(defaults:speech-2.8-hd,Chinese (Mandarin)_Lyrical_Voice) with optional voice cloning - Global
proxyapplies when a section'suse_proxy = true; section-levelproxyoverrides are still supported ark_api_keyin the preamble aliases[volcengine] api_key
See LICENSE file for details.
Happy Sliding! π¨β¨ Transform your markdown into beautiful presentations in minutes, not hours.