@officexapp/vidfarm-devcli 0.21.27 → 0.21.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -77,6 +77,32 @@ Audio is natively **multi-track**. The timeline mixes UNLIMITED simultaneous `<a
77
77
  - **The headline move — split a combined original when recreating.** When the user recreates a template whose ORIGINAL had music + narration baked into ONE audio track, do NOT reproduce a single combined bed. Rebuild it as TWO independent tracks: a fresh narration track (`/api/v1/primitives/audio/speech`, or same-voice reword via `/api/v1/primitives/audio/regenerate-speech`) at ~1.0, and a separate real music track at ~0.1–0.2 — then mute or `remove_layer` the original combined source-audio layer so the old voice doesn't play under the new one. This hands the user independent voice/music volume and is the elegant workaround for AI TTS being unable to emit narration+music in one file.
78
78
  - **Honesty (ties to the create-media rules):** you cannot un-mix / stem-separate the original's baked audio — the two tracks are BUILT from a fresh narration track PLUS a real music file (owned / user-provided / `browse_files` across `/files` and `/raws`), never a faked "music" layer and never the voice track duplicated. There is no music-generation primitive.
79
79
 
80
+ ## The TEXT axis — social-native only, zero HTML slop (hard constraint)
81
+
82
+ You author into HTML, which makes it dangerously easy to build a **web page instead of a video**. This is the #1 way an AI-edited composition betrays itself. Apply on every text/graphic you place — and strip it when a decomposed fork or a pasted brand asset brings one in.
83
+
84
+ **The test:** *would this element exist if the video were shot on a phone and captioned in CapCut?* If its whole job is to look **clickable**, cut it. Nothing in a video is clickable.
85
+
86
+ **BANNED (never `add_layer` / `replace_composition_html` these):**
87
+ - **CTA buttons** — a filled/gradient capsule with action copy ("Sign Up for a Free Trial →", "Get Started", "Book a Call"), glow or drop shadow. A social CTA is *spoken* or a plain caption line.
88
+ - **Badge / chip / pill rows** — "✓ ID-Verified · ✓ No Credit Card Needed · ✓ 30-Min Trial". Say them as three *timed caption lines* on the footage instead.
89
+ - **Cards / panels** — a bordered, shadowed, or `backdrop-filter`-frosted rounded box holding a headline + subheading/URL. Text goes ON the footage, not in a floating panel.
90
+ - **Gradient text fills, neon border glows, elevation shadows, glassmorphism**, navbars, hero sections, feature grids, `<ul>` bullet lists, tables, "as seen in" strips.
91
+ - **Web-default type** — Inter/Roboto/system-ui/Arial/Helvetica at weight 400–600 and 16–24px.
92
+
93
+ **Greppable smell test:** typing `btn`, `badge`, `chip`, `tag`, `card`, `panel`, `container`, `row`/`col-`, `hero`, `cta`, `rounded-full`, `shadow-lg`, `backdrop-blur`, `bg-gradient-to-r`, `border border-…` → **stop and rewrite as timed text on footage.** A re-theme onto a SaaS/product subject is exactly where this creeps in — do NOT import the brand's website design language into the video.
94
+
95
+ **ALLOWED / encouraged:** arrows (drawn or animated), circle / scribble / underline highlight strokes, **italics**, ALL-CAPS, a single word in a pop color, mixed weights, inline emoji (sparingly), transparent cut-out stickers via `create-overlay`, and mock social UI (iMessage bubbles, a TikTok comment card, a countdown bar) — all native to the platform.
96
+
97
+ **Font + background regime (every caption/title, via `set_captions` / `set_layer_style` / `add_layer`):**
98
+ - **Font:** Montserrat (default) or TikTok Sans / Abel / Source Code Pro / Yesteryear — a family the composition actually imports, or it silently falls back to the slop sans. Weight **700–900**. `font_size` in px of the render canvas: **~36–64px** on a 1080-wide frame; never <28, never 0 (invisible). ~2 lines, ~5 words per line; `line_height` 0.95–1.15.
99
+ - **Position:** inside the **8%–85%** vertical safe zone (phone UI clips the edges) and clear of the right ~12% action rail — a centered box at `x:10 width:80` is safe. Lower-third ≈ `y:70`; a "POV:" top line ≈ `y:8`, never `y:0`.
100
+ - **Background — exactly one of four:** `background_style:"outline"` (stroke, the default look) · `"plain"` (bare + soft shadow) · an **active-word highlight pill** via `set_captions caption_style:"spotlight"|"karaoke"` (the *only* legitimate pill — it tracks the spoken word) · `"highlight-solid"`/`"highlight-translucent"` as a band that **hugs** the text (radius ≤~8px, no border, no shadow, no gradient, no blur, one text run — never a heading+subheading+URL stacked inside it). Anything else is a web card.
101
+
102
+ **There is no QA tool for you.** The devcli ships `vidfarm qa <dir>` — a free local blocklist pass over exactly the rules above — but it is **devcli-only with no REST twin**, so in the web editor you enforce this by reading your own output. When you hand a heavy job off to a local coding agent, tell them to run `vidfarm qa ./work` before rendering.
103
+
104
+ Deeper rationale and the devcli-side twins live in `vidfarm` → `references/editor-workflows.md` ("Social-native visual standard" / "TikTok-native caption standard").
105
+
80
106
  ## The `editor_action` verb catalog
81
107
 
82
108
  Every mutation is one `editor_action` call with an `explanation`. Reference keys/tracks/durations from the latest `editor_context`; never invent `layer_key`s. Grouped by axis of work:
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: vidfarm
3
- description: Use Vidfarm as a director. Browse/add inspiration videos, browse the free public raws catalog BY CATEGORY (curated shelves like scroll-stoppers/greenscreen/reaction — the cheapest way to source footage for one video, and a ready-made clip pool for bulk scripting N variants), fork a template into a composition, edit it in the Trackpad Editor (timeline-based like Premiere/DaVinci), auto-decompose source video into scenes, render to MP4, approve into a shareable post, and schedule it. Includes login, provider keys, discovery, versioning, uploads/downloads, and billing. Every step is available as raw REST; `vidfarm-devcli` wraps those routes and composes the file-backed scripting flows.
3
+ description: Use Vidfarm as a director. Run a strategy **consultation** (the `brainstorm/*` chain — cold-start interview, awareness stages, persuasive angles, hooks, product placement). Browse/add inspiration videos, browse the free public raws catalog BY CATEGORY (curated shelves like scroll-stoppers/greenscreen/reaction — the cheapest way to source footage for one video, and a ready-made clip pool for bulk scripting N variants), fork a template into a composition, edit it in the Trackpad Editor (timeline-based like Premiere/DaVinci), auto-decompose source video into scenes, render to MP4, approve into a shareable post, and schedule it. Includes login, provider keys, discovery, versioning, uploads/downloads, and billing. Every step is available as raw REST; `vidfarm-devcli` wraps those routes and composes the file-backed scripting flows.
4
4
  ---
5
5
 
6
6
  # Vidfarm Director
@@ -223,7 +223,9 @@ You may be running as the **in-web AI chat** (the /editor copilot, the chat dock
223
223
 
224
224
  - **Determine the surface before claiming capabilities.** The web chat has only its declared tools and REST routes. It cannot execute arbitrary JavaScript/Python, open a shell, create a local repository script, or use the user's filesystem. Never tell a web-chat user that you ran code or wrote a script unless a dedicated declared tool actually did so. A desktop coding agent has a real shell and filesystem and MAY write/run scripts, perform arbitrary local computations over paginated API results, create reports/CSVs/JSON, edit composition files, and orchestrate long devcli workflows within the user's authorization.
225
225
  - **The web AI chat can do all three paintbrushes** — clip raws, author HTML/hyperframe motion, and generate AI media — and it drives edits directly on the live timeline. Keep small-to-medium jobs here: text/caption swaps, a scene or two replaced, single generations, captions, approve/schedule. Just do them.
226
- - **Every production adheres to the TikTok-native caption standard.** On-screen text lives inside the readable safe zone (**~8%–85%** of a 9:16 frame never pinned to the top/bottom edges the phone UI clips) and uses the composition's bold font regime (**Montserrat** default / TikTok Sans, weight ≥700). Decomposed forks often inherit the source's edge-pinned caption in an off-regime font fix it, don't inherit it. Local devcli renders auto-normalize this, but author it correctly so the editor preview matches. Full rules in `references/editor-workflows.md` ("TikTok-native caption standard").
226
+ - **No HTML slop a video is not a web page.** Compositions are authored in HTML, so the #1 tell of an AI-edited video is landing-page furniture: gradient CTA "buttons" ("Sign Up for a Free Trial →"), rows of benefit chips/badges ("✓ No Credit Card Needed"), frosted/bordered cards holding a gradient headline + a URL, feature grids, bullet lists, web-default type (Inter/Roboto/Arial at weight 400-600). None of that exists in a real TikTok/Reel — **nothing in a video is clickable**. If you are typing `btn` / `badge` / `chip` / `card` / `rounded-full` / `shadow-lg` / `backdrop-blur` / `bg-gradient-to-r`, stop and rewrite it as timed text on the footage. Arrows, scribble/underline marks, italics, ALL-CAPS, single-word color pops, emoji, transparent cut-out stickers, and mock social UI (iMessage bubbles, comment cards) are all *fine* — they're native to the platform.
227
+ - **On devcli, QA every video you produce: `vidfarm qa ./work`.** Free, instant, local-only — it blocklists exactly the slop above plus font-regime/safe-zone drift, and prints a concrete fix per finding. **Feedback, not a gate**: it exits 0 even on findings, never runs automatically, and is a blocklist (unusual/stylized compositions pass untouched), so there's no reason not to run it before every publish. `--json` for scripted batches, `--strict` only if you want a CI failure. **Web-chat copilot: this command does not exist for you** (devcli-only, no REST twin) — apply the standard by hand, and when handing a heavy job to a local coding agent, tell them to run `vidfarm qa`.
228
+ - **Every production adheres to the TikTok-native caption standard.** On-screen text lives inside the readable safe zone (**~8%–85%** of a 9:16 frame — never pinned to the top/bottom edges the phone UI clips), uses the composition's bold font regime (**Montserrat** default / TikTok Sans, weight **700–900**, ~36–64px on a 1080-wide frame), and uses **exactly one of four valid backgrounds**: outline/stroke (`background_style:"outline"`, the default), plain + shadow (`"plain"`), an active-word highlight pill (`set_captions` `spotlight`/`karaoke` — the only legitimate pill), or a tight-hugging solid band (`"highlight-solid"`, radius ≤8px, no border/shadow/gradient/blur). Decomposed forks often inherit the source's edge-pinned caption in an off-regime font — fix it, don't inherit it. Local devcli renders auto-normalize position + font family only (never the slop), so author it correctly. Full rules in `references/editor-workflows.md` ("Social-native visual standard" + "TikTok-native caption standard").
227
229
  - **Where the web chat struggles: complex, long, multi-step transformations.** A full multi-scene re-theme, an iterative render-critique-iterate loop, heavy scripted or batch work, or anything needing a real filesystem and many sequential tool calls will hit context limits, turn/timeout ceilings, and the web editor's constraints (CSS/declarative motion only — JS animation adapters are stripped on save). Don't grind a big transformation one layer at a time in a chat turn and stall.
228
230
  - **Practical workaround — hand the heavy job to local devcli.** When a task is genuinely large or long-running, **proactively recommend the director run it locally with an AI coding agent** (Claude Code / OpenAI Codex / any capable agent): `vidfarm pull <forkId>` writes the composition + the `.harness/` grounding bundle to disk, the agent edits with the full devcli verb set and JS animation adapters, renders free with `vidfarm serve`, and `vidfarm publish` pushes it back. This is the **best-quality (B) harness's** natural home (adversarial grading with a coding agent). Frame it as "this is a big rebuild — you'll get a better, faster result running it locally with a coding agent; here's how," not as a dead end.
229
231
  - **Offer a handoff, do not impersonate the desktop agent.** When web chat reaches that boundary, offer to save a Markdown handoff in My Files containing the objective, selected template/fork IDs, asset paths, grounding, constraints, completed work, and suggested devcli commands. Create it only after the user agrees. The desktop agent should read that document, pull the referenced fork, and then use its actual code/shell capabilities.
@@ -237,7 +239,7 @@ Read only the relevant reference file for the current task.
237
239
  - Timeline editing, decompose, captions, motion, AI placement: `references/editor-workflows.md`
238
240
  - Raws hunts, My Files, recurring characters, asset retrieval: `references/assets-and-sourcing.md`
239
241
  - REST automation, `vidfarm` command surface, local serve loop, skill packs: `references/automation-and-local-dev.md`
240
- - Getting-started interviews, strategy docs, onboarding flow: `references/onboarding.md`
242
+ - Getting-started interviews, **consultations**, strategy docs, onboarding flow: `references/onboarding.md`
241
243
  - Primitive routes such as TTS, STT, music, overlays, background removal, product placement: `references/primitives.md`
242
244
  - Complete REST API map and raw-HTTP conventions: `references/rest-api.md`. Load it only when the user asks for REST, an endpoint/schema, direct HTTP integration, or exhaustive API coverage. For the entire specification, follow its domain links and load every listed reference; do not preload them into ordinary director conversations.
243
245
 
@@ -259,7 +261,7 @@ HyperFrames authoring and rendering in this package are Vidfarm-native: local wo
259
261
 
260
262
  Choose the narrowest path that satisfies the request.
261
263
 
262
- 1. If the user needs help figuring out what to make, read `references/onboarding.md` first.
264
+ 1. If the user needs help figuring out what to make, **or asks for a "consultation"** (the `brainstorm/*` chain: cold-start interview → awareness stages → angles → hooks), read `references/onboarding.md` first.
263
265
  2. If the user already knows the goal and needs a suitable template, read `references/core-workflows.md` and use the template discovery flow.
264
266
  3. If the task is “change this video,” read `references/editor-workflows.md`.
265
267
  4. If the task is “find footage” or “use our existing assets,” read `references/assets-and-sourcing.md`.
@@ -7,8 +7,9 @@ Use this when a coding agent is doing the work locally or the user wants a repro
7
7
  3. Read `./work/.harness/agent-guide.md` and `./work/.harness/context.json` before editing.
8
8
  4. Make deterministic edits to `composition.html` and optionally `composition.json`.
9
9
  5. Validate with `vidfarm lint` or `vidfarm stills` when useful.
10
- 6. Render with `vidfarm render <forkId> --dir ./work --wait`.
11
- 7. Approve the finished MP4 with `vidfarm approve --video <url|./final.mp4> --caption "..."`. This prints the shareable `share_url`.
10
+ 6. **QA before you render: `vidfarm qa ./work`.** Free, instant, devcli-only. It blocklists HTML slop (CTA buttons, benefit chip rows, frosted cards, gradient text, web-page classes/fonts) and checks the caption font regime + safe zone. Feedback only — exit 0 even on findings, never automatic — but it catches the #1 tell of an agent-made video, so run it on every production. Fix what's real, ignore what's a deliberate style call, then render.
11
+ 7. Render with `vidfarm render <forkId> --dir ./work --wait`.
12
+ 8. Approve the finished MP4 with `vidfarm approve --video <url|./final.mp4> --caption "..."`. This prints the shareable `share_url`.
12
13
 
13
14
  **Approving a locally rendered file → cloud preview link.** Approve takes media by **URL**, not bytes, and an approved post is **permanent** — so the local MP4 must land in **durable My Files**, not the 30-day temp store (a temp video would 404 the share page after 30 days). The devcli presigns, PUTs the bytes **direct to S3**, finalizes, then approves with that durable URL — so `vidfarm approve --video ./final.mp4` handles files up to **200 MB**, bypasses the ~6 MB Lambda request-body limit, and the share link never breaks. By raw REST: `POST /api/v1/user/me/attachments/presign` → PUT to the returned S3 URL → `POST /api/v1/user/me/attachments` (finalize) → pass the returned `viewUrl` in the approve `media` array. Do not multipart-POST a big file to `.../attachments/upload` against the cloud host (Lambda-bound, ~6 MB cap). Add `vidfarm approve --temp` only when you want a disposable 30-day preview.
14
15
 
@@ -94,11 +94,11 @@ Local scans persist to a SQLite library under `~/.vidfarm` (free compute, subscr
94
94
 
95
95
  ## My Files (the user's asset library)
96
96
 
97
- Each user has a persistent **My Files** filesystem — their own uploaded videos (mp4/mov/webm), images (png/jpg/jpeg/gif/webp/svg), audio (mp3/wav/m4a/aac), and documents (pdf/md/txt/csv) — organized into **virtual folders**. This is where a user keeps footage, brand assets, logos, music, briefs, scripts, and the durable strategy docs from Getting Started (About.md, awareness-levels.md, etc.). Both the editor AI copilot and an agent CLI can **read and write** it to find or save assets instead of asking the user to re-upload or paste URLs.
97
+ Each user has a persistent **My Files** filesystem — their own uploaded videos (mp4/mov/webm), images (png/jpg/jpeg/gif/webp/svg), audio (mp3/wav/m4a/aac), and documents (pdf/md/txt/csv) — organized into **virtual folders**. This is where a user keeps footage, brand assets, logos, music, briefs, scripts, and the durable strategy docs from Getting Started / a consultation (OFFER.md, awareness-levels.md, etc.). Both the editor AI copilot and an agent CLI can **read and write** it to find or save assets instead of asking the user to re-upload or paste URLs.
98
98
 
99
99
  - **List** — `GET /api/v1/user/me/attachments` → `{ attachments: [{ id, fileName, contentType, sizeBytes, folderPath, viewUrl, createdAt }], folders: [string] }`. The `viewUrl` is a durable URL you can drop into a composition layer or pass into a primitive route. devcli: `vidfarm files [--folder <path>]`.
100
100
  - **Read one** — resolve it from the list and stream/read its `viewUrl`. devcli: `vidfarm get-file <id> [dest]` (add `--print` to print text contents of md/txt/csv/json inline). For images/video/audio/pdf, reference the `viewUrl` as media — you can't read their bytes as text.
101
- - **Write into My Files** — `POST /api/v1/user/me/attachments/upload` (multipart `file` + optional `folder_path`) persists a file into the library. devcli: `vidfarm put-file <localFile> --folder <path>`, or pipe/inline text without a temp file: `echo "…" | vidfarm put-file --stdin --as About.md --folder acme` / `vidfarm put-file --content "…" --as About.md`. This is how an agent saves Getting Started context (About.md, Interview.md, awareness-levels.md, persuasive-angles.md, ad-hooks.md) or drops a logo/product demo into a namescoped folder. In the web editor copilot the same capability is the **`browse_files` write action** (`action=write` with `file_name` + `content` + optional `folder_path`), which accepts text files (md/txt/csv/json/srt/vtt).
101
+ - **Write into My Files** — `POST /api/v1/user/me/attachments/upload` (multipart `file` + optional `folder_path`) persists a file into the library. devcli: `vidfarm put-file <localFile> --folder <path>`, or pipe/inline text without a temp file: `echo "…" | vidfarm put-file --stdin --as OFFER.md --folder acme` / `vidfarm put-file --content "…" --as OFFER.md`. This is how an agent saves Getting Started / consultation context (OFFER.md — or OFFER_<NAME>.md for a multi-offer director — plus awareness-levels.md, persuasive-angles.md, ad-hooks.md) or drops a logo/product demo into a namescoped folder. In the web editor copilot the same capability is the **`browse_files` write action** (`action=write` with `file_name` + `content` + optional `folder_path`), which accepts text files (md/txt/csv/json/srt/vtt).
102
102
  - **`vidfarm upload` is different** — it targets the **ephemeral** temporary-files store (`POST /api/v1/user/me/temporary-files/upload`) for a throwaway durable URL to drop straight into a composition or approved post. Use `put-file` when you want the asset to persist in the user's My Files library; use `upload` for one-shot media you just need a URL for. Namescope scratch uploads under a **`temp/` folder** (`vidfarm upload clip.mp4 --folder temp`) so they stay in one purgeable place. Better still, if you're on a `vidfarm serve` box, skip the upload entirely and reference the file off disk with `place --src ./clip.mp4` (see "Local file paths as media").
103
103
 
104
104
  ### Metadata notes + vector search (find assets by meaning)
@@ -163,4 +163,4 @@ The web copilot exposes the same three as `browse_files action=rename|move|copy`
163
163
 
164
164
  **Assume My Files is multi-offer.** A user often runs more than one product, offer, brand, or region, and namescopes assets into folders accordingly — by product (`acme-skincare/`, `zensleep/`), by offer/campaign (`summer-sale/`), by region (`us/`, `eu/`), by asset type (`logos/`, `ugc-clips/`), or any arbitrary scheme. There is **no fixed layout** — read the `folders` tree first and reason about how this user organized things. Before pulling assets for a task, infer which folder(s) match the product/offer/region the current composition is about (match folder names to the composition title, video context, and what the user said), then scope reads to that folder so you never mix one brand's logo/product-shot/music into another brand's video. If the target offer is ambiguous, ask which product/offer/region (or which folder) this work is for rather than guessing across offers.
165
165
 
166
- In the editor web copilot the same filesystem is exposed via the **`browse_files` tool** (`action=list` / `action=search` / `action=read` / `action=write` / `action=annotate` / `action=move` / `action=copy` / `action=rename`), so the copilot follows the identical reasoning: search or list to find the right offer's folder, then read an asset — or `write` a text doc (About.md, awareness-levels.md, …) or import a media URL (`source_url`) into that folder, annotating anything worth finding again. `action=rename`/`move`/`copy` reorganize the tree (see the three routes above) — use them to keep character folders and asset names tidy. `browse_files list` defaults to `path='/'` when `path` is omitted, so the `/raws` (hunted raws) and `/temp` (scratch) roots surface alongside the My Files folders instead of being hidden; a `/raws` listing also accepts a `content_type` filter (exact shot-kind — `talking_head`, `b_roll`, `product_shot`, `screen_recording`, …), and every listing paginates via `offset` / `limit`. The devcli equivalents are `vidfarm files [--search]` / `get-file` / `put-file [--notes]` / `annotate-file` / `directory rename|move|copy`.
166
+ In the editor web copilot the same filesystem is exposed via the **`browse_files` tool** (`action=list` / `action=search` / `action=read` / `action=write` / `action=annotate` / `action=move` / `action=copy` / `action=rename`), so the copilot follows the identical reasoning: search or list to find the right offer's folder, then read an asset — or `write` a text doc (OFFER.md, awareness-levels.md, …) or import a media URL (`source_url`) into that folder, annotating anything worth finding again. `action=rename`/`move`/`copy` reorganize the tree (see the three routes above) — use them to keep character folders and asset names tidy. `browse_files list` defaults to `path='/'` when `path` is omitted, so the `/raws` (hunted raws) and `/temp` (scratch) roots surface alongside the My Files folders instead of being hidden; a `/raws` listing also accepts a `content_type` filter (exact shot-kind — `talking_head`, `b_roll`, `product_shot`, `screen_recording`, …), and every listing paginates via `offset` / `limit`. The devcli equivalents are `vidfarm files [--search]` / `get-file` / `put-file [--notes]` / `annotate-file` / `directory rename|move|copy`.
@@ -219,6 +219,7 @@ The licensed harness also carries the **generative build workflow** guidance (ch
219
219
  | `vidfarm raws preset list\|run\|save` / `raws export <ids…> --to <dir>` | (local library) | saved queries; copy raw MP4s out |
220
220
  | `vidfarm lint <dir\|composition.html>` | (local static validation) | pre-publish composition check: timing, overlaps, preset names, media src |
221
221
  | `vidfarm stills <dir> [--at 0,2.5,…]` | (local in-process render of PNG frames) | visually verify an edit without a full render |
222
+ | `vidfarm qa <dir\|composition.html> [--json] [--strict]` | (local static QA — **devcli-only**, no cloud/REST twin) | **social-native QA: HTML slop + font regime. Run it on EVERY video you produce.** Free, instant, feedback-only |
222
223
  | `vidfarm doctor` | (local environment triage) | check ffmpeg/node/keys/agent CLI/poisoned env + list local serve/preview processes before debugging anything else; `--kill-orphans` reaps dead servers squatting ports (fixes the "Waiting for preview server…" hang) |
223
224
  | `vidfarm skills list\|add <name>\|update` | `GET /skill-pack/index.json` · `/skill-pack/:name/*` | install/refresh skill packs (see "Skill packs — import on demand") |
224
225
  | `vidfarm tts "…" --engine local` / `vidfarm stt <file> --engine whisper` | (keyless LOCAL engines: Kokoro-82M TTS, whisper.cpp STT) | narration + word-timestamp transcripts with zero keys and zero accounts |
@@ -232,6 +233,37 @@ The licensed harness also carries the **generative build workflow** guidance (ch
232
233
 
233
234
  **Approving a locally rendered MP4 (the URL-first rule + the durability rule).** The approve route (`POST /api/v1/approved/posts`) and every media-taking route accept a `url`, never raw file bytes — so you never "upload to approve" in one shot. An approved post is a **permanent** share page, so the media must live in **durable My Files**, not the 30-day temp store (a temp-hosted video would 404 the share page after 30 days). The correct sequence for a big local file is: (1) `POST /api/v1/user/me/attachments/presign` with `{ file_name, content_type, size_bytes }` → (2) PUT the raw bytes to the returned presigned S3 URL → (3) `POST /api/v1/user/me/attachments` (finalize) → use the returned durable `viewUrl` as the approve media `url`. `vidfarm approve --video ./final.mp4` does all of this automatically (durable by default; up to **200 MB**). Never POST a large file as multipart to `.../attachments/upload` against the cloud host: that path proxies through Lambda and caps near 6 MB (it exists only as a fallback for local-storage `vidfarm serve` boxes). Only use the temp-store route (`.../temporary-files/*`, or `vidfarm approve --temp`) for a **throwaway** preview you don't mind losing in 30 days.
234
235
 
236
+ ## `vidfarm qa` — the social-native QA pass (devcli-only, heavily recommended)
237
+
238
+ ```bash
239
+ vidfarm qa ./work # human-readable findings + verdict
240
+ vidfarm qa ./work --json # machine-readable: rule / severity / where / fix
241
+ vidfarm qa ./work --strict # ALSO exit 1 on slop (only if you want a CI gate)
242
+ ```
243
+
244
+ **Run this on every video you produce.** It is free, instant (pure DOM, no ffmpeg/Chrome/network), and it is the only automated check for the thing that most often ruins an agent-made video: **HTML slop**. Compositions are authored in HTML, so an agent's web-page instincts leak straight onto the frame as landing-page furniture that appears on every website and in **zero** real TikToks.
245
+
246
+ **It is feedback, not a gate.** Default exit code is **0** even when it finds slop; nothing in the render or publish path calls it; it never runs automatically. `--strict` exists only if you deliberately want a CI failure. A finding you disagree with is fine to ignore and say so — it is a lint, not a verdict on the work.
247
+
248
+ **It is a BLOCKLIST, not an allowlist.** It names specific known-bad web patterns. Everything it doesn't name is legal, so a weird, ugly, hand-made, or wildly stylized composition passes untouched. It will never push your videos toward one house style — if it fires on a genuine creative choice, that's a bug in the rule, not in your video.
249
+
250
+ What it flags:
251
+
252
+ | Rule | Severity | What trips it |
253
+ |---|---|---|
254
+ | `cta-button` | error | Action copy ("Sign Up for a Free Trial →") **inside** a filled/gradient rounded capsule. Bare CTA copy in a caption is fine — "BUY NOW" is real social copy |
255
+ | `badge-chip-row` | error | 2+ sibling small rounded filled tags — the "✓ No Credit Card Needed · ✓ 30-Min Trial" strip |
256
+ | `card-panel` / `glass-card` | error | A rounded box with a border/shadow/`backdrop-filter` wrapping 2+ elements. A tight legibility **band** (radius ≤8px, one text run, no border/shadow) stays legal |
257
+ | `gradient-text` | error | `background-clip:text` gradient headline fills |
258
+ | `clickable-element` | error/warn | `<button>`, `<form>`, `<input>`; `<a href>` warns |
259
+ | `web-framework-classes` | error/warn | Bootstrap/Tailwind class tokens (`btn`, `badge`, `card`, `hero`, `col-*`, `rounded-full`, `shadow-lg`, `backdrop-blur`, `bg-gradient-to-*`) or a linked CSS framework. A `<script>` CDN for GSAP/anime.js is fine |
260
+ | `page-structure` / `bullet-list` | error/warn | `<nav>`/`<header>`/`<footer>`/`<table>`; a `<ul>` with visible bullet markers |
261
+ | `font-regime` | error/warn | A text layer in a website body font (Inter/Roboto/Arial/system-ui → **error**) or any family outside the imported regime (Montserrat, TikTok Sans, Abel, Source Code Pro, Yesteryear → warn, it silently falls back at render) |
262
+ | `font-size` / `font-weight` | error/warn | `font-size:0` (invisible) is an error; sub-2.6%-of-canvas-width text and weight <600 warn |
263
+ | `caption-safe-zone` | warn | Text outside the 8%–85% band on a **portrait** canvas (landscape/square are exempt) |
264
+
265
+ Every finding carries a concrete `fix` line — the answer is always "say it as timed text on the footage", never just "delete it". Fold `--json` into scripted batch runs to QA N variants at once.
266
+
235
267
  ## Cost mode — the devcli's money-saving guardrail
236
268
 
237
269
  The four modes, quoted as **cost per finished video**. The first two are spend postures; the last two are also *production methods* — `rich-ai` buys reusable greenscreen raws and animates them in HTML, `pure-videogen` generates every frame:
@@ -405,16 +405,52 @@ Three surfaces:
405
405
  - **Editor UI (human)** — the Inspector's **Animate (Ken Burns)** dropdown on any selected image.
406
406
  - **Desktop agents (devcli)** — `vidfarm place ./work --src <img> --kind image --ken-burns zoom-in [--ken-burns-intensity 0.3]`, or fused with generation: `vidfarm generate image --prompt "…" --place ./work --ken-burns pan-left`.
407
407
 
408
- ### TikTok-native caption standard (position + font) always adhere
408
+ ### Social-native visual standard no HTML slop, ever
409
409
 
410
- Short-form is watched on a phone, and the phone's UI eats the frame's edges. **Never pin on-screen text to the extreme top or bottom** the top ~8% sits under the status bar / "Following · For You" tabs and the bottom ~15% under the username, caption text, music marquee, and action rail. Text there is literally clipped and reads as amateur. Two rules, applied to **every** caption/title/overlay you place or inherit:
410
+ Compositions are authored in HTML, so the single most common way an AI-edited video betrays itself is by **looking like a web page**. A landing-page button, a row of feature chips, a frosted card with a gradient headline these are everywhere on the web and **nowhere in a real social post**. A director scrolling TikTok/Reels/Shorts never sees them, so the moment one appears the video reads as machine-made and the retention dies. HTML and hyperframes are the right tool; **Bootstrap/Tailwind furniture is not.**
411
+
412
+ **The one test:** *would this element exist if the video were shot on a phone and captioned in CapCut?* If it only makes sense inside a browser — if its whole job is to look **clickable** — cut it. **Nothing in a video is clickable.**
413
+
414
+ **BANNED — never author, and strip on sight when a fork or a paste brings one in:**
415
+
416
+ - **CTA / "button" shapes.** Any filled capsule or rounded rect containing action copy — "Sign Up for a Free Trial →", "Learn More", "Get Started", "Book a Call" — especially gradient-filled with a glow or drop shadow. A social CTA is *spoken*, or a plain caption line, or an arrow pointing at the real UI. Not a button.
417
+ - **Badge / chip / pill rows.** The "✓ ID-Verified Tutors · ✓ No Credit Card Needed · ✓ 30-Min Trial" strip of small rounded tags. Nothing on TikTok is a `<span class="badge">`. Say the three benefits as three timed caption lines instead — one at a time, on the footage.
418
+ - **Cards, panels, containers.** A rounded-rect box (dark, bordered, shadowed, or `backdrop-filter` frosted) holding a heading plus a subheading/URL. Text belongs **on the footage**, not inside a floating panel with padding around it.
419
+ - **Gradient text fills, neon border glows, elevation shadows, glassmorphism, hover-implying strokes.**
420
+ - **Web page furniture of any kind:** navbars, hero sections, feature grids, two-column layouts, `<ul>` bullet lists with disc markers, tables, alerts, progress bars as decoration, logo-in-a-circle avatars, "as seen in" strips.
421
+ - **Corporate web type:** Inter, Roboto, system-ui, Arial, Helvetica, Georgia, Times — at web weights (400–600) and web sizes (16–24px). Instantly reads as "a screenshot of a website."
422
+
423
+ **Greppable smell test.** If you are typing `class="btn…"`, `badge`, `chip`, `tag`, `card`, `panel`, `container`, `row`/`col-`, `hero`, `cta`, `rounded-full`, `shadow-lg`, `backdrop-blur`, `bg-gradient-to-r`, `border border-…` — **stop.** You are building a web page, not a video. Rewrite as timed text on footage.
424
+
425
+ **ALLOWED and encouraged — these ARE social-native:**
426
+
427
+ - **Arrows** (drawn, animated, hand-style), circles/scribbles/underline strokes highlighting part of the frame, hand-drawn marks, checkmarks *as glyphs inside a caption line* (not as chips).
428
+ - **Italic / oblique** for emphasis, ALL-CAPS, a single word in a pop color, mixed weights across a stacked line.
429
+ - **Emoji inline in text** (sparingly), **sticker/cut-out overlays** on transparent PNG (`create-overlay`), mock social UI when the format calls for it (iMessage bubbles, a TikTok comment card, a fake DM, a countdown/progress bar) — these are native artifacts of the platform, not web furniture.
430
+ - **Full-bleed footage** with text sitting directly on it.
431
+
432
+ **On devcli there's a checker: `vidfarm qa <dir|composition.html>`.** Free, instant, local-only — a blocklist pass for everything above plus the font regime and safe zone, with a concrete fix per finding. **Run it on every video you produce.** It is feedback, not a gate (exit 0 even on findings, never runs automatically, `--strict` only if you want a CI failure) and a blocklist, not an allowlist (stylized/hand-made compositions pass untouched — it will not homogenize your videos). No cloud/REST twin: the web copilot enforces this standard by hand. Details in `references/automation-and-local-dev.md` ("`vidfarm qa`").
433
+
434
+ ### TikTok-native caption standard (position + font + background) — always adhere
435
+
436
+ Short-form is watched on a phone, and the phone's UI eats the frame's edges. **Never pin on-screen text to the extreme top or bottom** — the top ~8% sits under the status bar / "Following · For You" tabs and the bottom ~15% under the username, caption text, music marquee, and action rail. Text there is literally clipped and reads as amateur. Three rules, applied to **every** caption/title/overlay you place or inherit:
411
437
 
412
438
  - **Position → readable safe zone.** Keep the text box's vertical extent inside **~8%–85%** of canvas height (9:16). A lower-third subtitle lives around **y≈70%** (the `captions generate` default); a "me when…" / "POV:" meme line sits near the **top of the safe zone (y≈8%), not y:0**. Preserve the original top-vs-bottom anchoring — just pull it off the dead-zone edge, don't recenter everything. Also keep wide captions clear of the **right ~12%** action rail (a centered box at `x:10 width:80` is safe).
413
- - **Font → the composition regime.** Use the bundled display fonts only — **Montserrat** (bold default, weight 700), **TikTok Sans**, Abel, Source Code Pro, Yesteryear. Don't request a font the composition doesn't import (it silently falls back). For captions, heavy weight + an outline/pill background (`spotlight` preset, `background_style:"outline"`) is the TikTok-native look.
439
+ - **Font → the composition regime.** Use the bundled display fonts only — **Montserrat** (bold default, weight **700–900**), **TikTok Sans**, Abel, Source Code Pro, Yesteryear. Don't request a font the composition doesn't import (it silently falls back to a web-default sans, which is exactly the slop look). Sizes are in PIXELS of a 1080-wide frame: **~36–64px** reads well, never below ~28px and **never 0** (invisible). Keep captions to ~2 lines / ~5 words per line; `line_height` 0.95–1.15 for stacked display lines.
440
+ - **Background → one of exactly four valid treatments.** Any text you place uses one of these and nothing else:
441
+
442
+ | # | Treatment | How to set it | When |
443
+ |---|---|---|---|
444
+ | 1 | **Bare text + heavy outline/stroke** (black stroke on white or a bright fill) | `background_style:"outline"` | The default TikTok look |
445
+ | 2 | **Bare text + soft drop shadow only**, no box | `background_style:"plain"` | Clean/cinematic over busy footage |
446
+ | 3 | **Highlight pill behind the ACTIVE word only** | `set_captions caption_style:"spotlight"` / `"karaoke"` (+ `caption_highlight_color`) | Hormozi/CapCut word-by-word. **The only legitimate "pill" in a video** — it tracks the spoken word, so it isn't a badge |
447
+ | 4 | **Solid band that tightly hugs the text lines** (CapCut "text box") | `background_style:"highlight-solid"` (or `"highlight-translucent"`) + a `background` color | Guaranteed legibility over noisy footage |
448
+
449
+ Treatment 4 is a **band, not a card**: it hugs the glyphs with minimal padding, corner radius ≤ ~8px, **no border, no drop shadow, no gradient, no blur**, and it wraps *one* text run — never a heading + subheading + URL stacked inside one rounded box. The moment it grows padding, a stroke, or a second element inside it, it has become a web card. Fix it.
414
450
 
415
- **A common trap: decomposed templates mirror the source's caption placement**, so a forked meme can arrive with its caption pinned at `top:0` in a non-regime font. When you re-theme or recaption, **fix it to the standard** rather than inheriting the edge-pinned original. When placing text yourself (`set_captions`, `set_layer_text`, `add_layer`, devcli `place`/`captions`), set `y`/`font_family` to the standard from the start.
451
+ **A common trap: decomposed templates mirror the source's caption placement**, so a forked meme can arrive with its caption pinned at `top:0` in a non-regime font and a re-theme prompt ("make this for my tutoring service") is exactly where an agent starts inventing landing-page CTAs and benefit chips because the *subject* is a SaaS product. **Fix to the standard, don't inherit it, and don't import the website's design language into the video.** When placing text yourself (`set_captions`, `set_layer_text`, `set_layer_style`, `add_layer`, devcli `place`/`captions`), set `y` / `font_family` / `font_weight` / `background_style` to the standard from the start.
416
452
 
417
- > Local devcli renders enforce this automatically: `renderCompositionLocally` runs `normalizeTikTokCaptionLayout` (src/devcli/composition-edit.ts) on every production, clamping caption/text layers into the 8%–85% safe zone and coercing off-regime primary fonts to Montserrat. It's a safety net, not a license to author edge-pinned captions get it right in the composition so the editor preview and any cloud render match.
453
+ > Local devcli renders enforce part of this automatically: `renderCompositionLocally` runs `normalizeTikTokCaptionLayout` (src/devcli/composition-edit.ts) on every production, clamping caption/text layers into the 8%–85% safe zone and coercing off-regime primary fonts to Montserrat. It only fixes position and font family it will happily render your Bootstrap card. Get it right in the composition so the editor preview, the local render, and any cloud render match.
418
454
 
419
455
  ### Animated captions — word-by-word caption styles (TikTok/CapCut)
420
456
 
@@ -1,10 +1,10 @@
1
- ## Getting started / onboarding a new director
1
+ ## Getting started / onboarding / consultation
2
2
 
3
- Only run this flow when the director signals they **don't know where to start** ("getting started", "help me set up", "I don't know where to begin"). If they already know what they want, skip straight to it — never force onboarding. Directors can also jump to any single step (e.g. "just find me a good template for X" → go straight to template discovery).
3
+ Run this flow when the director signals they **don't know where to start** ("getting started", "help me set up", "I don't know where to begin") **or asks for a "consultation"** ("give me a consultation", "consult me on my offer", "I want a strategy consultation"). **"Consultation" is a first-class trigger for the brainstorm chain** — treat it as a request for the full run: the cold-start interview → awareness stages → angles → hooks (steps 1-4 below), driven by the `brainstorm/*` primitives rather than brainstormed in chat from memory. If they already know what they want, skip straight to it — never force onboarding. Directors can also jump to any single step (e.g. "just find me a good template for X" → go straight to template discovery, or "consult me on hooks" → run `brainstorm/hooks` alone).
4
4
 
5
5
  The point of onboarding is to build **durable, reusable context** in My Files, namescoped under the director's product/offer folder, so future chats and agents can read it back. Save each artifact as Markdown with `browse_files` write (web copilot) or `vidfarm put-file` (devcli):
6
6
 
7
- 1. **Product context** → `About.md` (basic offer/product) or a deeper `Interview.md` if they want depth. Drive the interview questions with `brainstorm/coldstart`.
7
+ 1. **Product context** → **`OFFER.md`** (or `OFFER_<NAME>.md` e.g. `OFFER_ACME_SKINCARE.md` when the director runs more than one offer). Drive the interview questions with `brainstorm/coldstart` and **recommend the director save their answers into that file**, either locally next to their project or in cloud My Files. This is the durable artifact every later step reads back; `About.md` / `Interview.md` are legacy names — prefer `OFFER*.md` for anything new.
8
8
  2. **Awareness level** (Eugene Schwartz — problem-aware, solution-unaware, …) → `awareness-levels.md`. If it's genuinely unknown after thinking it through, note that ads for **every** level should be made and tested. Use `brainstorm/awareness_stages`.
9
9
  3. **Persuasive angles** → `persuasive-angles.md`, via `brainstorm/angles`.
10
10
  4. **Hooks** → `ad-hooks.md`, via `brainstorm/hooks`.
@@ -12,7 +12,7 @@ The point of onboarding is to build **durable, reusable context** in My Files, n
12
12
  6. **Budget** — ask roughly what they want to spend per video, and map it to the Cost spectrum (free reuse+local render → pennies for cloud render → ~$1 for some AI scenes → $10+ for heavy AI gen). This sets which approach you default to and whether AI **video** generation is on the table (ask permission before using it; image gen is cheap and fine). Budget can also be revisited per editor project.
13
13
  7. **Recommend & adapt a template** — pair what you now know about the offer against the decomposed template catalog (`GET /discover/feed?q=<offer>`, read each result's `promotions`/`keywords`/`summary`), recommend the best 3-6, then fork and **modify** the winner to fit their offer. Prefer already-decomposed templates so the director skips the ~$0.10 decompose cost.
14
14
 
15
- **Assume multiple offers.** My Files is multi-offer (see the My Files section) — namescope every onboarding artifact under the right product/offer/region folder (`acme-skincare/About.md`, not a bare `About.md`) so one brand's context never bleeds into another's.
15
+ **Assume multiple offers.** My Files is multi-offer (see the My Files section) — namescope every onboarding artifact under the right product/offer/region folder (`acme-skincare/OFFER.md`, not a bare `OFFER.md`) so one brand's context never bleeds into another's. When a director keeps several offers in one flat folder, name the files `OFFER_ACME_SKINCARE.md` / `OFFER_ACME_SUPPLEMENTS.md` instead.
16
16
 
17
17
  ## Default assistance pattern
18
18
 
@@ -314,7 +314,11 @@ curl -X POST "$VIDFARM_BASE/api/v1/primitives/audio/transcribe" \
314
314
 
315
315
  ## Brainstorm primitives
316
316
 
317
- The `brainstorm/*` primitives are the strategy toolkit. They are reusable, billable AI reasoning steps — the same family the AI Copilot exposes as chip suggestions. Treat **product placement** as a first-class member of this family, right alongside angles and hooks:
317
+ The `brainstorm/*` primitives are the strategy toolkit. They are reusable, billable AI reasoning steps — the same family the AI Copilot exposes as chip suggestions. Treat **product placement** as a first-class member of this family, right alongside angles and hooks.
318
+
319
+ When a director asks for a **"consultation"**, that is this toolkit: run the chain `coldstart` (the interview) → `awareness_stages` → `angles` → `hooks`, saving each output to My Files as you go (see `references/onboarding.md`). Never improvise a consultation from memory when these routes exist. "Consult me on hooks/angles" → run just that primitive.
320
+
321
+ `coldstart` returns *questions*, not answers — always recommend the director write their answers into an **`OFFER.md`** (or `OFFER_<NAME>.md` when they run several offers), locally or in cloud My Files. That file becomes the `offer_description` you feed into `awareness_stages`, `angles`, `hooks`, and `product_placement`.
318
322
 
319
323
  - `POST /api/v1/primitives/brainstorm/coldstart` — `{ payload: { user_message } }` → foundational questionnaire for a customer starting from zero.
320
324
  - `POST /api/v1/primitives/brainstorm/awareness_stages` — `{ payload: { offer_description } }` → which Eugene-Schwartz awareness stages to target first.