pr-to-video
pr-to-video workflow - a GitHub pull request (URL like github.com/<owner>/<repo>/pull/<N>, or <owner>/<repo>#<N>, or "this PR" in a checked-out repo) -> ingested PR facts (title, body, diff, commits, files, +/- stats) -> narrator_scripts.json + audio (voice + BGM) + section_plan.md -> code-diff / before-after / impact explainer video. Input is a CODE CHANGE. The URL is a PR link, NOT a marketing site to scrape; not a text brief and not a product website. For a non-PR input (product site, general website, topic text), see /hyperframes-read-first.
다음 행동
/pr-to-video기술 README 원문 보기
설치 옵션, 예시 코드, 세부 사용법을 영어 README 원문 그대로 확인합니다.
pr-to-video - dispatch entry
Input is a GitHub pull request (a code change), supplied as a PR URL, an <owner>/<repo>#<N> ref, or "this PR" while a repo with an open PR is checked out. Output is a code-change explainer: what shipped, why, and how it works — rendered from the diff/commits as before-after, diff-highlight, file-tree, and impact scenes. Default length up to ~3 min (sweet spot ~30-90s); a genuinely longer or exhaustive every-file walkthrough (5 min+) is a different register → /general-video. There is no website scrape and no headless Chrome for ingest — ingest is the gh CLI. The shipped style preset is always claude (warm editorial; signature navy code window).
> Confirm the route before Step 0. This skill explains a GitHub pull request (a code change read via gh). If the input is a marketing / product site → /product-launch-video; a general website → /website-to-video; a topic / article with no PR → /faceless-explainer; a whole-repo tour or multi-PR release → /general-video. Out of scope: live / at-render-time data — PR facts are read once at author time and baked in. Handed a non-PR input, or unsure? Read `/hyperframes-read-first` first.
This workflow owns only the PR-specific front (ingest + story-design); every phase marked _shared_ reuses the engine copied from faceless-explainer unchanged (it lives under this skill's own scripts/ + agents/ + phases/, so <SKILL_DIR> resolves to pr-to-video).
All artifacts go to PROJECT_DIR = videos/<project-name>/ (created in Step 0); all paths below are relative to it. Dispatch is harness-portable: before the first subagent dispatch, read <SKILL_DIR>/../hyperframes-core/references/subagent-dispatch.md once — it maps the dispatch verbs (parallel fan-out / background / wait) to your harness's primitives; a concurrency cap below N means waves of the cap size, never fewer workers. This file is a binding runbook, not background reading: execute the steps in order and produce every phase artifact with its designated script or agent role — do not substitute a freestyle pipeline, and do not skip a pause step because the request seems clear. A step you cannot perform → stop and report.
| Phase | Execution | Primary artifact | Detailed flow |
|---|---|---|---|
| init | Bash | hyperframes.json | Step 0 |
| ingest (own) | Bash (gh CLI + ingest.mjs + fetch-people-avatars.mjs, NO agent, NO scrape) | capture/pr.json + diff.patch + extracted/{tokens.json,visible-text.txt,people.json} + public/avatars/ | Step 1 |
| design-system (shared) | Bash (no agent, deterministic claude) | design-system/design.html + chunks/ | Step 1b |
| story-design (own) | subagent | narrator_scripts.json | agents/story-design.md |
| audio (shared) | audio.mjs in Bash | audio_meta.json | phases/audio/guide.md |
| visual-design (shared) | subagent | section_plan.md | agents/visual-design.md |
| prep (shared) | prep.mjs in Bash | group_spec.json | scripts/prep.mjs |
| captions (shared, det.) | captions.mjs group -> captions.mjs html in Bash (no subagent) | caption_groups.json + compositions/captions.html | scripts/captions.mjs |
| scenes (shared) | N x subagent (parallel) | compositions/scene_*.html or compositions/group_w*.html | agents/hyperframes-scene.md |
| finalize (shared) | 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) | renders/video.mp4 | Step 7 / agents/hyperframes-finalize.md |
Prerequisites
macOS Apple Silicon or Linux x64. System tools: brew install python@3.11 node ffmpeg (use Homebrew Python, not /usr/bin/python3, or pip install is blocked by PEP 668); then npx hyperframes doctor once (downloads Chrome — needed for snapshot/render, not for ingest). The rendered overlap gate (scripts/check-overlap.mjs, run in worker self-checks and preflight) reuses that same cached Chrome — it never downloads a browser; its only dep is the puppeteer-core npm module, ensured once before scene fan-out (Step 5.5, --ensure-deps, ~5s, no full puppeteer install). CLIs: `gh` (GitHub CLI, authenticated — gh auth status must pass) and hyperframes. Optional cloud keys (else local fallbacks) — inject in Step 0.5:
| Key / requirement | Used for | Default / fallback |
|---|---|---|
gh auth status OK | Reading the PR (public or private) | required — fail fast with the auth hint |
HEYGEN_API_KEY (or hyperframes auth login) | TTS (cloud, word-level timestamps) | voice: auto (first English starfish voice; override --voice) |
ELEVENLABS_API_KEY | TTS (cloud; needs pip install elevenlabs) | voice 21m00Tcm4TlvDq8ikWAM (Rachel) |
| neither, and not logged in | TTS | local Kokoro, voice am_michael (non-English: pass --voice) |
GEMINI_API_KEY / GOOGLE_API_KEY (aliases) | Lyria BGM | unset -> local MusicGen (first run downloads ~300 MB) |
Flow
Step 0.0 - Confirm the brief (ALWAYS ask one round, then build)
Before Step 0, always pause and ask the brief in one message, then wait for the user — never skip this, even for a request that looks complete. Lead with a recommended default for each field and pre-fill anything the user already gave (confirm it rather than re-asking blindly): the angle (changelog / feature reveal / fix / refactor — default: infer from the PR), the audience (developers vs general users — default: developers), length (default ~60-90s), and — if /hyperframes-read-first didn't set them — aspect (default 16:9) and language. Style is always claude. Proceed to Step 0 only after the user replies; a "go" / "use the defaults" is a valid reply that accepts every default.
Step 0 - Initialize the video project
cwd is the agent workspace root (e.g. /tmp/pr-video-...). Write all video artifacts under PROJECT_DIR = videos/<project-name>/.
<project-name>: use the directory the user gave (e.g. Use ./videos/retry-pr), else a short kebab-case name derived from the PR (<repo>-pr-<N>, e.g. widgets-pr-1187). Not the workspace basename or a timestamp.
Only when $PROJECT_DIR/hyperframes.json is absent:
PROJECT_DIR="${PR_VIDEO_DIR:-videos/<project-name>}"
mkdir -p "$(dirname "$PROJECT_DIR")"
npx hyperframes init "$PROJECT_DIR" --non-interactive --skip-skills --example=blank> hyperframes init drops a generic AGENTS.md / CLAUDE.md into $PROJECT_DIR; leave them in place — they are agent scaffolding for whoever opens the finished project later.
Constraints: never run hyperframes init / generate AGENTS.md / CLAUDE.md in the workspace root; never nest another hyperframes/ inside PROJECT_DIR; every Bash command (master + subagents) is a (cd "$PROJECT_DIR" && ...) subshell — never bare cd.
Step 0.5 - API key guidance
Skip if $PROJECT_DIR/.env exists or context.log is non-empty (= not the first run). Otherwise first detect what's available (HeyGen TTS on if $HEYGEN_API_KEY / $HYPERFRAMES_API_KEY set or ~/.heygen/credentials exists from hyperframes auth login; ElevenLabs / Gemini only if their env keys set), then always pause and offer the menu — wait for the user; do not proceed on your own even when a workable config is detected (the user may want to add a key like Gemini). State what's detected, then: paste keys (→ Write $PROJECT_DIR/.env, one KEY=value per line, overwrite same-name) / "go" (proceed with what's configured — env, .env, or hyperframes auth login) / "skip" (proceed with local fallbacks for anything unconfigured). Then proceed to Step 1.
Step 1 - Ingest (Bash, NO agent, NO scrape)
Resolve the PR ref and pull structured facts with gh, then fold them into the synthetic capture package the shared backend expects (mirrors faceless-explainer's no-scrape scaffold). gh runs here, in the orchestrator, so auth / not-found / private-repo errors surface with gh's own stderr; ingest.mjs is a pure offline transform.
# PR ref: a full URL, "<owner>/<repo>#<N>", or "<N>" inside a checked-out repo.
PR="<url | owner/repo#N | N>"
# Fail fast if gh is not authenticated.
gh auth status || { echo "gh not authenticated — run: gh auth login"; exit 1; }
(cd "$PROJECT_DIR" && mkdir -p capture/extracted capture/assets)
(cd "$PROJECT_DIR" && gh pr view "$PR" \
--json number,title,body,author,url,baseRefName,headRefName,commits,files,additions,deletions,changedFiles,labels,reviews,latestReviews,comments,assignees,reviewDecision,mergedBy \
> capture/pr.json)
(cd "$PROJECT_DIR" && gh pr diff "$PR" > capture/diff.patch)
# Fold pr.json + diff.patch into tokens.json (colors:[] → claude native palette) +
# visible-text.txt (the narrative brief) + people.json (PR author + commit authors w/ counts +
# reviewers / commenters / assignees, bot-filtered + deduped, each with a GitHub avatar URL).
# (The PR `author` is only the opener; commit authors from commits[].authors[] are tracked too.)
# ingest is OFFLINE.
(cd "$PROJECT_DIR" && node <SKILL_DIR>/scripts/ingest.mjs \
--pr-json ./capture/pr.json --diff ./capture/diff.patch --out-dir ./capture/extracted)
# Network step (the people front's only one — ingest stays offline): download each
# contributor's GitHub avatar to public/avatars/<login>.png for an optional credits /
# shipped-by close. Best-effort — a missing avatar or offline run never blocks (exit 0).
# Avatars + that close are the ONE place pr-to-video relaxes the faceless default.
(cd "$PROJECT_DIR" && node <SKILL_DIR>/scripts/fetch-people-avatars.mjs \
--people ./capture/extracted/people.json)Validation:
[ -s "$PROJECT_DIR/capture/pr.json" ] && \ [ -s "$PROJECT_DIR/capture/diff.patch" ] && \ [ -s "$PROJECT_DIR/capture/extracted/tokens.json" ] && \ [ -s "$PROJECT_DIR/capture/extracted/visible-text.txt" ] && \ [ -s "$PROJECT_DIR/capture/extracted/people.json" ] && \ [ -d "$PROJECT_DIR/capture/assets" ] && echo ok || echo missing # public/avatars/ is best-effort — its absence is NOT a failure (no avatars resolved / offline).
If gh errors (auth / not found / private), report the exact stderr and stop — do not fabricate PR contents. If ingest.mjs exits 1, read its stderr (usually a malformed pr.json), fix, rerun (deterministic, finishes instantly). fetch-people-avatars.mjs always exits 0; if avatars are missing, story-design simply has no credits scene to author.
Step 1b - Design system (Bash, NO agent, deterministic — SHARED)
Three deterministic commands produce a fully-styled design.html + chunks against the synthetic input, with the claude preset (its code-window / number-lockup / stat-card components are the PR visual vocabulary):
(cd "$PROJECT_DIR" && node <SKILL_DIR>/phases/design-system/scripts/build-design.mjs ./design-system --no-emit --style claude) (cd "$PROJECT_DIR" && node <SKILL_DIR>/phases/design-system/scripts/build-design.mjs ./design-system --style claude) (cd "$PROJECT_DIR" && node <SKILL_DIR>/phases/design-system/scripts/emit-chunks.mjs ./design-system)
Validation:
[ -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 missing
If any is missing, read the build-design / emit-chunks stderr, fix the invocation, and rerun (deterministic, finishes in seconds).
Step 2 - Story-design (subagent) — OWN
Dispatch one subagent. prompt = full contents of agents/story-design.md + the ## Dispatch context below, passed through verbatim:
SKILL_DIR: <absolute path> PROJECT_DIR: <video project root> Schema validator: <SKILL_DIR>/scripts/validate-narrator.mjs PR facts: ./capture/pr.json # title / body / commits / files / +/- stats — read first Diff: ./capture/diff.patch # the actual change — pull 2-4 representative hunks Brief: ./capture/extracted/visible-text.txt # the assembled narrative brief People: ./capture/extracted/people.json # contributors (PR author + commit authors w/ commitCount + reviewers/commenters) + avatarFile; avatars in public/avatars/ — optional credits close Design DNA: ./design-system/inference.json # Read site_dna once to set register (soft hint only) Orientation: <landscape | portrait | square> # From the Step 0.0 aspect (16:9→landscape, 9:16→portrait, 1:1→square; default landscape). Emit VERBATIM as the top-level `orientation` field — dictated, not a choice; sets the canvas (portrait→1080×1920) for the whole pipeline. Script style: concise, dev-facing — 1-2 sentences/scene, <=20 words; name the change, the why, the impact
The agent picks a PR archetype for narrativeArchetype (changelog / feature-reveal / fix-explainer / refactor-walkthrough, or "<outer> with <inner>"), echoes the dispatched `orientation` as a top-level field (Step 5 prep → canvas size), and emits narrator_scripts.json (it runs the validator before returning). continuity drives worker grouping: continue = same worker as the previous scene (cap=3); break = new worker; scene 1 is always break. intent / sharedMotif are soft hints. assetCandidates is [] on essentially every scene (faceless) — the one exception is an optional credits / shipped-by close that may reference the contributor avatars in public/avatars/<login>.png (from people.json).
Step 3 - Audio — SHARED
After narrator_scripts.json exists:
(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)
BGM generation runs detached in the background. Backend selection (audio.mjs Step 5b): cloud Lyria is used only when a GEMINI_API_KEY/GOOGLE_API_KEY is set, the --lyria-recipe exists, AND import google.genai actually succeeds — if the key is set but the package is missing, audio.mjs tries to pip install google-genai on demand. When Lyria can't run, it falls back to local MusicGen (facebook/musicgen-small via transformers, no key; deps auto-installed in the background, parallel with TTS). BGM is only skipped entirely when neither backend can be made to run (e.g. no network for pip). It never blocks the render. Flags + BGM mechanics: top of audio.mjs.
- exit 0 -> voice + transcribe complete (BGM may still be rendering;
audio_meta.jsonrecordsbgm_log/bgm_pid), continue. - exit 1 -> zero scenes produced voice; report and stop.
Step 4 - Visual-design (subagent) — SHARED
After design-system/chunks/index.json, narrator_scripts.json, and audio_meta.json exist, concatenate all inputs into one dispatch packet (contracts first, static references middle, work items last):
# 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 h이것도 같이 보면 좋다
같은 업무 태그와 카테고리가 겹치는 항목부터 보여줍니다.

embedded-captions
Add captions to a talking-head video. ONE catalog (CATALOG.md) of 32 visual identities behind two engines: column-flow (captions composited INTO the scene — matte occlusion + mix-blend; cream/ink/editorial/keynote/documentary/loud/neon/glitch/chrome/velocity) and themed constitutions (anchor/ordnance/terminal/neonsign/stardust/stomp/scoreboard/transit/vhs/arcade/dossier/laser/thunder/hologram/biolume/aurora/spectrum/papercut/popup/chalkboard/graffiti/brush/inkwater/ransom/lastpage/nightcity — e.g. a glyph-decode climax, a neon sign WRITTEN stroke by stroke, or the quiet `anchor` rail default). Route by identity, never by mode. Trigger on "captions/subtitles", "embed/cinematic captions", "VFX captions", "炸/特效/酷炫字幕", a named identity, or top-tier motion-graphics asks. Embedding every word is wrong for most talking-head content — `anchor` is the verbatim default. Pipeline: transcription → hyperframes remove-background matting → HTML render → ffmpeg overlay. Requires hyperframes and a single-subject clip.

faceless-explainer
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.

agents-sdk
Build AI agents on Cloudflare Workers using the Agents SDK. Load when creating stateful agents, durable workflows, real-time WebSocket apps, scheduled tasks, MCP servers, chat applications, voice agents, or browser automation. Covers Agent class, state management, callable RPC, Workflows, durable execution, queues, retries, observability, and React hooks. Biases towards retrieval from Cloudflare docs over pre-trained knowledge.