@mevdragon/vidfarm-devcli 0.20.5 → 0.20.8

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.
Files changed (154) hide show
  1. package/.agents/skills/editor-capabilities/SKILL.md +15 -1
  2. package/.agents/skills/vidfarm-director/SKILL.md +106 -0
  3. package/.agents/skills/vidfarm-director/recipes/find-and-fork-template.md +16 -0
  4. package/.agents/skills/vidfarm-director/recipes/local-edit-render-approve.md +13 -0
  5. package/.agents/skills/vidfarm-director/recipes/onboard-a-new-director.md +13 -0
  6. package/.agents/skills/vidfarm-director/recipes/retheme-template.md +17 -0
  7. package/.agents/skills/vidfarm-director/references/assets-and-sourcing.md +111 -0
  8. package/.agents/skills/vidfarm-director/references/automation-and-local-dev.md +243 -0
  9. package/.agents/skills/vidfarm-director/references/core-workflows.md +273 -0
  10. package/.agents/skills/vidfarm-director/references/editor-workflows.md +365 -0
  11. package/.agents/skills/vidfarm-director/references/onboarding.md +28 -0
  12. package/.agents/skills/vidfarm-director/references/primitives.md +265 -0
  13. package/SKILL.director.md +521 -261
  14. package/dist/src/cli.js +404 -96
  15. package/dist/src/devcli/clips.js +15 -10
  16. package/dist/src/devcli/doctor.js +2 -2
  17. package/dist/src/devcli/local-backend.js +46 -2
  18. package/dist/src/services/clip-curation/cost.js +5 -1
  19. package/dist/src/services/clip-curation/gemini.js +10 -0
  20. package/package.json +30 -10
  21. package/SKILL.platform.md +0 -432
  22. package/demo/README.md +0 -28
  23. package/demo/dist/app.css +0 -1
  24. package/demo/dist/app.js +0 -1850
  25. package/demo/dist/chunks/chunk-DXB73IDG.js +0 -1
  26. package/demo/dist/chunks/chunk-S7OWAJDS.js +0 -36
  27. package/demo/dist/chunks/chunk-VTIBZ6AN.js +0 -1
  28. package/demo/dist/chunks/dist-ADSJKBVE.js +0 -332
  29. package/demo/dist/chunks/domEditingLayers-VZMLL4AP-SGHWPND4.js +0 -1
  30. package/demo/dist/chunks/hyperframes-player-XB65TCD6.js +0 -425
  31. package/demo/dist/chunks/lib-XAQ37YOE.js +0 -1
  32. package/demo/dist/chunks/src-TJ2QYA4U.js +0 -207
  33. package/demo/dist/favicon.ico +0 -0
  34. package/demo/dist/icons/timeline/audio.svg +0 -7
  35. package/demo/dist/icons/timeline/captions.svg +0 -5
  36. package/demo/dist/icons/timeline/composition.svg +0 -12
  37. package/demo/dist/icons/timeline/image.svg +0 -18
  38. package/demo/dist/icons/timeline/music.svg +0 -10
  39. package/demo/dist/icons/timeline/text.svg +0 -3
  40. package/demo/dist/index.html +0 -15
  41. package/dist/src/account-pages-legacy.js +0 -9396
  42. package/dist/src/account-pages.js +0 -61
  43. package/dist/src/app.js +0 -21602
  44. package/dist/src/composition-runtime.js +0 -1053
  45. package/dist/src/config.js +0 -217
  46. package/dist/src/context.js +0 -447
  47. package/dist/src/dev-app-legacy.js +0 -739
  48. package/dist/src/dev-app.js +0 -6
  49. package/dist/src/devcli/migrate-local.js +0 -140
  50. package/dist/src/devcli/sync.js +0 -368
  51. package/dist/src/domain.js +0 -5
  52. package/dist/src/editor-chat-history.js +0 -82
  53. package/dist/src/editor-chat.js +0 -756
  54. package/dist/src/editor-dark-theme.js +0 -1128
  55. package/dist/src/frontend/debug.js +0 -71
  56. package/dist/src/frontend/discover-client.js +0 -130
  57. package/dist/src/frontend/discover-store.js +0 -23
  58. package/dist/src/frontend/file-directory.js +0 -1018
  59. package/dist/src/frontend/flockposter-cache-store.js +0 -124
  60. package/dist/src/frontend/homepage-client.js +0 -446
  61. package/dist/src/frontend/homepage-shared.js +0 -201
  62. package/dist/src/frontend/homepage-store.js +0 -66
  63. package/dist/src/frontend/homepage-view.js +0 -705
  64. package/dist/src/frontend/page-runtime-client.js +0 -132
  65. package/dist/src/frontend/page-runtime-store.js +0 -9
  66. package/dist/src/frontend/sentry.js +0 -42
  67. package/dist/src/frontend/template-editor-chat.js +0 -4181
  68. package/dist/src/help-page.js +0 -346
  69. package/dist/src/homepage.js +0 -1458
  70. package/dist/src/index.js +0 -16
  71. package/dist/src/instrument.js +0 -30
  72. package/dist/src/landing-page.js +0 -384
  73. package/dist/src/page-runtime.js +0 -2
  74. package/dist/src/page-shell.js +0 -1452
  75. package/dist/src/primitive-context.js +0 -416
  76. package/dist/src/primitive-registry.js +0 -3940
  77. package/dist/src/primitive-sdk.js +0 -4
  78. package/dist/src/primitives/hyperframes-media.js +0 -108
  79. package/dist/src/react-page-shell.js +0 -35
  80. package/dist/src/ready-post-schedule-component.js +0 -1540
  81. package/dist/src/registry.js +0 -296
  82. package/dist/src/reskin/agency-page.js +0 -299
  83. package/dist/src/reskin/calendar-page.js +0 -568
  84. package/dist/src/reskin/chat-page.js +0 -942
  85. package/dist/src/reskin/discover-page.js +0 -1788
  86. package/dist/src/reskin/document.js +0 -1587
  87. package/dist/src/reskin/help-page.js +0 -357
  88. package/dist/src/reskin/index-page.js +0 -62
  89. package/dist/src/reskin/inpaint-clipper-page.js +0 -890
  90. package/dist/src/reskin/inpaint-page.js +0 -2554
  91. package/dist/src/reskin/inpaint-video-page.js +0 -1339
  92. package/dist/src/reskin/job-runs-page.js +0 -477
  93. package/dist/src/reskin/library-page.js +0 -1634
  94. package/dist/src/reskin/login-page.js +0 -262
  95. package/dist/src/reskin/portfolio-page.js +0 -687
  96. package/dist/src/reskin/pricing-page.js +0 -390
  97. package/dist/src/reskin/settings-page.js +0 -732
  98. package/dist/src/reskin/theme.js +0 -711
  99. package/dist/src/runtime.js +0 -35
  100. package/dist/src/services/api-call-history.js +0 -249
  101. package/dist/src/services/auth.js +0 -152
  102. package/dist/src/services/billing-pricing.js +0 -39
  103. package/dist/src/services/billing.js +0 -241
  104. package/dist/src/services/cast.js +0 -127
  105. package/dist/src/services/chat-threads.js +0 -92
  106. package/dist/src/services/clip-records.js +0 -250
  107. package/dist/src/services/clip-search.js +0 -77
  108. package/dist/src/services/clip-vectors.js +0 -125
  109. package/dist/src/services/composition-sanitize.js +0 -124
  110. package/dist/src/services/composition-watch.js +0 -79
  111. package/dist/src/services/elevenlabs.js +0 -222
  112. package/dist/src/services/file-directory.js +0 -117
  113. package/dist/src/services/fork-access.js +0 -93
  114. package/dist/src/services/fork-manifest.js +0 -42
  115. package/dist/src/services/ghostcut.js +0 -179
  116. package/dist/src/services/hyperframes.js +0 -3517
  117. package/dist/src/services/job-capacity.js +0 -14
  118. package/dist/src/services/job-logs.js +0 -197
  119. package/dist/src/services/jobs.js +0 -136
  120. package/dist/src/services/local-dynamo.js +0 -0
  121. package/dist/src/services/media-processing.js +0 -766
  122. package/dist/src/services/primitive-media-lambda.js +0 -280
  123. package/dist/src/services/providers.js +0 -2748
  124. package/dist/src/services/rate-limits.js +0 -262
  125. package/dist/src/services/scene-annotations.js +0 -32
  126. package/dist/src/services/serverless-auth.js +0 -382
  127. package/dist/src/services/serverless-jobs.js +0 -1084
  128. package/dist/src/services/serverless-provider-keys.js +0 -409
  129. package/dist/src/services/serverless-records.js +0 -1515
  130. package/dist/src/services/serverless-template-configs.js +0 -75
  131. package/dist/src/services/storage.js +0 -461
  132. package/dist/src/services/swipe-customize.js +0 -437
  133. package/dist/src/services/template-certification.js +0 -413
  134. package/dist/src/services/template-loader.js +0 -99
  135. package/dist/src/services/template-runtime-bundles.js +0 -217
  136. package/dist/src/services/template-sources.js +0 -1017
  137. package/dist/src/services/upstream.js +0 -248
  138. package/dist/src/services/video-normalization.js +0 -2
  139. package/dist/src/services/webhooks.js +0 -62
  140. package/dist/src/template-editor-pages.js +0 -2576
  141. package/dist/src/template-editor-shell.js +0 -2893
  142. package/dist/src/template-sdk.js +0 -4
  143. package/dist/src/worker.js +0 -17
  144. package/public/assets/discover-client-app.js +0 -1
  145. package/public/assets/file-directory-app.js +0 -3
  146. package/public/assets/homepage-app.js +0 -54
  147. package/public/assets/homepage-client-app.js +0 -80
  148. package/public/assets/page-runtime-client-app.js +0 -94
  149. package/public/assets/placeholders/scene-placeholder.png +0 -0
  150. package/src/assets/SELLING_AWARENESS_STAGES.md +0 -579
  151. package/src/assets/SELLING_WITH_HOOKS.md +0 -377
  152. package/src/assets/SELLING_WITH_VSLS.md +0 -606
  153. package/src/assets/favicon.ico +0 -0
  154. package/src/assets/logo-vidfarm.png +0 -0
package/SKILL.director.md CHANGED
@@ -1,24 +1,109 @@
1
1
  ---
2
2
  name: vidfarm-director
3
- description: Use Vidfarm as a director. Browse/add inspiration videos, 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. Browse/add inspiration videos, browse/save public raws, 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
7
7
 
8
- Vidfarm is a **video composition studio**. Directors fork a published template, edit it on a timeline in the **Trackpad Editor**, publish to MP4, and share.
8
+ Vidfarm is a video composition studio. Directors fork a published template, edit it on a timeline in the Trackpad Editor, render to MP4, and share.
9
9
 
10
10
  Use this skill when the user wants to:
11
11
 
12
- - log in with OTP and save their AI provider keys
13
- - discover published composition templates
14
- - fork a template into an editable composition
15
- - edit the timeline (add/remove/reorder layers, retime clips, swap media)
16
- - auto-decompose a raw source video into scenes with AI
17
- - publish a fork to MP4 and share the result
18
- - browse version history and revert
19
- - manage sharing/visibility
12
+ - log in and save provider keys
13
+ - discover templates or add a new inspiration
14
+ - fork a template and edit a composition
15
+ - re-theme or rebuild a video while preserving the viral DNA
16
+ - render, approve, and schedule a post
17
+ - automate Vidfarm through REST, `vidfarm`, or a local `vidfarm serve` loop
18
+ - manage files, raws, recurring characters, versions, or sharing
20
19
 
21
- Do **not** use this skill to author new templates from scratch, deploy platform infrastructure, or discuss internal architecture those belong to `SKILL.platform.md` (if it exists) or a separate developer workflow.
20
+ Do not use this skill to author new templates from scratch, deploy platform infrastructure, or discuss internal platform architecture. Those belong in a separate developer or platform workflow.
21
+
22
+ ## Default stance
23
+
24
+ - Treat the Trackpad Editor as the primary surface. Reach for templates and forks before primitives.
25
+ - Default to the cheapest approach that works. Reuse footage first, use image generation freely, and ask before AI video generation.
26
+ - Prefer already-decomposed templates when they match the user’s goal.
27
+ - For heavy edits, read the grounding artifacts before acting: `video-context.json`, `editor-harness.json`, and local `.harness/*` bundles when present.
28
+ - For agentic rewrites, think in the three axes: scenes, audio, text. Decide whether each axis is a SWAP or a REPLACE.
29
+
30
+ ## The three paintbrushes (Vidfarm's operating philosophy)
31
+
32
+ Vidfarm is founder-friendly and pragmatic: **we do not burn expensive AI credits on everything.** Every visual on the timeline is painted with one of three "paintbrushes," and for bulk creation it is often combinatorially cheaper to reach for the first two before the third:
33
+
34
+ 1. **Raw clips** — cut and remix footage from existing long-form or short-form video (the director's own library, or freshly hunted out of a URL/VOD). Cheapest, and the workhorse for scene REPLACE.
35
+ 2. **HTML/JS hyperframes** — video-from-HTML: CSS/declarative animation, anime.js/GSAP motion, animated image + text elements, data-viz, modeling. Cheap, deterministic, infinitely re-themeable.
36
+ 3. **Pure AI generation** — AI image/video/voice/music. The most expensive brush; AI **video** especially. Use last, only where the other two genuinely cannot cover the beat.
37
+
38
+ Directors also accumulate a **reusable media asset library** — logos, stickers, reactions, b-roll, a-roll, a brand media kit. Recreation should have an opinion on **when and where** to reuse these. But respect the format's viral DNA: a director can accidentally reskin away the very thing that makes the template land, so tie every asset/paintbrush recommendation back to the harness (`viral_dna`, `editor-harness.json`) rather than swapping freely.
39
+
40
+ **A technical replication decomposition names, per template, the actual method for each beat — when/where/what is raw clips vs HTML hyperframes vs pure AI gen — and offers two harnesses:**
41
+
42
+ - **(A) Cheap & efficient** *(default)* — recaption text; background-video + foreground-video memes; animate HTML/image elements with hyperframes; reuse library media; AI-generate a reusable element **once** then reuse it; greenscreen; raw-clip long-form and remix; lean on the memes/reactions/b-roll/a-roll library and brand media kit; only if genuinely needed, reach for AI image/video/voice/music.
43
+ - **(B) Best quality** — AI video generation by default; storyboard with AI **image** first; then adversarially grade the result with a coding agent (Claude Code / Codex / any capable AI agent) and iterate.
44
+
45
+ Present both harnesses to the director, recommend (A) unless they've asked for premium or budget covers it, and explain the tradeoff in these terms. Full methodology: `references/editor-workflows.md` (“The three paintbrushes & two replication harnesses”); cost bands: `references/core-workflows.md` (Cost spectrum).
46
+
47
+ **Be generous to free-tier / no-account users.** Vidfarm's harness is open source; an everyday capable AI agent should be able to follow this decomposition and recreate a template on its own, without a Vidfarm wallet — so keep the recommendation self-contained and pragmatic, not gated behind paid primitives.
48
+
49
+ **Free tier vs. paid — who does the decomposition.** On the free tier (local devcli, no Vidfarm account) the harness gives the *method*, not the pre-computed answer: **the user (and their AI agent) is responsible for watching the reference video, decomposing it into its elements, and deciding the three paintbrushes themselves** — there is no `video-context.json` / `editor-harness.json` / `scene-annotations.json` handed to them. Be honest about that and coach them through doing it. **Paid Vidfarm accounts** get the leverage: a massive library of **pre-decomposed viral videos** (the decompose passes already run — viral DNA, emotional punch, editor harness, per-scene recreation annotations) plus **prompt-harness best practices distilled from platform-scale learnings**. When a free-tier user is grinding the decomposition by hand, it's fair to mention that a Vidfarm account would hand them the decomposition and the proven harness instead.
50
+
51
+ ## Web AI chat vs. local devcli — know your surface's limits
52
+
53
+ You may be running as the **in-web AI chat** (the /editor copilot, the chat dock, or the /chat and /library assistants) or as a **local coding agent** driving `vidfarm-devcli` (Claude Code / Codex on the user's machine). Same three paintbrushes, different reach — be honest about which surface you are and route heavy work accordingly:
54
+
55
+ - **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.
56
+ - **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.
57
+ - **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.
58
+ - **Never send the user away just to read knowledge.** Deeper skill knowledge is always a **tool call** away in-place: call `load_skill` (e.g. `load_skill('vidfarm-director', file='references/editor-workflows.md')`, or a craft pack like `editor-capabilities` / `hyperframes-animation`) to pull the exact reference you need mid-conversation. Only recommend switching surfaces for the WORK (a heavy transformation), never for the information.
59
+
60
+ ## Read Only What You Need
61
+
62
+ Read only the relevant reference file for the current task.
63
+
64
+ - Template discovery, auth, fork/publish/share/cost flow: `references/core-workflows.md`
65
+ - Timeline editing, decompose, captions, motion, AI placement: `references/editor-workflows.md`
66
+ - Raws hunts, My Files, recurring characters, asset retrieval: `references/assets-and-sourcing.md`
67
+ - REST automation, `vidfarm` command surface, local serve loop, skill packs: `references/automation-and-local-dev.md`
68
+ - Getting-started interviews, strategy docs, onboarding flow: `references/onboarding.md`
69
+ - Primitive routes such as TTS, STT, music, overlays, background removal, product placement: `references/primitives.md`
70
+
71
+ ## Quick Router
72
+
73
+ Choose the narrowest path that satisfies the request.
74
+
75
+ 1. If the user needs help figuring out what to make, read `references/onboarding.md` first.
76
+ 2. If the user already knows the goal and needs a suitable template, read `references/core-workflows.md` and use the template discovery flow.
77
+ 3. If the task is “change this video,” read `references/editor-workflows.md`.
78
+ 4. If the task is “find footage” or “use our existing assets,” read `references/assets-and-sourcing.md`.
79
+ 5. If the task is scripted, local, CI-driven, or `vidfarm serve`-based, read `references/automation-and-local-dev.md`.
80
+ 6. If the task explicitly asks for a primitive or needs specialized generation/transcription work, read `references/primitives.md`.
81
+
82
+ ## Non-Negotiables
83
+
84
+ - API-key auth is the `vidfarm-api-key` header. Do not use `Authorization: Bearer`.
85
+ - Do not manipulate composition HTML by string concatenation. Parse, edit, and re-serialize the DOM.
86
+ - Do not call the renderer directly. Rendering goes through `POST /api/v1/compositions/:forkId/render`.
87
+ - Do not store provider secrets in composition HTML or JSON.
88
+ - Treat `forkId` as an unguessable bearer token for read access.
89
+ - Submission routes are generally not idempotent. Especially for renders and expensive primitives, check status before retrying.
90
+ - In the web editor, use CSS/declarative motion only. Script-bearing HTML is stripped or rejected there.
91
+
92
+ ## Recommended Recipes
93
+
94
+ Use these when the user’s task matches the pattern closely.
95
+
96
+ - Template selection and first fork: `recipes/find-and-fork-template.md`
97
+ - Full re-theme while preserving the format’s feel: `recipes/retheme-template.md`
98
+ - Local pull/edit/render/approve loop: `recipes/local-edit-render-approve.md`
99
+ - New-director onboarding and durable context capture: `recipes/onboard-a-new-director.md`
100
+
101
+ ## Output Posture
102
+
103
+ - Prefer concrete actions over abstract discussion.
104
+ - Name the chosen path explicitly: template reuse, raws hunt, local serve, cloud render, etc.
105
+ - Surface cost tradeoffs before expensive generation.
106
+ - When in doubt between a broad reference and a recipe, start with the recipe.
22
107
 
23
108
  ## Mental model
24
109
 
@@ -95,7 +180,7 @@ To bring a new viral video into the catalog as a **private** template you own, i
95
180
  - `DELETE /discover/templates/:entryId` — remove a private inspiration/template you own (accepts either the `inspiration_...` or minted `template_...` id).
96
181
  - `POST /api/v1/inspirations/:inspirationId/decompose { user_prompt? }` — AI-decompose an inspiration's downloaded video into scenes (requires a saved provider key; same 120s source cap as auto-decompose).
97
182
 
98
- devcli: `vidfarm inspiration-add <url|file.mp4>` (a local file path runs the presign→PUT→finalize flow for you), `vidfarm inspiration-rm <id>`, `vidfarm inspiration-decompose <id>`, `vidfarm discover [query]` to browse/search templates, and `vidfarm videos [query] [--mine]` to browse/search source inspirations. On the web, the Discover page's **Add Template** modal accepts either a URL or a file upload.
183
+ devcli: `vidfarm inspiration-add <url|file.mp4>` (a local file path runs the presign→PUT→finalize flow for you), `vidfarm inspiration-rm <id>`, `vidfarm inspiration-decompose <id>`, `vidfarm discover [query]` to browse/search templates, `vidfarm public-raws [query] [--category <key>] [--type <raw_type>] [--bookmark <raw_id>]` to browse/save the public raws catalog, and `vidfarm videos [query] [--mine]` to browse/search source inspirations. On the web, the Discover page's **Add Template** modal accepts either a URL or a file upload.
99
184
 
100
185
  ## Fork a template
101
186
 
@@ -136,18 +221,163 @@ Content-Type: application/json
136
221
 
137
222
  Returns the same fork metadata shape as `POST /api/v1/compositions` (201). devcli: `vidfarm clone <forkId> [--from current|default] [--version N] [--title …]`. In the in-editor AI chat this is the `editor_action` `fork_composition` action (`fork_from: current|default`).
138
223
 
139
- ## Automate a template via REST
224
+ ## Render (publish to MP4)
140
225
 
141
- Templates now have the same job-backed REST pattern as primitives, so a script can run them repeatably without the editor UI:
226
+ Rendering publishes the fork's current working state to an MP4 using HyperFrames (cloud Lambda fan-out on the deployed host, or the free in-process renderer on a local `vidfarm serve` box). In the Trackpad Editor this is the **Render** button. The REST route is `/render`; the older `/export` path is kept as a deprecated alias for already-published devcli clients (treat "Render", "publish", `/render`, and `/export` as the same operation). The devcli exposes it as `vidfarm render <forkId> [--wait]`; scripted local dirs can use `vidfarm render <forkId> --dir ./work --wait`.
142
227
 
143
228
  ```
144
- POST /api/v1/templates/:templateId/operations/:operationName
229
+ POST /api/v1/compositions/:forkId/render
145
230
  Content-Type: application/json
146
231
 
147
- { "tracer": "my-run", "payload": { ... }, "webhook_url": "https://..." }
232
+ { "title": "optional", "version": null, "tracer": "optional-trace-id", "html": "optional HTML to save to working state before rendering" }
148
233
  ```
149
234
 
150
- The route returns `202` with a `job_id`, and you can poll the job with `GET /api/v1/user/me/jobs/:jobId` or the template-scoped `GET /api/v1/templates/:templateId/jobs/:jobId`. devcli wraps the same flow as `vidfarm template run <templateId> <operationName> --payload-file payload.json --wait`.
235
+ Returns 202 with `{ ok, renderId, status, progress, expectedOutputPublicUrl, outputUrl, cost, title, version, ... }` where `expectedOutputPublicUrl` is the deterministic public MP4 URL and `version` is the snapshot version created for this publish. Failure modes: `402` if the fork owner is not on a paid plan, `409` while GhostCut subtitle removal is still `pending` (retry after `/remove-video-captions-poll` reports done/failed), `412` if the provider-key preflight fails. Poll:
236
+
237
+ ```
238
+ GET /api/v1/compositions/:forkId/renders/:renderId
239
+ ```
240
+
241
+ Response includes `{ ok, renderId, status: "RUNNING" | "SUCCEEDED" | "FAILED", progress, framesRendered, totalFrames, cost, expectedOutputPublicUrl, outputUrl, outputS3Uri, errors }` (`progress` is 0..1). On success, `expectedOutputPublicUrl` is the durable public MP4 URL and `outputUrl` remains the completion-time field.
242
+
243
+ Every publish creates an immutable version snapshot at `versions/<N>/composition.html` and `versions/<N>/composition.json`.
244
+
245
+ The Web UI **Render** button and devcli render both use this same endpoint. The fast `202` response includes the deterministic `expectedOutputPublicUrl` so a caller can store or pass along the final public S3 URL before the render has completed, then poll by `renderId` until `status` settles.
246
+
247
+ ## Approve a finished post
248
+
249
+ A render produces a bare MP4 URL. **Approving** wraps that MP4 (plus caption, title, pinned comment, and any carousel slides) into a shareable preview page — the phone-mockup page a human opens to review and copy the post.
250
+
251
+ ```
252
+ POST /api/v1/approved/posts
253
+ Content-Type: application/json
254
+
255
+ { "caption": "required", "title": "optional", "pinned_comment": "optional", "tracer": "optional",
256
+ "media": [ { "url": "https://.../output.mp4", "kind": "video", "role": "primary" } ] }
257
+ ```
258
+
259
+ `caption` is required. `media[]` items take `{ url, kind?: "image"|"video"|..., role?: "primary"|"slide"|... }`. Response (`201`) is the approved post including **`share_url`** — the prod frontend page for previewing/sharing. Surface that URL to the user; it is the headline output of this step.
260
+
261
+ - `GET /api/v1/approved/posts` — list your approved posts
262
+ - `GET /api/v1/approved/posts/:postId` — read one (returns `share_url`, `download_zip_url`)
263
+
264
+ devcli: `vidfarm approve --video <mp4-url> --caption "..."` prints the `share_url` as a first-class openable link; `vidfarm posts` lists, `vidfarm post <id>` reads one.
265
+
266
+ ## Schedule a post
267
+
268
+ Schedule an approved post to a connected destination channel (FlockPoster social account or email) at one ISO timestamp:
269
+
270
+ ```
271
+ POST /api/v1/approved/posts/:postId/schedules
272
+ Content-Type: application/json
273
+
274
+ { "destination_type": "flockposter" | "email", "destination_id": "<channel or email>",
275
+ "scheduled_at": "2026-07-10T14:00:00Z", "timezone": "America/New_York", "additional_notes": "optional" }
276
+ ```
277
+
278
+ Minimum 10-minute lead time. Response (`201`) is the schedule record. Browse existing schedules with `GET /api/v1/approved/posts/:postId/schedules`.
279
+
280
+ devcli: `vidfarm schedule <postId> --at <iso> --to <destinationId> [--type flockposter|email]`, and `vidfarm schedules <postId>` to browse.
281
+
282
+ ## Version history
283
+
284
+ ```
285
+ GET /api/v1/compositions/:forkId/versions?limit=100
286
+ ```
287
+
288
+ Returns `{ versions: [{ version, reason, message, created_by, created_at, render_job_id, composition_url, composition_data_url }], next_cursor }`. Reason is `publish`, `clone`, `manual`, or `migration`. To snapshot the working state manually, `POST /api/v1/compositions/:forkId/versions { "message": "optional" }`.
289
+
290
+ To view a past cut without reverting:
291
+
292
+ ```
293
+ GET /api/v1/compositions/:forkId/versions/:version/composition.html
294
+ ```
295
+
296
+ To revert the working state to a version, `PUT` the historical HTML/JSON back to the working keys.
297
+
298
+ ## Sharing and visibility
299
+
300
+ Every fork has a visibility: `private` (default), `unlisted` (legacy), or `public` (view-only).
301
+
302
+ ```
303
+ PATCH /api/v1/compositions/:forkId/visibility { "visibility": "public" }
304
+ ```
305
+
306
+ Public forks are accessible at `/editor/<templateId>?fork=<forkId>` (view-only for non-owners). Public visibility is the simplest sharing surface.
307
+
308
+ Fine-grained sharing uses per-user permissions and share links (all body fields are snake_case):
309
+
310
+ - `POST /api/v1/compositions/:forkId/permissions { grantee_email | grantee_customer_id, role, expires_at? }` — grant a specific registered user access. Exactly one of `grantee_email` or `grantee_customer_id` is required. Roles: `viewer`, `editor`, `publisher`. Returns `{ permission_id, fork_id, grantee_type, grantee_id, role, granted_by, expires_at, created_at }`.
311
+ - `POST /api/v1/compositions/:forkId/share-links { role, expires_at? }` — create a token URL. Anyone with the token gets the role. Returns `{ token, share_url, role, expires_at, ... }`.
312
+ - `GET /api/v1/compositions/shared/:token` — access via share link
313
+ - `DELETE /api/v1/compositions/:forkId/permissions/:permissionId` — revoke a grant
314
+ - `DELETE /api/v1/compositions/:forkId/share-links/:token` — revoke a link
315
+
316
+ ## Cloning a fork
317
+
318
+ Directors can clone another fork (their own or a shared one) as a new starting point:
319
+
320
+ ```
321
+ POST /api/v1/compositions/:forkId/clone
322
+ Content-Type: application/json
323
+
324
+ { "parent_version": "<N or omit for latest>", "title": "optional" }
325
+ ```
326
+
327
+ The `:forkId` in the path is the fork being cloned. The new fork inherits the parent's HTML/JSON at the specified version and creates a version-1 snapshot with `reason: "clone"`.
328
+
329
+ ## Delete a fork
330
+
331
+ ```
332
+ DELETE /api/v1/compositions/:forkId
333
+ ```
334
+
335
+ Soft-delete only. The fork's `deletedAt` is set. Versions and share links are preserved for audit.
336
+
337
+ ## Cost spectrum (free → $10+/video) — default to saving the director money
338
+
339
+ There is no single price for a video. The **approach** the director picks sets the cost, across a wide spectrum. Always steer toward the cheapest approach that still meets their goal, and make the tradeoff explicit rather than silently choosing an expensive path.
340
+
341
+ | Approach | Typical cost | How |
342
+ |---|---|---|
343
+ | **Reuse + render locally** | **free** | Fork an already-decomposed template, swap captions / images / video with existing MP4s (from **My Files**, the director's **local computer** — reference files straight off disk with `place --src ./file`, no upload — or a **web search**), and render **locally** via `vidfarm serve` (native in-process HyperFrames render — free and unguarded, no cloud). |
344
+ | **Reuse + cloud render** | **~$0.001 – $0.03** | Same reuse, but render on the cloud renderer (`POST /compositions/:forkId/render`, ~$0.01–$0.10 depending on length/res). Cheap **image** generation/edits fit in this band too. |
345
+ | **AI-generate some scenes** | **~$1** | Replace a few scenes with AI-generated video clips for high specificity/customization (see the "Generate AI media" section). |
346
+ | **Heavy AI generation** | **$10+** | Many/long AI video clips, custom characters, fully bespoke scenes. |
347
+
348
+ Rules of thumb:
349
+
350
+ - **Reusing existing footage — or footage the director films themselves — is the cheapest.** AI-generating characters/scenes is the most expensive. Editing captions/images on a reused template is nearly free.
351
+ - **Local render is free; cloud render costs pennies.** If the director is iterating with a coding agent on a `vidfarm serve` box, they can render locally at no charge until they want the durable cloud MP4.
352
+ - **Decompose is a one-time ~$0.10** (smart decompose ≈ provider passthrough + GhostCut ~$0.10/30s) per **new** source video — but the director can **skip it entirely** by forking a template that's **already decomposed** in the catalog.
353
+ - **Image generation is cheap** — use it freely, no need to ask permission.
354
+ - **AI video generation is expensive — ask the director's permission before using it.** Default to reuse/local/image approaches unless they've okayed video gen or told you the budget covers it.
355
+ - **Ask about budget.** During Getting Started (and whenever it's relevant per editor project, or when the director asks about cost), ask roughly what they want to spend per video, and pick the approach band that fits. If they haven't said, assume the cheapest approach that works.
356
+
357
+ The director always chooses the method that works for them — the point is to surface the tradeoff and default to thrift, not to force the cheapest path.
358
+
359
+ **How the approach is painted — the three paintbrushes.** The cost of a video is set by *which brush* recreates each scene: (1) **raw clips** remixed from existing footage, (2) **HTML/JS hyperframes** (animated text/images/graphics), (3) **pure AI generation** (image/video/voice/music, most expensive). A technical replication decomposition names, per beat, when/where/what each brush should be, and offers two harnesses — **(A) cheap & efficient** (recaption, bg+fg video memes, hyperframe animation, reuse/greenscreen the asset library, AI only if needed) and **(B) best quality** (AI video by default, AI-image storyboarding first, adversarial grading with a coding agent). Default to (A); reserve (B) for premium/budgeted work. Full methodology in `references/editor-workflows.md` (“The three paintbrushes & two replication harnesses”).
360
+
361
+ ## Billing
362
+
363
+ Vidfarm charges directly in USD from the caller's wallet. There are no credits.
364
+
365
+ - **Wallet top-up** — Stripe checkout via the Settings UI, backed by `POST /settings/wallet/funding-link` (browser session; returns `{ checkout_url, client_reference_id }`)
366
+ - **Balance & history** — shown on the Settings page; billing events via `GET /u/:customerId/settings/wallet/events` (browser session). There is no `/api/v1/user/me/wallet` REST endpoint.
367
+ - **Cost anchors** (see the Cost spectrum section above for how these combine per approach):
368
+ - Local render on a `vidfarm serve` box (in-process HyperFrames): **free**
369
+ - Cloud render (HyperFrames Lambda fan-out): typically $0.01 – $0.10 per MP4 depending on length/resolution
370
+ - Image generation / edit: cheap (provider passthrough) — use freely
371
+ - AI **video** generation: expensive ($1–$10+/video territory) — ask permission first
372
+ - Auto-decompose smart mode: pass-through of caller's AI provider spend (~$0.10 one-time with GhostCut), or skip it by forking an already-decomposed template
373
+ - Auto-decompose time-slice: free
374
+ - GhostCut subtitle removal: ~$0.10 per 30 seconds of source video
375
+
376
+ Framing:
377
+
378
+ - Vidfarm is optimized for cost efficiency, not markup
379
+ - Total director cost per video is typically far below alternatives
380
+ - Platform retains a small safety buffer for operational correctness
151
381
 
152
382
  ## Agentic editing: the three axes (SWAP ↔ REPLACE)
153
383
 
@@ -162,6 +392,39 @@ Name the plan back in these terms ("I'll SWAP the captions and REPLACE the scene
162
392
 
163
393
  **Audio is natively multi-track — overlay narration + music + SFX, each at its own volume.** A composition mixes UNLIMITED simultaneous `<audio>` layers; each sits on its own `data-track-index` and carries its own `data-volume` (0–2, default 1), and the runtime mixes them with per-track volume honored identically in the preview and the exported MP4 (a real ffmpeg `amix` of every audio layer at render). So you never need a pre-mixed file — lay **narration/voiceover at ~1.0 on one track, a music bed at ~0.1–0.2 on a separate track, and SFX on their own tracks**, each via `add_layer kind=audio` (web) / `vidfarm place --kind audio --volume …` (devcli), tuning levels later with the Inspector's Volume slider or `set_layer_media` (`volume`, `muted`). **The key move when recreating a template whose original baked music + narration into ONE audio track: rebuild it as TWO independent tracks** — a fresh narration track (`/audio/speech`, or same-voice reword via `/audio/regenerate-speech` / `vidfarm speech regenerate`) at ~1.0 and a separate real music track at ~0.1–0.2 — then mute/remove the original combined source-audio layer. This gives the director independent voice and music volume, and works around AI TTS being unable to emit narration+music in one file: you compose the mix on the timeline. Honesty: you can't un-mix / stem-separate the original's baked audio — the two tracks are a fresh narration track **plus** a real music file (owned / user-provided / `browse_files` / generated with `/api/v1/primitives/music/generate` (`vidfarm music`)), never a faked or duplicated voice layer.
164
394
 
395
+ ## The three paintbrushes & two replication harnesses
396
+
397
+ Vidfarm is deliberately founder-friendly and thrift-first: **we do not spend expensive AI credits on every scene.** The three axes above tell you *what* is being re-worked; the **three paintbrushes** tell you *how* to paint each scene once you've decided to REPLACE it. A **technical replication decomposition** is exactly this — for the target template, name **when / where / what** each beat should be painted with.
398
+
399
+ > **This decomposition is now a materialized artifact.** Every decomposed fork carries a **replication harness** (`replication-harness.json` / `editor_context.replication_harness` / `GET .../replication-harness.json`) that already contains BOTH plans as concrete per-beat brush assignments plus reusable-asset guidance and viral-DNA guardrails. When it's present, **execute its beats** instead of re-deriving them; the methodology below is how it was built and the fallback when it's absent (older forks / free tier). See the route section further down.
400
+
401
+ 1. **Raw clips** — cut + remix existing long-form / short-form footage. The workhorse. Source in cost order: the director's own `/raws` + `/files` library → HUNT new raws out of a URL/VOD (`vidfarm raws scan`) → only then generate. Combining a **background video + a foreground video** (greenscreen / picture-in-picture) covers a huge share of "video meme" formats with zero generation.
402
+ 2. **HTML/JS hyperframes** — video-from-HTML. Animate text, image elements, logos, stickers, charts and lower-thirds with CSS/declarative motion (web editor) or JS adapters (anime.js/GSAP/Lottie/Three, devcli only). Deterministic, cheap, re-themeable — the right brush for titles, kinetic captions, data beats, brand cards, and any "graphic" scene.
403
+ 3. **Pure AI generation** — AI image / video / voice / music. The most expensive brush (AI **video** especially, $1–$10+); the last resort for a beat no clip or hyperframe can cover.
404
+
405
+ **Reusable asset library.** Directors accumulate logos, stickers, reactions, b-roll, a-roll, and a brand media kit. The decomposition should have an **opinion on when and where to reuse these** — reactions and stickers for beats, the media kit for brand identity, b-roll to cover cuts. But usage can quietly break the format: reskinning past a load-bearing beat kills what made it go viral. **Anchor every asset/paintbrush call to the harness** (`viral_dna`, `viral_dna.emotional_punch`, `editor-harness.json`, `static_vs_pivot`) — reuse and re-skin the *dressing*, preserve the *DNA*.
406
+
407
+ For any replication, offer the director **two harnesses** and recommend (A) unless they've asked for premium or the budget covers it:
408
+
409
+ **(A) Cheap & efficient** — the default. Reach for, in roughly this order:
410
+ - **Recaption text** — often the whole job is rewriting the caption/title layers (`set_captions` / `set_layer_text`). A two-minute change.
411
+ - **Background + foreground video** memes — composite two clips instead of generating one.
412
+ - **Animate HTML + image elements** with hyperframes (Ken Burns on a still, kinetic type, animated logo/sticker) instead of AI motion.
413
+ - **Reuse media elements** from the library; **AI-generate a reusable element once** (a sticker, a logo treatment, a character sprite) then reuse it across scenes rather than regenerating per-scene.
414
+ - **Greenscreen** — chroma-key a subject onto new backgrounds (`image_remove_background_greenscreen` / `media_overlay`).
415
+ - **Raw-clip long-form and remix** — hunt + rearrange existing footage.
416
+ - Lean on the **library of memes / reactions / b-roll / a-roll** and the **brand media kit**.
417
+ - **Only if genuinely needed**, use AI image → and, last, AI video / voice / music.
418
+
419
+ **(B) Best quality** — when the director wants premium and budget allows:
420
+ - **AI video generation by default** for hero scenes.
421
+ - **Storyboard with AI image first** (cheap stills to lock composition/subject), then generate motion from those references for consistency.
422
+ - **Adversarially grade** the output with a coding agent (Claude Code / Codex / any capable AI agent) — render, critique against the harness, iterate — rather than shipping the first generation.
423
+
424
+ **Be generous to free-tier / no-account directors.** Vidfarm's replication harness is open source, and an everyday capable AI agent should be able to follow this decomposition and rebuild a template **without a Vidfarm wallet** — clipping, hyperframes, and local render (`vidfarm serve`) all run on free compute. Keep the recommendation self-contained and pragmatic; don't gate the cheap path behind paid primitives.
425
+
426
+ **Free tier does its OWN decomposition.** The catch on the free tier (local devcli, no account): nobody has pre-decomposed the reference video for them. There is no `video-context.json`, `editor-harness.json`, `replication-harness.json`, or `scene-annotations.json` to `vidfarm pull` — **the user and their agent must watch the video, break it into scenes/audio/text, read the viral DNA, and choose the three paintbrushes by hand.** Coach them through that; the harness above is the method to do it. **Paid Vidfarm accounts skip the manual pass**: they get a large library of **pre-decomposed viral templates** (the decompose passes already run) plus **prompt-harness best practices distilled from platform-scale learnings** — so a fork arrives already carrying its DNA, editor harness, **replication harness (both cheap + best-quality plans, per-beat brush assignments)**, and per-scene recreation annotations. It's fair to surface that upgrade when a free-tier user is decomposing by hand.
427
+
165
428
  ## Edit in the Trackpad Editor
166
429
 
167
430
  The editor is a full timeline surface:
@@ -249,6 +512,41 @@ Read-only state (does **not** advance the job or bill): `GET /api/v1/composition
249
512
  "delivery": "flat VO cadence, hold the beat before the punch, beat drop lands on the reveal",
250
513
  "preserve": ["keep the beat drop on the reveal cut", "keep the flat unbothered delivery"]
251
514
  },
515
+ "static_vs_pivot": {
516
+ "summary": "Keep the reveal structure and audio swagger; swap the subject-specific visuals and copy.",
517
+ "scene_replacement": {
518
+ "overall": "replace_some",
519
+ "reason": "the proof/reveal beats are load-bearing, but the subject matter can pivot",
520
+ "must_keep": ["final reveal beat", "setup-to-payoff contrast"],
521
+ "should_replace": ["subject-specific graphics", "brand-specific screenshots"],
522
+ "load_bearing_scenes": ["hook setup", "payoff reveal"]
523
+ },
524
+ "narration": {
525
+ "overall": "customize_script",
526
+ "reason": "the spoken words are source-specific and stop making sense after a subject swap",
527
+ "same_timing_required": true,
528
+ "use_premium_tts_when_customizing": true
529
+ },
530
+ "music": {
531
+ "overall": "recreate_under_narration",
532
+ "reason": "the music energy matters, but the original combined track should not stay under a rewritten script",
533
+ "volume_guidance": "keep low under VO unless the beat-drop is the joke engine"
534
+ },
535
+ "captions": {
536
+ "overall": "retime_to_new_narration",
537
+ "reason": "caption animation is tied to the spoken words",
538
+ "animation_dependency": "word-by-word timing follows narration cadence"
539
+ },
540
+ "visual_rebuild": {
541
+ "default_strategy": "ai_images_with_ken_burns",
542
+ "reason": "most replacement scenes are graphic/still-like and do not need expensive motion video"
543
+ },
544
+ "viral_dna": {
545
+ "must_keep": ["hook structure", "payoff timing", "audio attitude"],
546
+ "flexible": ["subject matter", "product imagery", "copy details"],
547
+ "do_not_change": ["core reveal mechanic", "caption cadence"]
548
+ }
549
+ },
252
550
  "preserve": ["..."], "avoid": ["..."], "promotions": ["..."], "keywords": ["..."]
253
551
  }
254
552
  }
@@ -258,6 +556,15 @@ Read-only state (does **not** advance the job or bill): `GET /api/v1/composition
258
556
 
259
557
  **`viral_dna.emotional_punch`** is the FEELING of the format and what makes it land — the part a remix most often flattens. Where hook/retention/payoff are the mechanical structure, this captures the vibe, the joke, and the intonation. It is now **audio-aware**: the decompose feeds the transcript into the reasoning, so `mechanism`/`delivery` account for the trending sound, the music-bed drop, and vocal cadence (on short-form the sound is frequently the biggest driver of the punch). When you re-theme or remix, rebuild `humor`'s joke around the new subject rather than dropping it, match `tone`/`delivery` so intonation and comedic timing survive, and never trade the `peak_moment` payoff for a flat product plug.
260
558
 
559
+ **`viral_dna.static_vs_pivot`** is the first-class lazy-prompt playbook. It tells you what must stay versus what can pivot when the user gives a thin prompt like "recreate this for my business" and little else. Use it to decide:
560
+
561
+ - whether most scenes should be replaced or only some
562
+ - whether narration must be customized or the original audio should stay
563
+ - whether background music must be recreated under a new voice
564
+ - whether animated captions need fresh timing against new narration
565
+ - whether cheap AI images + Ken Burns are sufficient, or whether real/AI motion footage is required
566
+ - which beats are load-bearing viral DNA versus merely subject-specific dressing
567
+
261
568
  Use it whenever you need to know what the video says or shows: writing/translating captions, matching hooks or dubs to spoken audio, or planning scene-level edits.
262
569
 
263
570
  - **Editor chat (frontend AI)** exposes this as the optional `video_context` tool — the copilot calls it on demand.
@@ -287,63 +594,37 @@ Use it whenever you need to know what the video says or shows: writing/translati
287
594
  }
288
595
  ```
289
596
 
290
- **Use it to pick concrete moves:** `typography.caption_style` maps to a `set_captions` preset; `transitions.*` to a `set_transitions` call; keep `pacing` (cut rhythm / avg_scene_seconds) when adding or splitting scenes; follow `broll.reliance`/`sourcing` to decide HUNT raws (`/raws/scan`) vs generate; lay `audio.*`. **`emotional`** is HOW to keep the FEELING while you swap the subject — the vibe/joke/intonation/sound are the first things a remix flattens, so honor `emotional.comedic_timing` (the held beat / hard cut that sells the joke), preserve `emotional.intonation` when you re-voice narration, and treat every `emotional.vibe_anchors` entry as a must-do (on short-form the trending sound / beat drop is often the biggest carrier of the punch — keep it and land it on the same cut). **Protect** any beat in `important_scenes` or with `must_keep:true` / `importance:"critical"` — swap the subject but preserve its timing, role, and caption cadence. Treat `editing_bias`/`do`/`dont` as hard constraints; an explicit user instruction still wins. `status:"none"` → run `POST /auto-decompose` first.
597
+ **Use it to pick concrete moves:** `typography.caption_style` maps to a `set_captions` preset; `transitions.*` to a `set_transitions` call; keep `pacing` (cut rhythm / avg_scene_seconds) when adding or splitting scenes; follow `broll.reliance`/`sourcing` to decide HUNT raws (`/raws/scan`) vs generate; lay `audio.*`. **`emotional`** is HOW to keep the FEELING while you swap the subject — the vibe/joke/intonation/sound are the first things a remix flattens, so honor `emotional.comedic_timing` (the held beat / hard cut that sells the joke), preserve `emotional.intonation` when you re-voice narration, and treat every `emotional.vibe_anchors` entry as a must-do (on short-form the trending sound / beat drop is often the biggest carrier of the punch — keep it and land it on the same cut). Pair that with `viral_dna.static_vs_pivot`: if narration is `customize_script`, revoice and then retime captions/scenes that depend on the old VO; if visual_rebuild is `ai_images_with_ken_burns`, do the cheap still+motion path before reaching for AI video; if a scene is listed under `load_bearing_scenes`, preserve its beat even when you swap the subject. **Protect** any beat in `important_scenes` or with `must_keep:true` / `importance:"critical"` — swap the subject but preserve its timing, role, and caption cadence. Treat `editing_bias`/`do`/`dont` as hard constraints; an explicit user instruction still wins. `status:"none"` → run `POST /auto-decompose` first.
291
598
 
292
599
  - **Editor chat (frontend AI)** already receives the harness inline in `editor_context.editor_harness` (no tool call needed); it can also fetch this route via `http_request`.
293
600
  - **Desktop agents (Claude Code / Codex)**: `vidfarm pull` writes `editor-harness.json` to the fork dir alongside `video-context.json`, and also materializes a merged agent bundle at `.harness/context.json` plus `.harness/agent-guide.md`. This deliberately does **not** write a top-level `AGENTS.md`, because many user repos already own that file. For local scripting or agentic edits, read `.harness/agent-guide.md` and `.harness/context.json` FIRST; do not freestyle from `composition.html` alone. The `pull` grounding line reports the generated brief paths.
294
601
 
295
- ## Raws (long-form short-form raws)
602
+ ### `GET /api/v1/compositions/:forkId/replication-harness.json` the technical replication analysis
296
603
 
297
- Mine a **long-form** video (podcast, stream VOD, webinar, any YouTube/TikTok/IG/X URL, or an upload) into a library of tagged, searchable **raws**. This is the `/raws` surfacebrowse it at `https://vidfarm.cc/library/raws` (the Library page's "Approved / Raws" tabs).
604
+ Read-only, non-billing. The **BUILD** companion to the editor harness (which is STYLE): the same "three paintbrushes & two replication harnesses" methodology above, but materialized per-beat for THIS template. Where `editor-harness.json` says *how to edit to feel right*, this says *which tool to reach for* so an agent (or a free-tier user) rebuilds thrift-first instead of AI-generating every scene.
298
605
 
299
- **Start a hunt** — `POST /raws/scan` (async: returns `202 { scan_id }` immediately):
300
-
301
- ```jsonc
606
+ ```
302
607
  {
303
- // ONE source (pick one):
304
- "source_url": "https://youtube.com/watch?v=…", // downloaded into your temp folder first
305
- "temp_file_id": "…", // a video you uploaded to temporary-files
306
- "attachment_id": "…", // a video already in My Files
307
- "s3_key": "" , // an already-staged object
308
-
309
- // What to hunt for free text; inline hints are parsed too
310
- "prompt": "people holding food up to their face, no text on screen",
311
-
312
- // Hunt controls (all optional; also expressible inline in the prompt):
313
- "ranges": ["12:30-15:45", "20:00-22:10"], // ONLY hunt these source windows big cost saver on long videos
314
- "target_duration_sec": 30, // SOFT length band, not a hard cut: 10→5-20s, 30→20-40s, 60→40-80s, 120→80-160s
315
- "aspect": "9:16", // crop every raw: 9:16 | 16:9 | 4:3 | 1:1 (synonyms: vertical/portrait, horizontal/landscape, square)
316
- "crop_focus": "center", // center | top | bottom | left | right (top biases toward faces)
317
- "avoid_text": true, // prefer scenes WITHOUT burned-in captions/on-screen text — a scene-SELECTION filter, NEVER GhostCut on the source
318
- "tracer": "my-campaign" // rolls up all the hunt's billing/observability events
608
+ "ok": true,
609
+ "status": "ready" | "empty" | "none",
610
+ "harness": {
611
+ "summary": "Recaption + reuse: 90% of this is raw clips + hyperframes captions; AI only for the impossible reveal shot.",
612
+ "recommended_strategy": "cheap_efficient",
613
+ "paintbrush_rationale": "Talking-head + text overlays — footage is huntable and every graphic is HTML; AI video buys almost nothing here.",
614
+ "motion_style": { "animation_feel": "punchy kinetic type, hard cuts, sticker pop-ins", "graphic_elements": ["count-up counter", "arrow pointer", "sticker reactions"], "hyperframes_candidates": ["all captions", "the stat counter", "the end-card logo lockup"], "typography_motion": "per-word pop, active word highlighted" },
615
+ "asset_opportunities": ["reuse the brand-kit logo lockup on the end card", "a 'mind-blown' sticker for the reveal beat"],
616
+ "viral_dna_guardrails": ["never cover the reveal frame with a sticker", "keep the trending sound; land the beat drop on the reveal cut"],
617
+ "free_tier_note": "ffmpeg to clip the source, an HTML→video renderer (hyperframes) for every caption/graphic, chroma-key for greenscreen, a local TTS for narration — no paid primitive needed.",
618
+ "cheap": { "strategy": "cheap_efficient", "one_liner": "...", "method_mix": "80% raw_clip + hyperframes; ai_gen only the reveal", "brush_breakdown": { "raw_clip": "...", "hyperframes": "...", "reusable_asset": "...", "ai_gen": "..." },
619
+ "scenes": [{ "role": "hook", "timestamp": "0:00-0:03", "method": "raw_clip", "fallback_method": "hyperframes", "technique": "recaption over a hunted talking-head clip", "instructions": "...", "assets_needed": ["hook clip from /raws"], "viral_dna_guard": "keep the on-cam energy + first 3 words", "est_credits": "low" }],
620
+ "reusable_assets": [{ "asset_type": "sticker", "where": "reveal beat", "source": "user_library", "purpose": "punch up the reveal", "reuse_note": "reuse across the series", "viral_dna_risk": "do NOT let it cover the reveal frame" }],
621
+ "pipeline": ["hunt hook+payoff raws", "recaption in hyperframes", "reuse logo end-card", "AI-gen only the reveal"], "ai_spend": "one image→video for the reveal only", "tradeoffs": "...", "do": ["..."], "dont": ["..."] },
622
+ "quality": { "strategy": "best_quality", "one_liner": "AI-gen hero scenes, storyboard-first", ... }
623
+ }
319
624
  }
320
625
  ```
321
626
 
322
- - **Poll** `GET /raws/scan/:scanId` until the source `status` is `complete` (or `failed` — crashed pipelines are reconciled to `failed`, pollers never spin).
323
- - **Results**: `GET /raws/feed?source=<source_video_id>` (or the whole library), hybrid search via `POST /raws/search` `{ query: "someone looks confused" }`, per-raw download at `GET /raws/:clipId/download`. Raws carry taxonomy tags, a description, a transcript, an `aspect` field when cropped, the hunt's `tracer`, and (with a gemini/openai key) a semantic embedding.
324
- - **Billing** — **AWS compute only** (`clip_scan_lambda` GB-seconds + Step Functions transitions, a fraction of a cent for typical hunts; the 202 response includes a `compute_estimate`). The AI tagging/refine runs on **your saved provider key (BYOK)** and is never wallet-billed. Submission is wallet-gated (402 when empty).
325
- - **The original stays temporary** — URL-ingested and uploaded sources live ONLY in your temp folder (`users/…/temporary/clip-sources/…`) with a hard **30-day TTL** (auto-deleted from S3 + the temp-file list). The hunted raws themselves are durable.
326
- - **Captions rule** — "no captions / no on-screen text" hunts are handled by scene SELECTION (the refine pass drops text-heavy scenes). **Never run GhostCut caption removal on a long-form source** (it is hard-capped at ~15 minutes); to actually erase burned-in text, apply `POST /api/v1/primitives/videos/remove-captions` to individual FINISHED raws afterwards.
327
- - **Clip an EXACT subrange (no AI hunt)** — when you want one precise hand-picked in/out rather than an open-ended hunt, use the **Clipper**: the web page `https://vidfarm.cc/tools/clipper` (previews the source straight from its URL, HH:MM:SS:NNNN in/out, live length estimate, multi-clip session grouped by tracer), the endpoint `POST /raws/clip-range` `{ source_url|preview_url|temp_file_id, start_sec, end_sec, tracer?, folder_path?, name? }` (trims exactly that window into one raw — compute-only, no AI/key), or the devcli twin `vidfarm clipper <video-url|file> --start <t> --end <t> [--tracer NAME --folder NAME --name TXT]`. The Library "Import source" button opens the Clipper; each save lands in `/raws/<folder>` (defaults to the tracer). Reach for `/raws/scan` for multi-clip hunts, `/raws/clip-range` for surgical single cuts. (The masked-image and time-scoped-video editors live alongside it at `/tools/image` and `/tools/video`.)
328
-
329
- **devcli (local-first — this is the default way to hunt on your own machine):**
330
-
331
- ```bash
332
- # Local machine power: local ffmpeg + your local claude/codex CLI subscription (no API key needed)
333
- vidfarm raws scan ./podcast.mp4 --range "12:30-15:45" --duration 30 --aspect vertical --no-text \
334
- --prompt "guest reaction moments"
335
-
336
- # Provider keys are the fallback (--provider gemini|openai|openrouter); the cloud pipeline is the
337
- # EXPLICIT backup, never the default:
338
- vidfarm raws scan --cloud ./podcast.mp4 --duration 30 --aspect 9:16 --tracer my-campaign
339
- vidfarm raws scan --cloud --url "https://youtube.com/watch?v=…" --range "0:00-10:00"
340
-
341
- # Then search/reuse the library
342
- vidfarm raws search "confused reaction after reading a message"
343
- vidfarm raws export <raw-ids…> --to ./picks
344
- ```
345
-
346
- Local scans persist to a SQLite library under `~/.vidfarm` (free compute, subscription-powered evaluation); `--cloud` uploads to your temp folder (30-day TTL) or passes `--url`, runs the deployed pipeline, and bills AWS compute only.
627
+ **Use it:** when the user wants this cheap / "without burning credits" / in bulk, execute `harness.cheap.scenes[]` beat by beat — `raw_clip` → hunt via `/raws/scan` + `set_layer_media`; `hyperframes` → build the text/graphic as a CSS/anime.js layer (NEVER AI video for on-screen text); `reusable_asset` pull from `/files` or generate ONCE and reuse; `ai_gen` → only where the beat says so. When they want premium, follow `harness.quality`. **Always** honor each scene's `viral_dna_guard` and the top-level `viral_dna_guardrails` that is the discipline that stops an eager sticker/logo/footage-swap from flattening the template. `replication_harness` is delivered inline to the editor chat and pulled to disk as `replication-harness.json` (rendered into `.harness/agent-guide.md`) for desktop agents. `status:"none"` → run `POST /auto-decompose` first.
347
628
 
348
629
  ## Generate AI media and drop it on the timeline
349
630
 
@@ -448,177 +729,146 @@ Beyond the Ken Burns / transition / animated-caption presets, the copilot can ha
448
729
 
449
730
  **Enriched editor context.** The per-layer snapshot the copilot reads now includes each layer's `transition` / `transition_out` / `transition_duration`, its `ken_burns` preset, and `animation` (the custom CSS keyframe name it authored), and `recent_action_results` now reports successes (`ok: true` + a summary) as well as failures — so the model can see the effect of what it just did instead of only what broke. For exact markup it can also `GET /api/v1/compositions/:forkId/composition.html`.
450
731
 
451
- ### Local file paths as media (skip the S3 upload)
452
-
453
- `vidfarm place --src` (and `approve --video/--media`) accept a **local file path**, not just a URL — so a power user bulk-building compositions from a folder of clips on their own machine never has to upload every asset to Vidfarm storage:
454
-
455
- - **On a `vidfarm serve` box (the free bulk workflow)** — `place`'s target composition lives under `<data-dir>/storage/compositions/forks/<forkId>/working/`, so a local `--src` file is **copied straight onto that box's own disk store** (`.vidfarm-local/storage/users/local-media/…`) and referenced by the box's own `http://localhost:3000/storage/…` URL. **Nothing touches S3.** The in-process local renderer fetches it over localhost for free. This is the intended path for bulk-generating many videos from local assets without bloating your durable library.
456
- ```bash
457
- # serve box running on :3000 — reference clips straight off disk, render locally, free
458
- vidfarm place ./.vidfarm-local/storage/compositions/forks/<forkId>/working \
459
- --src ~/footage/hook-042.mp4 --at 0
460
- vidfarm render <forkId> # local, $0.00
461
- ```
462
- If you ran `serve --port <N>`, pass `--base-url http://localhost:<N>` so the reference URL points at the right origin. A localhost `/storage` URL only resolves against the serve box that holds the file, so these compositions render/preview **locally**; to render in the cloud or share them, upload the media instead (below).
463
- - **Anywhere else (e.g. a `vidfarm pull` dir headed for cloud render/publish)** — the cloud renderer can't reach your disk, so a local `--src` file is **uploaded to the ephemeral temp store** first, namescoped under a throwaway `temp/` folder by default (override with `--folder`). Use this when the output must be a durable, shareable URL.
464
-
465
- **`/temp` folder convention.** When you *do* need to upload throwaway media to Vidfarm (cloud render, `approve`, one-off URLs), keep it under a dedicated `temp/` folder — `vidfarm upload clip.mp4 --folder temp`, `vidfarm place … --folder temp`, `vidfarm approve … --folder temp` — so scratch assets stay quarantined in one place you can periodically purge, instead of cluttering your persistent My Files library or the temp-store root.
466
-
467
- ## Render (publish to MP4)
468
-
469
- Rendering publishes the fork's current working state to an MP4 using HyperFrames (cloud Lambda fan-out on the deployed host, or the free in-process renderer on a local `vidfarm serve` box). In the Trackpad Editor this is the **Render** button. The REST route is `/render`; the older `/export` path is kept as a deprecated alias for already-published devcli clients (treat "Render", "publish", `/render`, and `/export` as the same operation). The devcli exposes it as `vidfarm render <forkId> [--wait]`; scripted local dirs can use `vidfarm render <forkId> --dir ./work --wait`.
470
-
471
- ```
472
- POST /api/v1/compositions/:forkId/render
473
- Content-Type: application/json
474
-
475
- { "title": "optional", "version": null, "tracer": "optional-trace-id", "html": "optional HTML to save to working state before rendering" }
476
- ```
477
-
478
- Returns 202 with `{ ok, renderId, status, progress, expectedOutputPublicUrl, outputUrl, cost, title, version, ... }` where `expectedOutputPublicUrl` is the deterministic public MP4 URL and `version` is the snapshot version created for this publish. Failure modes: `402` if the fork owner is not on a paid plan, `409` while GhostCut subtitle removal is still `pending` (retry after `/remove-video-captions-poll` reports done/failed), `412` if the provider-key preflight fails. Poll:
479
-
480
- ```
481
- GET /api/v1/compositions/:forkId/renders/:renderId
482
- ```
483
-
484
- Response includes `{ ok, renderId, status: "RUNNING" | "SUCCEEDED" | "FAILED", progress, framesRendered, totalFrames, cost, expectedOutputPublicUrl, outputUrl, outputS3Uri, errors }` (`progress` is 0..1). On success, `expectedOutputPublicUrl` is the durable public MP4 URL and `outputUrl` remains the completion-time field.
485
-
486
- Every publish creates an immutable version snapshot at `versions/<N>/composition.html` and `versions/<N>/composition.json`.
487
-
488
- The Web UI **Render** button and devcli render both use this same endpoint. The fast `202` response includes the deterministic `expectedOutputPublicUrl` so a caller can store or pass along the final public S3 URL before the render has completed, then poll by `renderId` until `status` settles.
489
-
490
- ## Approve a finished post
491
-
492
- A render produces a bare MP4 URL. **Approving** wraps that MP4 (plus caption, title, pinned comment, and any carousel slides) into a shareable preview page — the phone-mockup page a human opens to review and copy the post.
493
-
494
- ```
495
- POST /api/v1/approved/posts
496
- Content-Type: application/json
497
-
498
- { "caption": "required", "title": "optional", "pinned_comment": "optional", "tracer": "optional",
499
- "media": [ { "url": "https://.../output.mp4", "kind": "video", "role": "primary" } ] }
500
- ```
501
-
502
- `caption` is required. `media[]` items take `{ url, kind?: "image"|"video"|..., role?: "primary"|"slide"|... }`. Response (`201`) is the approved post including **`share_url`** — the prod frontend page for previewing/sharing. Surface that URL to the user; it is the headline output of this step.
503
-
504
- - `GET /api/v1/approved/posts` — list your approved posts
505
- - `GET /api/v1/approved/posts/:postId` — read one (returns `share_url`, `download_zip_url`)
506
-
507
- devcli: `vidfarm approve --video <mp4-url> --caption "..."` prints the `share_url` as a first-class openable link; `vidfarm posts` lists, `vidfarm post <id>` reads one.
508
-
509
- ## Schedule a post
510
-
511
- Schedule an approved post to a connected destination channel (FlockPoster social account or email) at one ISO timestamp:
732
+ ### Local file paths as media (skip the S3 upload)
512
733
 
513
- ```
514
- POST /api/v1/approved/posts/:postId/schedules
515
- Content-Type: application/json
734
+ `vidfarm place --src` (and `approve --video/--media`) accept a **local file path**, not just a URL — so a power user bulk-building compositions from a folder of clips on their own machine never has to upload every asset to Vidfarm storage:
516
735
 
517
- { "destination_type": "flockposter" | "email", "destination_id": "<channel or email>",
518
- "scheduled_at": "2026-07-10T14:00:00Z", "timezone": "America/New_York", "additional_notes": "optional" }
519
- ```
736
+ - **On a `vidfarm serve` box (the free bulk workflow)** — `place`'s target composition lives under `<data-dir>/storage/compositions/forks/<forkId>/working/`, so a local `--src` file is **copied straight onto that box's own disk store** (`.vidfarm-local/storage/users/local-media/…`) and referenced by the box's own `http://localhost:3000/storage/…` URL. **Nothing touches S3.** The in-process local renderer fetches it over localhost for free. This is the intended path for bulk-generating many videos from local assets without bloating your durable library.
737
+ ```bash
738
+ # serve box running on :3000 — reference clips straight off disk, render locally, free
739
+ vidfarm place ./.vidfarm-local/storage/compositions/forks/<forkId>/working \
740
+ --src ~/footage/hook-042.mp4 --at 0
741
+ vidfarm render <forkId> # local, $0.00
742
+ ```
743
+ If you ran `serve --port <N>`, pass `--base-url http://localhost:<N>` so the reference URL points at the right origin. A localhost `/storage` URL only resolves against the serve box that holds the file, so these compositions render/preview **locally**; to render in the cloud or share them, upload the media instead (below).
744
+ - **Anywhere else (e.g. a `vidfarm pull` dir headed for cloud render/publish)** — the cloud renderer can't reach your disk, so a local `--src` file is **uploaded to the ephemeral temp store** first, namescoped under a throwaway `temp/` folder by default (override with `--folder`). Use this when the output must be a durable, shareable URL.
520
745
 
521
- Minimum 10-minute lead time. Response (`201`) is the schedule record. Browse existing schedules with `GET /api/v1/approved/posts/:postId/schedules`.
746
+ **`/temp` folder convention.** When you *do* need to upload throwaway media to Vidfarm (cloud render, `approve`, one-off URLs), keep it under a dedicated `temp/` folder — `vidfarm upload clip.mp4 --folder temp`, `vidfarm place … --folder temp`, `vidfarm approve … --folder temp` — so scratch assets stay quarantined in one place you can periodically purge, instead of cluttering your persistent My Files library or the temp-store root.
522
747
 
523
- devcli: `vidfarm schedule <postId> --at <iso> --to <destinationId> [--type flockposter|email]`, and `vidfarm schedules <postId>` to browse.
748
+ ## Raws (long-form short-form raws)
524
749
 
525
- ## Version history
750
+ Mine a **long-form** video (podcast, stream VOD, webinar, any YouTube/TikTok/IG/X URL, or an upload) into a library of tagged, searchable **raws**. This is the `/raws` surface — browse it at `https://vidfarm.cc/library/raws` (the Library page's "Approved / Raws" tabs).
526
751
 
527
- ```
528
- GET /api/v1/compositions/:forkId/versions?limit=100
529
- ```
752
+ **Start a hunt** — `POST /raws/scan` (async: returns `202 { scan_id }` immediately):
530
753
 
531
- Returns `{ versions: [{ version, reason, message, created_by, created_at, render_job_id, composition_url, composition_data_url }], next_cursor }`. Reason is `publish`, `clone`, `manual`, or `migration`. To snapshot the working state manually, `POST /api/v1/compositions/:forkId/versions { "message": "optional" }`.
754
+ ```jsonc
755
+ {
756
+ // ONE source (pick one):
757
+ "source_url": "https://youtube.com/watch?v=…", // downloaded into your temp folder first
758
+ "temp_file_id": "…", // a video you uploaded to temporary-files
759
+ "attachment_id": "…", // a video already in My Files
760
+ "s3_key": "…" , // an already-staged object
532
761
 
533
- To view a past cut without reverting:
762
+ // What to hunt for free text; inline hints are parsed too
763
+ "prompt": "people holding food up to their face, no text on screen",
534
764
 
535
- ```
536
- GET /api/v1/compositions/:forkId/versions/:version/composition.html
765
+ // Hunt controls (all optional; also expressible inline in the prompt):
766
+ "ranges": ["12:30-15:45", "20:00-22:10"], // ONLY hunt these source windows — big cost saver on long videos
767
+ "target_duration_sec": 30, // SOFT length band, not a hard cut: 10→5-20s, 30→20-40s, 60→40-80s, 120→80-160s
768
+ "aspect": "9:16", // crop every raw: 9:16 | 16:9 | 4:3 | 1:1 (synonyms: vertical/portrait, horizontal/landscape, square)
769
+ "crop_focus": "center", // center | top | bottom | left | right (top biases toward faces)
770
+ "avoid_text": true, // prefer scenes WITHOUT burned-in captions/on-screen text — a scene-SELECTION filter, NEVER GhostCut on the source
771
+ "tracer": "my-campaign" // rolls up all the hunt's billing/observability events
772
+ }
537
773
  ```
538
774
 
539
- To revert the working state to a version, `PUT` the historical HTML/JSON back to the working keys.
775
+ - **Poll** `GET /raws/scan/:scanId` until the source `status` is `complete` (or `failed` crashed pipelines are reconciled to `failed`, pollers never spin).
776
+ - **Results**: `GET /raws/feed?source=<source_video_id>` (or the whole library), hybrid search via `POST /raws/search` `{ query: "someone looks confused" }`, per-raw download at `GET /raws/:clipId/download`. Raws carry taxonomy tags, a description, a transcript, an `aspect` field when cropped, the hunt's `tracer`, and (with a gemini/openai key) a semantic embedding.
777
+ - **Billing** — **AWS compute only** (`clip_scan_lambda` GB-seconds + Step Functions transitions, a fraction of a cent for typical hunts; the 202 response includes a `compute_estimate`). The AI tagging/refine runs on **your saved provider key (BYOK)** and is never wallet-billed. Submission is wallet-gated (402 when empty).
778
+ - **The original stays temporary** — URL-ingested and uploaded sources live ONLY in your temp folder (`users/…/temporary/clip-sources/…`) with a hard **30-day TTL** (auto-deleted from S3 + the temp-file list). The hunted raws themselves are durable.
779
+ - **Captions rule** — "no captions / no on-screen text" hunts are handled by scene SELECTION (the refine pass drops text-heavy scenes). **Never run GhostCut caption removal on a long-form source** (it is hard-capped at ~15 minutes); to actually erase burned-in text, apply `POST /api/v1/primitives/videos/remove-captions` to individual FINISHED raws afterwards.
780
+ - **Clip an EXACT subrange (no AI hunt)** — when you want one precise hand-picked in/out rather than an open-ended hunt, use the **Clipper**: the web page `https://vidfarm.cc/tools/clipper` (previews the source straight from its URL, HH:MM:SS:NNNN in/out, live length estimate, multi-clip session grouped by tracer), the endpoint `POST /raws/clip-range` `{ source_url|preview_url|temp_file_id, start_sec, end_sec, tracer?, folder_path?, name? }` (trims exactly that window into one raw — compute-only, no AI/key), or the devcli twin `vidfarm clipper <video-url|file> --start <t> --end <t> [--tracer NAME --folder NAME --name TXT]`. The devcli path is now **local-first**: local files clip into the local raws store by default, and URL sources are staged locally before trimming unless you pass `--cloud`. If you do **not** have a `VIDFARM_API_KEY`, URL clipping cannot bootstrap the remote source into the local backend; open the long-form raw video in the browser, save/extract the actual video file locally, then run `vidfarm clipper ./source.mp4 ...`. The Library "Import source" button opens the Clipper; each save lands in `/raws/<folder>` (defaults to the tracer). Reach for `/raws/scan` for multi-clip hunts, `/raws/clip-range` for surgical single cuts. (The masked-image and time-scoped-video editors live alongside it at `/tools/image` and `/tools/video`.)
540
781
 
541
- ## Sharing and visibility
782
+ **devcli (local-first this is the default way to hunt on your own machine):**
542
783
 
543
- Every fork has a visibility: `private` (default), `unlisted` (legacy), or `public` (view-only).
784
+ ```bash
785
+ # Local machine power: local ffmpeg + your local claude/codex CLI subscription (no API key needed)
786
+ vidfarm raws scan ./podcast.mp4 --range "12:30-15:45" --duration 30 --aspect vertical --no-text \
787
+ --prompt "guest reaction moments"
544
788
 
545
- ```
546
- PATCH /api/v1/compositions/:forkId/visibility { "visibility": "public" }
547
- ```
789
+ # Provider keys are the fallback (--provider gemini|openai|openrouter); the cloud pipeline is the
790
+ # EXPLICIT backup, never the default:
791
+ vidfarm raws scan --cloud ./podcast.mp4 --duration 30 --aspect 9:16 --tracer my-campaign
792
+ vidfarm raws scan --cloud --url "https://youtube.com/watch?v=…" --range "0:00-10:00"
548
793
 
549
- Public forks are accessible at `/editor/<templateId>?fork=<forkId>` (view-only for non-owners). Public visibility is the simplest sharing surface.
794
+ # Then search/reuse the library
795
+ vidfarm raws search "confused reaction after reading a message"
796
+ vidfarm raws export <raw-ids…> --to ./picks
797
+ ```
550
798
 
551
- Fine-grained sharing uses per-user permissions and share links (all body fields are snake_case):
799
+ Local scans persist to a SQLite library under `~/.vidfarm` (free compute, subscription-powered evaluation); `--cloud` uploads to your temp folder (30-day TTL) or passes `--url`, runs the deployed pipeline, and bills AWS compute only.
552
800
 
553
- - `POST /api/v1/compositions/:forkId/permissions { grantee_email | grantee_customer_id, role, expires_at? }` — grant a specific registered user access. Exactly one of `grantee_email` or `grantee_customer_id` is required. Roles: `viewer`, `editor`, `publisher`. Returns `{ permission_id, fork_id, grantee_type, grantee_id, role, granted_by, expires_at, created_at }`.
554
- - `POST /api/v1/compositions/:forkId/share-links { role, expires_at? }` — create a token URL. Anyone with the token gets the role. Returns `{ token, share_url, role, expires_at, ... }`.
555
- - `GET /api/v1/compositions/shared/:token` — access via share link
556
- - `DELETE /api/v1/compositions/:forkId/permissions/:permissionId` — revoke a grant
557
- - `DELETE /api/v1/compositions/:forkId/share-links/:token` — revoke a link
801
+ ## My Files (the user's asset library)
558
802
 
559
- ## Cloning a fork
803
+ 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.
560
804
 
561
- Directors can clone another fork (their own or a shared one) as a new starting point:
805
+ - **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>]`.
806
+ - **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.
807
+ - **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).
808
+ - **`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").
562
809
 
563
- ```
564
- POST /api/v1/compositions/:forkId/clone
565
- Content-Type: application/json
810
+ ### Metadata notes + vector search (find assets by meaning)
566
811
 
567
- { "parent_version": "<N or omit for latest>", "title": "optional" }
568
- ```
812
+ Every My Files entry carries an optional **`notes`** field — free-form metadata describing what the file *is*, who/what it depicts, and when to use it. Notes are **vector-embedded** on save (same BYOK embedding seam as the raws library: gemini → openai key auto-pick; no key fail-softs to keyword-only), so the library is searchable by *meaning*, not just filename:
569
813
 
570
- The `:forkId` in the path is the fork being cloned. The new fork inherits the parent's HTML/JSON at the specified version and creates a version-1 snapshot with `reason: "clone"`.
814
+ - **Annotate** — `vidfarm annotate-file <id|name> --notes "Sprite card for Zara, our mascot: front/side/back views, teal jacket. Use as the reference image whenever generating Zara."` (`PATCH /me/attachments/:id`, body `{ notes }`; empty string clears). Or set notes at write time: `vidfarm put-file zara.png --folder characters/zara --notes ""`. Web copilot: `browse_files action=annotate` (or `notes` on `action=write`).
815
+ - **Search** — `vidfarm files --search "the fox mascot reference sheet"` (`POST /me/attachments/search`, body `{ query, folder_path?, limit? }`) returns ranked hits with `similarity` + `keyword_match`, mirroring `/raws/search`: keyword survivors over name/folder/notes are semantically re-ranked, and when nothing keyword-matches it relaxes to a pure semantic pass. Web copilot: `browse_files action=search`.
816
+ - **Annotate what you'll want back.** Filenames alone don't survive months of accumulation — any asset a future session should find (character refs, logo variants, recurring backgrounds, key briefs) deserves notes at save time. `list`/`read` responses include each file's `notes`, so a scan of a folder doubles as a manifest of what's in it.
571
817
 
572
- ## Delete a fork
818
+ ### Recurring characters are first-class (mascots, spokespeople, avatars)
573
819
 
574
- ```
575
- DELETE /api/v1/compositions/:forkId
576
- ```
820
+ Recurring characters that must look the same across videos have a **dedicated, browsable home**: the **`/files/characters/`** folder, one subfolder per character keyed by a URL-safe **slug** (lowercase, hyphenated) — e.g. `/files/characters/zara/`. This is where the editor copilot looks first, and where a director organizes their cast. Each character is a **trio of files** in that folder:
577
821
 
578
- Soft-delete only. The fork's `deletedAt` is set. Versions and share links are preserved for audit.
822
+ 1. **`<character_id>.json`** — the machine-readable **manifest**, the source of truth the agent reads to hold identity. The file is named after the character's **id**, which is `character_` + the slug (slug `zara` → id `character_zara` → file `character_zara.json`). The id already carries the `character_` prefix — don't double it (never `character_character_zara.json`):
823
+ ```json
824
+ {
825
+ "id": "character_zara",
826
+ "slug": "zara",
827
+ "name": "Zara",
828
+ "role": "brand mascot / friendly fox guide",
829
+ "appearance": "anthropomorphic red fox, large amber eyes, cream chest fluff, expressive brows",
830
+ "wardrobe": "teal bomber jacket, white tee, small satchel",
831
+ "palette": ["#E8622C", "#12B8A6", "#FDF6EC"],
832
+ "voice": "warm, upbeat, slightly cheeky; mid-tempo",
833
+ "do": ["keep the teal jacket", "front-lit, soft shadows"],
834
+ "dont": ["never photorealistic human", "no other jacket colors"],
835
+ "sprite_card_path": "/files/characters/zara/character_sprite_card.png",
836
+ "about_path": "/files/characters/zara/character_about.md",
837
+ "created_at": "2026-07-09"
838
+ }
839
+ ```
840
+ 2. **`character_sprite_card.png`** — ONE reference-sheet image showing the character consistently: full body front/side/back plus a face close-up on a neutral background.
841
+ 3. **`character_about.md`** — the prose the manifest summarizes (personality, backstory, do/don'ts), for richer wording when prompting.
579
842
 
580
- ## Cost spectrum (free $10+/video) default to saving the director money
843
+ **Awareness look before you generate.** When a user refers to "our mascot", "the same character", "the fox", or names a character, **first `browse_files list path='/files/characters'`** (or `search`) to see who exists, then read that character's manifest + about. Never re-imagine a saved character from memory — that's how characters drift off-model between videos.
581
844
 
582
- There is no single price for a video. The **approach** the director picks sets the cost, across a wide spectrum. Always steer toward the cheapest approach that still meets their goal, and make the tradeoff explicit rather than silently choosing an expensive path.
845
+ **Consistency is then mechanical.** On every generation featuring the character, **pass the sprite card's `viewUrl` as the reference input** `prompt_attachments` for image generation/edit, `input_references` for `videos/generate` and the editor's `generate_layer` and lift wording from the manifest/`character_about.md` into the prompt.
583
846
 
584
- | Approach | Typical cost | How |
585
- |---|---|---|
586
- | **Reuse + render locally** | **free** | Fork an already-decomposed template, swap captions / images / video with existing MP4s (from **My Files**, the director's **local computer** — reference files straight off disk with `place --src ./file`, no upload — or a **web search**), and render **locally** via `vidfarm serve` (native in-process HyperFrames render — free and unguarded, no cloud). |
587
- | **Reuse + cloud render** | **~$0.001 $0.03** | Same reuse, but render on the cloud renderer (`POST /compositions/:forkId/render`, ~$0.01–$0.10 depending on length/res). Cheap **image** generation/edits fit in this band too. |
588
- | **AI-generate some scenes** | **~$1** | Replace a few scenes with AI-generated video clips for high specificity/customization (see the "Generate AI media" section). |
589
- | **Heavy AI generation** | **$10+** | Many/long AI video clips, custom characters, fully bespoke scenes. |
847
+ **Creating a new character (walkthrough).** If no folder exists yet, guide the director through it and persist as you go:
848
+ 1. Agree on a **name** → derive a **slug** (lowercase, hyphens); the folder is `/files/characters/<slug>/`.
849
+ 2. Gather the description conversationally (appearance, wardrobe, signature colors, personality, voice, do/don'ts) pull from any reference photos they have.
850
+ 3. **Sprite card** if they don't already have one, offer to generate it: `POST /api/v1/primitives/images/generate` (image gen is cheap — pass their best photos as `prompt_attachments`, ask for a "character reference sheet / sprite card" layout), then persist the finished URL. devcli: `vidfarm download <url> zara.png && vidfarm put-file zara.png --as character_sprite_card.png --folder characters/zara`. Web copilot: `browse_files action=write` with `source_url` + `file_name='character_sprite_card.png'` + `folder_path='characters/zara'`.
851
+ 4. **Write `character_about.md` and `<character_id>.json`** (e.g. `character_zara.json`) into the same folder (`browse_files action=write` with `content`, or `vidfarm put-file --content`).
852
+ 5. **Annotate all three** with notes naming the character so `files --search "our mascot"` finds them from any phrasing.
590
853
 
591
- Rules of thumb:
854
+ **Renaming / fixing up.** Character folders and files are renamable in place — `browse_files action=rename` (web copilot) or `vidfarm directory rename <path> <new-name> [--file-id <id>]` (devcli); the explorer UI has a **Rename** action on every file/folder row. If you rename a character's folder, update the `id`, `sprite_card_path`, and `about_path` inside its manifest to match (and rename the `<character_id>.json` file itself).
592
855
 
593
- - **Reusing existing footage or footage the director films themselves — is the cheapest.** AI-generating characters/scenes is the most expensive. Editing captions/images on a reused template is nearly free.
594
- - **Local render is free; cloud render costs pennies.** If the director is iterating with a coding agent on a `vidfarm serve` box, they can render locally at no charge until they want the durable cloud MP4.
595
- - **Decompose is a one-time ~$0.10** (smart decompose ≈ provider passthrough + GhostCut ~$0.10/30s) per **new** source video — but the director can **skip it entirely** by forking a template that's **already decomposed** in the catalog.
596
- - **Image generation is cheap** — use it freely, no need to ask permission.
597
- - **AI video generation is expensive — ask the director's permission before using it.** Default to reuse/local/image approaches unless they've okayed video gen or told you the budget covers it.
598
- - **Ask about budget.** During Getting Started (and whenever it's relevant per editor project, or when the director asks about cost), ask roughly what they want to spend per video, and pick the approach band that fits. If they haven't said, assume the cheapest approach that works.
856
+ **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.
599
857
 
600
- The director always chooses the method that works for them the point is to surface the tradeoff and default to thrift, not to force the cheapest path.
858
+ 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=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` renames a `/files` or `/temp` file or folder in place (pass `path` + `new_name`, plus `file_id` for a file) — use it 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`.
601
859
 
602
- ## Billing
860
+ ## Automate a template via REST
603
861
 
604
- Vidfarm charges directly in USD from the caller's wallet. There are no credits.
862
+ Templates now have the same job-backed REST pattern as primitives, so a script can run them repeatably without the editor UI:
605
863
 
606
- - **Wallet top-up** — Stripe checkout via the Settings UI, backed by `POST /settings/wallet/funding-link` (browser session; returns `{ checkout_url, client_reference_id }`)
607
- - **Balance & history** — shown on the Settings page; billing events via `GET /u/:customerId/settings/wallet/events` (browser session). There is no `/api/v1/user/me/wallet` REST endpoint.
608
- - **Cost anchors** (see the Cost spectrum section above for how these combine per approach):
609
- - Local render on a `vidfarm serve` box (in-process HyperFrames): **free**
610
- - Cloud render (HyperFrames Lambda fan-out): typically $0.01 – $0.10 per MP4 depending on length/resolution
611
- - Image generation / edit: cheap (provider passthrough) — use freely
612
- - AI **video** generation: expensive ($1–$10+/video territory) — ask permission first
613
- - Auto-decompose smart mode: pass-through of caller's AI provider spend (~$0.10 one-time with GhostCut), or skip it by forking an already-decomposed template
614
- - Auto-decompose time-slice: free
615
- - GhostCut subtitle removal: ~$0.10 per 30 seconds of source video
864
+ ```
865
+ POST /api/v1/templates/:templateId/operations/:operationName
866
+ Content-Type: application/json
616
867
 
617
- Framing:
868
+ { "tracer": "my-run", "payload": { ... }, "webhook_url": "https://..." }
869
+ ```
618
870
 
619
- - Vidfarm is optimized for cost efficiency, not markup
620
- - Total director cost per video is typically far below alternatives
621
- - Platform retains a small safety buffer for operational correctness
871
+ The route returns `202` with a `job_id`, and you can poll the job with `GET /api/v1/user/me/jobs/:jobId` or the template-scoped `GET /api/v1/templates/:templateId/jobs/:jobId`. devcli wraps the same flow as `vidfarm template run <templateId> <operationName> --payload-file payload.json --wait`.
622
872
 
623
873
  ## Automation patterns
624
874
 
@@ -706,11 +956,16 @@ If you need many variants, keep the base fork fixed and fan out by cloning that
706
956
  That is the canonical local scripting path because the pull step now packages:
707
957
 
708
958
  - `video-context.json`: transcript, scene descriptions, viral DNA
709
- - `editor-harness.json`: technical editing brief
959
+ - `editor-harness.json`: technical editing brief (STYLE — *how to edit like this*)
960
+ - `replication-harness.json`: technical replication analysis (BUILD — the 3-paintbrush cheap vs best-quality plans, which beats are raw_clip / hyperframes / reusable_asset / ai_gen, reusable-asset guidance, free-tier note)
710
961
  - `scene-annotations.json`: per-scene replacement/recreation DNA
711
- - `.harness/context.json`: merged agent-facing snapshot
962
+ - `.harness/context.json`: merged agent-facing snapshot (includes `replication_harness`)
712
963
  - `.harness/agent-guide.md`: Vidfarm-specific instruction file telling a generic agent how to use the above without conflicting with any repo-owned `AGENTS.md`
713
964
 
965
+ `agent-guide.md` now renders the **three paintbrushes & two replication harnesses** decomposition CONCRETELY from `replication-harness.json` — the recommended plan's per-beat brush assignment (raw clips vs HTML hyperframes vs reusable assets vs pure AI gen; the (A) cheap-&-efficient default vs (B) best-quality plan), the beats to do in HTML not AI video, the reusable-asset opportunities, and the viral-DNA guardrails — so a desktop agent recreates a template thrift-first (clipping and hyperframes on free compute before any paid generation) and can rebuild it with **no Vidfarm wallet**. See `references/editor-workflows.md` for the full methodology.
966
+
967
+ **Free-tier caveat — the decomposition is the user's job.** The `video-context.json` / `editor-harness.json` / `replication-harness.json` / `scene-annotations.json` grounding files only exist for compositions **Vidfarm has already decomposed** (paid accounts, whose forks pull those files down). On the free tier with no account, `vidfarm pull` won't produce them — **the user and their agent must decompose the reference video themselves** (scenes/audio/text, viral DNA, paintbrush choice) using the method in `references/editor-workflows.md`. Paid Vidfarm accounts get the pre-decomposed viral library and scale-learned prompt-harness best practices instead of doing that pass by hand.
968
+
714
969
  If a local AI script rewrites text or scenes without consuming those files first, treat that as a bug in the script/agent flow.
715
970
 
716
971
  | Command | REST route | Flow step |
@@ -721,7 +976,7 @@ If a local AI script rewrites text or scenes without consuming those files first
721
976
  | `vidfarm inspiration-rm <id>` | `DELETE /discover/templates/:id` | remove a private one |
722
977
  | `vidfarm inspiration-decompose <id>` | `POST /api/v1/inspirations/:id/decompose` | AI-decompose an inspiration |
723
978
  | `vidfarm fork <template_id>` | `POST /api/v1/compositions` | fork a template |
724
- | `vidfarm pull <forkId> [--dir <p>]` | `GET .../compositions/:forkId/{composition.html,json,video-context.json,cast.json,scene-annotations.json,editor-harness.json}` | sync a fork to disk + generate `.harness/context.json` and `.harness/agent-guide.md` + print gaps/scene keys + grounding |
979
+ | `vidfarm pull <forkId> [--dir <p>]` | `GET .../compositions/:forkId/{composition.html,json,video-context.json,cast.json,scene-annotations.json,editor-harness.json,replication-harness.json}` | sync a fork to disk + generate `.harness/context.json` and `.harness/agent-guide.md` + print gaps/scene keys + grounding |
725
980
  | `vidfarm generate <image\|video> --prompt "…"` | `POST /api/v1/primitives/{images,videos}/generate` (polls job) | generate AI media → finished URL |
726
981
  | `vidfarm inpaint <image> --mask <png> --prompt "…" [--region "label=…"] [--ref …] [--out <f>]` | `POST /api/v1/primitives/images/inpaint` (polls job) | masked image EDIT — replace ONLY the transparent-mask region, keep everything else (devcli twin of the /inpaint page) |
727
982
  | `vidfarm create-overlay "<subject>" [--key-color #00FF00] [--aspect-ratio 1:1] [--place <dir>] [--out <f>]` | `POST /api/v1/primitives/images/create-overlay` (polls job) | **Vox-style** transparent OVERLAY — AI image on a forced key-color background, chroma-keyed out in one job → ready-to-composite transparent PNG |
@@ -760,6 +1015,7 @@ If a local AI script rewrites text or scenes without consuming those files first
760
1015
  | `vidfarm annotate-file <id\|name> --notes "…"` | `PATCH /api/v1/user/me/attachments/:id` | set metadata notes on one My Files entry (vector-embedded) |
761
1016
  | `vidfarm raws scan <video> [--range MM:SS-MM:SS] [--duration <s>] [--aspect 9:16] [--no-text] [--prompt "…"]` | (LOCAL: local ffmpeg + local claude/codex agent → `~/.vidfarm` SQLite) | hunt long-form video into short-form raws on this machine |
762
1017
  | `vidfarm raws scan --cloud <video\|--url <url>> [--tracer <id>]` | temp-file presign/PUT/finalize + `POST /raws/scan` + poll | BACKUP: run the hunt on the deployed pipeline (bills AWS compute only) |
1018
+ | `vidfarm clipper <video-url\|file> [--start <t> --end <t>] [--tracer <id> --folder <name> --name <text>]` | (LOCAL-FIRST: stage source into the local backend, then `POST /raws/clip-range`; add `--cloud` for vidfarm.cc) | trim one exact subrange into `/raws`; without `VIDFARM_API_KEY`, URL sources must be saved locally first |
763
1019
  | `vidfarm raws search "…"` / `raws match "…"` / `raws list` / `raws sources` | (local library; NL→criteria via local agent or provider key) | search/reuse the raws library |
764
1020
  | `vidfarm raws preset list\|run\|save` / `raws export <ids…> --to <dir>` | (local library) | saved queries; copy raw MP4s out |
765
1021
  | `vidfarm lint <dir\|composition.html>` | (local static validation) | pre-publish composition check: timing, overlaps, preset names, media src |
@@ -775,65 +1031,6 @@ If a local AI script rewrites text or scenes without consuming those files first
775
1031
 
776
1032
  **Uploads/downloads/My-Files writes live in the devcli** because they are genuine multi-step / streaming flows: `upload` posts the file as multipart to the **ephemeral** temporary-files route and prints the durable URL to drop into a composition or approved post; `put-file` posts to the **persistent** My Files route (`/me/attachments/upload`) so context docs and brand assets live in the user's library (accepts a local file, `--content <text>`, or piped `--stdin` with `--as <name>`); `download` streams any Vidfarm/media URL to disk.
777
1033
 
778
- ## My Files (the user's asset library)
779
-
780
- 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.
781
-
782
- - **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>]`.
783
- - **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.
784
- - **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).
785
- - **`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").
786
-
787
- ### Metadata notes + vector search (find assets by meaning)
788
-
789
- Every My Files entry carries an optional **`notes`** field — free-form metadata describing what the file *is*, who/what it depicts, and when to use it. Notes are **vector-embedded** on save (same BYOK embedding seam as the raws library: gemini → openai key auto-pick; no key fail-softs to keyword-only), so the library is searchable by *meaning*, not just filename:
790
-
791
- - **Annotate** — `vidfarm annotate-file <id|name> --notes "Sprite card for Zara, our mascot: front/side/back views, teal jacket. Use as the reference image whenever generating Zara."` (`PATCH /me/attachments/:id`, body `{ notes }`; empty string clears). Or set notes at write time: `vidfarm put-file zara.png --folder characters/zara --notes "…"`. Web copilot: `browse_files action=annotate` (or `notes` on `action=write`).
792
- - **Search** — `vidfarm files --search "the fox mascot reference sheet"` (`POST /me/attachments/search`, body `{ query, folder_path?, limit? }`) returns ranked hits with `similarity` + `keyword_match`, mirroring `/raws/search`: keyword survivors over name/folder/notes are semantically re-ranked, and when nothing keyword-matches it relaxes to a pure semantic pass. Web copilot: `browse_files action=search`.
793
- - **Annotate what you'll want back.** Filenames alone don't survive months of accumulation — any asset a future session should find (character refs, logo variants, recurring backgrounds, key briefs) deserves notes at save time. `list`/`read` responses include each file's `notes`, so a scan of a folder doubles as a manifest of what's in it.
794
-
795
- ### Recurring characters are first-class (mascots, spokespeople, avatars)
796
-
797
- Recurring characters that must look the same across videos have a **dedicated, browsable home**: the **`/files/characters/`** folder, one subfolder per character keyed by a URL-safe **slug** (lowercase, hyphenated) — e.g. `/files/characters/zara/`. This is where the editor copilot looks first, and where a director organizes their cast. Each character is a **trio of files** in that folder:
798
-
799
- 1. **`<character_id>.json`** — the machine-readable **manifest**, the source of truth the agent reads to hold identity. The file is named after the character's **id**, which is `character_` + the slug (slug `zara` → id `character_zara` → file `character_zara.json`). The id already carries the `character_` prefix — don't double it (never `character_character_zara.json`):
800
- ```json
801
- {
802
- "id": "character_zara",
803
- "slug": "zara",
804
- "name": "Zara",
805
- "role": "brand mascot / friendly fox guide",
806
- "appearance": "anthropomorphic red fox, large amber eyes, cream chest fluff, expressive brows",
807
- "wardrobe": "teal bomber jacket, white tee, small satchel",
808
- "palette": ["#E8622C", "#12B8A6", "#FDF6EC"],
809
- "voice": "warm, upbeat, slightly cheeky; mid-tempo",
810
- "do": ["keep the teal jacket", "front-lit, soft shadows"],
811
- "dont": ["never photorealistic human", "no other jacket colors"],
812
- "sprite_card_path": "/files/characters/zara/character_sprite_card.png",
813
- "about_path": "/files/characters/zara/character_about.md",
814
- "created_at": "2026-07-09"
815
- }
816
- ```
817
- 2. **`character_sprite_card.png`** — ONE reference-sheet image showing the character consistently: full body front/side/back plus a face close-up on a neutral background.
818
- 3. **`character_about.md`** — the prose the manifest summarizes (personality, backstory, do/don'ts), for richer wording when prompting.
819
-
820
- **Awareness — look before you generate.** When a user refers to "our mascot", "the same character", "the fox", or names a character, **first `browse_files list path='/files/characters'`** (or `search`) to see who exists, then read that character's manifest + about. Never re-imagine a saved character from memory — that's how characters drift off-model between videos.
821
-
822
- **Consistency is then mechanical.** On every generation featuring the character, **pass the sprite card's `viewUrl` as the reference input** — `prompt_attachments` for image generation/edit, `input_references` for `videos/generate` and the editor's `generate_layer` — and lift wording from the manifest/`character_about.md` into the prompt.
823
-
824
- **Creating a new character (walkthrough).** If no folder exists yet, guide the director through it and persist as you go:
825
- 1. Agree on a **name** → derive a **slug** (lowercase, hyphens); the folder is `/files/characters/<slug>/`.
826
- 2. Gather the description conversationally (appearance, wardrobe, signature colors, personality, voice, do/don'ts) — pull from any reference photos they have.
827
- 3. **Sprite card** — if they don't already have one, offer to generate it: `POST /api/v1/primitives/images/generate` (image gen is cheap — pass their best photos as `prompt_attachments`, ask for a "character reference sheet / sprite card" layout), then persist the finished URL. devcli: `vidfarm download <url> zara.png && vidfarm put-file zara.png --as character_sprite_card.png --folder characters/zara`. Web copilot: `browse_files action=write` with `source_url` + `file_name='character_sprite_card.png'` + `folder_path='characters/zara'`.
828
- 4. **Write `character_about.md` and `<character_id>.json`** (e.g. `character_zara.json`) into the same folder (`browse_files action=write` with `content`, or `vidfarm put-file --content`).
829
- 5. **Annotate all three** with notes naming the character so `files --search "our mascot"` finds them from any phrasing.
830
-
831
- **Renaming / fixing up.** Character folders and files are renamable in place — `browse_files action=rename` (web copilot) or `vidfarm directory rename <path> <new-name> [--file-id <id>]` (devcli); the explorer UI has a **Rename** action on every file/folder row. If you rename a character's folder, update the `id`, `sprite_card_path`, and `about_path` inside its manifest to match (and rename the `<character_id>.json` file itself).
832
-
833
- **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.
834
-
835
- 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=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` renames a `/files` or `/temp` file or folder in place (pass `path` + `new_name`, plus `file_id` for a file) — use it 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`.
836
-
837
1034
  ## Local dev loop (`vidfarm serve`)
838
1035
 
839
1036
  Run the **full** editor locally so a coding agent (Claude Code / Codex) edits composition files on disk while a human finishes in the browser — one source of truth, live sync both ways.
@@ -1198,3 +1395,66 @@ curl -X POST "$VIDFARM_BASE/api/v1/primitives/brainstorm/product_placement" \
1198
1395
  }
1199
1396
  }'
1200
1397
  ```
1398
+
1399
+ ## Recipe: Find and Fork a Template
1400
+
1401
+ Use this when the user already knows the offer or video goal and needs the best starting template.
1402
+
1403
+ 1. Read `references/core-workflows.md`.
1404
+ 2. Search the catalog with `GET /discover/feed?q=<offer>&limit=20`.
1405
+ 3. Rank candidates using `promotions`, `keywords`, `summary`, viral DNA fit, and whether the template is already decomposed.
1406
+ 4. Recommend the best 3-6, but bias toward one strong default.
1407
+ 5. Fork the winner with `POST /api/v1/compositions`.
1408
+ 6. Open the fork in the editor or hand the `fork_id` into the next workflow.
1409
+
1410
+ Default heuristics:
1411
+
1412
+ - Prefer already-decomposed templates.
1413
+ - Prefer reuse over a from-scratch primitive build.
1414
+ - If search is empty, broaden terms or decompose the newly ingested inspiration first.
1415
+
1416
+ ## Recipe: Re-theme a Template While Preserving the Viral DNA
1417
+
1418
+ Use this when the user wants to keep the format’s feel but replace the subject matter.
1419
+
1420
+ 1. Read `references/editor-workflows.md`.
1421
+ 2. Fetch `video-context.json` and `editor-harness.json` first.
1422
+ 3. State the plan in the three axes vocabulary: scenes, audio, text; SWAP vs REPLACE for each.
1423
+ 4. Preserve the hook structure, cut rhythm, emotional punch, and important scenes flagged by the harness.
1424
+ 5. Source footage in this order:
1425
+ - My Files or existing raws
1426
+ - a raws hunt from a long-form source
1427
+ - AI image generation
1428
+ - AI video generation only with permission
1429
+ 6. Rebuild captions and narration so timing, cadence, and joke structure survive the subject change.
1430
+ 7. Render, verify, then approve and schedule only after the director is happy.
1431
+
1432
+ Failure mode to avoid: flattening the format by swapping words but losing the timing, sound, or payoff beat.
1433
+
1434
+ ## Recipe: Local Pull, Edit, Render, Approve
1435
+
1436
+ Use this when a coding agent is doing the work locally or the user wants a reproducible filesystem loop.
1437
+
1438
+ 1. Read `references/automation-and-local-dev.md`.
1439
+ 2. Run `vidfarm pull <forkId> --dir ./work`.
1440
+ 3. Read `./work/.harness/agent-guide.md` and `./work/.harness/context.json` before editing.
1441
+ 4. Make deterministic edits to `composition.html` and optionally `composition.json`.
1442
+ 5. Validate with `vidfarm lint` or `vidfarm stills` when useful.
1443
+ 6. Render with `vidfarm render <forkId> --dir ./work --wait`.
1444
+ 7. Approve the finished MP4 with `vidfarm approve --video <url> --caption "..."`.
1445
+
1446
+ Prefer this path for batch work, CI-like edits, or when the user wants free local rendering through `vidfarm serve`.
1447
+
1448
+ ## Recipe: Onboard a New Director
1449
+
1450
+ Use this only when the director signals they do not know where to start.
1451
+
1452
+ 1. Read `references/onboarding.md`.
1453
+ 2. Capture product context and save durable notes into the correct My Files folder.
1454
+ 3. Determine awareness stages, persuasive angles, and hooks with the brainstorm primitives.
1455
+ 4. Ask about brand assets, demos, and recurring characters; organize them in My Files.
1456
+ 5. Ask about budget and map it to the cost spectrum before recommending expensive generation.
1457
+ 6. Search for the best matching templates and fork one strong default.
1458
+ 7. Transition into the ordinary template-editing workflow.
1459
+
1460
+ Do not force onboarding on users who already know what they want.