SuperAPI AI Directory

remix-script

Design & creation

View repository

Updated 10-15 21:55

About this skill

remix-script

Turn brand details + a creative prompt into a render-ready ad script. Template-driven: you pick a script format from templates/ (the first is podcast-script), the scaffolder lays out the correct scene structure, and you (the agent) write the lines. The output is a script.json the matching production template renders.

Two modes:

  • Fresh — brand details + prompt → a new script from scratch.
  • Remix — a source ad's remix payload (from the remix-ad / library flow) → inherit its format + beat arc (NOT its literal scene count), then write the new brand's script with as many scenes as that brand needs.

When to use

  • "Write the script for [brand] / this idea."
  • "Remix this ad's script for [other brand]." (works for any format — podcast-skit, iMessage thread, sung music video, single-host UGC, animated explainer.)
  • Any step that needs a structured script before generation (this is the script stage of the remix pipeline, extracted as its own skill).

Do NOT use to: research the brand (use brand-research), or render the ad (hand the finished script.json to the format-matching production template — create-podcast-skit-ad, create-imessage-video-ad, etc. — see step 9's route table).

Inputs

  • Brand details (one or both):
    • a brand-context pack (brand-research/*.md — voice, audience, what-to-never-say, claims) from the brand-research skill, and/or
    • a source sample payload (source-sample.json from remix-ad's fetch_sample.py) for remix mode.
  • User prompt — the creative direction / angle.
  • formatProfile — comes from source-sample.json in remix mode; asked from the user in fresh mode. There is no default format. The skill never picks podcast-script to be safe — if the source is iMessage, the output is iMessage (hand-authored). See step 3's route table.

Templates

A "template" here means a scaffolder + validator pair for one formatProfile. Templates live in templates/<name>.json (a _meta block with scene count, word caps, intonation legend, guardrails).

Only one template is shipped today — and that does not mean every remix routes to it. Most formats are hand-authored (see step 5 Path B). The full set of formats this skill handles is in step 3's route table.

  • podcast-script (only shipped template) — two-host fabricated podcast skit (HER + HIM), 20-24 alternating scenes, intonation legend, ≤10 words/line, ≤3 words/caption, themed-set mismatch as the joke. Renders via create-podcast-skit-ad. Scene shape: {scene, who, time, still, caption, text}. Used only when the source formatProfile is podcast-skit-fabricated (or fresh mode with the user explicitly picking this format).

To add a template for another format, drop a new <name>.json here following the same _meta.rules shape, plus a matching scaffolder clause in scripts/new_script.py and validator clause in scripts/validate_script.py.

Setup

No API keys, no heavy deps — the agent writes the copy; the scripts only scaffold and validate.

pip install -r requirements.txt   # stdlib only

Workflow

  1. Watch the source FIRST (remix mode).

    In remix mode you are rewriting somebody's ad. Don't trust extracted_script alone — it's a transcript, not the ad. The pacing, sight gags, on-screen text, where the brand surfaces, and the tone often only land once you've seen it. Run the /watch skill on the source video before writing any lines:

    # source-sample.json has a media_url field (CDN URL or file:// path).
    # Pipe it into /watch — frames + transcript come back in the same turn.
    jq -r .media_url ./remix/source-sample.json
    # then: /watch <that-url>  "what's the format, beat arc, and visual joke?"
    

    Read the frames /watch returns. Note: archetype (skit / single-host / sung / explainer), set + blocking, where the brand logo / pack lands, what the on-screen text says vs. what's spoken, the actual hook beat. These observations belong in the storyboard preview at step 7 — don't throw them away. The format you see here is also what drives step 3.

    Skip this step in fresh mode (no source video). Also skip if the user has already provided their own brief storyboard for the source.

  2. Gather brand details.

    Completeness-check before asking. Slug the target brand (lowercase, dashes), then ls clients/ case-insensitively — clients/hume-health/ and clients/Hume-health/ are the same brand; normalize.

    Then run brand-research's verify gate against the brand root:

    python ../brand-research/scripts/verify_pack.py --brand-dir clients/<slug>
    
    • Exit 0 (PASS) — the pack is complete. Read every file in clients/<slug>/brand-research/. If clients/<slug>/concept-brief.md exists, treat its concepts as the starting menu the user picks from in step 4 (clarifying questions). Proceed to step 3.
    • Non-zero exit — the pack is missing or incomplete (no folder, stub TBDs, missing headers, manifest gaps, etc.). Do not write the script with partial info, and do not ask the user. Invoke the brand-research skill against the same --brand-dir. It is step-idempotent: every phase that's already complete is skipped, and only the missing pieces run. When verify_pack.py finally returns PASS, resume here at step 3.

    Do NOT ask the user "do you have a brand pack?" — answer that with verify_pack.py. Likewise, do not ask "should I run research?" — if the gate failed, run it.

    Then read the pack (brand-summary.md voice + "what to never say", audience.md verbatim phrasing, claim limits, visual-identity.md palette for end-card decisions). In remix mode, also read source-sample.json — note its recipe beats, extracted_script (the skeleton), kept voices, how_to gotchas, and formatProfile (the field that drives which path you take below).

  3. Identify the format profile. This is the routing key for everything below — do not default to podcast-script.

    Read source-sample.json.formatProfile. Cross-check it against the frames /watch returned in step 1. If the field is missing or doesn't match what you saw, trust the watched frames and surface the discrepancy to the user. Common slugs you'll see:

    formatProfile What it looks like Script template Production template
    podcast-skit-fabricated Two-host skit, themed set, broadcast mics, HER/HIM banter podcast-script (shipped) create-podcast-skit-ad
    imessage-thread iPhone screen, blue/grey bubbles, typing dots, optional VO none — hand-author create-imessage-video-ad
    single-host-ugc One person to camera, selfie register none — hand-author create-podcast-skit-ad's single-host sibling, or per-run
    music-video-sung Sung lyrics, on-beat cuts, lyric meter locks scene count none — hand-author per-run
    animated-explainer-villain Animated VO, villain/hero arc none — hand-author per-run

    Fresh mode (no source): ask the user which format they want. Don't pick podcast-script for them just because it's the only shipped template. Surface the table above as options.

    Remix mode: the source's formatProfile wins. If the source is imessage-thread and you scaffold a podcast-script, you are producing the wrong ad. The user is rewriting this source for their brand — they don't want a different format.

    If formatProfile is null or unrecognized, ask the user (one AskUserQuestion): "source format read as <value>; treat it as <best guess from watched frames>, or pick a different one?" Don't guess silently.

    From here, <format> refers to the chosen formatProfile. Steps 4–8 branch on it.

  4. Ask the user clarifying questions before writing.

    Batch one AskUserQuestion call. The question set is conditioned on <format> (step 3) — do not ask about a themed podcast set when the source is iMessage.

    Always ask:

    • Angle / hook — when concept-brief.md lists multiple concepts, surface the top 3 (by claim strength + audience fit) as options with one-line previews; let them pick or paste a different angle. If no concept-brief.md exists, ask for the angle in one sentence.
    • Reference material — "anything I should mirror? (a reference video URL, a competitor ad, a mood/tone keyword, an existing ad you liked)". If they paste a URL, /watch it the same way as the source.
    • Must-include and must-avoid — anything beyond the brand pack's what-to-never-say (a specific phrase, competitor jab, stat).

    Add at most one format-specific question (skip if the source already answers it):

    • podcast-skit-fabricated: themed set ("laundromat at 2am, kitchen mid-bake, ...") — keep source's, or swap?
    • imessage-thread: who's texting whom (friend ↔ friend, partner ↔ partner, customer ↔ brand?), iOS bubble palette, optional voiceover y/n, end-card style.
    • single-host-ugc: who's the talent register (founder / unboxing creator / skeptical reviewer), location + wardrobe.
    • music-video-sung: genre / BPM / vocal gender; confirm scene count is locked to source lyric meter.
    • animated-explainer-villain: villain identity (the problem the brand defeats), art register (flat vector, hand-drawn, 3D).

    Don't drip questions across turns. Skip any item the brand pack or user's opening message already answered.

  5. Scaffold the script — using the path the format dictates. Two paths only; pick by <format>, not by what's easy:

    Path A — template-backed (<format> has a shipped templates/<name>.json). Today that's only podcast-skit-fabricated → podcast-script. Run the scaffolder:

    # fresh
    python scripts/new_script.py --template <name> --scenes <N> \
      --title "<brand> — <hook>" --duration <sec> \
      [--set "<themed set>"]                     # podcast-script only
      --out ./working/script.json
    # remix
    python scripts/new_script.py --template <name> \
      --source ./remix/source-sample.json --title "<brand> remix" \
      [--scenes <N>] \
      --out ./working/script.json
    

    Scene-count rule: start from source count, flex within the template's _meta.rules.scene_count range (e.g. 20-24 for podcast-script) based on the new brand's copy density.

    Path B — hand-author (every other <format> — imessage-thread, single-host-ugc, music-video-sung, animated-explainer-villain, anything else). Do NOT run new_script.py --template podcast-script as a fallback. That would change the format and produce the wrong ad.

    Instead:

    1. Read the source's actual script shape. It lives in source-sample.json.extracted_script and (if present) the source ad's production/script-drafts.json or working/script.json.
    2. Hand-write ./working/script.json in that same shape. For imessage-thread, scenes are message bubbles ({scene, sender, side: "left"|"right", text, typing_delay, ...}), not HER/HIM lines. For music-video-sung, scenes are lyric lines ({scene, bar, lyric, on_screen_text, ...}). Mirror what the source did.
    3. Honor source-sample.json.formatProfileProperties. If sceneCount: "locked-to-source" (music-video-sung), mirror 1:1.
    4. You will skip validate_script.py (step 8) for this path; you'll add a validationNote block instead.

    Add a new templates/<name>.json (with _meta.rules, a scaffolder, and a validator) when the same non-podcast format shows up across 3+ remixes — that's the threshold for "worth templating." Until then, hand-author.

  6. Write every line / bubble / lyric. Fill the scene payload — what that means depends on <format>.

    The angle is already chosen (step 4); don't re-litigate it here. Do NOT invent angles unprompted when concept-brief.md enumerates them — that's how Hume run-03 ended up choosing between 3 invented angles while 7 pre-derived ones sat unread in clients/Hume-health/concept-brief.md.

    Cross-format rules (always apply):

    • No acronyms anywhere the script gets spoken — spell them out; TTS / sung vocals read them letter-by-letter. (For purely visual formats like imessage-thread without VO, acronyms are fine inside bubbles — they're rendered as text, not spoken.)
    • Apply the brand's guardrails: no competitor names, no transformation/outcome claims unless allowed (pull these from brand-summary.md "what to never say").
    • Brand surfaces early-mid, payoff lands on an end card / final beat — but how it surfaces is format-dependent (logo bug, mention in line, pinned reply, lyric, etc.).

    Format-specific rules (apply only the row that matches <format>):

    • podcast-skit-fabricated (podcast-script template): text ≤10 words, conversational, intonation marks from the legend (…, —, ?, CAPS). caption ≤3 words. Alternate HER/HIM strictly. 20-24 scenes. Remix: preserve format + beat arc (hook → mechanism → payoff), not literal scene count.
    • imessage-thread: one bubble per scene. Keep bubbles short (real texts are short — ~3-12 words). Alternate side (left/right) the way the source does. Use typing-dots scenes between sender swaps when the source uses them. On-screen brand surface is a pinned message, reaction, link preview, or in-bubble mention — never a podcast-style end card.
    • single-host-ugc: scenes are {scene, time, line, b_roll}. Lines are first-person ≤14 words, register matches the talent type chosen in step 4. No HER/HIM alternation.
    • music-video-sung: lyrics scan to the source's meter (count syllables, match stress pattern). Technical / acronym / Latinate brand terms (GABA, L-theanine, FDA, GLP-1) go in on-screen hyperframes, never sung lyrics — ElevenLabs Music vocal pronunciation is unreliable on multi-syllable Latinate words. Scene count locked to source.
    • animated-explainer-villain: scenes are VO + visual beats. The villain (the problem the brand defeats) is named within the first 2 scenes; the brand surfaces as the resolver mid-arc.
  7. Storyboard preview — show the user what each scene will look like BEFORE handoff. Production renders per-scene imagery differently per format (podcast-skit composites 2 base stills × expression variants; iMessage paints bubbles on an iPhone frame; sung paints on-screen text over B-roll). The user reading a script can't tell what each scene will look like — surprises at render time are expensive. The storyboard is the bridge.

    Write ./working/storyboard.json — a structured artifact the consuming app reads to render the storyboard UI. The skill emits data, not presentation; the app owns layout/copy. Shape:

    {
      "formatProfile": "<format>",       // so the app picks the right cell renderer
      "scriptRef": "./script.json",      // sibling script the scenes pair with
      "scenes": [
        {
          "scene": 1,                    // matches script.json scene number
          "still": "<one-line visual description for this scene>",
          "frame": null,                 // null until step (a)/(b) renders one; then a relative path
          "note": null                   // optional: sight gag, brand surface, end-card cue
        }
        // ...one entry per script scene, in order
      ]
    }
    

    What goes in still is format-dependent — describe what the eye sees in that scene:

    • podcast-skit-fabricated: "HER curious-questioning look, broadcast mic foreground, leaning toward HIM"
    • imessage-thread: "grey bubble left: '...', timestamp 11:42pm, read-receipt below"
    • single-host-ugc: "talent mid-pour into glass, kitchen daylight, hand-held"
    • music-video-sung: "wide shot, talent on rooftop golden-hour, on-screen lyric burnin centred"
    • animated-explainer-villain: "villain (Brain Fog) shoulders sagging, scribble lines, flat-vector palette"

    Rules:

    • One entry per script.json scene; scene numbers must align 1:1.
    • still is required and free-text. Keep it grounded in the source /watch observations and the brand's visual-identity.md.
    • frame is null until a preview is rendered; then a path relative to working/ (e.g. ./storyboard-frames/T01.png) or an absolute URL the app can fetch.
    • note is optional; omit / null when there's nothing to say.
    • Do NOT put stills into script.json's still field (if it has one — only podcast-script does) — leave that blank for the production template to reassign. The storyboard is a script-stage preview, not a render contract.

    Then ask the user with AskUserQuestion:

    • "Preview keyframes — (a) all N scenes, (b) hooks only (1, mid, end-card), (c) skip preview"
    • For (a) or (b): if image-gen is wired in this run (e.g. image-generation/create-product-images-nanobanana atom is available), render the requested stills into ./working/storyboard-frames/T01.png … TN.png and write the paths back into storyboard.json's matching frame fields. Unrendered scenes keep frame: null. If image-gen isn't wired, leave every frame null and tell the user the app will display text-only stills.

    Loop on edits: if the user says "redo scene 7's blocking", update that scene's still (and the script line if needed), regenerate that one keyframe, rewrite storyboard.json, re-surface. Do not move on until they sign off or explicitly say "skip the preview, ship it".

  8. Validate — only on Path A (template-backed scaffold from step 5). Skip for Path B (hand-authored); add a validationNote instead (see below).

    python scripts/validate_script.py --script ./working/script.json [--source ./remix/source-sample.json]
    

    Must print PASS (checks scene count 20-24, strict speaker alternation, ≤10-word lines, ≤3-word captions, no acronyms). In remix mode the source is not enforced as a scene-count constraint — only the template's own range applies.

    For any source format without a shipped template: the validator's invariants don't apply. Instead, write a top-level validationNote block into working/script.json documenting which invariants don't apply and why. The shape is generic — list the entries that fit the format you're working with:

    "validationNote": {
      "formatProfile": "<source formatProfile slug>",
      "templateInvariantsThatDontApply": [
        "<which template rule doesn't fit and why — one per line>"
      ],
      "reasonValidatorSkipped": "<one-sentence rationale>"
    }
    

    Example items you might include (depending on format): "scene count 20-24 doesn't apply (lyric meter locks to N)", "HER/HIM alternation doesn't apply (single narrator)", "≤10-word lines doesn't apply (sung lyrics use musical meter, not word count)". The next agent (remix-ad) reads this block to confirm the script is intentionally non-standard, not malformed.

  9. Hand off — to the production template that matches <format>, not a default. Use the route table from step 3:

    <format> Production template
    podcast-skit-fabricated create-podcast-skit-ad
    imessage-thread create-imessage-video-ad
    single-host-ugc per-run (no fixed skill) — confirm with user
    music-video-sung per-run (no fixed skill) — confirm with user
    animated-explainer-villain per-run (no fixed skill) — confirm with user

    Pass working/script.json (+ working/storyboard.json + any rendered storyboard-frames/) to that production template. The app reads storyboard.json directly; the production template ignores it (it re-derives stills from the script).

Decision rules

  • The template's _meta.rules are binding. Don't exceed the word/caption caps or the scene range.
  • Remix preserves the format + arc, not the scene count — for the flexible-count formats (podcast-skit-fabricated, single-host-ugc, animated-explainer-villain). The archetype (two-host skit, single narrator, villain-explainer) and the beat arc (hook → mechanism → payoff) carry from the source; the new ad's scene count flexes with the new brand's copy density.
  • For formatProfile: music-video-sung: scene count and per-scene timing are LOCKED to the source — lyric meter sets timing, not copy density. Mirror the source 1:1. The arc is verse → pre-chorus → chorus → outro (or whatever the source declares), not hook → mechanism → payoff. Flexing scene count would desync lyrics from the music.
  • For formatProfile: music-video-sung: technical / acronym / Latinate brand terms (GABA, L-theanine, FDA, GLP-1) go in on-screen hyperframes, NEVER sung lyrics. ElevenLabs Music vocal pronunciation is unreliable on multi-syllable Latinate words; lead with the brand-promise / emotional layer in lyrics, deliver technical claims via burned-in type.
  • Voice is the brand's, not the source's. Match the brand-context pack's voice + never-say list, even when remixing.
  • Brand-pack-current-SKU wins over the user's one-liner. If the user's brief mentions a product variant that the brand pack flags as EOL / replaced (e.g. user says "can on nightstand" but 01-company-overview.md says cans are discontinued), use the current SKU and flag the swap to the user. No question needed unless they explicitly override.
  • No acronyms in text — spell out or rephrase.
  • Lock duration to rendered VO later — duration_target_sec is advisory; the production template re-times after TTS.
  • One template = one format. Add a new templates/<name>.json rather than overloading podcast-script.

Output

  • ./working/script.json — validated, render-ready, in the chosen template's shape. For podcast-script: {title, duration_target_sec, aspect_ratio, intonation_legend, voices{HER,HIM}, set_description, scenes[]}.
  • ./working/storyboard.json — structured per-scene still descriptions
    • optional frame paths. The consuming app reads this directly to render the storyboard UI. Shape: { scriptRef, scenes: [{scene, still, frame, note}] }.
  • ./working/storyboard-frames/T01.png … TN.png — optional, rendered only when the user opts in at step 7 and an image-gen atom is wired.

Failure modes

  • Source has no recipe.shots → can't inherit beats; pass --scenes N and write fresh.
  • validate_script.py flags an acronym → spell it out or rephrase the line.
  • Caption >3 words → it's a caption, not the line; compress to the punch.
  • Same speaker twice → intentional double-beats are rare; default to strict alternation.