What does one-take do?
Ask everything once. Then make the whole film, every format, checked, with no more questions.
This skill orchestrates; HyperFrames does the rendering. It needs the HyperFrames skills (hyperframes, hyperframes-core, hyperframes-animation) and is better with hyperframes-registry, hyperframes-keyframes, media-use, figma and humanizer. If hyperframes-core is missing, stop and give the install line: npx skills add heygen-com/hyperframes.
SKILL_DIR below means this skill's folder. Scripts are plain Node (≥ 22) plus ffmpeg, with no installs.
The contract
- One intake round. Detect first, then ask once (Phase 1). After the answers, ask nothing more. The only exception is a hard blocker you cannot default around, such as a file that doesn't exist or a site that refuses to load. Then ask that one exact question and continue.
- The intake answer authorises the renders. It does not authorise publishing, uploading, posting, pushing or downloading anything the user didn't name. Ask for those separately, after the report.
- Facts are computed, never typed. Every number on screen comes from
facts.jsonor from a source the user gave. Never invent stats, quotes, logos or testimonials. - The picture writes the sound. Every motion that should be heard calls
T.cue()at the moment it happens. The score is built from those cues, so sound and picture can't drift. - Done means QA-passed. A file is delivered when
qa.mjspasses it, or when a failure is written up in the report with the reason it couldn't be fixed in two passes.
Phase 0: detect (silent, before asking)
node SKILL_DIR/scripts/detect.mjs <subject paths…> --out /tmp/one-take-detect.json
- Run it on the current repo and any path or site the user mentioned.
- It returns, per source: README title and tagline, git span and commit count, logos, font files and families, colour tokens. It also returns the toolchain: node, ffmpeg, cached
hyperframesversion, Chrome, heygen CLI, and whether music API keys are set. - If
take.jsonalready exists in the target project, this is a resume. Read it, skip the intake, and continue from the first phase whose output is missing.
Phase 1: the intake (one message, one round)
Read references/intake.md. In one message:
-
Show the intake sheet: every field pre-filled from detection or a recommended default, each on one line. The user changes only what's wrong.
-
Call AskUserQuestion once with the four decisions that most change the work: formats (multi-select), length, sound, and voice/captions. The question text tells them to add any sheet changes as a note on any answer.
If AskUserQuestion isn't available, put the same four as numbered choices at the end of the sheet and ask for one reply.
Then, without asking again:
- Write
take.json(schema:references/take-json.md). Language codes, format ids, the slug andoutput.namemust match[a-z0-9][a-z0-9._-]{0,63}(any case, no..), or the run stops. Leavehyperframesout unless the user asks: the default version is pinned inscripts/lib/platforms.mjs(HF_VERSION, 0.8.223). - Write a HyperFrames
BRIEF.mdwithworkflow: general-video,flow: automation,storyboard: no. This means a later/hyperframescall resumes this project instead of re-interviewing. - Record the answers in BRIEF.md's body: formats, sound, captions, platform caps.
Phase 2: sources
- Git facts. Run
node SKILL_DIR/scripts/facts.mjs --repo <path> [--repo …] [--since …] --out <project>/facts.json. It collects commits, span, active days, longest streak, busiest day, lines by language, lines added and removed, and top commit words, plus the first and last commit subject. Author names and emails are never recorded. Use only the facts the user allowed. - Sites. For a URL to show, run
npx hyperframes capture <url> -o <project>/src/assets/cap. Stop onok:falseorBLOCKED.md: that's a blocker. - References. For each one, write one line in STORYBOARD.md on what you're taking from it: pacing, type, colour, a transition. For a Figma link, run
/figmafirst. - Brand. Copy the logo SVG and font files into
src/assets/brandandsrc/assets/fonts, declare them intake.jsonfonts, and lift colour tokens into the template's:root.
Phase 3: direction and storyboard
Read references/direction.md before writing anything visual. Write STORYBOARD.md:
- Direction, one paragraph: the one idea, the signature move, the type pairing, the palette, the reference each choice came from.
- Tempo first. Pick bpm so the target length is a whole number of bars (4/4). Then give every scene a length in bars, an
energy(0–1, which drives the score's arrangement), and how it enters (in). - Scene table: id, bars, energy, what's on screen, motion, cues, copy.
- The last scene is the end card,
end: true, with enough bars to hold it complete for 3 s or more. - Mark scenes that can go in a cutdown
optional(1–9, higher goes first).
- The last scene is the end card,
- Copy goes in
copy/<lang>.json, one key per line of text. Run it through/humanizerif available. Keep headlines short enough to read twice in the time they're on screen.
Phase 4: build
- Copy
SKILL_DIR/templates/index.htmlto<project>/src/index.htmland rewrite its scenes from the storyboard. - Read
references/kit.md(theT.*API) andhyperframes-core's non-negotiable rules. For motion, readhyperframes-animationrules and blueprints. Before hand-building any named effect, searchhyperframes-registry.
Rules that keep every format and the QA honest:
- Scenes are top-level
<section class="clip scene" data-scene="id">, never nested, never with inlinestyle. - Size in design px through
--u(1% of the short side). Readable text is never below--t-min(3.4u ≈ 37 px at 1080). - Keep readable text inside
.safe, the platform UI bands for that format. - Re-lay out per format with
.f-tall,.f-wide,.f-square,.f-portraitrules, not by scaling one layout. - Clear a scene's words before the next scene's transition covers them (
T.sink/T.leavenears.end). Text under a reveal fails the layout check. - Every scene moves:
T.driftthe camera across it. Nothing stands still before the end card. T.endCard(t)at the moment the end card is complete. The last frame must still show it: no fade to black over the hold.- Cue everything that should be heard (
references/sound.mdlists the categories). Most kit helpers cue by default; passcue: falseto keep a moment silent. - Numbers come from
T.factsor the copy.T.countneeds a finite number and throws without one, so a missing fact stops the probe instead of putting an invented number on screen. - End the script with
window.__timelines["main"] = T.done();.T.done()registers the timeline once, after every tween is added, and the template repeats it in that line. Build all tweens first, not inside a font-ready or other async callback: HyperFrames lint rejects an early registration.
Phase 5: draft pass
node SKILL_DIR/scripts/run.mjs <project> --draft
This runs, per format × language: build → probe (cues, text sizes, end-card mark from headless Chrome) → score → hyperframes check → draft render → encode → QA → REPORT.md.
HyperFrames runs at the version pinned as HF_VERSION in scripts/lib/platforms.mjs (0.8.223), or at hyperframes from take.json. The first run fetches it with npx, and the build fetches GSAP 3.14.2 once from jsDelivr into ~/.cache/one-take/. The GSAP file is checked against a pinned sha256 after download and each time it is read from the cache. A file that doesn't match is never used: a bad download isn't cached, and a bad cached file is deleted and the build stops. Run it again to fetch a fresh copy.
Then:
- Open every
*.sheet.jpgcontact sheet and look at it. - Read
REPORT.md. - Fix failures and anything that looks wrong: overlaps, crops, dead frames, text too small, a cut that doesn't land.
There are at most two fix passes. Each one reruns run.mjs --draft; use --only <format>-<lang> to rerun just what changed.
Phase 6: final
node SKILL_DIR/scripts/run.mjs <project>
This is the full quality render of every output. Deliverables land in renders/ (or output.dir). Each output gets:
<name>.mp4: x264 CRF 12, AAC 320k, faststart.cover.jpg: the end card.sheet.jpg.qa.json
Score stems and marks.json are kept in out/<format>-<lang>/score/.
Phase 7: report
Reply with:
- the outputs table from
REPORT.md(file, size, length, verdict) - the contact sheet of the main format
- one line on the sound
- open items, if any
Offer the next steps without doing them: post copy, more languages, a cutdown, publishing. Nothing gets posted or pushed unless the user asks.
Sound modes
sound.mode | What happens |
|---|---|
score (default) | score.mjs synthesises bed, band and drums from the scene energies on the bar grid: chord plan from mood and key, sidechained to the kick, risers into every lift, an impact on each arrival. The fx stem is built from the cues, tuned to the chord under each one and panned from where it happens on screen. Mixed, limited, then two-pass loudnorm to lufs (default −14) with true peak −1.5. Original, so nothing to license. |
track | The user's file. Get bpm and first beat with score.mjs --analyze <file> (or npx hyperframes beats), set bpm to it and lay the scenes on its bars. The fx stem plays over it. |
library | /media-use resolve --type bgm (needs the heygen CLI) or its generators. The result is used as track, and its licence is recorded in sound.licence. |
none | No audio element at all. |
For a voiceover, put it at src/assets/audio/vo.wav, from Kokoro via npx hyperframes tts or the user's recording. The score carves the music under it (duckDb, default −9). For captions, time them from npx hyperframes transcribe vo.wav --model <model>. Burned captions are scene text placed inside .safe at --t-cap or larger. For sidecar captions, write <name>.srt from the same transcript next to each output.
Files
scripts/detect.mjs,facts.mjs: what's there, and the numbersscripts/build.mjs: one source →out/<format>-<lang>/HyperFrames projects. Fills sizes, safe bands and scene timing, inlinesTAKE(copy, facts, scenes), vendors the kit and GSAP 3.14.2 (sha256-checked), drops scenes for cutdownsscripts/probe.mjs: headless Chrome pass that reads cues, text sizes and positions over time, and marksscripts/score.mjs: synth score, stems, mix, master,marks.json;--analyzefor a track's temposcripts/encode.mjs,qa.mjs,report.mjs,run.mjs: delivery, checks, report, and the whole runscripts/selftest.mjs: checks the toolchain, then proves QA fails a file built to failscripts/lib/: shared helpers (git, platforms withHF_VERSIONand the name check, WAV, DSP)kit/take.js: the motion kit (references/kit.md)templates/index.html: the starting stagereferences/:intake.md,take-json.md,direction.md,sound.md,formats.md,qa.md,kit.md