Entry point for PPT generation. Asks the user to choose a mode (fast, standard, or creative), then collects role / audience / scene / page_count as needed. For standard mode, also asks how images should be sourced (AI generation, web search, or none), whether charts should use AI-generated infographics or ECharts, and whether the final deliverable should be PPTX or PDF. Parses uploaded pdf/docx/md/txt files, produces task_pack.json + info_pack.json in a new deck_dir, then dispatches to sn-ppt-creative or sn-ppt-standard. Fast mode skips optional questions and gets straight to building. Use when the user asks to make a PPT / presentation / 演示 / PPT. If the user asks to open, preview, inspect, or edit previously generated HTML slides in the WebUI/workbench without regenerating, dispatch to sn-ppt-workbench instead of this generation entry.
73
92%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Low
Low-risk findings worth noting
Run sn-ppt-doctor hard checks (SN_API_KEY or capability-specific API keys / node / sn-image-base) at the start of this skill. If any fails, stop and tell the user to run /skill sn-ppt-doctor.
If the user is not asking to generate a new deck and only wants to open an existing/generated deck in the WebUI, do not run these generation preconditions. Dispatch directly to /skill sn-ppt-workbench.
Extract parameters from the user's message:
role (speaker identity)audiencescene (where the deck will be used)page_countlanguage — detect from the user's query: zh-Hans (Simplified Chinese), zh-Hant (Traditional Chinese), or en (English). Do NOT ask the user; just infer and record it. If unsure, use zh-Hans.If the user asks only to open/preview/edit an existing generated deck in the WebUI, dispatch to /skill sn-ppt-workbench deck_dir=<abs-or-user-provided-path> and stop. Do not ask mode questions.
If task_pack.json + info_pack.json already exist in a deck_dir the user refers to and the user asks to continue generation, read them and jump to step 10 (see "Resume" below).
Always ask the user which mode to use first. Call ask_user:
Question — Mode: "Which generation mode should I use?"
Store as ppt_mode in task_pack.
Only ask standard-mode option questions for standard mode. Fast mode and creative mode have fixed defaults — asking extra questions defeats the purpose of "fast."
If ppt_mode == "standard", ask three more questions:
Question — Normal images (decorative / conceptual): "Should I include images, and how should they be sourced?"
If the user picks web search and SERPER_API_KEY is not set, tell them how to get a free key at https://serper.dev. Store as image_source in task_pack.params.
Question — Infographics (charts, flowcharts, diagrams): "For charts and diagrams, should I use AI-generated infographics or ECharts?"
Store as infographic_source in task_pack.params ("ai-gen" or "echarts").
Question — Final output: "Which final file format should I generate?"
Store as output_format in task_pack.params ("pptx" or "pdf"). Default to "pptx" only if resuming an older task_pack.json that lacks this field; do not silently default during a new standard-mode run.
If ppt_mode == "fast": skip image/output questions. Default to image_source = "ai-gen", infographic_source = "echarts", and output_format = "pptx". Also skip role/audience/scene/page_count questions — infer reasonable defaults from the user's query and move directly to building slides. Fast mode means fewer questions, faster start. If the user didn't explicitly state these, make your best guess and proceed.
If ppt_mode == "creative": skip image/output questions. Default to image_source = "ai-gen" (full-page T2I rendering) and output_format = "pptx". Infographics are not applicable. Skip role/audience/scene/page_count unless explicitly stated.
Collect role -> audience -> scene -> page_count — for standard mode only. Use the wording in references/ask_user_templates.md. 2-3 options per question; do not write "其他". For fast/creative modes, infer from the query and move on.
Create deck_dir — location is FIXED, do not guess:
$(pwd)/ppt_decks/. In OpenClaw, cwd at skill-invocation time is the agent's workspace directory (e.g. ~/.openclaw/workspace/). Do NOT use /tmp, the home directory, the repo root, or $SKILL_DIR as the parent. Do NOT honor $PPT_DECK_ROOT either — it's been removed to avoid drift.mkdir -p $(pwd)/ppt_decks.<topic_concise>_<YYYYMMDD_HHMMSS>.$(pwd)/ppt_decks/<topic_concise>_<YYYYMMDD_HHMMSS>/.realpath / Path.resolve()) before writing it into task_pack.json — downstream must see an absolute path.pages/ always; images/ if ppt_mode in {standard, fast}.$(pwd)/ppt_decks/ cannot be created (permission denied) → abort, tell the user to check workspace permissions.If user attached reference_docs (pdf/docx/md/txt):
$SKILL_DIR/scripts/parse_user_docs.py --files <paths...> --output <deck_dir>/raw_documents.json. The --output flag tells the script to write the JSON itself (recommended — works reliably even on agents that don't handle shell redirection well). The script prints a single-line JSON status {"status":"ok","output":"...","documents":N,"errors":M} to stdout when --output is used.$SKILL_DIR/prompts/document_digest.md as system prompt + (user_query + concatenated document text) as user prompt. See "Invoking the LLM" below.document_digest JSON into info_pack.document_digest.info_pack.document_digest = null, continue (do NOT abort entry).Write task_pack.json + info_pack.json to deck_dir (see "Schemas" below). All path-bearing fields absolute.
Start the generation progress WebUI (best-effort, non-blocking):
python3 $PPT_STANDARD_DIR/scripts/progress_event.py --deck-dir <deck_dir> --stage entry --status ok --artifact "task_pack.json / info_pack.json" --label "task_pack.json / info_pack.json 已写入"generation_url:
python3 $PPT_STANDARD_DIR/scripts/launch_workbench.py --deck-dir <deck_dir> --source-session-id "${HERMES_SESSION_KEY:-}" --agent-managed 1python3 is unavailable, use python.{"status":"ok",...}, tell the user 生成进度工作台已启动:<generation_url> before continuing. The returned generation_url is the progress page at /progress; the editor is a separate /editor URL exposed as editor_url.{"status":"skipped","reason":"nodejs_missing",...}, ask the user whether to install NodeJS/dependencies. If they decline, say generation will continue without the WebUI and proceed. If they agree, first use any approved dependency-install skill/tool exposed by the active environment; otherwise use platform install means only after explicit dangerous-operation confirmation.0.0.0.0, native hosts bind localhost unless the user requested another IP/host.info_pack.json is written so both pools are visible):PPT_STANDARD_DIR="$(dirname "$SKILL_DIR")/sn-ppt-standard" python3 $SKILL_DIR/scripts/caption_images.py --deck-dir <deck_dir>⚠️ Set PPT_STANDARD_DIR — caption_images.py imports from sn-ppt-standard/lib/model_client.py and resolves it via $PPT_STANDARD_DIR. Without this env var, the script fails with FileNotFoundError: ppt-standard/lib/model_client.py not found. On Windows, use python instead of python3 and set the env var inline:
set PPT_STANDARD_DIR=C:\Users\...\Repository\ppt-editor\skills\sn-ppt-standard && python %SKILL_DIR%\scripts\caption_images.py --deck-dir <deck_dir>Safe to skip when there are no attachment images (user_assets.reference_images empty and no doc-embedded images) — the script is a no-op with no images, and the failure is harmless.
This script is the single source of truth for image-content descriptions:
raw_documents.json documents[*].inherited_images[*]): caption written into the same JSON as vlm_caption.info_pack.user_assets.reference_images): caption written into a sister field info_pack.user_assets.reference_image_captions: {abs_path: caption}.cmd_page_html) reads these cached captions and never re-captions — that's the "single source of truth" rule. If you change image files in a deck, delete their vlm_caption (or reference_image_captions[path]) entry and re-run this script to refresh.sn-ppt-creative or sn-ppt-standard based on task_pack.ppt_mode.sn-text-optimize call; skip asked-already params.parse_user_docs.py --output <deck_dir>/raw_documents.json already creates the file. Then call the LLM with a user prompt that gives only counts + indices of tables/images (not row contents) so the LLM can't accidentally paraphrase numbers:
python3 -c "
import sys, json, pathlib
sys.path.insert(0, '$PPT_STANDARD_DIR/lib')
from model_client import llm
raw = json.loads(pathlib.Path('<deck_dir>/raw_documents.json').read_text())
# Build the digest-safe view: strip tables[] and image paths, keep text + indices
docs_view = []
for d in raw.get('documents', []):
docs_view.append({
'doc_index': d['doc_index'],
'type': d['type'],
'text': d.get('text',''),
'tables_count': len(d.get('tables') or []),
'images_count': len(d.get('inherited_images') or []),
})
user_prompt = json.dumps({
'user_query': '<the user's original query>',
'documents': docs_view,
}, ensure_ascii=False)
sys_prompt = open('$SKILL_DIR/prompts/document_digest.md').read()
out = llm(sys_prompt, user_prompt)
# Parse JSON; if it fails, degrade digest to null (not abort entry)
try:
digest = json.loads(out)
except Exception:
digest = None
pathlib.Path('<deck_dir>/digest_tmp.json').write_text(json.dumps(digest, ensure_ascii=False))
"The digest JSON then merges into info_pack.document_digest. Downstream stages (outline, page_html) read both info_pack.document_digest (structured summary + inherited_tables/images index lists) AND raw_documents.json (actual table rows + image paths).
Substitute $PPT_STANDARD_DIR with the sn-ppt-standard skill install dir.
task_pack.json:
{
"deck_id": "AI产品发布会_20260318_154500",
"deck_dir": "/abs/path/ppt_decks/AI产品发布会_20260318_154500",
"ppt_mode": "standard",
"params": {
"role": "...",
"audience": "...",
"scene": "...",
"page_count": 10,
"language": "zh",
"image_source": "ai-gen",
"infographic_source": "ai-gen",
"output_format": "pptx"
},
"created_at": "2026-04-21T15:45:00+08:00",
"skill_version": "0.1.0"
}info_pack.json:
{
"user_query": "...",
"user_assets": {
"reference_images": ["/abs/..."],
"reference_docs": ["/abs/..."],
"reference_docs_failed": []
},
"document_digest": {
"topic_summary": "...",
"key_sections": [],
"key_points": [],
"data_highlights": [],
"inherited_tables": [{"doc_index": 0, "table_index": 2, "title_hint": "..."}],
"inherited_images": [{"doc_index": 0, "image_index": 0, "caption_hint": "..."}]
},
"raw_document_excerpts": {
"enabled": true,
"path": "/abs/.../raw_documents.json"
}
}pip install python-pptx or write Node scripts that import pptxgenjs.ask_user responses. When you ask the user a question, do NOT proceed until they reply. Never continue with assumed or default values.ls or pwd to verify the current working directory before creating files. The only valid output location is $(pwd)/ppt_decks/<deck_dir>/. Never write to /workspace/, /tmp/, ~/, or any hallucinated path. If a path doesn't start with the verified $(pwd), it's wrong./skill sn-ppt-doctor.$(pwd)/ppt_decks/ not creatable / not writable -> stop, tell user to check workspace permissions.reference_docs_failed, continue.document_digest LLM failure -> set to null, continue.Emit a short chat reply at each boundary. Silence between ask_user rounds and mode dispatch is a bug.
| When | Example |
|---|---|
| Right after entering sn-ppt-entry | 已进入 sn-ppt-entry,开始收集参数... |
| Missing a param | 缺少参数:<role>,马上问你 (then ask_user) |
| All params collected | 参数齐备:mode=standard, image_source=ai-gen, output_format=pptx, role=...。开始创建 deck_dir... |
| Before doc parse | 检测到 2 个附件,开始解析... |
| After doc parse | 解析完成:sample.pdf (12 页) / sample.docx (45 段) |
| Before digest | [LLM] 正在汇总文档要点... |
| After digest | 文档摘要已入 info_pack.json |
| task_pack / info_pack written | task_pack.json / info_pack.json 已写入 <deck_dir> |
| After WebUI launch | 生成进度工作台已启动:<url> or NodeJS 不可用;将继续生成但不启动 WebUI |
| Dispatching | 分发到 sn-ppt-creative(deck_dir=...) |
Final message includes a short summary:
准备就绪:
- 模式: <creative | standard>
- 页数: <n>
- deck_dir: <abs path>
即将进入<创意 | 标准>模式...Then dispatch:
/skill sn-ppt-creative deck_dir=<abs>/skill sn-ppt-standard deck_dir=<abs>179fea1
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.