Use when the user wants the complete marketing asset suite for a product in one run — "/marketing", "build all the marketing assets", "generate everything for the launch", "all the animations and brand assets for X". Not for a single asset (use that asset's own skill) and not for launch copywriting alone (use /launch).
Scanned 9/3/2026
Install to Claude Code
npx -y skills add ucsandman/marketing-studio --skill marketing --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Marketing?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/ucsandman-marketing)More formats (shields.io, HTML) on the badges page.
---
name: marketing
description: Use when the user wants the complete marketing asset suite for a product in one run — "/marketing", "build all the marketing assets", "generate everything for the launch", "all the animations and brand assets for X". Not for a single asset (use that asset's own skill) and not for launch copywriting alone (use /launch).
---
# Marketing Mega-Pipeline
One command produces a product's full asset suite: brand onboarding, UI polish, logo reveal, product demo, launch video with audio, social clips, and OG assets. The individual asset skills own their recipes; this skill owns sequencing, gates, and run state.
**REQUIRED BACKGROUND:** marketing-studio (engine-repo workflow shape and non-negotiables). All PLAYBOOK rules apply.
## Resume check — before anything else
Glob `out/*/marketing/run.json` in the engine repo. If an incomplete run exists (any asset not `delivered`), this invocation is a RESUME: skip Phase 0, load the saved intake answers from the manifest, and continue at the first asset whose status is not `approved`/`delivered`. If several incomplete runs match, ask which one. Only a fresh run proceeds to Phase 0.
On resume, still run the Phase 1 environment checks (shared-repo guard + `launch.py --check` — the previous process died mid-flight), but skip brand onboarding and Phase 2 polish if the manifest marks them complete. Trust a `rendered`/`approved` status only after confirming its artifact actually exists on disk; missing or truncated artifact → demote that asset to `planned`.
## Phase 0 — Intake: ONE batched question round, then silence
Fresh runs only. Ask everything in a single AskUserQuestion, then run without asking again (exceptions: per-asset stills gates in gated mode, final delivery):
1. **Product/brand + destination** — which brand, and where finished assets land in the product repo (default: its existing media/marketing dir).
2. **UI polish before filming?** Default YES: the demo films the real running app, so rough edges get rendered at 60fps forever. YES = impeccable → polish → frontend-verify on the product repo before any capture. NO = film as-is. This edits product code, so it is always the user's call — never silently skip it AND never silently do it.
3. **Audio** — music + voiceover is the default and the only thing a film ships with; ask only WHO narrates (brand voice id vs default) and whether music leads or narration leads. "None" is not an option and music-only is a recorded exception (`--music-only`), never a default: a film with no voice explaining the product is not done (CLAUDE.md, learned 2026-09-01).
4. **Social clips** — platforms and count (default: X + LinkedIn, one each).
5. **Checkpoint mode** — full-auto (self-check stills, user reviews the final gallery) or gated (user approves stills before each full render).
## Phase 1 — Foundation
1. Shared-repo guard + `python launch.py --check` (marketing-studio steps 0–1), then `node scripts/install-skills.mjs --check` — it warns when a bundled `skills/` copy and its installed `~/.claude/skills` copy have drifted, so the run does not follow a stale checklist. Warn only; fix the drift or note it, then continue.
2. Brand: if `brands/<id>.json` is missing, onboard per PLAYBOOK. Brand-token judgment stays in the main loop — do not delegate it.
3. Create the run manifest `out/<brand>/marketing/run.json`. It stores BOTH the Phase 0 intake answers (brand, destination, polish flag, audio choice, social config, checkpoint mode) AND one entry per planned asset. Statuses mean exactly this:
- `planned` — not rendered yet. Gated mode: user approves pre-render stills before the render starts (that is the ONLY user gate per asset).
- `rendered` — artifact on disk, post-render frame check pending (extract 2–3 frames from the artifact and inspect them — self-check in both modes).
- `approved` — frame check passed; approval is recorded in the manifest the moment it happens, never inferred from chat history. A resumed session redoes the frame check for any `rendered` asset, showing the frames to the user first in gated mode.
- `delivered` — copied to the product repo.
Update the manifest after every status change. Never restart a run from scratch because the session died.
4. Infographic bridge: `node scripts/build-infographic-style.mjs <brand>` writes `out/<brand>/marketing/infographic-style.md`, the /epic-infographics design language built from the brand tokens; copy it to `~/.claude/skills/epic-infographics/references/design-languages/<brand>.md` and name it at that skill's step 6, so any infographic in this run renders on brand hexes instead of an invented palette.
**Content approval gate.** Once `out/<brand>/marketing/brief.json` is synthesized and passes `node scripts/lint-copy.mjs`, run `node scripts/build-storyboard.mjs <brand>` and show the user `storyboard.html` for content approval BEFORE any rendering; in full-auto mode the main-loop judge reviews it instead.
**Brief synthesis rules.** The zod schema (`studio/src/lib/brief.ts`) is the contract; fill the grounding sections, not just the copy: `audience` + `customerLanguage` + `objections` + `switchingForces` from the brief-inputs grounding, and a `proofPoints` entry (claim + source) for EVERY number the copy cites — an unsourced stat is fabrication, omit it instead (lint-copy WARNs on stat-shaped claims in a brief with no proofPoints). Draw `hook.headline` and each of the (up to two) `altHeadlines` from DIFFERENT hook categories per `references/hook-formulas.md`, record the categories in `hook.strategies`, and include at least one emotion-forward category (story/contrarian) against a value hook — evidence in `references/campaign-evidence.md` (read it before synthesis; it also lists debunked claims that must never appear in copy). Build `cta` with the [Action Verb] + [What They Get] formula. The storyboard renders the grounding sections so the approver can check copy against facts.
**Copy council (full-auto mode).** Before the main-loop judge accepts the storyboard, run a 3-judge council on the brief copy: three parallel Sonnet subagents (`model: claude-sonnet-5`, explicit), one per lens — positioning sharpness (would April Dunford sign it?), emotional resonance vs feature-dump, and evidence honesty (every claim traced to a proofPoint) — with the third judge additionally instructed to argue AGAINST shipping the copy (mandatory dissenter; kills the echo chamber). Issues raised by 2+ judges go back to synthesis; single-judge nits are noted, not blocking. In gated mode the human storyboard review replaces the council — offer it only if the user asks for a copy critique. (Pattern adapted from Corey Haines' marketing-council skill, MIT.)
## Phase 2 — UI polish (only if opted in)
In the PRODUCT repo: impeccable → polish → frontend-verify. Must fully complete before Phase 3 — re-shooting every asset because the UI changed after capture doubles the run. Commit product-repo polish separately from asset delivery.
## Phase 3 — Asset pipeline: STRICTLY SEQUENTIAL
The engine repo is shared mutable state (props builders, registries, render queue). Two asset skills at once collide and renders saturate the CPU. One asset at a time, in this order. (One blessed exception: /logo-reveal touches only the engine repo and captures nothing, so it may run concurrently with Phase 2 polish, which touches only the product repo — zero shared state, real wall-clock savings.)
| # | Skill | Why this position |
|---|-------|-------------------|
| 1 | /logo-reveal | Cheapest comp — surfaces brand-token bugs before the expensive assets |
| 2 | /product-demo | Films the (now polished) UI; footage feeds everything downstream |
| 3 | /launch-video | Picture-lock the hero video (composes demo + logo + copy). Before locking, when the brief carries altHeadlines: `node scripts/render-hook-variants.mjs <brand>` renders the competing hook takes and registers them as Mission Control variants — pick the winner there, then lock. Optionally `node scripts/render-variants.mjs <brand> logo-reveal` for hero takes. |
| 4 | /audio-track | MANDATORY, never skipped. Score the locked film: a bespoke film (studio/src/films/<brand>/) via `node scripts/build-<brand>-film-audio.mjs` + `node scripts/score-film.mjs <brand> <film.mp4>`; a LaunchVideo template film via build-<brand>-audio.mjs + merge + master to `launch-final.mp4`. Then set the launch-video asset's `artifact` to the SCORED file (keep the silent lock as `silentLock`) and copy it to `out/<brand>/launch.mp4` so postkit and the gallery carry the narrated cut. A silent launch card is a defect, not a variant (postflop shipped two silent cuts on 2026-09-01) |
| 5 | /social-clip × N | Reuse demo/logo footage per platform. SocialClip renders silent, so EVERY delivered clip is scored before it counts: `node scripts/score-social-clip.mjs <brand> <clipId> --vo <act>` per clip (music bed plus the ONE VO line that matches the plate, e.g. hook for the X clip), leaving `<clipId>-final.mp4` in out/<brand>/ so build-postkit picks it over the generic matrix row. The captioned 9:16/1:1 matrix rows are for MUTED autoplay, which is a viewer default, not a licence to ship them with no track (postflop's tiktok/shorts/instagram rows had none, 2026-09-01) |
| 6 | /og-assets | Statics + README GIF pulled from final footage |
| 7 | Cards (`node scripts/build-cards.mjs <brand>`) | One stat card per brief proofPoint plus a quote card from hook.headline, rendered as Card stills into out/<brand>/marketing/cards/; skips clean with exit 0 when there is no brief. Not an asset skill, a builder |
Per asset: stills gate before full render — `node scripts/contact-sheet.mjs <brand> <Comp>` is the standard gate artifact (each sub-skill enforces it), render logs `| tail -2`, then update the manifest. Assets #1 (/logo-reveal's Blender staging) and #2 (/product-demo's Playwright capture) check the content-hash footage cache first and skip the expensive stage when their inputs (product git state, capture/staging script, and resolved config) are byte-identical to the last run; pass `--force` to re-capture.
**Render budget: one full-resolution render per asset.** The renderer is not where a
two-hour run goes; correction rounds are. Measured 2026-09-01 on the 24-core box: a
full-res LaunchVideo render is ~9 minutes and `--x264-preset` barely moves it (Chrome
frame rendering is the bottleneck, not encoding), while `--scale=0.5` renders 3.3x
faster. So: judge every round from the contact sheet, and when a round needs motion,
render the preview at `--scale=0.5` as `launch-vN-preview.mp4`. The full-res render
happens ONCE, after the stills pass. The postflop launch step rendered three full
passes (27 minutes of the step's 48); this rule makes that one. A product bug found
mid-capture is a note in `run.json` judgeNotes for the user, not a fix inside the
run (postflop's demo step spent 64 minutes that way).
**Budget the VO before you dispatch #4, not after.** Picture-lock (#3) and audio (#4) are separate steps, and the trap between them is that measured VO word timings DERIVE act lengths, so scoring a locked film can push it past the 30-90s band `launchTiming.test.ts` enforces. Do the arithmetic yourself at dispatch time and hand it to the executor: `frames_available = 2700 - current_total`, minus the demo act which is FIXED (the PLAYBOOK forbids shortening a recorded demonstration to fit narration). Estimate each act's need at ~150wpm plus `VO_LEAD` + `VO_PAD`. If the narration overruns, say so in the brief and name the act to cut hardest — copy is trimmed ONLY in `build-<brand>-audio.mjs`, never by editing act constants. Measured on the practicalsystems run (2026-08-17): an 80.8s lock left 276 frames of headroom against ~564 needed, so ~25 words had to go. **Name any claim that must survive the trim**, because the shortest phrasing is often the false one — "cold outreach never sends without a human" compresses to "nothing sends without a human", which was untrue there.
**Phase 3.5 — Responsive export matrix.** Once the launch video is picture-locked and the social clips are rendered, run `node scripts/render-matrix.mjs <brand>` to fan the launch video and social clips into all four aspects (16:9, 1:1, 4:5, 9:16) by responsive layout, not crops. Variants land in `out/<brand>/matrix/` and register in the manifest's `exports[]`. Add `--stills-only` first to prove the layout with one still per aspect before committing CPU to full renders. The muted-autoplay rows (9:16 and 1:1) additionally emit an `<id>-captioned` variant with the VO burned into on-screen captions, and `node scripts/build-captions.mjs <brand>` writes matching `launch.srt`/`launch.vtt` sidecars — both require the brand's audio props (skipped silently without them).
## Execution mode — pick by session model
- **Opus or Sonnet session** (default): run each asset skill inline in the main loop. Visual-tuning loops go to Sonnet subagents (`model: claude-sonnet-5`, always explicit) so iteration stills die with their context. Mechanical checks: Haiku or inline. Escalate to Fable at most once, standalone, only if a new template must be designed mid-run.
- **Fable session** (e.g. `ultracode "/marketing"` with Fable as the main model): executor-judge mode, below. Fable never executes asset recipes inline — its context grows for hours at judging-grade rates while doing checklist work.
## Executor-judge mode (Fable session)
Fable is the judge and orchestrator; it holds only the intake answers, the manifest, dispatch prompts, and verdicts. Everything heavy happens in disposable executor contexts.
1. **Brand onboarding stays in the main loop** — one-time judgment work, exactly what Fable is for. If `brands/<id>.json` is missing, derive tokens from the product repo (DESIGN.md, tailwind, CSS vars per PLAYBOOK) and fold any underivable values into the Phase 0 intake batch — never a mid-run question to an absent user.
2. **Phase 2, if opted in, goes to one `claude-opus-5` executor** that runs impeccable → polish → frontend-verify in the product repo and returns before/after screenshots plus the verify result as raw data. Fable judges those before any capture starts — the polish pass is heavy UI work and does not belong in the judge's context.
3. **One executor subagent per asset, strictly sequential** (the engine repo and CPU rules from Phase 3 apply unchanged). Models, always explicit: `claude-opus-5` for /product-demo and /launch-video (capture choreography and copywriting need judgment); `claude-sonnet-5` for /logo-reveal, /audio-track, /social-clip, /og-assets (recipe execution).
4. **Dispatch prompt contract**: tell the executor to read the asset's SKILL.md, the marketing-studio skill, and the PLAYBOOK gotchas before acting; give it the brand id, manifest path, and intake answers; have it execute the recipe through full render and return raw data — output path, 3 extracted still paths, and any deviations. No prose reports.
5. **Judge protocol per asset**: Fable Reads the returned stills and judges against the brand's `voice` rules, composition quality, and copy (no em dashes, no hype). Approve → manifest `approved`, next asset. Problems → send a numbered correction list via SendMessage to the SAME executor (its context is intact; never respawn a fresh executor to fix its own work). If the resume fails because the executor's transcript is gone (it happens), spawn a MINIMAL corrections executor whose prompt carries the complete defect list plus file-level context — never re-run the whole asset recipe. Maximum 3 correction rounds per asset; after that, record the asset as `rendered` with judge notes and move on — the user adjudicates it in the final gallery.
6. **This mode is full-auto by definition** — Fable replaces the per-asset user gates as a stronger judge. The user still sees the final gallery (Phase 5 is unchanged), and delivery + commits stay in the main loop.
7. **Fable never spawns Fable**, and the 3-Fable session cap applies. Executors are the fleet; the judge is singular.
## Dynamic workflows — the only two uses
Whatever the execution mode, the Workflow tool touches only read-only fan-outs. Asset execution NEVER goes in a workflow: workflow agents run in the background with no channel back to the judge or the user, renders are CPU-bound and serial so fan-out buys zero wall-clock, and each fresh `agent()` re-reads the PLAYBOOK per call. Corrections need SendMessage to a live executor — a workflow can't do that. Mechanical single commands (`smoke.mjs`, file copies, manifest I/O) stay inline in Bash; a subagent spawned to run one command costs more than the command.
The two legitimate workflows, all agents Sonnet with `model` explicit:
1. Phase 4 brand-compliance sweep: `parallel()` one reviewer per asset still, each finding adversarially verified before it triggers a re-render.
2. Optional pre-delivery judge panel: 3 judges score the full gallery; only issues flagged by 2+ judges go back to Phase 3. In executor-judge mode this panel is a pre-filter — the panel flags, the Fable judge adjudicates.
Fable never goes inside a workflow (the model-guard hook blocks it in `parallel()`/`pipeline()` constructs anyway).
## Phase 4 — Final QA
1. `node scripts/smoke.mjs` — must pass.
1a. `node scripts/check-audio.mjs <brand>` — HARD gate, exit 1 blocks delivery: every delivery-surface mp4 has a mastered track with narration (see CLAUDE.md). Run it again after postkit, because postkit is where silent matrix rows sneak back in.
1b. Mechanical judges before any agent sweep (cheap, run all six): `node scripts/judge-av-sync.mjs <brand>` (VO overruns/caption dwell), `node scripts/judge-demo-pacing.mjs <brand>` (dead air), `node scripts/judge-palette.mjs <brand> <still>` (forbidden colors; low-confidence findings are product-UI suspects, treat per step 2's false-positive rule), `node scripts/judge-motion.mjs <brand>` (motion-craft conventions in studio src + brand motion/grade token bands), `node scripts/judge-drift.mjs <brand>` (cross-asset drift: scores the whole `out/<brand>/` as a SET, which is the only judge that can catch assets that are each individually on-brand but collectively fragment into several — run it LAST, after everything else has rendered, or the set is incomplete; pass `--ref out/<brand>/approved/<latest>/` when that directory exists, so drift is measured as distance from approved rather than from the set average, and in full-auto mode run `node scripts/mission-control.mjs <brand> --snapshot-approved` first because writing run.json directly never fills approved/), and `node scripts/check-budgets.mjs <brand>` (hard size gate — an OVER blocks delivery). judge-av-sync also reports the frame at which the first claim copy lands (`hook copy on screen at frame N`); a 5.0s static open is the shared LOGO_LEN default and `actLengths.logo` is the override. Their JSON reports feed the judge; only findings the reports can't decide go to the agent sweep. judge-drift also writes `drift-sheet.html`, a worst-first review grid — open that rather than eyeballing the whole gallery, because attention is reliable over about six tiles, not twenty.
2. Brand-compliance sweep: one Sonnet subagent reviews a still from every asset against the brand's `voice` rules (e.g. noban: profit gold `#d6c23c`, never green). Re-render only violators — but VERIFY findings against the product repo's source first. Product screenshots inside assets show the PRODUCT's own fonts/tokens, not the engine brand's stand-ins; a reviewer expecting the engine's mono will misread the product's mono as a violation (paperroute run 2026-07-10: 4 of 5 sweep findings were this exact false positive; the fifth was a real product bug, fixed in the product repo, no asset re-render needed).
## Phase 5 — Delivery
1. Copy every asset to the destination dir; write a README there listing each file and its intended use.
2. Launch the operator console: `node scripts/mission-control.mjs <brand>` (add `--port N` if 4600 is taken). Tell the user the URL it prints (default `http://localhost:4600/`). This is a live click-to-approve gallery reading `out/<brand>/marketing/run.json` — one card per asset with the embedded artifact, an Approve button, a Redo box, and variant pickers. An advisory bar under the header shows the mechanical judges' verdicts (expandable findings), footage-staleness warnings ("capture footage: N commits behind" — computed from the cache's recorded product git state), and engagement results when they exist. Each asset card carries a Mark posted button: it prompts for the live post URL and writes the row into `out/<brand>/marketing/posts.json` (one row per platform, last write wins), which is the input `scripts/fetch-results.mjs` reads to close the hook A/B loop. **The run is not done until the user has seen the gallery.**
3. While it runs, poll `out/<brand>/marketing/run.json` and `out/<brand>/marketing/review.json` for the operator's actions: an asset flipped to `approved` in the manifest is that asset's approval gate cleared (approve is the manifest gate, never inferred from chat); a `redo` entry in `review.json` — with its note, also stored as `redoNote` on the asset (now back to `planned`) — feeds the Phase 3 correction loop. Re-render the redone asset per its skill, update the manifest, and the console picks up the new state on its next 2s poll.
**Phase 5.5 — Thumbnails and paste-ready post kits.** Before delivery, run `node scripts/extract-thumbs.mjs <brand>` to grab one poster still per aspect from `out/<brand>/matrix/` into `out/<brand>/thumbs/`, then `node scripts/build-postkit.mjs <brand>` to assemble `out/<brand>/postkit/{x,linkedin,tiktok,shorts,youtube,instagram}/` — each folder gets the right-aspect video, thumbnail, a lint-gated `caption.txt`, `alt.txt`, caption sidecars (YouTube/LinkedIn), and a `POST.md` checklist. This is what makes delivery paste-ready instead of raw files the user has to reassemble by hand.
**Phase 5.75 — Results loop (after publishing, usually a later session).** Multi-platform posting is `/launch` (the `launch/` CLI: `launch post <product-dir> --all`, dry-run by default, `--live` to publish, kit attached via `--kit out/<brand>/postkit`). The two studio-side scripts remain for Mission Control's buttons. Bluesky: `node scripts/publish-bluesky.mjs <brand>` (or Mission Control's Publish to Bluesky button) uploads the postkit's social-16x9 clip with caption.txt, records the URL in posts.json, and fetch-results reads its likes/reposts/replies with no token. YouTube is the second: `node scripts/publish-youtube.mjs <brand>` uploads the postkit's launch-16x9 as a PRIVATE video (run `--auth` once per machine; flip visibility in YouTube Studio after watching it), records the URL, and fetch-results reads its view/like/comment counts. Every other platform is still a paste. When posts go live (via `/launch`, which also writes `<product>/.launch/ledger.json`), record them in `out/<brand>/marketing/posts.json` — an array of `{platform, url, variant?, metrics?}` where `variant` is the run.json variant id the post carried (e.g. `hook-2`) and `metrics` is manual entry for platforms without API access (LinkedIn). Then `node scripts/fetch-results.mjs <brand>` pulls X engagement (X_BEARER_TOKEN in .env; exit 2 + unavailable markers without it) into `results.json`, and Mission Control shows engagement per hook variant. The winning hook CATEGORY feeds the next brief's `hook.strategies` pick — that closes the A/B loop with reality instead of taste. build-postkit seeds posts.json with one `published:false` row per platform, so the file always exists; fetch-results skips unpublished rows and prints `N of M rows published` beside its verdict.
**Phase 5.9 — Prove the link preview actually changed.** After delivery, and again the day the site redeploys, run `node scripts/verify-og-wired.mjs <brand>` (optionally pass a URL to override the brand's configured `url`). It fetches the LIVE page, parses its `og:image` and `twitter:image` meta tags, fetches the referenced image, and compares its dimensions against the delivered `out/<brand>/og.png` or `og-image.png`. A rendered still proves the BUILD is right; this proves the WIRE is right: a stale CDN, an unswapped meta tag, or a site that has not redeployed all pass every other judge and still ship a broken link preview. Advisory (exit 0) like the other judges; add `--strict` to gate. Skips clean when the brand has no `url` or the page is not reachable yet.
## Phase 6 — Close out
1. Commit the engine repo (tests + lint + smoke first; `out/`, `assets/`, `studio/public/*/` stay uncommitted). An uncommitted engine tree strands the run.
2. Commit product-repo delivery.
3. Final summary: per-asset table (file, duration, status) + deviations log.
## Red flags — stop and re-read this skill
- Running two asset skills concurrently "to save time" → engine-repo collision.
- A second full-res render of the same asset before its stills passed → the render budget above; preview at `--scale=0.5`.
- Fixing the product mid-run because the capture exposed a bug → note it, keep filming.
- Starting with /launch-video "because it matters most" → brand bugs found at the expensive end.
- Capturing before Phase 2 finished → everything gets re-shot.
- "Session died, start over" → read `run.json` and resume.
- Asking the user questions one at a time across the run → all questions live in Phase 0.
- Deciding yourself whether to edit the product's UI → that is intake question 2, the user's call.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!