#!/usr/bin/env bash # ───────────────────────────────────────────────────────────── # scaffold.sh —— 一键脚手架,创建一个 video-presentation 项目。 # # 用法: # bash scripts/scaffold.sh [--theme=] # bash scripts/scaffold.sh --list-themes # # 例子: # bash /scripts/scaffold.sh ./presentation # bash /scripts/scaffold.sh ./talk --theme=paper-press # bash /scripts/scaffold.sh --list-themes # # 跑完后,看 SKILL.md "Phase 2.4 实现单章" + references/CHAPTER-CRAFT.md # 了解每章怎么写。卡壳时翻 references/EXAMPLES/ 找完整章节 anchor。 # # 之后切换主题,覆盖一个文件即可: # cp /themes//tokens.css \ # /src/styles/tokens.css # ───────────────────────────────────────────────────────────── set -euo pipefail SKILL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" TEMPLATES="$SKILL_DIR/templates" THEMES_DIR="$SKILL_DIR/themes" DEFAULT_THEME="midnight-press" list_themes() { echo "可用主题(来自 ${THEMES_DIR}):" echo for dir in "$THEMES_DIR"/*/; do [[ -d "$dir" ]] || continue local meta="$dir/theme.json" [[ -f "$meta" ]] || continue # 没有 jq,简单 grep + sed 提字段 local id name desc id=$(grep -E '"id"' "$meta" | head -n1 | sed -E 's/.*"id":[[:space:]]*"([^"]+)".*/\1/') name=$(grep -E '"nameZh"' "$meta" | head -n1 | sed -E 's/.*"nameZh":[[:space:]]*"([^"]+)".*/\1/') desc=$(grep -E '"descriptionZh"' "$meta" | head -n1 | sed -E 's/.*"descriptionZh":[[:space:]]*"([^"]+)".*/\1/') printf " • %-18s %s\n %s\n\n" "$id" "$name" "$desc" done echo "用 --theme= 选定一个。默认:${DEFAULT_THEME}。" } # ── 解析参数 ── TARGET="" THEME="$DEFAULT_THEME" for arg in "$@"; do case "$arg" in --list-themes) list_themes exit 0 ;; --theme=*) THEME="${arg#--theme=}" ;; --*) echo "✗ 未知参数: $arg" >&2 exit 1 ;; *) if [[ -z "$TARGET" ]]; then TARGET="$arg"; fi ;; esac done TARGET="${TARGET:-presentation}" THEME_DIR="$THEMES_DIR/$THEME" THEME_TOKENS="$THEME_DIR/tokens.css" if [[ ! -d "$THEME_DIR" || ! -f "$THEME_TOKENS" ]]; then echo "✗ 找不到主题 '${THEME}'。可用主题:" >&2 echo >&2 for dir in "$THEMES_DIR"/*/; do [[ -d "$dir" ]] || continue echo " • $(basename "$dir")" >&2 done exit 1 fi if [[ -d "$TARGET" && -n "$(ls -A "$TARGET" 2>/dev/null || true)" ]]; then echo "✗ 目标目录 '${TARGET}' 已存在且非空,已中止。" >&2 exit 1 fi if ! command -v npm >/dev/null; then echo "✗ 需要 npm,但在 PATH 里没找到。" >&2 exit 1 fi echo "▸ 在 $TARGET 创建 Vite + React + TS 项目" echo "▸ 使用主题:$THEME" npm create vite@latest "$TARGET" -- --template react-ts >/dev/null cd "$TARGET" echo "▸ 安装依赖(可能要等一会)..." npm install >/dev/null 2>&1 echo "▸ 安装 tsx(用于 extract-narrations 脚本)..." npm install --save-dev tsx >/dev/null 2>&1 echo "▸ 用演示骨架替换默认 boilerplate" # 干掉我们不要的 Vite 默认 boilerplate rm -f \ src/App.tsx src/App.css \ src/main.tsx src/index.css \ src/assets/react.svg \ public/vite.svg \ README.md rmdir src/assets 2>/dev/null || true # 把脚手架文件拷到项目根 mkdir -p \ src/styles src/hooks src/components src/registry \ src/chapters/01-example \ public scripts cp "$TEMPLATES/vite.config.ts" . cp "$TEMPLATES/index.html" . cp "$TEMPLATES/src/main.tsx" src/main.tsx cp "$TEMPLATES/src/App.tsx" src/App.tsx # tokens.css 来自所选主题 cp "$THEME_TOKENS" src/styles/tokens.css cp "$TEMPLATES/src/styles/base.css" src/styles/base.css cp "$TEMPLATES/src/styles/animations.css" src/styles/animations.css cp "$TEMPLATES/src/styles/fonts.css" src/styles/fonts.css cp "$TEMPLATES/src/hooks/useStageScale.ts" src/hooks/useStageScale.ts cp "$TEMPLATES/src/hooks/useStepper.ts" src/hooks/useStepper.ts cp "$TEMPLATES/src/hooks/useAudioPlayer.ts" src/hooks/useAudioPlayer.ts cp "$TEMPLATES/src/hooks/useAutoMode.ts" src/hooks/useAutoMode.ts cp "$TEMPLATES/src/components/Stage.tsx" src/components/Stage.tsx cp "$TEMPLATES/src/components/MaskReveal.tsx" src/components/MaskReveal.tsx cp "$TEMPLATES/src/components/ProgressBar.tsx" src/components/ProgressBar.tsx cp "$TEMPLATES/src/components/ProgressBar.css" src/components/ProgressBar.css cp "$TEMPLATES/src/components/AutoStartGate.tsx" src/components/AutoStartGate.tsx cp "$TEMPLATES/src/components/AutoStartGate.css" src/components/AutoStartGate.css cp "$TEMPLATES/src/components/AutoToggle.tsx" src/components/AutoToggle.tsx cp "$TEMPLATES/src/components/AutoToggle.css" src/components/AutoToggle.css cp "$TEMPLATES/src/registry/types.ts" src/registry/types.ts cp "$TEMPLATES/src/registry/chapters.ts" src/registry/chapters.ts cp "$TEMPLATES/src/chapters/01-example/Example.tsx" src/chapters/01-example/Example.tsx cp "$TEMPLATES/src/chapters/01-example/Example.css" src/chapters/01-example/Example.css cp "$TEMPLATES/src/chapters/01-example/narrations.ts" src/chapters/01-example/narrations.ts # Audio pipeline scripts (extract-narrations + synthesize-audio runner + # pluggable TTS providers under tts-providers/). cp "$TEMPLATES/scripts/extract-narrations.ts" scripts/extract-narrations.ts cp "$TEMPLATES/scripts/synthesize-audio.sh" scripts/synthesize-audio.sh chmod +x scripts/synthesize-audio.sh mkdir -p scripts/tts-providers cp "$TEMPLATES/scripts/tts-providers/README.md" scripts/tts-providers/README.md cp "$TEMPLATES/scripts/tts-providers/minimax.sh" scripts/tts-providers/minimax.sh cp "$TEMPLATES/scripts/tts-providers/openai.sh" scripts/tts-providers/openai.sh # Wire the audio scripts into npm so contributors don't have to remember # the exact command. Uses node to merge into the existing package.json. node -e ' const fs = require("fs"); const p = JSON.parse(fs.readFileSync("package.json", "utf8")); p.scripts = Object.assign({}, p.scripts, { "extract-narrations": "tsx scripts/extract-narrations.ts", "synthesize-audio": "bash scripts/synthesize-audio.sh", }); fs.writeFileSync("package.json", JSON.stringify(p, null, 2) + "\n"); ' # 留个标记,以后能查这个项目从哪个主题起步的 { echo "$THEME" } > .theme # 跑一次 typecheck 确认接线 OK echo "▸ 跑 typecheck ..." if npx tsc --noEmit; then echo "✓ typecheck 通过" else echo "✗ typecheck 失败 —— 请看上面的错误" >&2 exit 1 fi cat </.mp3 # 换 provider:PRESENTATION_TTS= npm run synthesize-audio # 自定义 / 没装 mmx 见 scripts/tts-providers/README.md 写章节时必读(单一入口,路径在 SKILL 仓库内): • $SKILL_DIR/references/CHAPTER-CRAFT.md Part 0 十条原则 / Part 1 开工 5 问 / Part 2 关系→动作决策树 / Part 3 视觉工具箱 / Part 4 时长 / Part 5 反 AI 味反模式 / Part 6 代码硬规则 / Part 7 完工自检 / Part 8 反馈速查 • $SKILL_DIR/themes/$THEME/theme.json 看 descriptionZh / mood / bestFor —— 参考主题气质 (动画 / 时长 / 字号 / emoji 由 chapter agent 在每章自由决定) 卡壳时可翻: • $SKILL_DIR/references/EXAMPLES/ 完整章节 anchor(钩子型 / 列举型)—— 看"形",不要照搬 要换一个主题,覆盖 tokens.css 即可: cp $SKILL_DIR/themes//tokens.css src/styles/tokens.css 想自创主题,看 $SKILL_DIR/references/THEMES.md。 EOF