Loading...
Loading...
faceless-explainer video workflow - arbitrary text (article / notes / topic / brief) -> narrator_scripts.json + audio (voice + BGM) + section_plan.md -> typography / abstract-graphics / diagram / data-viz video. Typical length up to ~3 min (sweet spot ~30-90s); a genuinely longer piece is general-video, not this workflow. Generates its OWN narration (TTS) — it does not sync to a user-supplied / pre-recorded voiceover (that is general-video). No website capture, no real product screenshots. If the text names a product / its site to promote, that is /product-launch-video; when product-vs-topic is unclear, start at /hyperframes-read-first.
npx skill4agent add heygen-com/hyperframes faceless-explainerblock-framecapsuleclaudepin-and-paperscatterbrainpin-and-paperConfirm the route before Step 0. This skill explains a topic / concept with no product and no site to capture. If the text actually markets a product / names its site →; there's a URL to turn into a video →/product-launch-video; a GitHub PR →/website-to-video; existing footage to caption / package →/pr-to-video·/embedded-captions. Out of scope: timing visuals to a user-supplied / pre-recorded voiceover (faceless generates its own TTS →/graphic-overlays), or live / at-render-time data. Unsure product-vs-topic, or routed here on a vague request? Read/general-videofirst./hyperframes-read-first
PROJECT_DIR = videos/<project-name>/<SKILL_DIR>/../hyperframes-core/references/subagent-dispatch.md| Phase | Execution | Primary artifact | Detailed flow |
|---|---|---|---|
| init | Bash | | Step 0 |
| scaffold | Bash (no agent) | | Step 1 |
| scriptwriting | subagent | | Step 2 / |
| design-system | Bash (no agent, deterministic — style = | | Step 2b |
| audio | | | |
| visual-design | subagent | | |
| prep | | | |
| captions (deterministic) | | | |
| scenes | N x subagent (parallel) | | |
| finalize (Phase 4c) | Bash prelude (wait-bgm + assemble + inject/verify-transitions + hoist-videos + sfx-verify + preflight) -> finalize subagent (fix brief findings in place + one lean contact-sheet look + render) | | Step 7 / |
brew install python@3.11 node ffmpeg/usr/bin/python3pip installnpx hyperframes doctorscripts/check-overlap.mjspuppeteer-core--ensure-depspuppeteer| Key | Used for | Default / fallback |
|---|---|---|
| TTS (cloud, word-level timestamps) | voice: auto (first English starfish voice; override |
| TTS (cloud; needs | voice |
| neither, and not logged in | TTS | local Kokoro, voice |
| Lyria BGM | unset -> local MusicGen (first run downloads ~300 MB) |
/hyperframes-read-first/tmp/explainer-video-...PROJECT_DIR = videos/<project-name>/<project-name>Use ./videos/refactoring-explainer<topic>-explainer<topic>-howto$PROJECT_DIR/hyperframes.jsonPROJECT_DIR="${LAUNCH_VIDEO_DIR:-videos/<project-name>}"
mkdir -p "$(dirname "$PROJECT_DIR")"
npx hyperframes init "$PROJECT_DIR" --non-interactive --skip-skills --example=blankdrops a generichyperframes init/AGENTS.mdintoCLAUDE.md; leave them in place — they are agent scaffolding for whoever opens the finished project later. This skill (not those files) is the source of truth for the workflow, so do not treat their generic guidance as run-time constraints.$PROJECT_DIR
hyperframes initAGENTS.mdCLAUDE.mdhyperframes/PROJECT_DIR(cd "$PROJECT_DIR" && ...)cd$PROJECT_DIR/.envcontext.log$HEYGEN_API_KEY$HYPERFRAMES_API_KEY~/.heygen/credentialshyperframes auth login$PROJECT_DIR/.envKEY=value.envhyperframes auth loginbuild-design --captureprep --capturecapture/capture/assets/colors:[]colors[]colors[0](cd "$PROJECT_DIR" && mkdir -p capture/extracted capture/assets)
(cd "$PROJECT_DIR" && cat > capture/extracted/tokens.json <<'JSON'
{ "title": "<title>", "description": "<one-line>", "colors": [], "fonts": [], "headings": [], "sections": [], "ctas": [], "svgs": [], "cssVariables": {} }
JSON
)
(cd "$PROJECT_DIR" && printf '%s\n' "<full input text / article / notes / brief>" > capture/extracted/visible-text.txt)[ -s "$PROJECT_DIR/capture/extracted/tokens.json" ] && \
[ -s "$PROJECT_DIR/capture/extracted/visible-text.txt" ] && \
[ -d "$PROJECT_DIR/capture/assets" ] && echo ok || echo missingagents/scriptwriting.md## Dispatch contextSKILL_DIR: <absolute path>
PROJECT_DIR: <video project root>
Schema validator: <SKILL_DIR>/scripts/validate-narrator.mjs
Input text: ./capture/extracted/visible-text.txt # The source article / notes / brief — the agent reads this first
Style preset: pick one from the menu in the guide and emit it as the top-level `stylePreset` (default `pin-and-paper` when unsure); match the narration register to the chosen preset
Orientation: <landscape | portrait | square> # From the Step 0.0 aspect (16:9→landscape, 9:16→portrait, 1:1→square; default landscape). Emit it VERBATIM as the top-level `orientation` field — this is dictated, not a creative choice; it sets the canvas (portrait→1080×1920) for the whole pipeline.
Script style: Keep each scene's script concise — 1-2 sentences, no more than 20 wordsFill theline from the aspect confirmed in Step 0.0 (defaultOrientation:). prep readslandscape→ stampsnarrator_scripts.orientation; without it the video stays 16:9.group_spec.width/height
narrativeArchetypeconcept-explainerhow-to-processlisticlestory-explainer"<outer> with <inner>"stylePresetorientationnarrator_scripts.jsoncontinuitycontinuebreakbreakintentsharedMotifassetCandidates[]stylePresetnarrator_scripts.jsonpin-and-paperdesign.htmlSTYLE=$(cd "$PROJECT_DIR" && node -e 'try{const p=require("./narrator_scripts.json").stylePreset;process.stdout.write((p&&String(p).trim())||"pin-and-paper")}catch{process.stdout.write("pin-and-paper")}')
(cd "$PROJECT_DIR" && node <SKILL_DIR>/phases/design-system/scripts/build-design.mjs ./design-system --no-emit --style "$STYLE")
(cd "$PROJECT_DIR" && node <SKILL_DIR>/phases/design-system/scripts/build-design.mjs ./design-system --style "$STYLE")
(cd "$PROJECT_DIR" && node <SKILL_DIR>/phases/design-system/scripts/emit-chunks.mjs ./design-system)stylePresetblock-framecapsuleclaudepin-and-paperscatterbrainbuild-design.mjspin-and-papernarrator_scripts.json[ -s "$PROJECT_DIR/design-system/inference.json" ] && \
[ -s "$PROJECT_DIR/design-system/design.html" ] && \
[ -s "$PROJECT_DIR/design-system/chunks/index.json" ] && echo ok || echo missingnarrator_scripts.json(cd "$PROJECT_DIR" && node <SKILL_DIR>/scripts/audio.mjs \
--narrator-scripts ./narrator_scripts.json \
--hyperframes . \
--out ./audio_meta.json \
--lyria-recipe <SKILL_DIR>/phases/audio/lyria-recipe.py)audio.mjsaudio_meta.jsonbgm_logbgm_piddesign-system/chunks/index.jsonnarrator_scripts.jsonaudio_meta.json# Dispatch packets live in $PROJECT_DIR/.dispatch/ (transient; safe to delete after the run).
# NEVER use a fixed /tmp path: it persists across runs/projects, so a failed write silently
# reuses another project's stale packet and contaminates every worker.
mkdir -p "$PROJECT_DIR/.dispatch"
DP="$PROJECT_DIR/.dispatch/vd-dispatch.txt"
{
echo "## Design chunks"
(cd "$PROJECT_DIR" && cat design-system/chunks/index.json \
design-system/chunks/composition-hints.md design-system/chunks/voice.md \
design-system/chunks/tokens.css design-system/chunks/easings.js 2>/dev/null)
echo "## Effects catalog"; cat <SKILL_DIR>/phases/visual-design/effects-catalog.md
echo "## Design rules"; cat <SKILL_DIR>/phases/visual-design/rules/{typography,color-system,composition,motion-language}.md
echo "## SFX library"; cat <SKILL_DIR>/assets/sfx/manifest.json
echo "## Narrator scripts"; (cd "$PROJECT_DIR" && cat narrator_scripts.json)
echo "## Audio meta"; (cd "$PROJECT_DIR" && cat audio_meta.json 2>/dev/null) # Optional; overrides Duration if drift >10%
} > "$DP"
# Guard: a partially-failed build must fail LOUDLY here, not downstream in the subagent
grep -q '^## Narrator scripts' "$DP" || { echo "FATAL: vd-dispatch.txt incomplete — rebuild before dispatching"; }
# Captions planning hint (put it in the Captions: line of the dispatch below)
(cd "$PROJECT_DIR" && node -e 'try{const m=require("./audio_meta.json");process.stdout.write(Object.values(m.scenes||{}).some(s=>s.wordsPath)?"enabled":"disabled")}catch{process.stdout.write("enabled")}')agents/visual-design.md## Dispatch contextSKILL_DIR: <absolute path>
PROJECT_DIR: <video project root>
Schema validator: <SKILL_DIR>/scripts/validate-section.mjs
Canvas: <width>×<height> # default 1920×1080 (16:9 landscape); 1080×1920 (9:16 portrait) or 1080×1080 (1:1 square) if requested upstream (narrator_scripts.orientation/dimensions). Plan layouts for THIS aspect ratio — see composition.md "Portrait & square".
Captions: <enabled | disabled> # Planning hint from the node -e above: enabled => leave the bottom ~17% of canvas height as caption territory in prose
Dispatch packet: <PROJECT_DIR>/.dispatch/vd-dispatch.txt # Step 0 reads it once for all inputs
Visuals: faceless — every scene is typography / abstract graphics / diagram / data-viz invented from the script. assetCandidates is [] for most or all scenes; plan visuals from text, not from captured assets.section_plan.mdtype-roles.mdCaptions:group_spec.captions_enabledsection_plan.md(cd "$PROJECT_DIR" && node <SKILL_DIR>/scripts/prep.mjs \
--section-plan ./section_plan.md \
--narrator-scripts ./narrator_scripts.json \
$( [ -f audio_meta.json ] && echo "--audio-meta ./audio_meta.json" ) \
--rules-dir <SKILL_DIR>/../hyperframes-animation/rules \
--capture ./capture \
--design-system ./design-system \
--hyperframes . \
--sfx-lib <SKILL_DIR>/assets/sfx \
--out ./group_spec.json)group_spec.jsonsection_planContinuityvisual_clips[]group_wN.htmltransitions[]capture/assets/prep.mjscontext.log(cd "$PROJECT_DIR" && node <SKILL_DIR>/scripts/captions.mjs group \
--group-spec ./group_spec.json --hyperframes . \
--tokens design-system/chunks/tokens.css --out ./caption_groups.json)
(cd "$PROJECT_DIR" && node <SKILL_DIR>/scripts/captions.mjs html \
--hyperframes . --groups ./caption_groups.json \
--tokens design-system/chunks/tokens.css \
--inference design-system/inference.json \
--out compositions/captions.html)captions: skipped (<reason>)captions.htmlcaptions.mjs html--skin-filenpx hyperframes lintcaptions.htmlPROJECT_DIRnode_modules/node <SKILL_DIR>/scripts/check-overlap.mjs --ensure-deps
# Installs puppeteer-core (module only, no browser download) if not already resolvable; Chrome is
# reused from the hyperframes browser cache. Workers must NOT install it themselves (parallel npm race).group_spec.json.groups[]captions.mjs keepout --scenecheck-overlap.mjs --scenefilm directiontokenseasingsvoice# Same rule as Step 4: packets go in $PROJECT_DIR/.dispatch/, never a fixed /tmp path
# (a stale /tmp file from a previous project survives a failed write and silently
# poisons every worker with the wrong design system).
# `## Film direction` = the film-level invariants from group_spec.film_direction
# (palette system / motion defaults + budget / ambient system / negative list);
# each scene's creative_brief carries only scene-specific deltas on top of it.
mkdir -p "$PROJECT_DIR/.dispatch/scene-dispatch"
{
echo "## Film direction"
(cd "$PROJECT_DIR" && node -p 'JSON.parse(require("fs").readFileSync("group_spec.json","utf8")).film_direction || ""')
echo "## Tokens/easings/voice"
(cd "$PROJECT_DIR" && cat design-system/chunks/tokens.css design-system/chunks/easings.js design-system/chunks/voice.md 2>/dev/null)
} > "$PROJECT_DIR/.dispatch/scene-shared.txt"
# Guard BEFORE fan-out: the project's own brand token must be present; a contaminated
# packet here costs a full re-author round across every affected worker.
grep -q -- '--brand-primary' "$PROJECT_DIR/.dispatch/scene-shared.txt" || \
{ echo "FATAL: scene-shared.txt incomplete/stale — rebuild before dispatching workers"; }
# Then per worker: shared header + that worker's Scenes YAML -> $PROJECT_DIR/.dispatch/scene-dispatch/w<N>.txtagents/hyperframes-scene.md## Dispatch contextSKILL_DIRPROJECT_DIRWorker IDComposition widthComposition heightgroup_spec.widthgroup_spec.heightCaptions: <enabled|disabled>group_spec.captions_enabledDispatch packet: <PROJECT_DIR>/.dispatch/scene-dispatch/w<N>.txt## Film direction## Tokens/easings/voiceScenes:group_spec.json.groups[i]worker_idcomposition_idcomposition_fileduration_sscene_idsgroup_spec.jsonwidthheightCaptions: enabledCaption band top yheight − round(height × 0.1667)Foreground max yCaption band top y − 20Scenes:group_spec.json.groups[i].scenes[<sid>]scene_idlocal_start_seffectsrule_pathsassetCandidatesestimatedDuration_svoicePathdesign_chunkscreative_briefgroup_wN.htmlassetCandidates[]creative_briefdesign_chunks: null./design-system/design.htmlgroup_spec.visual_clips[]captions.html(cd "$PROJECT_DIR" && node <SKILL_DIR>/scripts/check-compositions.mjs \
--hyperframes . \
--group-spec ./group_spec.json)compositions/scene_N.htmlgroup_wN.html(cd "$PROJECT_DIR" && node <SKILL_DIR>/scripts/wait-bgm.mjs \
--audio-meta ./audio_meta.json \
--hyperframes . \
--timeout-ms 120000 \
--interval-ms 2000)
(cd "$PROJECT_DIR" && node <SKILL_DIR>/scripts/assemble-index.mjs --group-spec ./group_spec.json --hyperframes .)
(cd "$PROJECT_DIR" && node <SKILL_DIR>/scripts/transitions.mjs inject --group-spec ./group_spec.json --hyperframes .)
(cd "$PROJECT_DIR" && node <SKILL_DIR>/scripts/transitions.mjs verify --group-spec ./group_spec.json --index ./index.html)
(cd "$PROJECT_DIR" && node <SKILL_DIR>/scripts/hoist-videos.mjs --group-spec ./group_spec.json --hyperframes .)
(cd "$PROJECT_DIR" && node <SKILL_DIR>/scripts/verify-output.mjs sfx --group-spec ./group_spec.json --index ./index.html)injectindex.htmldata-startdata-durationdata-track-indexhoist-videosdata-video-src<video class="clip"><video>data-durationtransitions[]data-video-srcEditnode <SKILL_DIR>/scripts/check-overlap.mjs --ensure-deps(cd "$PROJECT_DIR" && node <SKILL_DIR>/scripts/preflight-finalize.mjs --group-spec ./group_spec.json --hyperframes .)finalize_brief.jsonnpx hyperframes@<version>--tolerancedata-layout-allow-overflowcheck-overlap.mjsstatus: unavailablecaptions_enabledcaptions.mjs keepoutmargin-topmargin-bottomedit_oldedit_newpreflight_cleangates_cleangates.*bgm.*overlap.*caption_keepout.*anomalies[]snapshot_times_s[]npx_prefixscenes[]internal_seams[]preflight-finalize.mjsoverlap.violations[]caption_keepout.violations[]overlap.status: "unavailable"node <SKILL_DIR>/scripts/check-overlap.mjs --ensure-depsnpx hyperframes doctorgroup_wN.htmlagents/hyperframes-scene.md## Repair contextnpx_prefixInspect at: <t1,t2,t3>midpoint_sbrief.scenes[]Captions: enabled|disabledinspect --atcheck-overlap.mjs --sceneanomalies[]messageagents/hyperframes-finalize.md## Dispatch contextSKILL_DIR: <absolute path>
PROJECT_DIR: <video project root>
Render quality: high # Or draft / standard
Finalize brief: <PROJECT_DIR>/finalize_brief.json # Preflight has already written it; agent reads once to get findings + npx_prefix + scene timings
Film direction: | # = group_spec.film_direction (film-level invariants the briefs assume)
<verbatim>
Visual clips: # One line per group_spec.visual_clips[] entry
- { id, file, kind, worker_id, scene_ids, start_s, duration_s }
Scenes: # One line per logical scene, copied verbatim from group_spec.json
- { scene_id, start_s, estimatedDuration_s, effects: [...], creative_brief: |
<Phase 3 prose for this scene> }index.htmloutput_tailoverlap.violations[]check-overlap --scenecaption_keepout.violations[]edit_oldedit_newdata-durationvisual_clips[].duration_shyperframes previewplayOptional: I can open a live preview so you can scrub frame-by-frame, change playback speed, or get a shareable link — say the word and I'll start it.
(cd "$PROJECT_DIR" && npx hyperframes preview) # Studio UI, e.g. http://localhost:3002/#project/<project-name>
# or a lightweight shareable player link instead:
(cd "$PROJECT_DIR" && npx hyperframes play) # plain http://localhost:<port>hyperframes-clireferences/preview-render.md$PROJECT_DIR/context.log| State | Continue from |
|---|---|
| log missing or empty | Full pipeline |
| Step 1 (scaffold) |
scaffold done, | Step 2 (scriptwriting). If the user supplied a final |
| Step 2b (design-system; |
| Step 3 (audio) |
| Step 4 (visual-design) |
| Step 5 (prep) |
| Step 5.5+6 (run |
all | Step 7 (rerun assemble + sfx-verify + preflight, overwriting |
| Report completed and stop |
./ # workspace root
├── .claude/skills/
├── node_modules/ package.json
└── videos/<project-name>/ # PROJECT_DIR - HyperFrames project root
├── hyperframes.json context.log
├── capture/ # synthetic package (NOT a scrape) — kept for backend layout compatibility
│ ├── extracted/ # tokens.json (synthetic) + visible-text.txt (the input text)
│ └── assets/ # empty (faceless)
├── design-system/ # build-design outputs: inference.json / design.html / chunks/ / fonts/
├── narrator_scripts.json audio_meta.json section_plan.md group_spec.json
├── public/ assets/ compositions/ snapshots/
└── renders/video.mp4