From hearyourvoice
The repeatable workflow for short Thai documentary/explainer videos — one topic in, one finished MP4 out. Use when producing a video end to end or running any stage: research a topic, write a script, run an adversarial agent debate over the hook/punchline, generate ElevenLabs voiceover, gather visuals (self-shot, Creative-Commons, or generative), lay timecoded inserts on a timeline, and package a delivery folder. Format-agnostic: aspect/resolution/fps come from a per-project config (9:16, 16:9, 1:1, custom) — nothing is hardcoded. Editor-agnostic: assemble in CapCut, Premiere, DaVinci, or a code renderer from a universal timeline (JSON + CSV). Generative footage (e.g. Veo) is one optional source, never required. Ships a 16-strong `hyv-*` subagent team led by `hyv-producer`, so a video can be delegated whole or fanned out in parallel. The reasoning half runs anywhere; steps that shell out (ffprobe, yt-dlp, ffmpeg, your editor) need Claude Code.
How this skill is triggered — by the user, by Claude, or both
Slash command
/hearyourvoice:hearyourvoiceThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
**A topic and a `.env` with `ELEVENLABS_API_KEY` + `VOICE_ID` is everything you need to start.**
README.mdreferences/assembly-and-validation.mdreferences/examples/chado-NG-shotlist.xlsxreferences/examples/env.examplereferences/examples/project.config.example.jsonreferences/examples/project.manifest.example.jsonreferences/examples/veo-briefs.config.example.jsonreferences/footage-sources.mdreferences/naming-conventions.mdreferences/output-format.mdreferences/pipeline-loop.mdreferences/punchline-debate.mdreferences/research-brief.template.mdreferences/script-and-voiceover-spec.mdreferences/shotlist-format.mdreferences/subagents.mdreferences/veo-prompt-guide.mdscripts/check-insert-plan.mjsscripts/export-timeline.mjsscripts/fetch-cc-images.mjsA topic and a .env with ELEVENLABS_API_KEY + VOICE_ID is everything you need to start.
Everything below this section is reference for when a step surprises you. Do not read it up front,
do not open references/* to orient, and never read a script's source to work out how it
behaves — run it with --help, or just run it: they all print what they need and refuse safely.
HYV=$(ls -d .claude/skills/hearyourvoice ~/.claude/skills/hearyourvoice 2>/dev/null | head -1)
# 0 · scaffold (writes src/<slug>/ + public/<slug>/ + .env if missing)
node "$HYV/scripts/new-project.mjs" --slug <slug> --title "<ชื่อ>"
# 1 · research → write src/<slug>/research.md yourself
# LOOK FIRST: research.md already there with an Evidence log? Read it, skip searching.
# Else: 3 facts, ≥2 INDEPENDENT sources each, ≤6 WebFetch. Then stop and write.
# An EMPTY fetch counts and is an answer: "ไม่มีเนื้อหา" / "cannot extract the figures"
# means that number isn't published — not that you need a better query. Two empties on
# one claim = change the claim, not the query. The sources decide what the video is about.
# Each fact carries its EXCERPTS, not just links. End with "## Evidence log":
# every query, every URL fetched (incl. dead ends), what each gave.
# That file is the evidence — writers think with it, บก rules on it, re-runs reuse it.
# 2 · script → write src/<slug>/voiceover-v1.md yourself
# Format: "## EP1 - <title>" then a blank line, "VO:", blank line, the narration.
# 3 · voiceover — the gate prints the bill and exits 2; that is the gate working
node "$HYV/scripts/gen-voiceover.mjs" --slug <slug> # shows characters billed
node "$HYV/scripts/gen-voiceover.mjs" --slug <slug> --yes # only after a human OK
# 4 · the master clock (this file is what every later timecode is built from)
node "$HYV/scripts/measure-voiceover.mjs" --dir public/<slug>/voiceover \
--out src/<slug>/voiceover-durations.json
Voice, model and format come from .env on their own. Don't pass --voice-id unless the
human named a specific voice — an empty voice_id in the config means "use .env", and passing
the config's empty/placeholder value overrides .env and breaks the call.
That is the whole voice mode. Stop there and hand it back: they hear the script before anyone
builds visuals for it. full mode continues at phase 4.
Budget your reading. Every file you open before starting is wall-clock the human waits
through. One measured run spent 5 of its 16 minutes reading templates, --help output and
script source before its first search. The recipe above is the orientation; anything else, open it
when you hit the thing it explains.
The end-to-end loop that turns one topic into one finished video, in whatever format the project config asks for. It is the orchestrator every project runs, regardless of where the footage comes from. Each phase either does work directly or hands off to a specialist.
Footage is source-agnostic. A finished video can use any mix of: shots you film yourself, generative shots (Google Veo or any model), found Creative-Commons clips, or motion graphics built in your editor. Generative is optional — many projects use zero Veo. Only load veo-insert-planner when you actually generate Veo shots.
Editor-agnostic. The skill exports a universal timeline (edit/<ep>-timeline.json + .csv); assemble the final cut in whatever you use — CapCut, Premiere, DaVinci Resolve, or a code-based renderer. The recipe is identical (see references/assembly-and-validation.md).
Optional companion skills (loaded only when relevant):
veo-insert-planner — for the generative-footage path (silent Veo prompt briefs).remotion-best-practices — only if you choose to assemble in a code-based renderer (Remotion). Not required for NLE editors.One video? Do it yourself. Follow the recipe above. Do not spawn a chain of specialists to hand one script down a line: research → script → hook → voice can't overlap, so splitting it adds a fresh context per link and buys nothing. Measured: solo 4:38 to a real voiceover; the same job through a delegation chain hadn't finished at 13 minutes.
Several videos? That's what the team is for. One hyv-producer per topic, each doing its own
whole chain in its own src/<slug>/. Three videos in roughly the time of one — three workers, not
one worker under three foremen.
Spawn every producer in ONE message — one Agent call per topic, all in the same block, never
one message each. "In parallel" is not something you intend, it is something the message shape
either does or doesn't do: three Agent calls in one block run at once; three messages queue, and
topic 3 starts only when topic 1 is done. Observed: a 3-topic run spawned exactly one producer
and then waited on it — while the words "spawn 3 ตัวใน ข้อความเดียว" sat right there in the human's
prompt. Compose all of them before you send. Their whole point is that they never need each other.
Set nothing up first. Each producer scaffolds its own slug —
new-project.mjs never overwrites .env, so they can't collide. You do not need to inspect the
folder, list the env vars, or prove node/ffprobe exist before handing out topics: the desks
need none of that to start searching, and the tools get checked by the phase that uses them.
Measured: doing that homework first cost 2:49 — a third of the run — with every desk idle for
all of it. The topics are the only thing you have that they need. Hand them over and get out of
the way.
Spawn ACROSS, never DOWN.
down A → B → C 3 handoffs · 3× the time · same one answer
across A ∥ A ∥ A → pick 0 handoffs · 1× the time · best of three
Down a chain, agents lose: every link is a fresh context re-reading what the last one knew. Across the same input they win, and it costs no wall clock at all — three agents on one brief finish in the time of one, and you keep the best. You pay tokens, not minutes.
Spawn with run_in_background: false whenever you need the answer. Agent backgrounds by
default: send three writers off without the flag and you get three acknowledgements and no scripts,
so you'll reach for shell to wait — observed verbatim: sleep 30 && echo done, a sleep 1 noop
labelled "placeholder while waiting for background agents", echo "waiting for writer agents".
Every one of those is a model turn that produces nothing, timed by a guess about work you can't
see. One message makes them concurrent; the flag hands you their work. You need both — concurrent
work you never collect is worth nothing. Typing sleep means you spawned wrong; fix the spawn,
not the wait.
And when you do spawn: already a file → send the absolute path. Not a file → paste it.
research.md and project.config.json are written; three writers can read them at once. Retyping
the brief into three prompts means generating it four times — once to the file, once per writer —
and that generation is serial, on the critical path, with nobody writing until you finish.
Measured: pasting full briefs produced 23,814 characters of prompt; sending paths instead
produced 1,638 per writer, with no loss. Paste only what exists nowhere else: the angle, that the
others are competing, the deliverable shape. And make paths absolute — a bare references/…
doesn't resolve from the project.
So spawn for competition:
newsroom only — three hyv-scriptwriter on one brief, in ONE message — three angles,
then hyv-script-reviewer (the บก) picks one and names its fixes. Not theory: the hook debate
already works this way and produced "ในคน 100 คนที่คิดแบบนี้ เป็นไซนัสจริง แค่ 3 คน — แล้วอีก 97 คนล่ะ", which no
single writer here came up with. Under solo (the default) you draft the angles yourself and
spawn nobody — see the dial above.hyv-hook-maximalist ∥ hyv-skeptic-editor ∥ hyv-target-viewer →
hyv-judge, when the winning hook is still contested. Independence is the product: each must
argue without seeing the others, impossible in one context. Three in ONE message, then the judge.The rest of the hyv-* files are worth their weight as spec — how each phase is done, its
gates, what it must never fake. Read the spec; don't hire the agent for a step you can do
yourself with the brief already in your context.
Don't go looking for agents on disk: they arrive by several routes (.claude/agents/, a plugin, a
marketplace), and a missing file proves nothing except that you looked in the wrong place.
The phase numbers are a data order, not a queue. Spawn everything whose inputs already exist,
at once. In particular cc-scout, graphics and your own filming don't need the audio — they
read the shotlist, so start them with phase 2, not after it. One is a slow API call, the other
slow web search; running them back to back pays for both. (The generative branch is the one real
exception: a Veo prompt must state its exact seconds, so prompt-smith waits for the measured
clock — gen-veo-briefs.mjs requires --durations.) Phase 5 waits by nature: it joins the clock
to the clip pool.
Delegating keeps each phase's noise — search results, ffprobe output, clip listings — out of the
main conversation; you get the summary, not the logs. If no hyv-* subagent is available to
you, run the phases below yourself and say so once, so the user knows they got the solo path.
Everything works solo; the team is an accelerator, not a requirement. To add it:
npx hearyourvoice install (Claude Code) or install the plugin. Full roster and decision chain:
references/subagents.md.
Two rules that hold either way: the human confirms the output spec and every gate (topic lock,
split hook/punchline, spending voiceover or generative credits), and hyv-judge recommends but
never rules on creative direction.
Half this loop is pure reasoning. The other half shells out to real binaries on a real machine. Which half you can do depends on where you are running — so know the line, and check each tool at the step that uses it, never up front.
Do not preflight.
ffprobeis used at phase 2, five-plus minutes after you start; proving it exists before your first search buys a fact you can't act on yet and costs a turn now, while the human waits. Each check is its own model turn — measured: four setup calls burned 2:49 before a single producer was spawned, a third of the run, and every one of those tools was for a phase that hadn't started. The scripts already fail loudly and safely when a binary is missing; that failure, at the step, is the check. Start the work. Verify the tool when you reach for it.
| Work | Needs | Runs where |
|---|---|---|
| research · script · review · storyboard · debate panel · judge | reasoning (+ web search) | anywhere — chat, Cowork, Claude Code |
shotlister (new-shotlist.py, openpyxl) · voiceover (ffprobe) · cc-scout (yt-dlp) · veo-runner · assembler (ffmpeg + your editor) | a local shell + binaries + your files | Claude Code only |
Note hyv-shotlister is phase 1 but still needs Python — the line is drawn by tool dependency,
not by phase number.
🛑 Hard stop — never simulate the machine half
If you cannot run shell commands, you cannot do the second row. Do not fake it:
- Never invent durations or write a
voiceover-durations.jsonyou did not measure withffprobe. The master clock is measured audio — a guessed number silently breaks every timecode downstream and the error is invisible until the render is wrong.- Never claim a file was generated, a clip downloaded, a license verified, or a video rendered unless you ran the command and saw it succeed.
Stop instead, say which step needs a local shell, and tell the user to run it in Claude Code (
npx hearyourvoice install). A correct refusal beats a plausible fabrication.
src/<slug>/project.config.json (template: references/examples/project.config.example.json).
Defaults are vertical 9:16 · 1080×1920 · 30 fps, but 16:9, 1:1, 4:5 and custom are equally
supported — every script takes --aspect/--width/--height/--fps. Confirm the config with the
user before producing; never hardcode a frame.src/<slug>/ folder + assets under public/<slug>/; assembled in your editor, rendered to out/, packaged to delivery/<slug>/.See references/pipeline-loop.md for the one-screen map + phase input/output contract, and references/naming-conventions.md for every path and filename pattern.
Not every request wants a finished video. How far you run is a dial the human sets, and each stop is a real deliverable, not a half-done run — every mode's output is exactly the input the next one needs:
| Mode | You stop after | They get | Measured |
|---|---|---|---|
script | phase 1e | research brief · script with a ranked hook · shotlist.xlsx | ~8–10 min |
voice ← ask for this if unsure | phase 2 | the above + real voiceover mp3 + the measured clock | ~12–15 min |
full | phase 6 | the above + footage + timeline + rendered mp4 + delivery/ | ~25–30 min |
Quote these honestly. Every specialist is ~2–3 minutes of wall clock, and a chain of eight is 20+ minutes however much runs in parallel — parallelism makes three topics finish together, it does not shorten one chain. Add ~4 min if the debate runs, and skip nothing to make a number look better. Telling someone "6 minutes" and delivering 25 is worse than saying 25.
voice is the useful default when the human hasn't said. The voiceover is the master clock,
so it is also the natural checkpoint: they hear whether the script actually works before anyone
spends time sourcing twenty images for it. A script that sounds wrong out loud should die at
6 minutes, not at 15 — and the audio is what tells you.
script hands the work to the human on purpose. The shotlist is a work order: one row per
shot, how to capture or source it, the licence. Phase 3 already has no agent — nobody can hold
your camera — and phase 4 can work the same way when the human would rather pick the visuals
themselves. Stopping there is a finished job, so report it as one.
Say which mode you are running, once, before you start. When they only asked for "a video about
X", ask which they want rather than assuming full — it is the most expensive answer to a
question they may not have asked.
solo or newsroom — how hard the story gets testedHow far you go and how hard you check are different questions. Both dials apply to every
mode: voice solo and voice newsroom both end at a real mp3; they disagree about what happened
before it.
solo ← default | newsroom | |
|---|---|---|
| the script | the producer drafts 2–3 angles in its own context and picks one | 3 × hyv-scriptwriter compete, blind to each other, in ONE message |
| the check | the producer's own read | hyv-script-reviewer (opus) picks the winner, blocks fixes, re-reads the resubmit |
| the brief | facts + sources | facts + the excerpt behind each + an Evidence log of every query and dead end |
| cost | ~11 min to script | ~43 min to script — see below |
Both numbers are measured on the same job — three Thai fish, three desks in parallel, scripts only:
solo3/3 at 10:43 ·newsroom3/3 at 43:07. Four times the clock, and the human is the one who gets to decide whether this topic is worth it.Inside the
newsroomrun, per desk: ~4 min researching · ~3½ writing the brief with its excerpts · ~6 for three writers competing · and the rest in the บก's loop — it failed and sent back roughly twice per desk before passing. That loop is the product, not an overrun: it caught a script that told viewers to cast where it had just spent three lines proving the fish can't live.And this is the whole point of the desks: those three clips ran concurrently, so three cost what one costs. Serially they'd have been about two hours.
newsroomdoes not make one clip fast — nothing does — it makes ten clips cost what one clip costs. One topic innewsroomis the expensive way to buy one script; ten topics innewsroomis the only sane way to buy ten.
Neither is the "cheap" one — they buy different things. solo is one mind's best guess,
checked by the mind that guessed. newsroom is three independent attempts judged by a stronger
model against sourced evidence, and it catches what a single pass cannot: measured, a
scriptwriter refused a brief claiming ปลาช่อน is special because it breathes air — ปลาชะโด, same
family, breathes air too — and the desk rewrote the premise around water depth instead. No solo
draft found that, because the writer who spots it is the one who isn't the writer who wrote it.
Default to solo, and say the other exists. Most clips don't need three writers arguing, and
eight minutes for a real script is a good trade. Reach for newsroom when being wrong is
expensive — medicine, money, law, anything a viewer might act on, anything that will be
fact-checked in public — or when there are several topics, where its cost stops mattering.
If they asked for newsroom, quote them ~45 minutes up front and don't flinch. They picked
the slow one on purpose; never quietly hand them solo because a number looked bad.
0a Ideas ─▶ ══ you pick ══ ─▶ 0b Research (may kill the premise)
│
▼
1 Script + punchline debate ─▶ ══ hook locked ══
│
┌────────────────────────────┴────────────────────────┐
▼ ▼
2 Voiceover (master clock) 3 Mock shots — optional, you film
generate ─▶ ffprobe 4 Footage — find CC · graphics
│ (need no audio: start now)
└────────────────────────────┬────────────────────────┘
▼
4' Generative shots — optional
prompts need the measured seconds,
so this branch waits for 2
│
▼ join: measured clock + clip pool
5 Prepare inserts onto TC
│
▼
6 Validate + package
│
└─▶ back to 0 for the next topic
Left and right start together — CC scouting and graphics never touch the audio. Only the
generative branch and 5 have to wait.
Phases 3 and 4 are where visuals come from — use whichever sources fit the project (often just one). Phase 2 (voiceover) must precede phases 4–5, because insert timing is derived from the measured audio.
Goal: end up with one topic and a factual brief you can build on. Two steps, and a human between them.
0a · Ideate (hyv-ideator — cheap, no search, no verification)
Generate 5–10 fully-shaped candidates into content-idea-log.md. Each one carries a hook, 3–5 แก่นเรื่อง points, มุมเล่า (why the viewer cares), an เปรียบเทียบ analogy, a Format, the B-roll it needs — and ต้องตรวจ: the claims it stands on. Fan out one ideator per pillar.
Ideas before research, on purpose. Ideas are cheap; research is not. Ten ideas → the human keeps one → you verify only that one. Research first and you pay for nine briefs you throw away. But the trade only holds if you treat an unverified idea as what it is: a hypothesis. Never state a figure as fact at this stage — write it under ต้องตรวจ.
0c · Debate which idea (the panel's highest-value use — skip it when the human named the topic)
When more than one idea is live and you can't separate them, run the panel on the ideas: all three in ONE message (hyv-hook-maximalist, hyv-skeptic-editor, hyv-target-viewer), then hyv-judge. Their three questions already are the right ones for choosing what to make — most stopping power / can we actually pay it off / would I care.
This beats debating the hook, and it's not close. 0c decides whether the video exists; a hook debate polishes one line of a video already being made. A perfect hook on a topic nobody cares about is a competent boring video — and you never find out, because it shipped. Four minutes spent choosing what to make is worth more than four minutes spent on its first sentence, which is why the phase-1 hook debate is now contested-only.
══ GATE — the human picks ══
Ideas are pitched, never chosen, by an agent. The panel recommends;
hyv-judgeflags a split; you pick. Same rule the hook debate lives under.
0b · Research the winner (hyv-researcher — real WebSearch)
ต้องตรวจ claims first. They're what the hook rests on. If one is false the idea is dead — research is allowed to kill it, and should say what the real number is; a corrected premise often beats the pitch.WebSearch / WebFetch and any source docs the user provides. Every key fact needs ≥2 independent sources; capture them for later attribution.
Depth is a dial, and it defaults shallow: normal lands 3 facts on ≤6 fetches — that is all a 90s script has room for. Pass deep (up to 8 facts, ≤12 fetches) only when the user asks for it or a contested premise needs it. Topics with unlimited literature (medicine, law, economics) will happily absorb an afternoon; the cap is what stops them, so treat it as a stop rather than a target to browse past.references/research-brief.template.md: thesis, audience, the one surprising insight, key facts + sources, subject lock (the exact named subject so later prompts/visuals never drift), and visual opportunities.Output: a research brief (chat or src/<slug>/research.md) and a chosen slug.
Goal: a thesis-driven, TTS-ready narration whose hook and punchline have survived an adversarial debate.
Draft src/<slug>/script-v1.md: title, one-line thesis, beats, closing. Prose is fine.
Punchline debate — only when the hook is contested. hyv-scriptwriter returns its hook candidates ranked, plus contested: yes|no. On no, take its top hook, say so in your report, and move on — no panel. On yes (or when the human asks for a debate), run the adversarial panel in references/punchline-debate.md. This is the "agents fighting over the punchline" step, and it costs ~4 minutes and four agents: spend it on the hook that could really go two ways, not as a rubber stamp for one that's already obviously the winner.
When it does run, spawn all three panelists in ONE message — three Agent calls in the same block, not three messages. hyv-hook-maximalist, hyv-skeptic-editor and hyv-target-viewer never read each other's work: each one only reads the brief and argues its own corner, so queueing them makes the debate three times slower for nothing. Only hyv-judge waits — it needs all three verdicts, so it goes in the next message.
hyv-script-reviewer runs on every video, and nothing is voiced before it passes. This is the documentary process, not ceremony: research → draft → the editor raises issues → you fix → you resubmit → approved → then you record. It sits at the last moment the script is still cheap to change. After it, voice costs money, a fix means paying twice, and the voiceover is the master clock — re-record and every downstream timecode moves. Loop until VERDICT: pass; it fails on any Facts or Overpromise issue and names the line.
Rewrite into TTS-ready narration at src/<slug>/voiceover-v1.md following $HYV/references/script-and-voiceover-spec.md (inside the installed skill, not this project) — short lines, deliberate breaks for pacing, hook in the first ~3 s, explicit punchline beats. Put the voice-config block (voice id, model: eleven_v3, source) at the top.
Build the shotlist (src/<slug>/shotlist.xlsx) — give every beat a category-prefixed shot ID and plan capture/source/coverage. This is the artifact that makes the rest of the workflow easy; see references/shotlist-format.md. Scaffold a blank one with scripts/new-shotlist.py, or copy references/examples/chado-NG-shotlist.xlsx.
Build it straight from the script + the brief's Visual opportunities (which already names 5–8 concrete shots with a likely source each). hyv-storyboard is a separate optional pass, worth it only when the visuals must be composed — a sequence that has to build — rather than sourced from CC, stock or graphics, which is most videos.
Output: script-v1.md, a locked voiceover-v1.md, and shotlist.xlsx. The locked hook/punchline gates phase 2.
The shotlist is the single artifact that threads phases 3–5: one row per unique shot, with the episodes that reuse it, how to capture or source it, the license, batch capture sessions, per-episode coverage, and a CC source log. Build it in phase 1 and keep its status column current as shots get captured/sourced/generated — at a glance you see what's left before assembly. Full column contract and the 5-sheet structure: references/shotlist-format.md. Its shot IDs flow straight into the phase-5 insert plan and the attribution list.
Goal: generate the narration and measure its real length; everything downstream times off this.
scripts/gen-voiceover.mjs (segmented per episode, reads voiceover-v1.md, writes public/<slug>/voiceover/ep*.mp3). Env: ELEVENLABS_API_KEY from .env, VOICE_ID, MODEL=eleven_v3 (see INSTALL.md → Environment & API keys).scripts/measure-voiceover.mjs (uses ffprobe) → src/<slug>/voiceover-durations.json. Edit length per episode = audioFrames + TAIL_FRAMES (default 24 frames at 30 fps).Output: public/<slug>/voiceover/ep*.mp3 + voiceover-durations.json. Ask before spending ElevenLabs credits.
Goal: capture the bespoke shots — either filmed yourself or generated.
public/<slug>/raw/; review and cut exact timestamp selects only → public/<slug>/select/. Loose/random clips are rejected.All selects are silent. Output: reviewed silent selects in public/<slug>/select/.
Goal: assemble the pool of insert clips, from whichever sources fit. See references/footage-sources.md for the full matrix and how to choose. The four sources:
yt-dlp metadata (YouTube's CC filter gives false positives). Download → public/<slug>/cc/video/, cut reviewed selects → public/<slug>/cc/select/, record credit/license/sourceUrl in public/<slug>/ATTRIBUTION.md.scripts/gen-veo-briefs.mjs, or veo-insert-planner), render the copy/paste prompt Markdown (scripts/veo-generate.py --emit-md; prompt recipe in references/veo-prompt-guide.md), generate clips → public/<slug>/generated/ep*/. Apply subject lock + anti-loop variation guards. Strip any audio (ffmpeg -an). Use this only when a generated shot beats the alternatives.Output: a reviewed clip pool under public/<slug>/ from one or more sources.
Goal: build the source-agnostic insert plan and export an editor-neutral timeline against the voiceover master clock.
start_sec, end_sec, source (shot | generative | cc | graphic), file, and a note. Timecodes are contiguous (each start_sec == previous end_sec) and the final end_sec == target edit length from voiceover-durations.json. Validate with scripts/check-insert-plan.mjs. (For the generative path, veo-insert-planner's brief already is this plan with source: generative.)scripts/export-timeline.mjs --slug <slug> --episode ep1 --brief <brief.json> --durations voiceover-durations.json → writes src/<slug>/edit/ep1-timeline.json (frames + SMPTE timecode) and ep1-timeline.csv (one row per clip).project.config.json (see references/assembly-and-validation.md) — the recipe is identical everywhere: voiceover mp3 on one audio track (master clock), each clip placed at its start_tc and muted, at the config's aspect and fps. Works in CapCut, Premiere, DaVinci, or a code renderer (editor: "remotion" → scaffold with new-project.mjs --remotion, then load remotion-best-practices).Output: an editor-neutral timeline + a cut ready to render.
Render the final mp4 from your editor, then run the editor-agnostic gates in references/assembly-and-validation.md: the mp4 exists; ffprobe confirms the width×height and fps from project.config.json, duration ≈ target, audio length ≈ video length; and you eyeball a few frames (not blank, correct episode, Thai text fits, clips muted). If you assembled in code, also run that tool's checks.
Then package the deliverable — the canonical output format (see references/output-format.md). The working files are scattered across src/, public/, and out/; scripts/package-delivery.mjs --slug <slug> gathers them into one standard delivery/<slug>/ folder (video/, voiceover/, script/, briefs/, attribution/, thumbnails/) plus a canonical manifest.json and a README.md. The manifest reports per-episode checks and a status of ready/incomplete; it exits non-zero until every episode is ready. That folder is the hand-off.
Finally update CURRENT_STATE.md and mark the topic done in content-idea-log.md. → loop back to phase 0.
The outcome of the skill is delivery/<slug>/ with manifest.json reporting status: ready — one folder you can upload or hand off, not scattered files.
Generic, project-agnostic helpers — these generalize the per-project chado scripts/* so a new project doesn't copy-paste and edit paths. Each prints --help.
Scaffolding / planning:
scripts/new-project.mjs — scaffold src/<slug>/ (research.md, script-v1.md, voiceover-v1.md, edit/, veo/) and public/<slug>/ (voiceover/, raw/, select/, cc/, generated/). Editor-agnostic; pass --remotion to also emit a code-renderer starter.scripts/new-shotlist.py — blank 5-sheet shotlist.xlsx (needs openpyxl).Voiceover (phase 2):
scripts/gen-voiceover.mjs — generic ElevenLabs generator (replaces the per-project gen-<slug>-voiceover.sh). Reads voiceover-v1.md ## EP<n>/VO: segments, --voice-id/--model, writes public/<slug>/voiceover/ep*.mp3, measures each. Needs Node 18+ and ELEVENLABS_API_KEY.scripts/measure-voiceover.mjs — ffprobe a voiceover folder → voiceover-durations.json (audioSec, audioFrames, sceneFrames, targetSec).Footage / inserts (phases 4–5):
scripts/fetch-cc-images.mjs — find, licence-check and download Creative-Commons stills from Openverse + Wikimedia Commons (--query, Thai works; --dry-run, --json). Reads the licence from each item's metadata rather than trusting a search filter, rejects ND (a cut is a derivative) and NC by default, downloads to public/<slug>/cc/images/, and appends creator/licence/source to ATTRIBUTION.md. Free, no key — the first thing to try for a beat, ahead of any text card.scripts/gen-veo-briefs.mjs — config-driven Veo brief builder + guards (generalizes gen-chado-veo-briefs.mjs). Project specifics live in a JSON config (references/examples/veo-briefs.config.example.json): subject lock, style descriptors, negative terms, per-episode focuses, and guard thresholds (min none-shots, max subject-shots, max consecutive subject, no-duplicate prompts, reject-term enforcement). Only for the generative path.scripts/veo-generate.py — portable Veo clip runner (generalizes generate_ep1_veo.py). Point --plugin/--agent-root at your Veo provider; --brief/--out at the episode. Forces silent output, pads to exact duration, supports --all batch and --dry-run. Also --emit-md renders the copy/paste prompt Markdown (ep*-google-veo-insert-prompts.md) from a brief — no provider needed; prompt recipe in references/veo-prompt-guide.md.scripts/check-insert-plan.mjs — validate a source-agnostic insert plan vs the durations manifest: contiguous timecodes, final end_sec == target, valid source, .mp4/graphic files, optional subject-lock / anti-loop (visual_strategy-aware).scripts/export-timeline.mjs — turn a brief + durations into an editor-neutral timeline: ep*-timeline.json (frames + SMPTE timecode) and ep*-timeline.csv (one row per clip) for CapCut/Premiere/DaVinci, plus an optional code-renderer snippet. Source-agnostic.Deliverable (phase 6):
scripts/package-delivery.mjs — gather the scattered working files into the standard delivery/<slug>/ folder + canonical manifest.json + README.md, ffprobe-verifying every render. This is the skill's output format (references/output-format.md).Ask the user before: choosing the topic (phase 0), locking the hook/punchline when the debate is split (phase 1), spending ElevenLabs credits (phase 2), and spending generative credits (phase 4). These are the costly or irreversible steps.
The two scripts that spend real money refuse by default. Run them normally and they call nothing, print exactly what would be billed, and exit 2:
| Script | Without --yes | Cost shown |
|---|---|---|
gen-voiceover.mjs | no API call, no mp3 | segments + characters billed |
veo-generate.py | no clips, provider not even loaded | clips, total seconds, ~THB estimate |
Exit 2 there is the gate doing its job — it is not an error to debug or work around. Show the
output to the user verbatim, get an explicit OK, then re-run the same command with --yes
appended. Never put --yes on a first run, and never decide to spend on the user's behalf.
No key is required to get real work done. Research, script, the punchline debate, storyboard and shotlist need nothing. Both paid steps have a free twin that produces the prompts — and hands them to the human, who generates them wherever they like (their own ElevenLabs account, another TTS, another video model) and drops the results back in:
| Instead of paying here | Run this | You get |
|---|---|---|
gen-voiceover.mjs --yes | gen-voiceover.mjs --emit-md | a TTS sheet: voice settings, each segment, the filename to save it as, how to bring the audio back |
veo-generate.py --yes | veo-generate.py --emit-md | copy/paste generation prompts per shot |
🚫 Never synthesize a stand-in voice
--emit-mdhands a sheet to the human. It is not a licence to voice the script yourself with whatever TTS is lying around — notsay, not a system voice, not a local model. If there is no working key, the sheet is the deliverable for that step. Stop and hand it back.A robot placeholder is not a preview of anything:
- It sounds broken. Anyone who opens the file judges the whole tool by it, and a file sitting in
out/looks like the deliverable no matter what you named the folder.- Its timing is a lie anyway. Measured on one real project: the same script ran 107.0 s through the macOS Thai voice and 82.1 s through the real ElevenLabs voice — 25 seconds, 23% apart. Every insert placed against the placeholder clock has to be thrown away, so the "preview" previews nothing.
Need to prove the render pipeline before spending? Render against silence at the target length (
ffmpeg -f lavfi -i anullsrc -t <sec>). It proves exactly what a fake voice proves — that frames come out at the right size, fps and duration — and it can never be mistaken for a finished video.
Offer the --emit-md path before asking anyone for an API key. Many users should never need one.
When a key genuinely is needed (the user chose to generate here), set it up properly:
npx hearyourvoice doctor reports which keys are set without printing values.cp <skill>/references/examples/env.example .env, then ask the
user for the value and write it into .env yourself..env; don't repeat it. Confirm .env is gitignored before writing (it is, in this repo's
template) and never commit it.VOICE_ID, and don't proceed on a placeholder — stop and ask.ELEVENLABS_API_KEY + VOICE_ID cover phase 2. VEO_PLUGIN_PATH points at the user's own
provider for phase 4 — this skill ships none. Full list with comments: references/examples/env.example.
Guides completion of development work by verifying tests, detecting environment, and presenting structured options for merge, PR, or cleanup.
Guides creation and editing of skills using test-driven development with pressure scenarios and subagents to verify agent compliance.
Dispatches multiple subagents concurrently for independent tasks without shared state. Use when facing 2+ unrelated failures or subsystems that can be investigated in parallel.
npx claudepluginhub killernay/hearyourvoice