--- name: pr-to-video description: pr-to-video workflow - a GitHub pull request (URL like github.com///pull/, or /#, 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. metadata: { "tags": "orchestrator, pipeline, pr-to-video, changelog, dev-rel, code-explainer, release-notes", } --- # pr-to-video - dispatch entry Input is a **GitHub pull request** (a code change), supplied as a PR URL, an `/#` 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` 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 `` resolves to pr-to-video). All artifacts go to `PROJECT_DIR = videos//` (created in Step 0); all paths below are relative to it. Dispatch is harness-portable: before the first subagent dispatch, read `/../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` 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//`. ``: use the directory the user gave (e.g. `Use ./videos/retry-pr`), else a short kebab-case name derived from the PR (`-pr-`, e.g. `widgets-pr-1187`). **Not** the workspace basename or a timestamp. Only when `$PROJECT_DIR/hyperframes.json` is absent: ```bash PROJECT_DIR="${PR_VIDEO_DIR:-videos/}" 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. ```bash # PR ref: a full URL, "/#", or "" inside a checked-out repo. PR="" # 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 /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/.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 /scripts/fetch-people-avatars.mjs \ --people ./capture/extracted/people.json) ``` Validation: ```bash [ -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): ```bash (cd "$PROJECT_DIR" && node /phases/design-system/scripts/build-design.mjs ./design-system --no-emit --style claude) (cd "$PROJECT_DIR" && node /phases/design-system/scripts/build-design.mjs ./design-system --style claude) (cd "$PROJECT_DIR" && node /phases/design-system/scripts/emit-chunks.mjs ./design-system) ``` Validation: ```bash [ -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: PROJECT_DIR: