@officexapp/vidfarm-devcli 0.21.10

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 (516) hide show
  1. package/.agents/skills/dollarplatoon-skill/SKILL.md +1227 -0
  2. package/.agents/skills/editor-capabilities/SKILL.md +182 -0
  3. package/.agents/skills/editor-capabilities/references/re-theme-walkthrough.md +58 -0
  4. package/.agents/skills/embedded-captions/CATALOG.md +93 -0
  5. package/.agents/skills/embedded-captions/SKILL.md +286 -0
  6. package/.agents/skills/embedded-captions/assets/brand/CDPR-fankit-terms.txt +35 -0
  7. package/.agents/skills/embedded-captions/assets/brand/cyberpunk-widths.json +115 -0
  8. package/.agents/skills/embedded-captions/assets/fonts/char-widths.json +2211 -0
  9. package/.agents/skills/embedded-captions/assets/strokefonts/HersheyScript1.svg +242 -0
  10. package/.agents/skills/embedded-captions/assets/strokefonts/HersheyScriptMed.svg +242 -0
  11. package/.agents/skills/embedded-captions/dna/README.md +148 -0
  12. package/.agents/skills/embedded-captions/dna/chrome.json +68 -0
  13. package/.agents/skills/embedded-captions/dna/cream.json +69 -0
  14. package/.agents/skills/embedded-captions/dna/documentary.json +62 -0
  15. package/.agents/skills/embedded-captions/dna/editorial.json +70 -0
  16. package/.agents/skills/embedded-captions/dna/glitch.json +82 -0
  17. package/.agents/skills/embedded-captions/dna/ink.json +65 -0
  18. package/.agents/skills/embedded-captions/dna/keynote.json +62 -0
  19. package/.agents/skills/embedded-captions/dna/loud.json +75 -0
  20. package/.agents/skills/embedded-captions/dna/neon.json +58 -0
  21. package/.agents/skills/embedded-captions/dna/velocity.json +89 -0
  22. package/.agents/skills/embedded-captions/modes/cinematic/README.md +48 -0
  23. package/.agents/skills/embedded-captions/modes/cinematic/_archive/champion/spec.md +98 -0
  24. package/.agents/skills/embedded-captions/modes/cinematic/_archive/champion/template.html +188 -0
  25. package/.agents/skills/embedded-captions/modes/cinematic/_archive/memory-wall/spec.md +134 -0
  26. package/.agents/skills/embedded-captions/modes/cinematic/_archive/memory-wall/template.html +179 -0
  27. package/.agents/skills/embedded-captions/modes/cinematic/_archive/portrait-header/spec.md +81 -0
  28. package/.agents/skills/embedded-captions/modes/cinematic/_archive/portrait-header/template.html +170 -0
  29. package/.agents/skills/embedded-captions/modes/cinematic/cinematic-cream/spec.md +16 -0
  30. package/.agents/skills/embedded-captions/modes/cinematic/cinematic-cream/template.html +226 -0
  31. package/.agents/skills/embedded-captions/modes/cinematic/engine.html +355 -0
  32. package/.agents/skills/embedded-captions/modes/standard/_anatomy.md +233 -0
  33. package/.agents/skills/embedded-captions/modes/standard/_motion.md +192 -0
  34. package/.agents/skills/embedded-captions/modes/standard/fonts/build-fonts-css.cjs +97 -0
  35. package/.agents/skills/embedded-captions/references/aesthetic-principles.md +168 -0
  36. package/.agents/skills/embedded-captions/references/anti-patterns.md +188 -0
  37. package/.agents/skills/embedded-captions/references/bespoke-vs-presets.md +178 -0
  38. package/.agents/skills/embedded-captions/references/caption-grouping.md +130 -0
  39. package/.agents/skills/embedded-captions/references/composition-craft.md +522 -0
  40. package/.agents/skills/embedded-captions/references/direction-catalog.md +138 -0
  41. package/.agents/skills/embedded-captions/references/example-renders/champion.html +371 -0
  42. package/.agents/skills/embedded-captions/references/example-renders/memory-wall.html +334 -0
  43. package/.agents/skills/embedded-captions/references/failure-modes.md +145 -0
  44. package/.agents/skills/embedded-captions/references/layout-heuristics.md +238 -0
  45. package/.agents/skills/embedded-captions/references/motion-vocabulary.md +148 -0
  46. package/.agents/skills/embedded-captions/references/rail.md +68 -0
  47. package/.agents/skills/embedded-captions/references/reference-bar.md +47 -0
  48. package/.agents/skills/embedded-captions/references/scene-types.md +94 -0
  49. package/.agents/skills/embedded-captions/references/test-set.md +66 -0
  50. package/.agents/skills/embedded-captions/references/typographic-moves.md +266 -0
  51. package/.agents/skills/embedded-captions/references/typography-presets.md +63 -0
  52. package/.agents/skills/embedded-captions/scripts/audio-envelope.cjs +95 -0
  53. package/.agents/skills/embedded-captions/scripts/check-occlusion.cjs +250 -0
  54. package/.agents/skills/embedded-captions/scripts/check-overflow.cjs +194 -0
  55. package/.agents/skills/embedded-captions/scripts/check-rail-climax.cjs +233 -0
  56. package/.agents/skills/embedded-captions/scripts/check-timing.cjs +173 -0
  57. package/.agents/skills/embedded-captions/scripts/fill-timings.cjs +124 -0
  58. package/.agents/skills/embedded-captions/scripts/fit-fonts.cjs +158 -0
  59. package/.agents/skills/embedded-captions/scripts/gen-stroke-path.py +46 -0
  60. package/.agents/skills/embedded-captions/scripts/inject-fonts.cjs +169 -0
  61. package/.agents/skills/embedded-captions/scripts/lib-dna.cjs +201 -0
  62. package/.agents/skills/embedded-captions/scripts/make-cinematic.cjs +1205 -0
  63. package/.agents/skills/embedded-captions/scripts/make-composition.cjs +404 -0
  64. package/.agents/skills/embedded-captions/scripts/make-theme.cjs +8781 -0
  65. package/.agents/skills/embedded-captions/scripts/matte.cjs +294 -0
  66. package/.agents/skills/embedded-captions/scripts/measure-layout.cjs +293 -0
  67. package/.agents/skills/embedded-captions/scripts/prepare.sh +39 -0
  68. package/.agents/skills/embedded-captions/scripts/preview-frames.cjs +268 -0
  69. package/.agents/skills/embedded-captions/scripts/render-and-composite.sh +509 -0
  70. package/.agents/skills/embedded-captions/scripts/render-theme.sh +34 -0
  71. package/.agents/skills/embedded-captions/scripts/safe-zones.cjs +813 -0
  72. package/.agents/skills/embedded-captions/scripts/transcribe.cjs +349 -0
  73. package/.agents/skills/embedded-captions/themes/PORTING.md +49 -0
  74. package/.agents/skills/embedded-captions/themes/README.md +244 -0
  75. package/.agents/skills/embedded-captions/themes/anchor.json +27 -0
  76. package/.agents/skills/embedded-captions/themes/arcade.json +51 -0
  77. package/.agents/skills/embedded-captions/themes/aurora.json +41 -0
  78. package/.agents/skills/embedded-captions/themes/biolume.json +35 -0
  79. package/.agents/skills/embedded-captions/themes/brush.json +74 -0
  80. package/.agents/skills/embedded-captions/themes/chalkboard.json +52 -0
  81. package/.agents/skills/embedded-captions/themes/dossier.json +56 -0
  82. package/.agents/skills/embedded-captions/themes/graffiti.json +62 -0
  83. package/.agents/skills/embedded-captions/themes/hologram.json +41 -0
  84. package/.agents/skills/embedded-captions/themes/inkwater.json +43 -0
  85. package/.agents/skills/embedded-captions/themes/laser.json +46 -0
  86. package/.agents/skills/embedded-captions/themes/lastpage.json +33 -0
  87. package/.agents/skills/embedded-captions/themes/neonsign.json +53 -0
  88. package/.agents/skills/embedded-captions/themes/nightcity.json +56 -0
  89. package/.agents/skills/embedded-captions/themes/ordnance.json +54 -0
  90. package/.agents/skills/embedded-captions/themes/papercut.json +37 -0
  91. package/.agents/skills/embedded-captions/themes/popup.json +46 -0
  92. package/.agents/skills/embedded-captions/themes/ransom.json +62 -0
  93. package/.agents/skills/embedded-captions/themes/scoreboard.json +54 -0
  94. package/.agents/skills/embedded-captions/themes/spectrum.json +46 -0
  95. package/.agents/skills/embedded-captions/themes/stardust.json +29 -0
  96. package/.agents/skills/embedded-captions/themes/stomp.json +43 -0
  97. package/.agents/skills/embedded-captions/themes/terminal.json +49 -0
  98. package/.agents/skills/embedded-captions/themes/thunder.json +44 -0
  99. package/.agents/skills/embedded-captions/themes/transit.json +44 -0
  100. package/.agents/skills/embedded-captions/themes/vhs.json +47 -0
  101. package/.agents/skills/faceless-explainer/SKILL.md +209 -0
  102. package/.agents/skills/faceless-explainer/references/cut-catalog.md +215 -0
  103. package/.agents/skills/faceless-explainer/references/motion-language.md +156 -0
  104. package/.agents/skills/faceless-explainer/references/story-design.md +248 -0
  105. package/.agents/skills/faceless-explainer/references/visual-design.md +146 -0
  106. package/.agents/skills/faceless-explainer/scripts/assemble-index.mjs +570 -0
  107. package/.agents/skills/faceless-explainer/scripts/audio.mjs +260 -0
  108. package/.agents/skills/faceless-explainer/scripts/build-frame.mjs +536 -0
  109. package/.agents/skills/faceless-explainer/scripts/captions.mjs +508 -0
  110. package/.agents/skills/faceless-explainer/scripts/lib/assets.mjs +55 -0
  111. package/.agents/skills/faceless-explainer/scripts/lib/dimensions.mjs +45 -0
  112. package/.agents/skills/faceless-explainer/scripts/lib/storyboard.mjs +249 -0
  113. package/.agents/skills/faceless-explainer/scripts/lib/tokens.mjs +204 -0
  114. package/.agents/skills/faceless-explainer/scripts/lib/transition-registry.mjs +38 -0
  115. package/.agents/skills/faceless-explainer/scripts/lib/transitions.json +71 -0
  116. package/.agents/skills/faceless-explainer/scripts/transitions.mjs +320 -0
  117. package/.agents/skills/faceless-explainer/sub-agents/frame-worker.md +75 -0
  118. package/.agents/skills/farmville-saas-ux/SKILL.md +156 -0
  119. package/.agents/skills/farmville-saas-ux/assets/starter.html +294 -0
  120. package/.agents/skills/farmville-saas-ux/references/components.md +340 -0
  121. package/.agents/skills/farmville-saas-ux/references/porting-guide.md +121 -0
  122. package/.agents/skills/farmville-saas-ux/references/tokens.md +271 -0
  123. package/.agents/skills/flockposter/LICENSE +17 -0
  124. package/.agents/skills/flockposter/README.md +72 -0
  125. package/.agents/skills/flockposter/SKILL.md +562 -0
  126. package/.agents/skills/flockposter/examples/cli-instagram-reel.sh +21 -0
  127. package/.agents/skills/flockposter/examples/cli-multi-platform.json +69 -0
  128. package/.agents/skills/flockposter/examples/cli-tiktok-upload-only.sh +22 -0
  129. package/.agents/skills/flockposter/examples/cli-x-thread.sh +17 -0
  130. package/.agents/skills/flockposter/examples/cli-youtube-short.sh +21 -0
  131. package/.agents/skills/flockposter/examples/public-api-instagram-reel.json +28 -0
  132. package/.agents/skills/general-video/SKILL.md +143 -0
  133. package/.agents/skills/hyperframes/SKILL.md +160 -0
  134. package/.agents/skills/hyperframes-animation/SKILL.md +84 -0
  135. package/.agents/skills/hyperframes-animation/adapters/animate-text.md +64 -0
  136. package/.agents/skills/hyperframes-animation/adapters/animejs.md +114 -0
  137. package/.agents/skills/hyperframes-animation/adapters/css-animations.md +143 -0
  138. package/.agents/skills/hyperframes-animation/adapters/gsap-easing-and-stagger.md +118 -0
  139. package/.agents/skills/hyperframes-animation/adapters/gsap-timeline-and-labels.md +96 -0
  140. package/.agents/skills/hyperframes-animation/adapters/gsap-transforms-and-perf.md +96 -0
  141. package/.agents/skills/hyperframes-animation/adapters/gsap.md +105 -0
  142. package/.agents/skills/hyperframes-animation/adapters/html-in-canvas-patterns.md +507 -0
  143. package/.agents/skills/hyperframes-animation/adapters/lottie.md +117 -0
  144. package/.agents/skills/hyperframes-animation/adapters/three.md +131 -0
  145. package/.agents/skills/hyperframes-animation/adapters/typegpu.md +178 -0
  146. package/.agents/skills/hyperframes-animation/adapters/waapi.md +101 -0
  147. package/.agents/skills/hyperframes-animation/blueprints/comparison-split.md +27 -0
  148. package/.agents/skills/hyperframes-animation/blueprints/constellation-hub.md +44 -0
  149. package/.agents/skills/hyperframes-animation/blueprints/cta-morph-press.md +28 -0
  150. package/.agents/skills/hyperframes-animation/blueprints/cursor-ui-demo.md +57 -0
  151. package/.agents/skills/hyperframes-animation/blueprints/dataviz-countup.md +46 -0
  152. package/.agents/skills/hyperframes-animation/blueprints/device-surface-showcase.md +53 -0
  153. package/.agents/skills/hyperframes-animation/blueprints/grid-card-assemble.md +66 -0
  154. package/.agents/skills/hyperframes-animation/blueprints/kinetic-type-beats.md +78 -0
  155. package/.agents/skills/hyperframes-animation/blueprints/logo-assemble-lockup.md +69 -0
  156. package/.agents/skills/hyperframes-animation/blueprints/overwhelm-surround.md +28 -0
  157. package/.agents/skills/hyperframes-animation/blueprints/spatial-pan-stations.md +37 -0
  158. package/.agents/skills/hyperframes-animation/blueprints/ticker-takeover.md +29 -0
  159. package/.agents/skills/hyperframes-animation/blueprints/titlecard-reveal.md +42 -0
  160. package/.agents/skills/hyperframes-animation/blueprints/typewriter-reveal.md +51 -0
  161. package/.agents/skills/hyperframes-animation/blueprints/video-text-pivot.md +30 -0
  162. package/.agents/skills/hyperframes-animation/blueprints-index.md +146 -0
  163. package/.agents/skills/hyperframes-animation/examples/assets/avatars/02.avif +0 -0
  164. package/.agents/skills/hyperframes-animation/examples/assets/brands/github.avif +0 -0
  165. package/.agents/skills/hyperframes-animation/examples/assets/brands/nvidia.avif +0 -0
  166. package/.agents/skills/hyperframes-animation/examples/assets/brands/visa.avif +0 -0
  167. package/.agents/skills/hyperframes-animation/examples/assets/brands/zoominfo.avif +0 -0
  168. package/.agents/skills/hyperframes-animation/examples/brand-reveal-assemble-zoom.html +382 -0
  169. package/.agents/skills/hyperframes-animation/examples/comparison-split-cards.html +649 -0
  170. package/.agents/skills/hyperframes-animation/examples/concept-demo-decode-pan.html +520 -0
  171. package/.agents/skills/hyperframes-animation/examples/cta-morph-press.html +468 -0
  172. package/.agents/skills/hyperframes-animation/examples/cta-orbit-collapse.html +1298 -0
  173. package/.agents/skills/hyperframes-animation/examples/demo-page-scroll-spotlight.html +759 -0
  174. package/.agents/skills/hyperframes-animation/examples/hook-counter-burst.html +729 -0
  175. package/.agents/skills/hyperframes-animation/examples/messaging-multi-phrase.html +352 -0
  176. package/.agents/skills/hyperframes-animation/examples/metric-video-text-pivot.html +779 -0
  177. package/.agents/skills/hyperframes-animation/examples/problem-mockup-overwhelm.html +1374 -0
  178. package/.agents/skills/hyperframes-animation/examples/proof-logo-chain.html +861 -0
  179. package/.agents/skills/hyperframes-animation/examples/takeover-ticker-displace.html +347 -0
  180. package/.agents/skills/hyperframes-animation/examples/workflow-approve-press.html +606 -0
  181. package/.agents/skills/hyperframes-animation/rules/3d-page-scroll.md +227 -0
  182. package/.agents/skills/hyperframes-animation/rules/3d-text-depth-layers.md +297 -0
  183. package/.agents/skills/hyperframes-animation/rules/ai-tracking-box.md +382 -0
  184. package/.agents/skills/hyperframes-animation/rules/ambient-glow-bloom.md +305 -0
  185. package/.agents/skills/hyperframes-animation/rules/asr-keyword-glow.md +286 -0
  186. package/.agents/skills/hyperframes-animation/rules/avatar-cloud-network.md +371 -0
  187. package/.agents/skills/hyperframes-animation/rules/camera-cursor-tracking.md +246 -0
  188. package/.agents/skills/hyperframes-animation/rules/card-morph-anchor.md +267 -0
  189. package/.agents/skills/hyperframes-animation/rules/center-outward-expansion.md +227 -0
  190. package/.agents/skills/hyperframes-animation/rules/context-sensitive-cursor.md +257 -0
  191. package/.agents/skills/hyperframes-animation/rules/coordinate-target-zoom.md +332 -0
  192. package/.agents/skills/hyperframes-animation/rules/counting-dynamic-scale.md +283 -0
  193. package/.agents/skills/hyperframes-animation/rules/css-marker-patterns.md +373 -0
  194. package/.agents/skills/hyperframes-animation/rules/cursor-click-ripple.md +262 -0
  195. package/.agents/skills/hyperframes-animation/rules/depth-of-field-blur.md +313 -0
  196. package/.agents/skills/hyperframes-animation/rules/depth-scatter-assemble.md +303 -0
  197. package/.agents/skills/hyperframes-animation/rules/discrete-text-sequence.md +273 -0
  198. package/.agents/skills/hyperframes-animation/rules/dynamic-content-sequencing.md +307 -0
  199. package/.agents/skills/hyperframes-animation/rules/gsap-effects.md +299 -0
  200. package/.agents/skills/hyperframes-animation/rules/hacker-flip-3d.md +223 -0
  201. package/.agents/skills/hyperframes-animation/rules/kinetic-beat-slam.md +183 -0
  202. package/.agents/skills/hyperframes-animation/rules/motion-blur-streak.md +328 -0
  203. package/.agents/skills/hyperframes-animation/rules/multi-phase-camera.md +273 -0
  204. package/.agents/skills/hyperframes-animation/rules/orbit-3d-entry.md +301 -0
  205. package/.agents/skills/hyperframes-animation/rules/physics-press-reaction.md +350 -0
  206. package/.agents/skills/hyperframes-animation/rules/press-release-spring.md +296 -0
  207. package/.agents/skills/hyperframes-animation/rules/reactive-displacement.md +277 -0
  208. package/.agents/skills/hyperframes-animation/rules/scale-swap-transition.md +298 -0
  209. package/.agents/skills/hyperframes-animation/rules/sine-wave-loop.md +278 -0
  210. package/.agents/skills/hyperframes-animation/rules/split-tilt-cards.md +277 -0
  211. package/.agents/skills/hyperframes-animation/rules/spring-pop-entrance.md +273 -0
  212. package/.agents/skills/hyperframes-animation/rules/stat-bars-and-fills.md +156 -0
  213. package/.agents/skills/hyperframes-animation/rules/svg-icon-enrichment.md +329 -0
  214. package/.agents/skills/hyperframes-animation/rules/svg-path-draw.md +274 -0
  215. package/.agents/skills/hyperframes-animation/rules/vertical-spring-ticker.md +239 -0
  216. package/.agents/skills/hyperframes-animation/rules/viewport-change.md +349 -0
  217. package/.agents/skills/hyperframes-animation/rules-index.md +86 -0
  218. package/.agents/skills/hyperframes-animation/scripts/animation-map.mjs +606 -0
  219. package/.agents/skills/hyperframes-animation/scripts/package-loader.mjs +288 -0
  220. package/.agents/skills/hyperframes-animation/scripts/package-loader.test.mjs +62 -0
  221. package/.agents/skills/hyperframes-animation/techniques.md +507 -0
  222. package/.agents/skills/hyperframes-animation/transitions/TRANSITION-REGISTRY.md +167 -0
  223. package/.agents/skills/hyperframes-animation/transitions/catalog.md +127 -0
  224. package/.agents/skills/hyperframes-animation/transitions/css-3d.md +12 -0
  225. package/.agents/skills/hyperframes-animation/transitions/css-blur.md +51 -0
  226. package/.agents/skills/hyperframes-animation/transitions/css-cover.md +43 -0
  227. package/.agents/skills/hyperframes-animation/transitions/css-destruction.md +95 -0
  228. package/.agents/skills/hyperframes-animation/transitions/css-dissolve.md +66 -0
  229. package/.agents/skills/hyperframes-animation/transitions/css-distortion.md +45 -0
  230. package/.agents/skills/hyperframes-animation/transitions/css-grid.md +10 -0
  231. package/.agents/skills/hyperframes-animation/transitions/css-light.md +49 -0
  232. package/.agents/skills/hyperframes-animation/transitions/css-mechanical.md +30 -0
  233. package/.agents/skills/hyperframes-animation/transitions/css-other.md +25 -0
  234. package/.agents/skills/hyperframes-animation/transitions/css-push.md +41 -0
  235. package/.agents/skills/hyperframes-animation/transitions/css-radial.md +37 -0
  236. package/.agents/skills/hyperframes-animation/transitions/css-scale.md +24 -0
  237. package/.agents/skills/hyperframes-animation/transitions/overview.md +153 -0
  238. package/.agents/skills/hyperframes-cli/SKILL.md +113 -0
  239. package/.agents/skills/hyperframes-cli/references/doctor-browser.md +45 -0
  240. package/.agents/skills/hyperframes-cli/references/init-and-scaffold.md +55 -0
  241. package/.agents/skills/hyperframes-cli/references/lambda.md +132 -0
  242. package/.agents/skills/hyperframes-cli/references/lint-validate-inspect.md +121 -0
  243. package/.agents/skills/hyperframes-cli/references/preview-render.md +149 -0
  244. package/.agents/skills/hyperframes-cli/references/upgrade-info-misc.md +75 -0
  245. package/.agents/skills/hyperframes-core/SKILL.md +78 -0
  246. package/.agents/skills/hyperframes-core/references/composition-patterns.md +263 -0
  247. package/.agents/skills/hyperframes-core/references/data-attributes.md +70 -0
  248. package/.agents/skills/hyperframes-core/references/determinism-rules.md +68 -0
  249. package/.agents/skills/hyperframes-core/references/full-screen-motion.md +62 -0
  250. package/.agents/skills/hyperframes-core/references/minimal-composition.md +66 -0
  251. package/.agents/skills/hyperframes-core/references/script-format.md +49 -0
  252. package/.agents/skills/hyperframes-core/references/storyboard-format.md +95 -0
  253. package/.agents/skills/hyperframes-core/references/sub-compositions.md +237 -0
  254. package/.agents/skills/hyperframes-core/references/subagent-dispatch.md +41 -0
  255. package/.agents/skills/hyperframes-core/references/tailwind.md +125 -0
  256. package/.agents/skills/hyperframes-core/references/tracks-and-clips.md +76 -0
  257. package/.agents/skills/hyperframes-core/references/variables-and-media.md +90 -0
  258. package/.agents/skills/hyperframes-creative/SKILL.md +70 -0
  259. package/.agents/skills/hyperframes-creative/frame-presets/biennale-yellow/FRAME.md +289 -0
  260. package/.agents/skills/hyperframes-creative/frame-presets/biennale-yellow/caption-skin.html +221 -0
  261. package/.agents/skills/hyperframes-creative/frame-presets/biennale-yellow/frame-showcase.html +1214 -0
  262. package/.agents/skills/hyperframes-creative/frame-presets/blockframe/FRAME.md +275 -0
  263. package/.agents/skills/hyperframes-creative/frame-presets/blockframe/caption-skin.html +224 -0
  264. package/.agents/skills/hyperframes-creative/frame-presets/blockframe/frame-showcase.html +1347 -0
  265. package/.agents/skills/hyperframes-creative/frame-presets/blue-professional/FRAME.md +307 -0
  266. package/.agents/skills/hyperframes-creative/frame-presets/blue-professional/caption-skin.html +223 -0
  267. package/.agents/skills/hyperframes-creative/frame-presets/blue-professional/frame-showcase.html +1372 -0
  268. package/.agents/skills/hyperframes-creative/frame-presets/bold-poster/FRAME.md +275 -0
  269. package/.agents/skills/hyperframes-creative/frame-presets/bold-poster/caption-skin.html +230 -0
  270. package/.agents/skills/hyperframes-creative/frame-presets/bold-poster/frame-showcase.html +1140 -0
  271. package/.agents/skills/hyperframes-creative/frame-presets/broadside/FRAME.md +284 -0
  272. package/.agents/skills/hyperframes-creative/frame-presets/broadside/caption-skin.html +225 -0
  273. package/.agents/skills/hyperframes-creative/frame-presets/broadside/frame-showcase.html +1166 -0
  274. package/.agents/skills/hyperframes-creative/frame-presets/capsule/FRAME.md +284 -0
  275. package/.agents/skills/hyperframes-creative/frame-presets/capsule/caption-skin.html +224 -0
  276. package/.agents/skills/hyperframes-creative/frame-presets/capsule/frame-showcase.html +1465 -0
  277. package/.agents/skills/hyperframes-creative/frame-presets/cartesian/FRAME.md +278 -0
  278. package/.agents/skills/hyperframes-creative/frame-presets/cartesian/caption-skin.html +222 -0
  279. package/.agents/skills/hyperframes-creative/frame-presets/cartesian/frame-showcase.html +1263 -0
  280. package/.agents/skills/hyperframes-creative/frame-presets/claude/FRAME.md +277 -0
  281. package/.agents/skills/hyperframes-creative/frame-presets/claude/caption-skin.html +224 -0
  282. package/.agents/skills/hyperframes-creative/frame-presets/claude/frame-showcase.html +1439 -0
  283. package/.agents/skills/hyperframes-creative/frame-presets/cobalt-grid/FRAME.md +265 -0
  284. package/.agents/skills/hyperframes-creative/frame-presets/cobalt-grid/caption-skin.html +238 -0
  285. package/.agents/skills/hyperframes-creative/frame-presets/cobalt-grid/frame-showcase.html +1468 -0
  286. package/.agents/skills/hyperframes-creative/frame-presets/coral/FRAME.md +314 -0
  287. package/.agents/skills/hyperframes-creative/frame-presets/coral/caption-skin.html +226 -0
  288. package/.agents/skills/hyperframes-creative/frame-presets/coral/frame-showcase.html +1268 -0
  289. package/.agents/skills/hyperframes-creative/frame-presets/creative-mode/FRAME.md +333 -0
  290. package/.agents/skills/hyperframes-creative/frame-presets/creative-mode/caption-skin.html +227 -0
  291. package/.agents/skills/hyperframes-creative/frame-presets/creative-mode/frame-showcase.html +1330 -0
  292. package/.agents/skills/hyperframes-creative/frame-presets/daisy-days/FRAME.md +274 -0
  293. package/.agents/skills/hyperframes-creative/frame-presets/daisy-days/caption-skin.html +230 -0
  294. package/.agents/skills/hyperframes-creative/frame-presets/daisy-days/frame-showcase.html +1444 -0
  295. package/.agents/skills/hyperframes-creative/frame-presets/editorial-forest/FRAME.md +265 -0
  296. package/.agents/skills/hyperframes-creative/frame-presets/editorial-forest/caption-skin.html +223 -0
  297. package/.agents/skills/hyperframes-creative/frame-presets/editorial-forest/frame-showcase.html +1273 -0
  298. package/.agents/skills/hyperframes-creative/palettes/bold-energetic.md +14 -0
  299. package/.agents/skills/hyperframes-creative/palettes/clean-corporate.md +14 -0
  300. package/.agents/skills/hyperframes-creative/palettes/dark-premium.md +14 -0
  301. package/.agents/skills/hyperframes-creative/palettes/jewel-rich.md +14 -0
  302. package/.agents/skills/hyperframes-creative/palettes/monochrome.md +14 -0
  303. package/.agents/skills/hyperframes-creative/palettes/nature-earth.md +14 -0
  304. package/.agents/skills/hyperframes-creative/palettes/neon-electric.md +14 -0
  305. package/.agents/skills/hyperframes-creative/palettes/pastel-soft.md +14 -0
  306. package/.agents/skills/hyperframes-creative/palettes/warm-editorial.md +14 -0
  307. package/.agents/skills/hyperframes-creative/references/audio-reactive.md +76 -0
  308. package/.agents/skills/hyperframes-creative/references/beat-direction.md +164 -0
  309. package/.agents/skills/hyperframes-creative/references/composition-patterns.md +199 -0
  310. package/.agents/skills/hyperframes-creative/references/data-in-motion.md +19 -0
  311. package/.agents/skills/hyperframes-creative/references/design-adherence.md +19 -0
  312. package/.agents/skills/hyperframes-creative/references/design-picker.md +123 -0
  313. package/.agents/skills/hyperframes-creative/references/design-spec.md +55 -0
  314. package/.agents/skills/hyperframes-creative/references/house-style.md +73 -0
  315. package/.agents/skills/hyperframes-creative/references/motion-principles.md +150 -0
  316. package/.agents/skills/hyperframes-creative/references/narration.md +92 -0
  317. package/.agents/skills/hyperframes-creative/references/prompt-expansion.md +68 -0
  318. package/.agents/skills/hyperframes-creative/references/typography.md +218 -0
  319. package/.agents/skills/hyperframes-creative/references/video-composition.md +63 -0
  320. package/.agents/skills/hyperframes-creative/references/visual-styles.md +457 -0
  321. package/.agents/skills/hyperframes-creative/scripts/contrast-report.mjs +353 -0
  322. package/.agents/skills/hyperframes-creative/scripts/extract-audio-data.py +188 -0
  323. package/.agents/skills/hyperframes-creative/scripts/package-loader.mjs +288 -0
  324. package/.agents/skills/hyperframes-creative/scripts/package-loader.test.mjs +62 -0
  325. package/.agents/skills/hyperframes-creative/templates/design-picker.html +1432 -0
  326. package/.agents/skills/hyperframes-keyframes/SKILL.md +237 -0
  327. package/.agents/skills/hyperframes-keyframes/agents/openai.yaml +4 -0
  328. package/.agents/skills/hyperframes-keyframes/references/keyframe-patterns.md +106 -0
  329. package/.agents/skills/motion-graphics/SKILL.md +172 -0
  330. package/.agents/skills/motion-graphics/agents/builder.md +40 -0
  331. package/.agents/skills/motion-graphics/agents/director.md +53 -0
  332. package/.agents/skills/motion-graphics/agents/finalize.md +17 -0
  333. package/.agents/skills/motion-graphics/catalog-map.md +40 -0
  334. package/.agents/skills/motion-graphics/categories/asset-fusion/module.md +37 -0
  335. package/.agents/skills/motion-graphics/categories/charts/module.md +21 -0
  336. package/.agents/skills/motion-graphics/categories/kinetic-type/module.md +19 -0
  337. package/.agents/skills/motion-graphics/categories/logo-reveal/module.md +17 -0
  338. package/.agents/skills/motion-graphics/categories/lower-thirds/module.md +16 -0
  339. package/.agents/skills/motion-graphics/categories/maps/bake-basemap.mjs +273 -0
  340. package/.agents/skills/motion-graphics/categories/maps/module.md +61 -0
  341. package/.agents/skills/motion-graphics/categories/news/module.md +56 -0
  342. package/.agents/skills/motion-graphics/categories/stat/module.md +21 -0
  343. package/.agents/skills/motion-graphics/categories/tweet/module.md +16 -0
  344. package/.agents/skills/motion-graphics/categories/webpage/module.md +21 -0
  345. package/.agents/skills/motion-graphics/grounding/PROTOCOL.md +64 -0
  346. package/.agents/skills/motion-graphics/grounding/locate.mjs +227 -0
  347. package/.agents/skills/motion-graphics/phases/source/guide.md +21 -0
  348. package/.agents/skills/motion-graphics/references/builder-contract.md +38 -0
  349. package/.agents/skills/motion-graphics/references/motion-vocabulary.md +43 -0
  350. package/.agents/skills/motion-graphics/references/shot-plan-ir.md +54 -0
  351. package/.agents/skills/motion-graphics/samples/asset-fusion/_ref-circle-highlight.html +306 -0
  352. package/.agents/skills/music/SKILL.md +416 -0
  353. package/.agents/skills/music/references/api_reference.md +519 -0
  354. package/.agents/skills/music/references/installation.md +65 -0
  355. package/.agents/skills/product-launch-video/SKILL.md +208 -0
  356. package/.agents/skills/product-launch-video/references/cut-catalog.md +220 -0
  357. package/.agents/skills/product-launch-video/references/motion-language.md +156 -0
  358. package/.agents/skills/product-launch-video/references/story-design.md +373 -0
  359. package/.agents/skills/product-launch-video/references/visual-design.md +126 -0
  360. package/.agents/skills/product-launch-video/scripts/assemble-index.mjs +570 -0
  361. package/.agents/skills/product-launch-video/scripts/audio.mjs +260 -0
  362. package/.agents/skills/product-launch-video/scripts/build-frame.mjs +536 -0
  363. package/.agents/skills/product-launch-video/scripts/captions.mjs +508 -0
  364. package/.agents/skills/product-launch-video/scripts/lib/assets.mjs +55 -0
  365. package/.agents/skills/product-launch-video/scripts/lib/dimensions.mjs +45 -0
  366. package/.agents/skills/product-launch-video/scripts/lib/pad-frame-duration.mjs +36 -0
  367. package/.agents/skills/product-launch-video/scripts/lib/pad-frame-duration.test.mjs +76 -0
  368. package/.agents/skills/product-launch-video/scripts/lib/storyboard.mjs +249 -0
  369. package/.agents/skills/product-launch-video/scripts/lib/tokens.mjs +204 -0
  370. package/.agents/skills/product-launch-video/scripts/lib/transition-registry.mjs +38 -0
  371. package/.agents/skills/product-launch-video/scripts/lib/transitions.json +71 -0
  372. package/.agents/skills/product-launch-video/scripts/stage-assets.mjs +39 -0
  373. package/.agents/skills/product-launch-video/scripts/transitions.mjs +327 -0
  374. package/.agents/skills/product-launch-video/sub-agents/frame-worker.md +75 -0
  375. package/.agents/skills/slideshow/SKILL.md +550 -0
  376. package/.agents/skills/slideshow/references/standalone-harness.md +1020 -0
  377. package/.agents/skills/talking-head-recut/NOTICE.md +39 -0
  378. package/.agents/skills/talking-head-recut/SKILL.md +1195 -0
  379. package/.agents/skills/talking-head-recut/assets/vendor/gsap.min.js +10 -0
  380. package/.agents/skills/talking-head-recut/references/DESIGN_INDEX.md +145 -0
  381. package/.agents/skills/talking-head-recut/references/frames/clean.html +92 -0
  382. package/.agents/skills/talking-head-recut/references/frames/hairline.html +171 -0
  383. package/.agents/skills/talking-head-recut/references/frames/polaroid.html +131 -0
  384. package/.agents/skills/talking-head-recut/references/layouts/overlay.html +164 -0
  385. package/.agents/skills/talking-head-recut/references/layouts/pip.html +143 -0
  386. package/.agents/skills/talking-head-recut/references/layouts/split.html +121 -0
  387. package/.agents/skills/talking-head-recut/references/layouts/stack.html +109 -0
  388. package/.agents/skills/talking-head-recut/references/styles/academic.html +172 -0
  389. package/.agents/skills/talking-head-recut/references/styles/audit.html +170 -0
  390. package/.agents/skills/talking-head-recut/references/styles/editorial.html +189 -0
  391. package/.agents/skills/talking-head-recut/references/styles/geom.html +181 -0
  392. package/.agents/skills/talking-head-recut/references/styles/minimal.html +130 -0
  393. package/.agents/skills/talking-head-recut/references/styles/spotlight.html +142 -0
  394. package/.agents/skills/talking-head-recut/references/styles/swiss.html +188 -0
  395. package/.agents/skills/talking-head-recut/references/styles/terminal.html +196 -0
  396. package/.agents/skills/talking-head-recut/references/styles/whiteboard.html +170 -0
  397. package/.agents/skills/talking-head-recut/references/styles/xhs.html +206 -0
  398. package/.agents/skills/text-to-speech/SKILL.md +226 -0
  399. package/.agents/skills/text-to-speech/references/installation.md +90 -0
  400. package/.agents/skills/text-to-speech/references/streaming.md +307 -0
  401. package/.agents/skills/text-to-speech/references/voice-settings.md +115 -0
  402. package/.agents/skills/vidfarm-director/SKILL.md +179 -0
  403. package/.agents/skills/vidfarm-director/recipes/find-and-fork-template.md +16 -0
  404. package/.agents/skills/vidfarm-director/recipes/local-edit-render-approve.md +13 -0
  405. package/.agents/skills/vidfarm-director/recipes/onboard-a-new-director.md +13 -0
  406. package/.agents/skills/vidfarm-director/recipes/retheme-template.md +17 -0
  407. package/.agents/skills/vidfarm-director/references/assets-and-sourcing.md +117 -0
  408. package/.agents/skills/vidfarm-director/references/automation-and-local-dev.md +276 -0
  409. package/.agents/skills/vidfarm-director/references/core-workflows.md +301 -0
  410. package/.agents/skills/vidfarm-director/references/editor-workflows.md +367 -0
  411. package/.agents/skills/vidfarm-director/references/onboarding.md +28 -0
  412. package/.agents/skills/vidfarm-director/references/primitives.md +307 -0
  413. package/.agents/skills/vidfarm-director/references/rest-api.md +84 -0
  414. package/.agents/skills/vidfarm-media/SKILL.md +158 -0
  415. package/.agents/skills/vidfarm-media/references/tts.md +111 -0
  416. package/.agents/skills/vidfarm-media/scripts/audio.mjs +628 -0
  417. package/.agents/skills/vidfarm-media/scripts/wait-bgm.mjs +31 -0
  418. package/.agents/skills/website-to-video/SKILL.md +145 -0
  419. package/.agents/skills/website-to-video/assets/sfx/CREDITS.md +35 -0
  420. package/.agents/skills/website-to-video/assets/sfx/manifest.json +97 -0
  421. package/.agents/skills/website-to-video/references/beat-builder-guide.md +263 -0
  422. package/.agents/skills/website-to-video/references/capabilities.md +713 -0
  423. package/.agents/skills/website-to-video/references/step-0-capture.md +55 -0
  424. package/.agents/skills/website-to-video/references/step-1-design.md +333 -0
  425. package/.agents/skills/website-to-video/references/step-2-brief.md +178 -0
  426. package/.agents/skills/website-to-video/references/step-3-storyboard.md +572 -0
  427. package/.agents/skills/website-to-video/references/step-4-vo.md +225 -0
  428. package/.agents/skills/website-to-video/references/step-5-build.md +483 -0
  429. package/.agents/skills/website-to-video/references/step-6-validate.md +341 -0
  430. package/.agents/skills/website-to-video/scripts/w2h-verify.mjs +759 -0
  431. package/README.md +97 -0
  432. package/SKILL.director.md +1644 -0
  433. package/SKILL.md +83 -0
  434. package/demo/README.md +28 -0
  435. package/demo/dist/app.css +1 -0
  436. package/demo/dist/app.js +968 -0
  437. package/demo/dist/chunks/chunk-3OT2D42H.js +1 -0
  438. package/demo/dist/chunks/chunk-DXB73IDG.js +1 -0
  439. package/demo/dist/chunks/domEditingLayers-AT7G6F4L-ZTHJHB7R.js +1 -0
  440. package/demo/dist/chunks/hyperframes-player-7HKH6NSO.js +459 -0
  441. package/demo/dist/chunks/lib-XAQ37YOE.js +1 -0
  442. package/demo/dist/chunks/src-TJ2QYA4U.js +207 -0
  443. package/demo/dist/favicon.ico +0 -0
  444. package/demo/dist/icons/timeline/audio.svg +7 -0
  445. package/demo/dist/icons/timeline/captions.svg +5 -0
  446. package/demo/dist/icons/timeline/composition.svg +12 -0
  447. package/demo/dist/icons/timeline/image.svg +18 -0
  448. package/demo/dist/icons/timeline/music.svg +10 -0
  449. package/demo/dist/icons/timeline/text.svg +3 -0
  450. package/demo/dist/index.html +16 -0
  451. package/dist/src/cli.js +7258 -0
  452. package/dist/src/devcli/auth-store.js +67 -0
  453. package/dist/src/devcli/captions.js +310 -0
  454. package/dist/src/devcli/clip-store.js +368 -0
  455. package/dist/src/devcli/clips.js +1138 -0
  456. package/dist/src/devcli/composition-edit.js +1292 -0
  457. package/dist/src/devcli/cost-mode.js +149 -0
  458. package/dist/src/devcli/doctor.js +192 -0
  459. package/dist/src/devcli/hyperframes-cli.js +118 -0
  460. package/dist/src/devcli/local-backend.js +167 -0
  461. package/dist/src/devcli/local-frontend-server.js +681 -0
  462. package/dist/src/devcli/local-render.js +136 -0
  463. package/dist/src/devcli/skills.js +476 -0
  464. package/dist/src/devcli/speech.js +178 -0
  465. package/dist/src/devcli/stills.js +249 -0
  466. package/dist/src/devcli/telemetry.js +236 -0
  467. package/dist/src/devcli/timeline-edit.js +490 -0
  468. package/dist/src/devcli/transitions.js +205 -0
  469. package/dist/src/hyperframes/composition.js +780 -0
  470. package/dist/src/lib/crypto.js +45 -0
  471. package/dist/src/lib/dev-log.js +54 -0
  472. package/dist/src/lib/display-name.js +11 -0
  473. package/dist/src/lib/ids.js +24 -0
  474. package/dist/src/lib/images.js +19 -0
  475. package/dist/src/lib/json.js +15 -0
  476. package/dist/src/lib/package-root.js +47 -0
  477. package/dist/src/lib/render-media-prep.js +293 -0
  478. package/dist/src/lib/template-paths.js +28 -0
  479. package/dist/src/lib/time.js +7 -0
  480. package/dist/src/lib/url-clean.js +85 -0
  481. package/dist/src/services/captions.js +123 -0
  482. package/dist/src/services/clip-curation/cost.js +116 -0
  483. package/dist/src/services/clip-curation/ffmpeg.js +352 -0
  484. package/dist/src/services/clip-curation/gemini.js +418 -0
  485. package/dist/src/services/clip-curation/hunt.js +470 -0
  486. package/dist/src/services/clip-curation/index.js +19 -0
  487. package/dist/src/services/clip-curation/local-agent.js +248 -0
  488. package/dist/src/services/clip-curation/media-select.js +152 -0
  489. package/dist/src/services/clip-curation/presets.js +20 -0
  490. package/dist/src/services/clip-curation/presets.v1.json +59 -0
  491. package/dist/src/services/clip-curation/query.js +167 -0
  492. package/dist/src/services/clip-curation/refine.js +165 -0
  493. package/dist/src/services/clip-curation/scan.js +189 -0
  494. package/dist/src/services/clip-curation/source-naming.js +131 -0
  495. package/dist/src/services/clip-curation/taxonomy.js +73 -0
  496. package/dist/src/services/clip-curation/taxonomy.v1.json +102 -0
  497. package/dist/src/services/clip-curation/types.js +7 -0
  498. package/dist/src/services/composition-lint.js +199 -0
  499. package/dist/src/services/provider-errors.js +128 -0
  500. package/dist/src/services/speech.js +629 -0
  501. package/package.json +154 -0
  502. package/public/assets/discover-client-app.js +1 -0
  503. package/public/assets/favicon.ico +0 -0
  504. package/public/assets/file-directory-app.js +89 -0
  505. package/public/assets/homepage-app.js +54 -0
  506. package/public/assets/homepage-client-app.js +80 -0
  507. package/public/assets/landing-page-client-app.js +87 -0
  508. package/public/assets/logo-vidfarm.png +0 -0
  509. package/public/assets/page-runtime-client-app.js +94 -0
  510. package/public/assets/placeholders/scene-placeholder.png +0 -0
  511. package/public/serve-shells/editor.html +3146 -0
  512. package/public/serve-shells/library-files.html +3450 -0
  513. package/public/serve-shells/library-raws.html +4404 -0
  514. package/public/serve-shells/tools-clipper.html +3991 -0
  515. package/public/serve-shells/tools-image.html +5584 -0
  516. package/public/serve-shells/tools-video.html +4404 -0
@@ -0,0 +1,1644 @@
1
+ ---
2
+ name: vidfarm-director
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
+ ---
5
+
6
+ # Vidfarm Director
7
+
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
+
10
+ ## Quickstart (desktop agents — do this first)
11
+
12
+ The CLI is `vidfarm`, from the npm package `@officexapp/vidfarm-devcli`. Install and authenticate before anything else:
13
+
14
+ ```bash
15
+ npm install -g @officexapp/vidfarm-devcli # installs the `vidfarm` command
16
+ vidfarm login --api-key vf_key_... # validates + persists the key durably
17
+ vidfarm serve template_<32hex> # local server + browser, opens that template
18
+ ```
19
+
20
+ - The API key comes from https://vidfarm.cc/settings and starts with `vf_key_`. Instead of `login`, setting the `VIDFARM_API_KEY` environment variable also works for every command — the CLI reads it from the environment or from a `.env` file in the current directory.
21
+ - No account or key? `vidfarm serve --no-cloud` still gives a fully local editor with free local renders.
22
+ - "Open/run template X locally" is exactly one command: `vidfarm serve <template_id>` (alias: `vidfarm <template_id>`). Do not hand-roll REST or hunt for local `.harness/` files first — `serve` and `pull` create those.
23
+
24
+ ### Entity ID formats
25
+
26
+ Every Vidfarm id is a type prefix + 32 hex characters (`prefix_<32hex>`). There are no short slugs and no `tpl_` prefix.
27
+
28
+ | Prefix | What it is | Typical use |
29
+ |---|---|---|
30
+ | `template_` | published template | `vidfarm serve template_<32hex>`, fork it |
31
+ | `fork_` | your editable composition | `vidfarm pull fork_<32hex>`, edit, render |
32
+ | `inspiration_` | imported source video | discovery, decompose |
33
+ | `raw_` / `clip_` | raw footage / cut clip | timeline media, raws library |
34
+
35
+ All of these are accepted verbatim by the feed search bars and exact-id API lookups. Never second-guess or "correct" a user-provided id because of its shape.
36
+
37
+ Use this skill when the user wants to:
38
+
39
+ - log in and save provider keys
40
+ - discover templates or add a new inspiration
41
+ - fork a template and edit a composition
42
+ - re-theme or rebuild a video while preserving the viral DNA
43
+ - render, approve, and schedule a post
44
+ - automate Vidfarm through REST, `vidfarm`, or a local `vidfarm serve` loop
45
+ - manage files, raws, recurring characters, versions, or sharing
46
+
47
+ 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.
48
+
49
+ ## Cost mode — ask before you spend, then remember it
50
+
51
+ Vidfarm work can burn real AI credits on the user's wallet / provider keys. **Save them money by default.** Before the first billed step of a session, ask the user which spend posture they want, and explain it in one plain line each:
52
+
53
+ - **minimize** — cheapest. Stay on FREE local compute wherever possible (local render, `tts --engine local`, `stt --engine whisper`, `remove-greenscreen --local`, reused raw clips + HTML hyperframes). No surprise AI spend.
54
+ - **hybrid** *(recommend this)* — free where it's free; pay for AI only where it clearly wins (a hero shot, a voice you can't fake locally).
55
+ - **pure-ai** — best quality; use AI image/video/voice/music freely.
56
+
57
+ Then **ask whether to remember the choice**, and save it where it will actually be read next time:
58
+
59
+ - **Local coding agent (devcli):** run `vidfarm cost-mode <choice>` — it persists to `~/.vidfarm/cost-mode.json`, and every billed devcli command then respects it (in `minimize`, billed ops refuse to run without `--yes`; in `hybrid`/`pure-ai` they print each op's cost). Also offer to record it in the agent's own memory so a *future* session recalls it — but which memory differs by agent, so ask, don't assume: Claude Code → `CLAUDE.md` (or its memory dir); Codex / OpenCode / most others → `AGENTS.md`; or a plain note file the user names. A third-party memory tool the user already uses is fine too. Never write to a memory file the user didn't approve.
60
+ - **Web app UI (the /editor copilot, chat dock, /chat, /library):** there is no agent-memory file to write, so **do not** try to persist it — just ask each time, unless the user tells you their standing preference in this conversation (then honor it for the session).
61
+
62
+ If the user hasn't picked yet and you're about to spend, name the cheaper path and the cost, and ask. `vidfarm cost-mode` with no argument prints the current mode + the three explanations for you to relay.
63
+
64
+ ## Default stance
65
+
66
+ - Treat the Trackpad Editor as the primary surface. Reach for templates and forks before primitives.
67
+ - Default to the cheapest approach that works. Reuse footage first, use image generation freely, and ask before AI video generation.
68
+ - Respect the active **cost mode** (above): in `minimize`, prefer free local engines and confirm any billed AI/cloud op with the user first.
69
+ - Prefer already-decomposed templates when they match the user’s goal.
70
+ - For heavy edits, read the grounding artifacts before acting: `video-context.json`, `editor-harness.json`, and local `.harness/*` bundles when present.
71
+ - For agentic rewrites, think in the three axes: scenes, audio, text. Decide whether each axis is a SWAP or a REPLACE.
72
+
73
+ ## The three paintbrushes (Vidfarm's operating philosophy)
74
+
75
+ 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:
76
+
77
+ 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.
78
+ 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.
79
+ 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.
80
+
81
+ 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.
82
+
83
+ **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:**
84
+
85
+ - **(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.
86
+ - **(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.
87
+
88
+ 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).
89
+
90
+ **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.
91
+
92
+ **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.
93
+
94
+ ## Web AI chat vs. local devcli — know your surface's limits
95
+
96
+ **Desktop users: this is a skill, not an SDK or required runtime.** It gives an AI agent Vidfarm's operating knowledge; it does not proxy requests, add hidden capabilities, or require the agent to use a particular client. Every cloud workflow described here can be performed with ordinary HTTP against the Vidfarm REST API. For convenience and safer composition/file workflows, recommend the `vidfarm` command from `@officexapp/vidfarm-devcli`: it wraps the same REST routes, handles auth, uploads, polling, pagination, local files, and pull/edit/render loops. Use raw REST whenever the user prefers it, the CLI lacks a new route, or the work belongs in an existing service or automation stack.
97
+
98
+ Choose the surface before choosing instructions:
99
+
100
+ | Surface | Live timeline | Shell / local files | Use it for |
101
+ |---|---:|---:|---|
102
+ | Web copilot | Yes, through `editor_action` | No | Short and medium edits on the open composition |
103
+ | Desktop coding agent | Through pulled files and devcli | Yes | Full rebuilds, scripts, batch work, and render/inspect loops |
104
+ | Devcli without an agent | Command-driven | Yes | Deterministic pull, edit, render, approve, and automation operations |
105
+
106
+ When this pack is installed locally, assume the desktop coding-agent surface unless trusted runtime context explicitly identifies the web editor. In web chat, declared tools and the latest `editor_context` override any capability described in this broad pack.
107
+
108
+ 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:
109
+
110
+ - **Determine the surface before claiming capabilities.** The web chat has only its declared tools and REST routes. It cannot execute arbitrary JavaScript/Python, open a shell, create a local repository script, or use the user's filesystem. Never tell a web-chat user that you ran code or wrote a script unless a dedicated declared tool actually did so. A desktop coding agent has a real shell and filesystem and MAY write/run scripts, perform arbitrary local computations over paginated API results, create reports/CSVs/JSON, edit composition files, and orchestrate long devcli workflows within the user's authorization.
111
+ - **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.
112
+ - **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.
113
+ - **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.
114
+ - **Offer a handoff, do not impersonate the desktop agent.** When web chat reaches that boundary, offer to save a Markdown handoff in My Files containing the objective, selected template/fork IDs, asset paths, grounding, constraints, completed work, and suggested devcli commands. Create it only after the user agrees. The desktop agent should read that document, pull the referenced fork, and then use its actual code/shell capabilities.
115
+ - **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.
116
+
117
+ ## Read Only What You Need
118
+
119
+ Read only the relevant reference file for the current task.
120
+
121
+ - Template discovery, auth, fork/publish/share/cost flow: `references/core-workflows.md`
122
+ - Timeline editing, decompose, captions, motion, AI placement: `references/editor-workflows.md`
123
+ - Raws hunts, My Files, recurring characters, asset retrieval: `references/assets-and-sourcing.md`
124
+ - REST automation, `vidfarm` command surface, local serve loop, skill packs: `references/automation-and-local-dev.md`
125
+ - Getting-started interviews, strategy docs, onboarding flow: `references/onboarding.md`
126
+ - Primitive routes such as TTS, STT, music, overlays, background removal, product placement: `references/primitives.md`
127
+ - Complete REST API map and raw-HTTP conventions: `references/rest-api.md`. Load it only when the user asks for REST, an endpoint/schema, direct HTTP integration, or exhaustive API coverage. For the entire specification, follow its domain links and load every listed reference; do not preload them into ordinary director conversations.
128
+
129
+ ## HyperFrames Skills — Load on Demand
130
+
131
+ Vidfarm ships a curated HyperFrames skill suite alongside this director pack. Use it for composition authoring and motion craft without loading the entire suite into context.
132
+
133
+ 1. Route broad video-creation requests through `hyperframes` first. It selects the appropriate workflow skill.
134
+ 2. Load only the selected workflow skill, then add the narrow domain skill required by the current step.
135
+ 3. Load `hyperframes-core` before writing or restructuring composition HTML.
136
+ 4. Load `hyperframes-animation`, `hyperframes-keyframes`, `hyperframes-creative`, or `hyperframes-cli` only when the task needs that specific capability.
137
+ 5. Use `vidfarm-media` for narration, music, transcription, captions timing, background removal, and media sourcing. Prefer Vidfarm primitives and the user's existing library; when unavailable, use an equivalent capability already exposed by the user's desktop AI agent.
138
+
139
+ On the web copilot, call `load_skill('<name>')` and load referenced files only when the selected skill reaches that step. On desktop, inspect the agent's available-skill catalog and local `.agents/skills` / `.claude/skills` entries before declaring a skill missing. If needed, install only the selected bundled pack with `vidfarm skills add <name>`; do not bulk-install or bulk-read the suite. If the Vidfarm CLI is unavailable, use the desktop agent's native skill discovery or local-skill mechanism and continue with the closest installed equivalent.
140
+
141
+ HyperFrames authoring and rendering in this package are Vidfarm-native: local work uses the bundled composition toolchain and `vidfarm serve`; cloud work uses Vidfarm render routes. Do not require an external vendor account, repository, publish service, or telemetry endpoint. Keep `HYPERFRAMES_SKIP_SKILLS=1` and `HYPERFRAMES_NO_TELEMETRY=1` in Vidfarm-managed environments so the bundled skills stay pinned and local work does not phone home.
142
+
143
+ ## Quick Router
144
+
145
+ Choose the narrowest path that satisfies the request.
146
+
147
+ 1. If the user needs help figuring out what to make, read `references/onboarding.md` first.
148
+ 2. If the user already knows the goal and needs a suitable template, read `references/core-workflows.md` and use the template discovery flow.
149
+ 3. If the task is “change this video,” read `references/editor-workflows.md`.
150
+ 4. If the task is “find footage” or “use our existing assets,” read `references/assets-and-sourcing.md`.
151
+ 5. If the task is scripted, local, CI-driven, or `vidfarm serve`-based, read `references/automation-and-local-dev.md`.
152
+ 6. If the task explicitly asks for a primitive or needs specialized generation/transcription work, read `references/primitives.md`.
153
+ 7. If the task is the MARKETPLACE (ordering videos from specialist agents): browsing is web-only for paying customers — send the human to https://vidfarm.cc/marketplace, never render it locally. Placing/listing orders is the thin REST wrapper in `references/core-workflows.md` (§ Marketplace); anything deeper on a gig (inbox, proofs, payouts) needs the external Dollar Platoon skill — `npx skills add https://github.com/OfficeXApp/dollarplatoon-skill` — the same way FlockPoster work beyond scheduling needs `npx skills add https://github.com/OfficeXApp/flockposter-skill`.
154
+
155
+ ## Non-Negotiables
156
+
157
+ - API-key auth is the `vidfarm-api-key` header. Do not use `Authorization: Bearer`.
158
+ - Do not manipulate composition HTML by string concatenation. Parse, edit, and re-serialize the DOM.
159
+ - Do not call the renderer directly. Rendering goes through `POST /api/v1/compositions/:forkId/render`.
160
+ - Do not store provider secrets in composition HTML or JSON.
161
+ - Treat `forkId` as an unguessable bearer token for read access.
162
+ - Submission routes are generally not idempotent. Especially for renders and expensive primitives, check status before retrying.
163
+ - In the web editor, use CSS/declarative motion only. Script-bearing HTML is stripped or rejected there.
164
+
165
+ ## Recommended Recipes
166
+
167
+ Use these when the user’s task matches the pattern closely.
168
+
169
+ - Template selection and first fork: `recipes/find-and-fork-template.md`
170
+ - Full re-theme while preserving the format’s feel: `recipes/retheme-template.md`
171
+ - Local pull/edit/render/approve loop: `recipes/local-edit-render-approve.md`
172
+ - New-director onboarding and durable context capture: `recipes/onboard-a-new-director.md`
173
+
174
+ ## Output Posture
175
+
176
+ - Prefer concrete actions over abstract discussion.
177
+ - Name the chosen path explicitly: template reuse, raws hunt, local serve, cloud render, etc.
178
+ - Surface cost tradeoffs before expensive generation.
179
+ - When in doubt between a broad reference and a recipe, start with the recipe.
180
+
181
+ ## Mental model
182
+
183
+ A Vidfarm composition is an **HTML document + JSON metadata**. Each layer (video clip, audio, image, text, caption) is a timed DOM element with `data-start`, `data-duration`, `data-track-index` attributes. The Trackpad Editor is a timeline UI over this document, similar to Premiere or DaVinci.
184
+
185
+ Every director workflow goes through three concepts:
186
+
187
+ 1. **Template** — a published composition anyone can fork. Template ids start with `template_`; each template opens in the editor at `/editor/:templateId` and has a source video, a decomposed timeline, and metadata (viral DNA, captions, scenes).
188
+ 2. **Composition fork** — a private editable copy of a template. Every fork has a `forkId`, an owner, a working state, and a version history. The Trackpad Editor operates on a fork.
189
+ 3. **Publish** — render the current fork state to MP4 via a Lambda render job. Each publish snapshots the current working state as an immutable version.
190
+
191
+ The Trackpad Editor is the primary surface. Most director actions happen there. This skill's REST endpoints exist for automation, CI, and headless flows.
192
+
193
+ ## Base URL and auth
194
+
195
+ The current API base URLs are:
196
+
197
+ - staging: `https://staging.vidfarm.cc`
198
+ - production: `https://vidfarm.cc`
199
+
200
+ Every browser action is authenticated by the session cookie set at `/login`. Headless API-key auth uses the `vidfarm-api-key` header set to the caller's API key (from Settings → API Keys).
201
+
202
+ **Auth contract: API-key auth is the `vidfarm-api-key: <key>` request header and nothing else. `Authorization: Bearer <key>` is NOT supported — the server never reads the `authorization` header (see `requireAuth` in `src/app.ts`).**
203
+
204
+ ```
205
+ vidfarm-api-key: vf_key_<id>
206
+ ```
207
+
208
+ Scope note (verified against `src/app.ts`): the `vidfarm-api-key` header authenticates **every** authenticated surface — the `requireAuth` routes (`/api/v1/user/*`, `/api/v1/agency/*`, `/api/v1/templates/*`, `/api/v1/primitives/*`, `/api/v1/rate-limit-status`, editor-chat threads) **and** the `/api/v1/compositions/*` + `/api/v1/videos/*` + editor page routes, where an explicit API key takes precedence over any browser session cookie. Cookies remain how the web UI authenticates; headless callers never need one. Reads on composition routes also work without any credential because the `forkId` acts as an unguessable view bearer token, and share-link tokens (header `vidfarm-share-token` or `?share=` query) can grant `viewer`/`editor`/`publisher` roles.
209
+
210
+ ## Login flow
211
+
212
+ Directors log in with OTP:
213
+
214
+ 1. `POST /api/v1/user/request-otp { email }` — sends a code to email; returns `{ ok, delivery }` (plus `dev_code` when delivery is `console` outside production)
215
+ 2. `POST /api/v1/user/verify-otp { email, code, name? }` — returns `{ customer, apiKey }` and mints a fresh API key
216
+
217
+ Save the `apiKey` in the client and use it for automation (as the `vidfarm-api-key` header). The browser login form uses the session-cookie variants `POST /login/otp/request` and `POST /login/otp/verify` instead.
218
+
219
+ ## Provider keys
220
+
221
+ Vidfarm executes many features (smart auto-decompose, editor chat) with the **caller's own AI provider keys**. Directors save keys once via:
222
+
223
+ - UI: Settings → Provider Keys
224
+ - API: `POST /api/v1/user/me/provider-keys { provider, secret, label? }` — one key per call; `provider` is `"openai" | "gemini" | "openrouter" | "perplexity"`. The key is validated against the provider before it is saved. List saved keys with `GET /api/v1/user/me/provider-keys`.
225
+
226
+ Priority: Gemini → OpenAI → OpenRouter. If a director has no keys, smart decompose falls back to a deterministic time-based split.
227
+
228
+ ## Discover templates
229
+
230
+ Templates are listed on the Vidfarm homepage and `/discover`. Each has a `template_...` id, a preview MP4, source video metadata, and viral DNA.
231
+
232
+ - Browser: browse `/discover`
233
+ - API: `GET /discover/feed` — returns `{ templates: [{ templateId, slugId, title, previewUrl, viralDna, durationSeconds, sourceType, promotions, keywords, summary, ... }], next_cursor }`
234
+ - Search: `GET /discover/feed?q=<offer>&limit=20&sort=relevance` hybrid-searches the eligible public catalog using semantic embeddings plus lexical matches. Semantic query embedding uses Vidfarm's canonical OpenRouter-routed model and bills the provider cost × the standard 1.2 markup to the user's wallet. The response's `search` block reports `mode`, `embedding_space`, and any `semantic_limitation`; when it says `lexical_structured`, disclose the limitation briefly and continue rather than refusing. Decomposition adds `promotions`, `keywords`, `summary`, `categoryTags`, and `catalogIntelligence` (`wowScore`, `wowReason`, `automationScore`, `automationReason`, `contentStyles`, `searchText`). Use `sort=wow` for highest-quality/client-impressing formats, `sort=automation` for cheap repeatable bulk formats, and `sort=recent` only when freshness is the intent. Follow `next_cursor` with `cursor=<value>`; never call page one the whole catalog. `GET /api/v1/videos?q=<offer>&limit=20[&mine=true]` searches source **inspirations**. Undecomposed inspirations have only sparse ingest metadata, so they are harder to retrieve semantically. Explain that somebody in the world needs to decompose one once and the shared enrichment then benefits everyone; the current user need not act unless they want that specific inspiration immediately.
235
+
236
+ Each template exposes a public preview:
237
+
238
+ - `GET /editor/:templateId` — opens the Trackpad Editor for the template (redirects to your fork of it, or to `/login`)
239
+ - `GET /editor/:templateId/composition` — raw composition HTML built from the template's source video
240
+ - `GET /discover/skills/:templateId` — the template's SKILL.md content as JSON (registry templates only)
241
+
242
+ Directors don't edit templates directly. They fork.
243
+
244
+ ### Add your own source (inspiration)
245
+
246
+ To bring a new viral video into the catalog as a **private** template you own, ingest its social URL — or upload a video file directly:
247
+
248
+ - `POST /discover/templates { source_url, tagline?, notes? }` — accepts TikTok / YouTube / Instagram / Twitter-X URLs only. The video download runs async; the response is the inspiration record (`202`, `status: "processing"`). The card finalizes into a private `template_...` the next time `GET /discover/feed` is polled (the feed poll doubles as the completion check).
249
+ - **Upload a file** (three steps, same finalize behavior as the URL flow):
250
+ 1. `POST /discover/templates/upload/presign { file_name, content_type?, size_bytes? }` — video files only (MP4/MOV/WebM), 200 MB cap. Returns `transport: "presigned"` with a `{ upload: { method, url, headers } }` S3 PUT target, or `transport: "server"` (local-storage boxes) pointing at the multipart fallback below. Both include the `storage_key` to finalize with.
251
+ 2. Send the bytes: `PUT` them to the presigned URL, or `POST /discover/templates/upload` (multipart, field `file`) when transport is `server`.
252
+ 3. `POST /discover/templates { upload: { storage_key, file_name }, title?, tagline?, notes? }` — queues an async `video_ingest` job (durable mirror + duration probe + thumbnail) and returns the inspiration record (`202`). Finalizes into a private template exactly like the URL flow. `title` names the /discover card; untitled templates display as their `template_...` id (the file name is only shown while processing, never persisted as the title).
253
+ - `DELETE /discover/templates/:entryId` — remove a private inspiration/template you own (accepts either the `inspiration_...` or minted `template_...` id).
254
+ - `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).
255
+
256
+ 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] [--sort relevance|wow|automation|recent] [--cursor <next_cursor>]` 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.
257
+
258
+ ## Fork a template
259
+
260
+ ```
261
+ POST /api/v1/compositions
262
+ Content-Type: application/json
263
+
264
+ { "template_id": "template_<...>", "title": "Optional title" }
265
+ ```
266
+
267
+ `template_id` is required and must start with `template_` (the handler also accepts the same value under the key `source`). Returns the new fork's metadata (201):
268
+
269
+ ```
270
+ { "fork_id": "<forkId>", "template_id": "template_...", "parent_fork_id": "...", "parent_version": 1, "title": "...", "visibility": "private", "latest_version": 0, "composition_url": "...", "composition_data_url": "...", "versions_url": "...", "role": "owner", "capabilities": { ... } }
271
+ ```
272
+
273
+ Take the returned `fork_id` and open the Trackpad Editor at:
274
+
275
+ ```
276
+ /editor/<templateId>/fork/<forkId>
277
+ ```
278
+
279
+ The editor loads `composition.html` and `composition.json` from the fork's working state.
280
+
281
+ ### New fork from an existing fork (branch this copy, or start fresh)
282
+
283
+ Once you already hold a fork, spawn ANOTHER editable copy — a branch — with one unified endpoint. This is what the editor's ⋯ menu **New fork** modal and the devcli `clone --from` command both call:
284
+
285
+ ```
286
+ POST /api/v1/compositions/:forkId/fork
287
+ Content-Type: application/json
288
+
289
+ { "from": "current" | "default", "parent_version": <optional N>, "title": "Optional" }
290
+ ```
291
+
292
+ - `from: "current"` (default) — branch a new private fork from **this** fork's working copy; all current edits carry over. Optionally pin `parent_version` to clone a saved version instead of the live working state. (The legacy `POST /:forkId/clone` route is an alias for exactly this.)
293
+ - `from: "default"` — start a **fresh** fork from the template's canonical default composition, discarding the current fork's edits (they stay in the fork you branched from).
294
+
295
+ 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`).
296
+
297
+ ## Render (publish to MP4)
298
+
299
+ 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`.
300
+
301
+ ```
302
+ POST /api/v1/compositions/:forkId/render
303
+ Content-Type: application/json
304
+
305
+ { "title": "optional", "version": null, "tracer": "optional-trace-id", "html": "optional HTML to save to working state before rendering" }
306
+ ```
307
+
308
+ 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:
309
+
310
+ ```
311
+ GET /api/v1/compositions/:forkId/renders/:renderId
312
+ ```
313
+
314
+ 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.
315
+
316
+ Every publish creates an immutable version snapshot at `versions/<N>/composition.html` and `versions/<N>/composition.json`.
317
+
318
+ 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.
319
+
320
+ ## Approve a finished post
321
+
322
+ 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.
323
+
324
+ ```
325
+ POST /api/v1/approved/posts
326
+ Content-Type: application/json
327
+
328
+ { "caption": "required", "title": "optional", "pinned_comment": "optional", "tracer": "optional",
329
+ "media": [ { "url": "https://.../output.mp4", "kind": "video", "role": "primary" } ] }
330
+ ```
331
+
332
+ `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.
333
+
334
+ - `GET /api/v1/approved/posts` — list your approved posts
335
+ - `GET /api/v1/approved/posts/:postId` — read one (returns `share_url`, `download_zip_url`)
336
+
337
+ 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.
338
+
339
+ ## Schedule a post
340
+
341
+ Schedule an approved post to a connected destination channel (FlockPoster social account or email) at one ISO timestamp:
342
+
343
+ ```
344
+ POST /api/v1/approved/posts/:postId/schedules
345
+ Content-Type: application/json
346
+
347
+ { "destination_type": "flockposter" | "email", "destination_id": "<channel or email>",
348
+ "scheduled_at": "2026-07-10T14:00:00Z", "timezone": "America/New_York", "additional_notes": "optional" }
349
+ ```
350
+
351
+ Minimum 10-minute lead time. Response (`201`) is the schedule record. Browse existing schedules with `GET /api/v1/approved/posts/:postId/schedules`.
352
+
353
+ devcli: `vidfarm schedule <postId> --at <iso> --to <destinationId> [--type flockposter|email]`, and `vidfarm schedules <postId>` to browse.
354
+
355
+ Deeper FlockPoster work (channel management, direct posting/analytics outside vidfarm's schedule wrapper) is FlockPoster's own API — grab its skill first: `npx skills add https://github.com/OfficeXApp/flockposter-skill` (mirrored as `vidfarm skills add flockposter`).
356
+
357
+ ## Marketplace — order videos from specialist agents
358
+
359
+ The marketplace is a **paid, web-only surface**. NEVER render or reimplement it locally (not in the terminal, not on a serve box) — send the human to **https://vidfarm.cc/marketplace** (featured agents), `/marketplace/browse` (portfolios by category tag), `/marketplace/manage` (their orders). `vidfarm marketplace` prints exactly those links.
360
+
361
+ An **order IS a Dollar Platoon gig** (tagged `vidfarm_order_<uuidv7>` plus category tags). Vidfarm exposes a deliberately thin REST wrapper — auth via session or the `vidfarm-api-key` header:
362
+
363
+ ```
364
+ GET /api/v1/marketplace/orders → { orders: [{ id, title, brief, tags, status, price, quantity, inboxUrl }] }
365
+ POST /api/v1/marketplace/orders { "title": "...", "brief": "...", "price": 0.50, "quantity": 10,
366
+ "rate_limit_per_day": 2, "tags": ["Slideshows"], "folder": "Brand Kit" }
367
+ POST /api/v1/marketplace/orders/:gigId/invite { "slug": "<agent portfolio slug>" }
368
+ ```
369
+
370
+ - `price` = **cost per video in USD** (the gig's price-per-proof on Dollar Platoon). Omit it for the recommended default **$0.50**; any number **greater than zero** is accepted (zero/negative → 400).
371
+ - `quantity` = **number of videos wanted** — default **10**, minimum **4**. DP has no quota field, so it travels as a `vidfarm_qty_<n>` gig tag plus a "Videos requested: N" line in the gig terms.
372
+ - `rate_limit_per_day` = **per-gigworker daily cap** — default **2** videos per day per mailbox (maps to DP `default_rate_limit_count`/`default_rate_limit_minutes` = N per 1440); `0` disables the throttle.
373
+ - Both routes 409 `{ setup_required: true, redirect: "/settings/marketplace" }` until the customer saves their **Dollar Platoon API key** there (new accounts are auto-provisioned one).
374
+ - `folder` shares a My Files subfolder as context: a read-only share link (`/directory/preview/<token>/…`) is embedded in the gig description.
375
+ - `inboxUrl` is an autologin deep link into the gig dashboard on dollarplatoon.com — the headline output after placing an order.
376
+ - Invite emails the agent portfolio's owner their personal gig invite link.
377
+
378
+ devcli: `vidfarm marketplace` (browse links), `vidfarm marketplace orders`, `vidfarm marketplace order --title <t> --brief <b> [--count <n>] [--price <usd>] [--rate-limit-per-day <n>] [--tags a,b] [--folder <path>]` (defaults: 10 videos, $0.50, 2/day per worker), `vidfarm marketplace invite <gigId> --portfolio <slug>`.
379
+
380
+ **Beyond the wrapper, switch skills.** Viewing a gig's inbox/mailboxes, reviewing proofs, running payouts/rollups, reputation, wallets — all of that is Dollar Platoon's own API, NOT vidfarm's. Exactly like grabbing the FlockPoster skill for posting, grab the Dollar Platoon skill before doing gig work: `npx skills add https://github.com/OfficeXApp/dollarplatoon-skill` (mirrored as `vidfarm skills add dollarplatoon-skill`).
381
+
382
+ ## Version history
383
+
384
+ ```
385
+ GET /api/v1/compositions/:forkId/versions?limit=100
386
+ ```
387
+
388
+ 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" }`.
389
+
390
+ To view a past cut without reverting:
391
+
392
+ ```
393
+ GET /api/v1/compositions/:forkId/versions/:version/composition.html
394
+ ```
395
+
396
+ To revert the working state to a version, `PUT` the historical HTML/JSON back to the working keys.
397
+
398
+ ## Sharing and visibility
399
+
400
+ Every fork has a visibility: `private` (default), `unlisted` (legacy), or `public` (view-only).
401
+
402
+ ```
403
+ PATCH /api/v1/compositions/:forkId/visibility { "visibility": "public" }
404
+ ```
405
+
406
+ Public forks are accessible at `/editor/<templateId>?fork=<forkId>` (view-only for non-owners). Public visibility is the simplest sharing surface.
407
+
408
+ Fine-grained sharing uses per-user permissions and share links (all body fields are snake_case):
409
+
410
+ - `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 }`.
411
+ - `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, ... }`.
412
+ - `GET /api/v1/compositions/shared/:token` — access via share link
413
+ - `DELETE /api/v1/compositions/:forkId/permissions/:permissionId` — revoke a grant
414
+ - `DELETE /api/v1/compositions/:forkId/share-links/:token` — revoke a link
415
+
416
+ ## Cloning a fork
417
+
418
+ Directors can clone another fork (their own or a shared one) as a new starting point:
419
+
420
+ ```
421
+ POST /api/v1/compositions/:forkId/clone
422
+ Content-Type: application/json
423
+
424
+ { "parent_version": "<N or omit for latest>", "title": "optional" }
425
+ ```
426
+
427
+ 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"`.
428
+
429
+ ## Delete a fork
430
+
431
+ ```
432
+ DELETE /api/v1/compositions/:forkId
433
+ ```
434
+
435
+ Soft-delete only. The fork's `deletedAt` is set. Versions and share links are preserved for audit.
436
+
437
+ ## Cost spectrum (free → $10+/video) — default to saving the director money
438
+
439
+ 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.
440
+
441
+ | Approach | Typical cost | How |
442
+ |---|---|---|
443
+ | **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). |
444
+ | **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. |
445
+ | **AI-generate some scenes** | **~$1** | Replace a few scenes with AI-generated video clips for high specificity/customization (see the "Generate AI media" section). |
446
+ | **Heavy AI generation** | **$10+** | Many/long AI video clips, custom characters, fully bespoke scenes. |
447
+
448
+ Rules of thumb:
449
+
450
+ - **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.
451
+ - **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.
452
+ - **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.
453
+ - **Image generation is cheap** — use it freely, no need to ask permission.
454
+ - **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.
455
+ - **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.
456
+
457
+ 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.
458
+
459
+ **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”).
460
+
461
+ ## Billing
462
+
463
+ Vidfarm charges directly in USD from the caller's wallet. There are no credits.
464
+
465
+ - **Wallet top-up** — Stripe checkout via the Settings UI, backed by `POST /settings/wallet/funding-link` (browser session; returns `{ checkout_url, client_reference_id }`)
466
+ - **Balance & history** — shown on the Settings page; browser session also has `GET /u/:customerId/settings/wallet/events`.
467
+ - **Cost log (api-key / agent-friendly)** — `GET /api/v1/user/me/wallet` returns `{ summary, events, next_cursor }`. `summary` carries `balance_usd`, `total_charge_usd` (lifetime spend), `total_funds_added_usd`, and `total_event_count`; each `events[]` entry carries `amount_usd` (**negative = a charge/spend**), `occurred_at_ms`, `cost_center_slug`, `job_id`, and `tracer`. **To answer "how much did this video cost?"** add `?job=<jobId>` (or `?tracer=<tracer>`): the response then includes `filtered_charge_usd` (the summed spend for that render/video) and only the matching events. Paid-plan only — the free tier has no wallet and this route 402s. This is the endpoint to use when a director asks what a video cost; devcli wraps it as `vidfarm wallet` / `vidfarm wallet --job <id>`.
468
+ - **Cost anchors** (see the Cost spectrum section above for how these combine per approach):
469
+ - Local render on a `vidfarm serve` box (in-process HyperFrames): **free**
470
+ - Cloud render (HyperFrames Lambda fan-out): typically $0.01 – $0.10 per MP4 depending on length/resolution
471
+ - Image generation / edit: cheap (provider passthrough) — use freely
472
+ - AI **video** generation: expensive ($1–$10+/video territory) — ask permission first
473
+ - 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
474
+ - Auto-decompose time-slice: free
475
+ - GhostCut subtitle removal: ~$0.10 per 30 seconds of source video
476
+
477
+ Framing:
478
+
479
+ - Vidfarm is optimized for cost efficiency, not markup
480
+ - Total director cost per video is typically far below alternatives
481
+ - Platform retains a small safety buffer for operational correctness
482
+
483
+ ## Agentic editing: the three axes (SWAP ↔ REPLACE)
484
+
485
+ Almost every editor session is a director taking a template / fork / project and **re-working** it, and a re-work only ever touches three independent axes — **SCENES** (the video/image clips carrying the visuals), **AUDIO** (narration/voiceover, music, SFX), and **TEXT** (captions, titles, overlays). On each axis the intent sits on a **SWAP ↔ REPLACE** spectrum, and the three axes in one request can sit at different points — decide each before you act:
486
+
487
+ - **SWAP (light, cheap, common)** — keep the structure, change content **in place**: rewrite a caption/text layer (`set_layer_text` / `set_captions`), swap one clip's media for another of the same kind keeping its timing + geometry (`set_layer_media`), or re-voice narration (`vidfarm speech regenerate` / `/audio/regenerate-speech` → mute the old span, `place`/`add_layer` the new audio). Many jobs are **only** a text swap — a two-minute change; just do it.
488
+ - **REPLACE (heavy)** — throw out that axis's content and rebuild it: restructure the scenes outright (new clip count / new beats via `remove_layer` + `add_layer`/`generate_layer`, or one `replace_composition_html`), lay a brand-new audio bed, or rewrite every caption. The **hardest** end is a full **re-theme** where the ONLY thing preserved is the **viral DNA** (hook shape, pacing, scene-count rhythm, transition/caption style) while every scene, every word, and the audio are all replaced for the director's new subject.
489
+
490
+ Name the plan back in these terms ("I'll SWAP the captions and REPLACE the scenes"), then execute axis by axis. Be **proactive at the heavy end** — carry the whole transformation, don't wait to be micro-managed one layer at a time.
491
+
492
+ **Fuel a scene REPLACE with raw clips, not expensive AI video.** A heavy scenes-axis replace needs footage; sources in cost order: (1) the director's own library — search `/raws` and `/files` (`vidfarm raws search …`, `vidfarm files --search …` / `browse_files`); (2) **HUNT new raws** out of a long-form source (podcast/VOD/webinar or any YouTube/TikTok/IG/X URL) — `vidfarm raws scan <src> --prompt "<what the new scenes need>" --aspect <canvas> [--duration N --no-text --range …]` (local-first, free compute) or the async `POST /clips/scan` / `/raws/scan`; then reuse the picks (`set_layer_media` / `vidfarm set-media` to swap in place, `add_layer`/`vidfarm place` for net-new scenes); (3) `generate_layer` / `vidfarm generate` AI generation — the **expensive last resort**, only for scenes no real clip can cover. When a big scene re-work is asked for but no footage is given, **ask for a source to hunt (or point at the raws library) before AI-generating** — see [Raws](#raws-long-form--short-form-raws) and [Generate AI media …](#generate-ai-media-and-drop-it-on-the-timeline).
493
+
494
+ **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.
495
+
496
+ ## The three paintbrushes & two replication harnesses
497
+
498
+ 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.
499
+
500
+ > **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.
501
+
502
+ 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.
503
+ 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.
504
+ 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.
505
+
506
+ **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*.
507
+
508
+ For any replication, offer the director **two harnesses** and recommend (A) unless they've asked for premium or the budget covers it:
509
+
510
+ **(A) Cheap & efficient** — the default. Reach for, in roughly this order:
511
+ - **Recaption text** — often the whole job is rewriting the caption/title layers (`set_captions` / `set_layer_text`). A two-minute change.
512
+ - **Background + foreground video** memes — composite two clips instead of generating one.
513
+ - **Animate HTML + image elements** with hyperframes (Ken Burns on a still, kinetic type, animated logo/sticker) instead of AI motion.
514
+ - **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.
515
+ - **Greenscreen** — chroma-key a subject onto new backgrounds (`image_remove_background_greenscreen` / `media_overlay`).
516
+ - **Raw-clip long-form and remix** — hunt + rearrange existing footage.
517
+ - Lean on the **library of memes / reactions / b-roll / a-roll** and the **brand media kit**.
518
+ - **Only if genuinely needed**, use AI image → and, last, AI video / voice / music.
519
+
520
+ **(B) Best quality** — when the director wants premium and budget allows:
521
+ - **AI video generation by default** for hero scenes.
522
+ - **Storyboard with AI image first** (cheap stills to lock composition/subject), then generate motion from those references for consistency.
523
+ - **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.
524
+
525
+ **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.
526
+
527
+ **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.
528
+
529
+ ## Edit in the Trackpad Editor
530
+
531
+ The editor is a full timeline surface:
532
+
533
+ - **Timeline** — multi-track, drag/trim/split clips, group/ungroup layers
534
+ - **Inspector panel** — per-layer property editor (text, color, font, position, size, media source, playback start, volume). Image layers also expose an **Animate (Ken Burns)** dropdown — a slow pan/zoom over the clip's full duration (see below). Text/caption layers expose an **Animated captions** preset picker — word-by-word TikTok/CapCut caption styles (see below) — plus active-word color, highlight color, and uppercase controls.
535
+ - **Canvas frame overlay** — 8-handle resize + WYSIWYG positioning
536
+ - **Chat panel** — AI assistant that can programmatically edit the composition (`add_layer`, `remove_layer`, `set_layer_timing/visual/style/media/text`, `set_layer_keyframes` — script-free CSS keyframe motion on one layer — `set_captions`, `group_layers`, `nudge_layers`, `ripple_edit`, `trim_layer`, `set_layer_zindex`, `generate_layer`, `replace_composition_html`, ...). The assistant uses the caller's saved provider keys, and reads an enriched `editor_context` (per-layer transitions / Ken Burns / custom-animation state; `recent_action_results` now reports successes as well as failures). See "Script-free keyframe motion & timeline verbs" below for the newer motion/timeline actions. **`generate_layer`** is the one-shot "generate AI media and place it": it submits the `/videos/generate` or `/images/generate` primitive job, drops a placeholder clip into the target slot (fill a blank gap, replace a scene, or overlay), and auto-swaps in the finished media when the job settles — no manual polling. In-flight generations surface in `editor_context.pending_generations`.
537
+ - **Auto-save** — every commit writes composition.html + composition.json to the working state. No explicit save button.
538
+
539
+ The **browser editor** is HyperFrames Studio (a sealed, timeline-based studio like Premiere/DaVinci); it saves each edit through its own live files API and reflects changes back into the fork's working composition automatically — you don't drive it directly from a script.
540
+
541
+ **Automation / devcli that needs to bypass the UI** writes the working composition through the stable REST routes (this is the path `vidfarm pull` / `vidfarm publish` and every fine-edit devcli command use):
542
+
543
+ - `PUT /api/v1/compositions/:forkId/composition.html` — replace HTML
544
+ - `PATCH /api/v1/compositions/:forkId/composition.json` — patch metadata
545
+
546
+ Edits made either way land in the same `working/composition.html`; on a local `vidfarm serve` box an open editor tab live-morphs a disk edit, so you can pull, edit on disk, and watch the browser update.
547
+
548
+ Reads also work anonymously since the `compositions/forks/*` S3 prefix is public-read — clients that only need to fetch (not mutate) can bypass the API and hit S3 directly:
549
+
550
+ - `GET https://<bucket>.s3.<region>.amazonaws.com/compositions/forks/<forkId>/working/composition.html`
551
+ - `GET .../working/composition.json`
552
+ - `GET .../working/manifest.json` — fork identity + genealogy (`parent_fork_id`, `parent_version`, `latest_version`, direct URLs for html/json/parent manifest). Also served via `GET /api/v1/compositions/:forkId/manifest.json` and `.../versions/:v/manifest.json`.
553
+
554
+ Treat the `forkId` as an unguessable bearer token — anyone with it can view the composition.
555
+
556
+ ## Auto-decompose
557
+
558
+ If the fork's source is a raw video (not a pre-decomposed template), call auto-decompose to split it into scenes:
559
+
560
+ ```
561
+ POST /api/v1/compositions/:forkId/auto-decompose
562
+ Content-Type: application/json
563
+
564
+ { "mode": "smart" | "time-slice", "html": "<optional current html>", "requested_scenes": null, "target_scene_seconds": null, "user_prompt": "optional guidance" }
565
+ ```
566
+
567
+ Two modes:
568
+
569
+ - **`smart`** — uses the caller's Gemini/OpenAI/OpenRouter key. Samples frames, extracts scenes with labels, literal visual **descriptions**, and viral notes, extracts on-screen captions with positions, **transcribes the audio track verbatim** (timestamped segments; Gemini first, OpenAI Whisper fallback; skipped when the video has no audio), and detects viral DNA (hook, retention, payoff, plus **`emotional_punch`** — the vibe/joke/intonation that makes it FEEL, reasoned over BOTH the frames and the transcript so the trending sound and vocal delivery are captured, not just the visuals). Takes 30-60 seconds. Auto-fires GhostCut for subtitle removal in the background.
570
+ - **`time-slice`** — deterministic equal-duration split. Instant. No AI provider needed.
571
+
572
+ **Source length cap.** Vidfarm caps auto-decompose (and all inspiration ingest) at **120 seconds** of source video. If the source is longer, the API responds with `400 { ok: false, code: "source_too_long", duration_seconds, max_duration_seconds }` and no scenes are written. Trim the source before forking / ingesting.
573
+
574
+ Response:
575
+
576
+ ```
577
+ { "ok": true, "mode": "smart", "scene_count": 6, "caption_count": 4, "duration_seconds": 12.4, "ghostcut_pending": true, "ghostcut_task_id": "..." }
578
+ ```
579
+
580
+ If `ghostcut_pending: true`, poll `POST /api/v1/compositions/:forkId/remove-video-captions-poll` (legacy alias: `/ghostcut-poll`) until status transitions to `done` or `failed`.
581
+
582
+ Read-only state (does **not** advance the job or bill): `GET /api/v1/compositions/:forkId/remove-video-captions` (legacy alias: `/ghostcut`). Returns `{ status, task_id, original_source_url, mirrored_url, submitted_at_ms, completed_at_ms, failure_reason }`. This is how you read the composition's **two video sources**: `original_source_url` is the **original video** (raw upload, captions intact) and `mirrored_url` is the **decomposed video** (the processed, caption-free copy the timeline renders from). Use this when you want both URLs without triggering another poll — the AI copilot uses it to pick between the original video and the decomposed video before feeding a video into a primitive route (image extract, dedupe, trim, ai video edit, etc.). Prefer the **decomposed video** (`mirrored_url`) when `status === "done"` and the downstream call should be caption-free; prefer the **original video** (`original_source_url`) when the user explicitly wants the raw upload or when `status !== "done"`.
583
+
584
+ ## Video context (transcript + scene descriptions)
585
+
586
+ `GET /api/v1/compositions/:forkId/video-context.json` — read-only, non-billing. Returns everything the smart decompose learned about the source video, so an agent can ground its work in what the video actually **says** and **shows**:
587
+
588
+ ```
589
+ {
590
+ "ok": true,
591
+ "status": "ready" | "none",
592
+ "summary": "...",
593
+ "duration_seconds": 12.4,
594
+ "transcript": {
595
+ "text": "full verbatim transcript",
596
+ "language": "en",
597
+ "provider": "gemini",
598
+ "segments": [{ "start": 0.0, "end": 2.4, "text": "..." }]
599
+ },
600
+ "scenes": [{
601
+ "slug": "hook_reveal",
602
+ "start": 0, "duration": 2.5,
603
+ "label": "Hook",
604
+ "description": "Literal visual description of what's on screen in this scene",
605
+ "viral_note": "...",
606
+ "transcript_excerpt": "what is spoken during this scene"
607
+ }],
608
+ "viral_dna": {
609
+ "trend_tagline": "...", "hook": "...", "retention": "...", "payoff": "...",
610
+ "emotional_punch": {
611
+ "core_emotion": "smug relief-laugh", "tone": "deadpan comedic",
612
+ "arc": "flat setup → mounting absurdity → sudden turn", "peak_moment": "the reveal at ~0:07",
613
+ "mechanism": "subversion + trending-sound beat drop synced to the cut",
614
+ "humor": "the joke is X — it works because Y (rebuild the SAME joke around a new subject)",
615
+ "delivery": "flat VO cadence, hold the beat before the punch, beat drop lands on the reveal",
616
+ "preserve": ["keep the beat drop on the reveal cut", "keep the flat unbothered delivery"]
617
+ },
618
+ "static_vs_pivot": {
619
+ "summary": "Keep the reveal structure and audio swagger; swap the subject-specific visuals and copy.",
620
+ "scene_replacement": {
621
+ "overall": "replace_some",
622
+ "reason": "the proof/reveal beats are load-bearing, but the subject matter can pivot",
623
+ "must_keep": ["final reveal beat", "setup-to-payoff contrast"],
624
+ "should_replace": ["subject-specific graphics", "brand-specific screenshots"],
625
+ "load_bearing_scenes": ["hook setup", "payoff reveal"]
626
+ },
627
+ "narration": {
628
+ "overall": "customize_script",
629
+ "reason": "the spoken words are source-specific and stop making sense after a subject swap",
630
+ "same_timing_required": true,
631
+ "use_premium_tts_when_customizing": true
632
+ },
633
+ "music": {
634
+ "overall": "recreate_under_narration",
635
+ "reason": "the music energy matters, but the original combined track should not stay under a rewritten script",
636
+ "volume_guidance": "keep low under VO unless the beat-drop is the joke engine"
637
+ },
638
+ "captions": {
639
+ "overall": "retime_to_new_narration",
640
+ "reason": "caption animation is tied to the spoken words",
641
+ "animation_dependency": "word-by-word timing follows narration cadence"
642
+ },
643
+ "visual_rebuild": {
644
+ "default_strategy": "ai_images_with_ken_burns",
645
+ "reason": "most replacement scenes are graphic/still-like and do not need expensive motion video"
646
+ },
647
+ "viral_dna": {
648
+ "must_keep": ["hook structure", "payoff timing", "audio attitude"],
649
+ "flexible": ["subject matter", "product imagery", "copy details"],
650
+ "do_not_change": ["core reveal mechanic", "caption cadence"]
651
+ }
652
+ },
653
+ "preserve": ["..."], "avoid": ["..."], "promotions": ["..."], "keywords": ["..."]
654
+ }
655
+ }
656
+ ```
657
+
658
+ `status: "none"` means the fork was never smart-decomposed — run `POST /auto-decompose` with `mode: "smart"` first. `transcript` is `null` when the source has no audio track or transcription failed.
659
+
660
+ **`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.
661
+
662
+ **`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:
663
+
664
+ - whether most scenes should be replaced or only some
665
+ - whether narration must be customized or the original audio should stay
666
+ - whether background music must be recreated under a new voice
667
+ - whether animated captions need fresh timing against new narration
668
+ - whether cheap AI images + Ken Burns are sufficient, or whether real/AI motion footage is required
669
+ - which beats are load-bearing viral DNA versus merely subject-specific dressing
670
+
671
+ 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.
672
+
673
+ - **Editor chat (frontend AI)** exposes this as the optional `video_context` tool — the copilot calls it on demand.
674
+ - **Desktop agents (Claude Code / Codex)**: fetch the `video-context.json` route directly (e.g. `vidfarm api GET /api/v1/compositions/<forkId>/video-context.json`) to ground edits in what the video says and shows.
675
+
676
+ ## Editor harness (how to edit like this)
677
+
678
+ `GET /api/v1/compositions/:forkId/editor-harness.json` — read-only, non-billing. The decompose pass's **technical editing direction**: *how* to edit so the result recreates the source's STYLE. It's the "how to edit like this" companion to the viral DNA's "why it works". Read it before any re-theme, multi-scene rebuild, or restyle.
679
+
680
+ ```
681
+ {
682
+ "ok": true,
683
+ "status": "ready" | "empty" | "none",
684
+ "harness": {
685
+ "one_liner": "Fast first-person VO over gameplay b-roll, karaoke captions, hard cuts.",
686
+ "format": "reddit_story_gameplay",
687
+ "aspect_ratio": "9:16",
688
+ "pacing": { "cut_rhythm": "fast", "avg_scene_seconds": 2.4, "cuts_per_10s": 4, "energy_curve": "front-loaded hook then steady" },
689
+ "typography": { "caption_style": "karaoke", "placement": "center-lower", "font_character": "bold condensed all-caps", "emphasis": "active-word yellow", "text_density": "heavy" },
690
+ "broll": { "reliance": "heavy", "sourcing": "screen-recording", "shot_kinds": ["b_roll"], "cadence": "new visual ~every 2s" },
691
+ "transitions": { "default": "cut", "intro": "fade", "outro": "none", "usage": "hard cuts only" },
692
+ "audio": { "voiceover": "first-person narration", "music": "upbeat lofi bed, low", "sfx": "whoosh on cuts", "captions_from": "voiceover" },
693
+ "emotional": { "target_feeling": "smug relief-laugh", "tone": "deadpan comedic", "comedic_timing": "hold a beat, then hard cut on the punch word", "intonation": "flat unbothered VO, no upspeak", "vibe_anchors": ["keep the trending sound, land the beat drop on the reveal cut", "hold silence before the payoff"] },
694
+ "scenes": [{ "role": "hook", "importance": "critical", "must_keep": true, "note": "...", "edit_bias": "swap subject, keep timing+caption cadence" }],
695
+ "important_scenes": ["..."], "editing_bias": ["..."], "do": ["..."], "dont": ["..."]
696
+ }
697
+ }
698
+ ```
699
+
700
+ **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.
701
+
702
+ - **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`.
703
+ - **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.
704
+
705
+ ### `GET /api/v1/compositions/:forkId/replication-harness.json` — the technical replication analysis
706
+
707
+ 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.
708
+
709
+ ```
710
+ {
711
+ "ok": true,
712
+ "status": "ready" | "empty" | "none",
713
+ "harness": {
714
+ "summary": "Recaption + reuse: 90% of this is raw clips + hyperframes captions; AI only for the impossible reveal shot.",
715
+ "recommended_strategy": "cheap_efficient",
716
+ "paintbrush_rationale": "Talking-head + text overlays — footage is huntable and every graphic is HTML; AI video buys almost nothing here.",
717
+ "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" },
718
+ "asset_opportunities": ["reuse the brand-kit logo lockup on the end card", "a 'mind-blown' sticker for the reveal beat"],
719
+ "viral_dna_guardrails": ["never cover the reveal frame with a sticker", "keep the trending sound; land the beat drop on the reveal cut"],
720
+ "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.",
721
+ "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": "..." },
722
+ "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" }],
723
+ "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" }],
724
+ "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": ["..."] },
725
+ "quality": { "strategy": "best_quality", "one_liner": "AI-gen hero scenes, storyboard-first", ... }
726
+ }
727
+ }
728
+ ```
729
+
730
+ **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.
731
+
732
+ ## Generate AI media and drop it on the timeline
733
+
734
+ Both surfaces can generate a NEW AI video/image (optionally conditioned on reference images) and place it into the timeline — filling a blank gap or replacing a scene — using the async generation primitives (`POST /api/v1/primitives/videos/generate`, `/images/generate`). Generation is a **queued job**: the POST returns `202 { job_id }`, and the finished media URL only appears after the job settles (`result.primary_file_url` / `video.file_url` / an artifact `public_url`). Poll `GET /api/v1/user/me/jobs/:jobId` until `status: "succeeded"`.
735
+
736
+ **Cost gate (see the Cost spectrum section):** AI **video** generation is the expensive end of the spectrum ($1–$10+/video) — **ask the director's permission before generating video**, and prefer reusing existing footage (My Files / their computer / a web search) or filming when it would do. **Image** generation is cheap — use it freely without asking. When a director wants specificity that only AI video can deliver and their budget allows, this is the right tool; just confirm first.
737
+
738
+ - **Editor web copilot**: use the `generate_layer` editor_action — it does the whole flow (submit job → placeholder clip → auto-swap on completion) and reads `pending_generations` from `editor_context`. Set `media_type`, `prompt`, `aspect_ratio` (match the canvas), and `intent` (`fill_gap` with `start`/`duration`, `replace_layer` with `replace_layer_key`, or `add`). For character consistency pass a cast `reference_url` — or the character's `character_sprite_card.png` viewUrl from `/files/characters/<slug>/` (see "Recurring characters are first-class") — in `input_references` (video) / `prompt_attachments` (image).
739
+
740
+ - **Desktop agents (Claude Code / Codex)** — two devcli commands:
741
+ ```bash
742
+ # 1) Sync the fork to disk and see where the blank space and scenes are
743
+ vidfarm pull <forkId> --dir ./work
744
+ # → prints timeline gaps (e.g. 4-7s) and scene/layer keys, canvas aspect ratio,
745
+ # and whether video-context.json / cast.json / editor-harness.json are present for grounding.
746
+
747
+ # 2) Generate + place in one call (fill a gap)
748
+ vidfarm generate video --prompt "her walking through the door" \
749
+ --ref ./cast/girl.png --aspect-ratio 9:16 --duration 4 \
750
+ --place ./work --at 4
751
+ # → polls the job to the finished MP4, then inserts it at 4s in ./work/composition.html
752
+
753
+ # …or replace a scene with a generated still
754
+ vidfarm generate image --prompt "product hero on marble" --aspect-ratio 9:16 \
755
+ --place ./work --replace <layer_key>
756
+
757
+ # 3) Push it back (or it live-morphs automatically under `vidfarm serve`)
758
+ vidfarm publish --dir ./work --fork <forkId>
759
+ ```
760
+ `--ref @localfile` (or a bare path) auto-uploads the image and uses its durable URL as a reference. `generate` prints the media URL (drop `--place` to just get the URL); `place` can be run separately on any media URL (`vidfarm place ./work --src <url> --at <time>|--replace <layer_key>`). Placement produces the exact same clip markup the browser editor makes, so a human can finish it in the Trackpad Editor.
761
+
762
+ ### Fitting imperfect-dimension media (aspect mismatch)
763
+
764
+ A clip/image whose **native aspect differs from the canvas** — a 16:9 landscape source on a 9:16 vertical frame is the classic case, also a tall screenshot on a wide frame, a square logo — will NOT look good on the default `object-fit:cover`, which fills the frame and **silently center-crops** the overflow (landscape → loses left/right, often cutting the subject in half; portrait → loses top/bottom). Decide the fit deliberately:
765
+
766
+ - **`object_fit:"cover"`** (default) fills + crops — right for most social footage **when paired with a subject-aware `object_position`**.
767
+ - **`object_position`** aims WHERE the cover-crop lands: a keyword (`left`, `right`, `top`, `bottom`, `"top left"`, …) or a **percentage pair** (`"30% 50%"` = 30% from the left, 50% down). Landscape clip with the subject on the left → `"left"`/`"25% 50%"`; tall screenshot with key text up top → `"top"`.
768
+ - **`object_fit:"contain"`** shows the WHOLE media with black letterbox/pillarbox bars — use ONLY when nothing may be cropped (an infographic, a whole screenshot, a chart, a logo); a 16:9 source full-canvas on a 9:16 frame reads unfinished in `contain`.
769
+ - **Blurred-letterbox band** for a whole landscape clip without ugly bars: place it as a centered band (`width:100, height:~34, y:~33` on 9:16) with `contain`, plus a DUPLICATE full-canvas copy on a lower track behind it at `cover` + heavy blur.
770
+ - Never `object_fit:"fill"` (stretch/distort) unless intentional. You usually know a clip's aspect from how you sourced it (a hunted raw's `--aspect`, a generated clip's `--aspect-ratio`); if you truly need exact dims, `vidfarm probe` / `GET /videos/probe`. When you REPLACE a full-canvas scene, keep it full canvas + `cover` + a focal `object_position` rather than shrinking; when you GENERATE, request the canvas aspect so no crop is needed.
771
+
772
+ Two surfaces:
773
+ - **Editor web/serve copilot** — `editor_action` `action_type=set_layer_media` with `object_fit` and `object_position` (both also seedable on `add_layer`/`generate_layer`). `editor_context.layers[]` reports each clip's current `object_fit`/`object_position` so you can read before re-cropping.
774
+ - **Desktop agents (devcli)** — `vidfarm place ./work --src <url> --object-fit cover --object-position "25% 50%"` (also on `--replace <layer_key>`).
775
+
776
+ ### Ken Burns — animate still images (slow pan/zoom)
777
+
778
+ Still images can carry a **Ken Burns effect**: a slow pan or zoom that runs across the clip's full duration, in the editor preview and both render paths identically. This is what makes a slideshow of stills feel like motion footage. It is a first-class layer property — the image layer's `<img>` gets `data-kenburns="<preset>"`; there is no separate keyframe authoring.
779
+
780
+ **Presets:** `zoom-in`, `zoom-out`, `pan-left`, `pan-right`, `pan-up`, `pan-down`, `zoom-in-left`, `zoom-in-right`. Optional **intensity** `0.04`–`0.5` (default `0.18`; ~`0.1` subtle, ~`0.3` dramatic) controls how far it travels/zooms.
781
+
782
+ When the director says **"animate the images"**, **"make the photos move"**, **"add Ken Burns"**, or similar, apply a preset to **every** still-image layer — vary presets across adjacent clips (zoom-in, then pan-left, then zoom-out, …) so consecutive stills don't repeat the same motion. Zoom presets suit centered subjects (products, faces); pan presets suit wide scenery or tall screenshots. Only images support it — never set it on video, text, or audio.
783
+
784
+ Three surfaces:
785
+ - **Editor web copilot** — `editor_action` with `action_type=set_layer_media`, `ken_burns=<preset>` (`none` removes it), optional `ken_burns_intensity`. One call per image layer. You can also seed it on `add_layer` (kind=image) or `generate_layer` (media_type=image) so a freshly placed still arrives already animated.
786
+ - **Editor UI (human)** — the Inspector's **Animate (Ken Burns)** dropdown on any selected image.
787
+ - **Desktop agents (devcli)** — `vidfarm place ./work --src <img> --kind image --ken-burns zoom-in [--ken-burns-intensity 0.3]`, or fused with generation: `vidfarm generate image --prompt "…" --place ./work --ken-burns pan-left`.
788
+
789
+ ### Animated captions — word-by-word caption styles (TikTok/CapCut)
790
+
791
+ Compositions support **first-class animated captions**: caption layers whose words animate one at a time, synced to the spoken audio, identical in the editor preview and both render paths. A caption *run* is one layer per cue (a page of ~3-5 words) on a shared track; each layer carries `data-caption-animation="<preset>"` and per-word `<span data-cap-word>` timing — no keyframe authoring, and the text stays editable like any text layer.
792
+
793
+ **Preset styles** (each = an animation + a look; every field individually overridable): `spotlight` (active word gets a rounded highlight pill on bold outlined text — the classic Hormozi/CapCut look), `karaoke` (words fill with color as spoken and stay lit), `word-pop` (one word at a time, punchy scale-in), `stack-up` (words fade/rise in and accumulate), `neon` (active word glow pulse), `bounce` (active word bounces). Overridables: `caption_active_color` (spoken-word color), `caption_highlight_color` (spotlight pill), `caption_uppercase`, plus the normal text fields (`color`, `background`, `background_style`, `font_family`, `font_size`).
794
+
795
+ **Word timings**: real word-level timestamps come from OpenAI STT (whisper-1); Gemini/OpenRouter transcripts get char-weighted estimated word windows, which still read well. Hand-typed text is auto-paged and estimated.
796
+
797
+ Four surfaces:
798
+ - **Editor web copilot** — for narration that needs TRANSCRIBING (a TTS voiceover layer, replaced audio, word-accurate karaoke), call the **captions primitive**: `POST /api/v1/primitives/audio/captions` `{ tracer, payload: { source_url: <narration URL>, style } }` — one job transcribes with word-level timings and returns `captions` (the cue array) plus `set_captions_action` (fully-formed `editor_action` arguments); apply verbatim with `editor_action action_type=set_captions`. For speech that is the decomposed source video's own audio, skip the job: derive cues from `video_context`'s timestamped segments and call `set_captions` directly with `captions=[{text,start,duration,words?}]`, or just `text` (+ `start`/`duration` window) to auto-page. Pick `caption_style`; the call **replaces** all existing animated caption layers, so it's also the "restyle everything" verb. Per-layer: `set_layer_style` with `caption_*` fields restyles one cue, `set_layer_text` rewrites its words, `caption_animation="none"` flattens back to static text. Never hand-build word-by-word captions from many `add_layer` calls.
799
+ - **REST one-step (headless/automation)** — `POST /api/v1/compositions/:forkId/captions` `{ audio_url?|text?, style?, animation?, active_color?, highlight_color?, uppercase?, max_words_per_cue?, offset_sec?, provider?, language? }` transcribes the fork's own narration (auto-resolves: explicit `audio_url` → audio layer → backing video → source video) and writes the animated caption layers into the working composition **server-side** in one synchronous call — BYOK STT, non-billing. On a local `vidfarm serve` box, open editor tabs live-morph the result. Do **not** use it while a cloud editor session is open (the editor's next auto-save would overwrite it) — in-editor, mutate through `set_captions` instead.
800
+ - **Editor UI (human)** — right-click → **Insert Captions** drops a starter cue; the Inspector's **Animated captions** picker switches styles on any selected text/caption layer, with active-word/highlight color and uppercase controls next to it.
801
+ - **Desktop agents (devcli)** — `vidfarm captions generate ./work [--style spotlight]` transcribes the composition's own narration LOCAL-FIRST on your key (prefers OpenAI for real word timestamps) and writes the cue layers into `composition.html` on disk (a running `serve` live-morphs it). Alternates: `--audio <file|url>`, `--srt <file>` (e.g. from `vidfarm stt`), or `--text "<script>"`. Then `vidfarm captions style ./work --style karaoke` (restyle, same text/timing), `captions list`, `captions clear`.
802
+
803
+ The plain STT primitive grew the same hook: `POST /api/v1/primitives/audio/transcribe` accepts `word_timestamps: true` to attach word-level timings to each transcript segment (real on an OpenAI key via whisper-1; other providers stay segment-level).
804
+
805
+ ### Script-free keyframe motion & timeline verbs
806
+
807
+ Beyond the Ken Burns / transition / animated-caption presets, the copilot can hand-author motion and re-time layers directly (all `editor_action` verbs; all preview AND render identically):
808
+
809
+ - **`set_layer_keyframes`** — author a script-free CSS `@keyframes` animation on ONE layer: `opacity`, `translate_x` / `translate_y` (% of the layer's own box), `scale`, and `rotate` (deg), keyed across the clip. Params: `layer_key`, `keyframes: [{ offset 0..1, opacity?, translate_x?, translate_y?, scale?, rotate? }]`, optional `keyframe_easing`, optional `keyframe_duration` (defaults to the clip's duration). This is the durable way to hand-craft motion beyond the Ken Burns / transition / caption preset vocabulary.
810
+ - **`nudge_layers`** — relative timeline move: `layer_key` (or `layer_keys`) + `delta_start` (seconds) and/or `delta_track` (lanes). Group-aware.
811
+ - **`ripple_edit`** — insert (`delta_start > 0`) or close (`delta_start < 0`) time at `at_time`, shifting every downstream clip.
812
+ - **`trim_layer`** — move one edge: `edge=start|end` + `to_time` (seconds). A left trim also advances the media in-point for video/audio.
813
+ - **`set_layer_zindex`** — restack: `z_order=front|back|forward|backward` (stacking == track index) or an explicit `track`.
814
+
815
+ **Named layer-edit verbs (web `editor_action` ↔ devcli command parity).** Every fine edit has a devcli twin that mutates a pulled `composition.html` on disk (same `data-*` contract, so a human can finish it in the editor):
816
+
817
+ | Intent | Web `editor_action` | devcli command |
818
+ |---|---|---|
819
+ | Rewrite a text/caption layer's words | `set_layer_text` | `vidfarm set-text <dir> --layer <k> --text "…"` |
820
+ | Restyle text/color/typography (+ `line_height`, `letter_spacing`, `opacity`) | `set_layer_style` | `vidfarm set-style <dir> --layer <k> [--color --background --background-style --font-family --font-weight --italic --underline --text-align --font-size --border-radius --line-height --letter-spacing --opacity]` |
821
+ | Geometry + constant opacity | `set_layer_visual` | `vidfarm set-visual <dir> --layer <k> [--x --y --width --height --font-size --border-radius --opacity]` |
822
+ | Replace/edit a media layer **IN PLACE** (src swap + subrange + audio + fit + ken-burns + transitions) | `set_layer_media` | `vidfarm set-media <dir> --layer <k> [--src <url\|/raws/…\|file> --playback-start --source-out --duration --volume --muted --object-fit --object-position --ken-burns --transition --transition-out]` |
823
+ | Set the stable slug/note handle | `set_layer_identity` | `vidfarm set-identity <dir> --layer <k> [--slug --note]` |
824
+ | Clone a layer | `duplicate_layer` | `vidfarm duplicate <dir> --layer <k> [--start --track --new-key]` |
825
+ | Cut a clip in two | `split_layer` | `vidfarm split <dir> --layer <k> --at <sec>` |
826
+ | Set timing directly (absolute) | `set_layer_timing` | `vidfarm retime <dir> --layer <k> [--start --duration --track --playback-start]` |
827
+ | **Canvas / theme** (whole composition) | `set_composition` | `vidfarm set-composition <dir> [--width --height --duration --background]` |
828
+
829
+ `set-media` edits the media layer **in place** — the src swap keeps the same node/key/geometry/timing (the `set_layer_media` twin), so downstream references and the layer's DNA survive. Reach for `place --replace <layer_key>` only when you *want* a fresh clip that inherits the slot's timing+geometry (it deletes the node and mints a new key). Subrange is the web in/out picker in one gesture: `--playback-start 3 --source-out 8` = "use seconds 3–8 of the source" (sets the in-point + a 5s timeline duration). `--opacity` is a **constant** level (a ghosted underlay); a fade over time is `set_layer_keyframes` / `vidfarm keyframes`. `line_height`/`letter_spacing` reach typography the presets can't (condensed all-caps, tight/loose leading). `set-composition` resizes the frame (`--width`/`--height`), retargets total render length (`--duration`), or recolors the canvas (`--background`) — written on the composition root per the hyperframes contract. Every command takes `--json` and writes the file in place.
830
+
831
+ **Web editor = declarative/CSS motion only.** In the web editor, motion is CSS-only. The JS runtime adapters (anime.js, GSAP, Lottie, Three.js, TypeGPU) are a **local-devcli-only** capability — they are stripped from the composition on save, and `replace_composition_html` now **rejects `<script>`-bearing HTML** in the web editor. On the web, author motion with CSS `@keyframes` (via `set_layer_keyframes` or a `<style>` block) plus the preset vocabulary (Ken Burns, transitions, animated captions). Scripted / adapter-driven compositions belong to the desktop `vidfarm serve` (or pulled-fork) flow, which renders them correctly through `vidfarm render`.
832
+
833
+ **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`.
834
+
835
+ ### Local file paths as media (skip the S3 upload)
836
+
837
+ `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:
838
+
839
+ - **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.
840
+ ```bash
841
+ # serve box running on :3000 — reference clips straight off disk, render locally, free
842
+ vidfarm place ./.vidfarm-local/storage/compositions/forks/<forkId>/working \
843
+ --src ~/footage/hook-042.mp4 --at 0
844
+ vidfarm render <forkId> # local, $0.00
845
+ ```
846
+ 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).
847
+ - **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.
848
+
849
+ **`/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.
850
+
851
+ ## Raws (long-form → short-form raws)
852
+
853
+ 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).
854
+
855
+ **Start a hunt** — `POST /raws/scan` (async: returns `202 { scan_id }` immediately):
856
+
857
+ ```jsonc
858
+ {
859
+ // ONE source (pick one):
860
+ "source_url": "https://youtube.com/watch?v=…", // downloaded into your temp folder first
861
+ "temp_file_id": "…", // a video you uploaded to temporary-files
862
+ "attachment_id": "…", // a video already in My Files
863
+ "s3_key": "…" , // an already-staged object
864
+
865
+ // What to hunt for — free text; inline hints are parsed too
866
+ "prompt": "people holding food up to their face, no text on screen",
867
+
868
+ // Hunt controls (all optional; also expressible inline in the prompt):
869
+ "ranges": ["12:30-15:45", "20:00-22:10"], // ONLY hunt these source windows — big cost saver on long videos
870
+ "target_duration_sec": 30, // SOFT length band, not a hard cut: 10→5-20s, 30→20-40s, 60→40-80s, 120→80-160s
871
+ "aspect": "9:16", // crop every raw: 9:16 | 16:9 | 4:3 | 1:1 (synonyms: vertical/portrait, horizontal/landscape, square)
872
+ "crop_focus": "center", // center | top | bottom | left | right (top biases toward faces)
873
+ "avoid_text": true, // prefer scenes WITHOUT burned-in captions/on-screen text — a scene-SELECTION filter, NEVER GhostCut on the source
874
+ "tracer": "my-campaign" // rolls up all the hunt's billing/observability events
875
+ }
876
+ ```
877
+
878
+ - **Poll** `GET /raws/scan/:scanId` until the source `status` is `complete` (or `failed` — crashed pipelines are reconciled to `failed`, pollers never spin).
879
+ - **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.
880
+ - **Billing** — scan compute (`clip_scan_lambda` GB-seconds + Step Functions transitions) and each persisted semantic embedding are wallet-metered; the 202 response includes a `compute_estimate`. AI tagging/refine runs on **your saved provider key (BYOK)** and is not wallet-billed. Submission is wallet-gated (402 when empty).
881
+ - **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.
882
+ - **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.
883
+ - **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`.)
884
+
885
+ **devcli (local-first — this is the default way to hunt on your own machine):**
886
+
887
+ ```bash
888
+ # Local machine power: local ffmpeg + your local claude/codex CLI subscription (no API key needed)
889
+ vidfarm raws scan ./podcast.mp4 --range "12:30-15:45" --duration 30 --aspect vertical --no-text \
890
+ --prompt "guest reaction moments"
891
+
892
+ # Provider keys are the fallback (--provider gemini|openai|openrouter); the cloud pipeline is the
893
+ # EXPLICIT backup, never the default:
894
+ vidfarm raws scan --cloud ./podcast.mp4 --duration 30 --aspect 9:16 --tracer my-campaign
895
+ vidfarm raws scan --cloud --url "https://youtube.com/watch?v=…" --range "0:00-10:00"
896
+
897
+ # Then search/reuse the library
898
+ vidfarm raws search "confused reaction after reading a message"
899
+ vidfarm raws export <raw-ids…> --to ./picks
900
+ ```
901
+
902
+ 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.
903
+
904
+ ## My Files (the user's asset library)
905
+
906
+ 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.
907
+
908
+ - **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>]`.
909
+ - **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.
910
+ - **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).
911
+ - **`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").
912
+
913
+ ### Metadata notes + vector search (find assets by meaning)
914
+
915
+ 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:
916
+
917
+ - **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`).
918
+ - **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`.
919
+ - **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.
920
+
921
+ ### Recurring characters are first-class (mascots, spokespeople, avatars)
922
+
923
+ 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:
924
+
925
+ 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`):
926
+ ```json
927
+ {
928
+ "id": "character_zara",
929
+ "slug": "zara",
930
+ "name": "Zara",
931
+ "role": "brand mascot / friendly fox guide",
932
+ "appearance": "anthropomorphic red fox, large amber eyes, cream chest fluff, expressive brows",
933
+ "wardrobe": "teal bomber jacket, white tee, small satchel",
934
+ "palette": ["#E8622C", "#12B8A6", "#FDF6EC"],
935
+ "voice": "warm, upbeat, slightly cheeky; mid-tempo",
936
+ "do": ["keep the teal jacket", "front-lit, soft shadows"],
937
+ "dont": ["never photorealistic human", "no other jacket colors"],
938
+ "sprite_card_path": "/files/characters/zara/character_sprite_card.png",
939
+ "about_path": "/files/characters/zara/character_about.md",
940
+ "created_at": "2026-07-09"
941
+ }
942
+ ```
943
+ 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.
944
+ 3. **`character_about.md`** — the prose the manifest summarizes (personality, backstory, do/don'ts), for richer wording when prompting.
945
+
946
+ **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.
947
+
948
+ **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.
949
+
950
+ **Creating a new character (walkthrough).** If no folder exists yet, guide the director through it and persist as you go:
951
+ 1. Agree on a **name** → derive a **slug** (lowercase, hyphens); the folder is `/files/characters/<slug>/`.
952
+ 2. Gather the description conversationally (appearance, wardrobe, signature colors, personality, voice, do/don'ts) — pull from any reference photos they have.
953
+ 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'`.
954
+ 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`).
955
+ 5. **Annotate all three** with notes naming the character so `files --search "our mascot"` finds them from any phrasing.
956
+
957
+ **Renaming / moving / copying.** The whole directory is reorganizable in place, for **paid AND free** users, via three REST routes, their devcli twins, and the explorer's kebab / right-click menu:
958
+
959
+ - **rename** — `POST /api/v1/user/me/directory/rename` `{ path, new_name, file_id? }` · `vidfarm directory rename <path> <new-name> [--file-id <id>]`. Renames a `/files`/`/temp` file (display name only — the view URL keeps working) or a folder in any writable root (its files + nested subfolders come along).
960
+ - **move** — `POST /api/v1/user/me/directory/move` `{ path, to, file_id? }` · `vidfarm directory move <path> <to-folder> [--file-id <id>]`. Relocates a FILE into `to` (with `file_id`) or nests a whole FOLDER under `to`. **Same root only** (`/files`·`/temp`·`/raws`·`/approved`); metadata-only, so S3 objects are untouched.
961
+ - **copy** — `POST /api/v1/user/me/directory/copy` `{ path, to?, file_id?, new_name? }` · `vidfarm directory copy <path> [<to-folder>] [--file-id <id>] [--as <name>]`. Duplicates a file/folder sharing the same underlying S3 object (cheap, no re-upload). Same root only, `/files`·`/temp`·`/raws` (not `/approved` — a ready post is a single publishable unit; not `/projects` — read-only). Omit `to` to duplicate in place.
962
+
963
+ The web copilot exposes the same three as `browse_files action=rename|move|copy`. If you rename or move 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).
964
+
965
+ **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.
966
+
967
+ In the editor web copilot the same filesystem is exposed via the **`browse_files` tool** (`action=list` / `action=search` / `action=read` / `action=write` / `action=annotate` / `action=move` / `action=copy` / `action=rename`), so the copilot follows the identical reasoning: search or list to find the right offer's folder, then read an asset — or `write` a text doc (About.md, awareness-levels.md, …) or import a media URL (`source_url`) into that folder, annotating anything worth finding again. `action=rename`/`move`/`copy` reorganize the tree (see the three routes above) — use them to keep character folders and asset names tidy. `browse_files list` defaults to `path='/'` when `path` is omitted, so the `/raws` (hunted raws) and `/temp` (scratch) roots surface alongside the My Files folders instead of being hidden; a `/raws` listing also accepts a `content_type` filter (exact shot-kind — `talking_head`, `b_roll`, `product_shot`, `screen_recording`, …), and every listing paginates via `offset` / `limit`. The devcli equivalents are `vidfarm files [--search]` / `get-file` / `put-file [--notes]` / `annotate-file` / `directory rename|move|copy`.
968
+
969
+ ## Automate a template via REST
970
+
971
+ Templates now have the same job-backed REST pattern as primitives, so a script can run them repeatably without the editor UI:
972
+
973
+ ```
974
+ POST /api/v1/templates/:templateId/operations/:operationName
975
+ Content-Type: application/json
976
+
977
+ { "tracer": "my-run", "payload": { ... }, "webhook_url": "https://..." }
978
+ ```
979
+
980
+ 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`.
981
+
982
+ ## Automation patterns
983
+
984
+ For agents that operate Vidfarm headlessly, the typical loop is:
985
+
986
+ 1. `POST /api/v1/compositions { template_id }` → fork
987
+ 2. `PUT /api/v1/compositions/:forkId/composition.html` → apply edits
988
+ 3. `PATCH /api/v1/compositions/:forkId/composition.json` → update metadata
989
+ 4. `POST /api/v1/compositions/:forkId/render { tracer }` → render
990
+ 5. Poll `GET /api/v1/compositions/:forkId/renders/:renderId` until `SUCCEEDED`
991
+ 6. Use `expectedOutputPublicUrl` in the next stage when you need the stable public URL immediately; otherwise fall back to `outputUrl` after completion
992
+
993
+ Send a stable `tracer` on export so retries are traceable and filterable in job history — but note that submission is **not idempotent**: every POST creates a new job (and a new charge) even with the same tracer. Do not blind-retry expensive submissions; check the render status first.
994
+
995
+ ## Scripting mode
996
+
997
+ **Scripting mode** is the recommended posture for repeatable template automation. Use it when a director wants to take a template they like, agree on a base fork, and then drive bulk or one-off edits entirely through REST or `vidfarm api` from a script, Lambda, or local machine.
998
+
999
+ This section is for a **desktop/local coding agent**, not the web copilot. A local Codex/Claude agent may use its shell and filesystem to write JavaScript/TypeScript/Python/shell scripts, fetch every API page, join and score catalog/library data, calculate statistics, emit CSV/JSON/Markdown reports, manipulate composition DOM files, and run iterative render/inspection loops. The web copilot cannot inherit those abilities from this document: it may only call its declared tools and bounded REST routes. If web chat prepares work for this flow, consume its My Files handoff document as input; do not claim the web chat itself executed the script.
1000
+
1001
+ Contract:
1002
+
1003
+ 1. Pick the template once, then fork once and treat the resulting `forkId` as the stable base.
1004
+ 2. Read the working state with `GET /api/v1/compositions/:forkId/composition.html` and `GET /api/v1/compositions/:forkId/composition.json`.
1005
+ 3. Modify the composition deterministically in your script.
1006
+ 4. Write back with `PUT /api/v1/compositions/:forkId/composition.html` and `PATCH /api/v1/compositions/:forkId/composition.json`.
1007
+ 5. Snapshot with `POST /api/v1/compositions/:forkId/versions` before or after render, depending on whether you want the version to capture the exact render input or the post-render state.
1008
+ 6. Render with `POST /api/v1/compositions/:forkId/render`, choosing local or cloud based on the host and runtime.
1009
+
1010
+ Devcli shortcut:
1011
+
1012
+ ```bash
1013
+ # FREE local render (the default): renders ./work on THIS machine via the
1014
+ # bundled hyperframes CLI — no cloud dispatch, no wallet charge.
1015
+ vidfarm render "$FORK_ID" --dir ./work --out ./renders/row-42.mp4
1016
+
1017
+ # Billed cloud render: pushes ./work to the fork's working state first, then
1018
+ # submits the same POST /api/v1/compositions/:forkId/render the Web UI uses.
1019
+ vidfarm render "$FORK_ID" --dir ./work --target cloud --tracer "batch-2026-07-09-row-42" --wait
1020
+ ```
1021
+
1022
+ `--dir` may also point directly at `composition.html`. Local renders don't push `./work` to the cloud — `vidfarm publish` (or the cloud-target render) does that. Render prep automatically downloads a long source once and cuts the timeline's segments from it, so referencing the same long raw N times is fine.
1023
+
1024
+ Best practices:
1025
+
1026
+ - Prefer one canonical base fork per automation run, then branch from that fork if you need variants.
1027
+ - Treat `composition.json` as a shallow merged metadata document. The server merges the posted JSON object into the existing object; it is not RFC 6902 JSON Patch.
1028
+ - Keep edits idempotent in your script. Re-run the script against the same fork only if it computes the same desired state.
1029
+ - Use a stable `tracer` on render jobs so logs and job history stay searchable.
1030
+ - Use the same REST sequence in Lambda, CI, or a local shell. The only thing that changes is the host and auth.
1031
+
1032
+ Canonical shell shape:
1033
+
1034
+ ```bash
1035
+ BASE_TEMPLATE_ID="template_..."
1036
+ FORK_ID="$(vidfarm api POST /api/v1/compositions --data "{\"template_id\":\"${BASE_TEMPLATE_ID}\"}" --json | jq -r '.fork_id')"
1037
+
1038
+ vidfarm api GET "/api/v1/compositions/${FORK_ID}/composition.html" --raw > /tmp/composition.html
1039
+ vidfarm api GET "/api/v1/compositions/${FORK_ID}/composition.json" --raw > /tmp/composition.json
1040
+
1041
+ # edit /tmp/composition.html and /tmp/composition.json in your script
1042
+
1043
+ vidfarm api PUT "/api/v1/compositions/${FORK_ID}/composition.html" --body-file /tmp/composition.html --content-type "text/html; charset=utf-8"
1044
+ vidfarm api PATCH "/api/v1/compositions/${FORK_ID}/composition.json" --data-file /tmp/composition.json
1045
+ vidfarm api POST "/api/v1/compositions/${FORK_ID}/versions" --data '{"message":"scripting mode snapshot"}'
1046
+ vidfarm api POST "/api/v1/compositions/${FORK_ID}/render" --data '{"tracer":"scripting-mode"}'
1047
+ ```
1048
+
1049
+ If you need many variants, keep the base fork fixed and fan out by cloning that fork or by reapplying the same edit function to multiple fork ids. Use the raw REST routes directly when you want maximum control; use `vidfarm-devcli` when you want auth, polling, and file helpers without writing the plumbing yourself.
1050
+
1051
+ ## `vidfarm-devcli` — full command surface
1052
+
1053
+ `@officexapp/vidfarm-devcli` wraps the **entire director REST flow** as CLI commands. It is a thin shell over the REST API, not a second implementation: most named commands map 1:1 to one REST route, and the file-backed commands compose the documented routes for upload/download + render-polling. Auth via `--api-key <key>` or `VIDFARM_API_KEY`.
1054
+
1055
+ **Raw REST vs devcli — your choice.** If you want full control, call the REST API directly (or use the raw passthrough `vidfarm api <METHOD> <path> [--data <json>] [--query k=v]`). If you want ergonomics + openable frontend URLs, use the named commands. They are interchangeable — pick per call.
1056
+
1057
+ **Scripting mode recommendation.** For automation, prefer:
1058
+
1059
+ - `vidfarm api` for the actual REST calls
1060
+ - `--data-file` for file-backed payloads
1061
+ - a single pinned base fork id per template family
1062
+ - `POST /api/v1/compositions/:forkId/versions` as the versioning boundary
1063
+ - `vidfarm render <forkId>` for free on-machine output (the default), or `POST /api/v1/compositions/:forkId/render` with a stable `tracer` / `vidfarm render <forkId> --dir ./work --target cloud --wait` for the billed cloud flow
1064
+ - `render_target: "cloud"` when you want the billed cloud renderer (from a `vidfarm serve` box or the devcli); the local default renders on your machine for free
1065
+
1066
+ **Required grounding for AI-authored scripts.** If an AI agent is going to WRITE or MODIFY a local Vidfarm script for a user, the safe default is:
1067
+
1068
+ 1. `vidfarm pull <forkId> --dir ./work`
1069
+ 2. Read `./work/.harness/agent-guide.md`
1070
+ 3. Read `./work/.harness/context.json`
1071
+ 4. Only then read/edit `composition.html`, `composition.json`, and write the automation logic
1072
+
1073
+ That is the canonical local scripting path because the pull step now packages:
1074
+
1075
+ - `video-context.json`: transcript, scene descriptions, viral DNA
1076
+ - `editor-harness.json`: technical editing brief (STYLE — *how to edit like this*)
1077
+ - `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)
1078
+ - `scene-annotations.json`: per-scene replacement/recreation DNA
1079
+ - `.harness/context.json`: merged agent-facing snapshot (includes `replication_harness`)
1080
+ - `.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`
1081
+
1082
+ `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.
1083
+
1084
+ **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.
1085
+
1086
+ If a local AI script rewrites text or scenes without consuming those files first, treat that as a bug in the script/agent flow.
1087
+
1088
+ | Command | REST route | Flow step |
1089
+ |---|---|---|
1090
+ | `vidfarm discover [query] [--sort relevance\|wow\|automation\|recent] [--cursor <cursor>]` | `GET /discover/feed[?q=&sort=&cursor=]` | hybrid-search templates and page through the catalog |
1091
+ | `vidfarm videos [query] [--mine]` | `GET /api/v1/videos[?q=&mine=]` | browse/search source inspirations |
1092
+ | `vidfarm inspiration-add <url\|file>` | `POST /discover/templates` (files: `POST /discover/templates/upload/presign` + PUT first) | add your own source (URL or local video upload) |
1093
+ | `vidfarm inspiration-rm <id>` | `DELETE /discover/templates/:id` | remove a private one |
1094
+ | `vidfarm inspiration-decompose <id>` | `POST /api/v1/inspirations/:id/decompose` | AI-decompose an inspiration |
1095
+ | `vidfarm fork <template_id>` | `POST /api/v1/compositions` | fork a template |
1096
+ | `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 |
1097
+ | `vidfarm generate <image\|video> --prompt "…"` | `POST /api/v1/primitives/{images,videos}/generate` (polls job) | generate AI media → finished URL |
1098
+ | `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) |
1099
+ | `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 |
1100
+ | `vidfarm remove-greenscreen <image\|video> [--preset green\|blue\|white\|black\|digital-green\|magenta] [--key-color #00FF00] [--tolerance 0.3] [--local] [--out <f>]` | `POST /api/v1/primitives/remove-greenscreen` (polls job) | chroma-key a FLAT solid background → transparent PNG/WebP (image) or WebM/VP9-alpha (video); auto-detects media kind. `--local` runs it FREE in-process (sharp/ffmpeg, no wallet); default cloud is billed at real compute × 1.2. Aliases: `greenscreen`, `remove-background-greenscreen`. |
1101
+ | `vidfarm tts "…" [--style "…"] [--voice <v>] [--out <file>]` | (LOCAL-FIRST: your own OPENAI/GEMINI/OPENROUTER_API_KEY → audio file on disk; `--cloud` = `POST /api/v1/primitives/audio/speech` + poll, ElevenLabs on the platform key by default, `--own-key` for yours) | text → narration audio; `--cloud --voice <voice_id>` picks an ElevenLabs voice |
1102
+ | `vidfarm music "<prompt>" [--length <sec>] [--out <f>] [--own-key]` | `POST /api/v1/primitives/music/generate` (polls job) | prompt → music track (ElevenLabs; platform key + wallet by default, `--own-key` for yours) |
1103
+ | `vidfarm voices [--own-key] [--limit N]` | `GET /api/v1/primitives/audio/voices` | list ElevenLabs voices (voice_id/name/labels) for `tts --voice`; default a voice + tell the user they can choose |
1104
+ | `vidfarm stt <file\|url> [--out <base>] [--no-diarize]` (alias: `transcribe`) | (LOCAL-FIRST: local ffmpeg demux + your own key; `--cloud` = `POST /api/v1/primitives/audio/transcribe` + poll, ElevenLabs Scribe on the platform key by default, `--own-key` for yours) | video/audio → transcript in BOTH formats: simple subtitles (txt + SRT) and multi-speaker segments (json) |
1105
+ | `vidfarm place <dir> --src <url\|file> [--at\|--replace]` | (edits local composition.html; local files → serve disk store or temp upload) | drop media (URL **or local file**) into a gap / over a scene |
1106
+ | `vidfarm captions generate <dir> [--style <preset>] [--audio <f>\|--srt <f>\|--text "…"]` | (LOCAL-FIRST: STT on your own key — OpenAI = real word timestamps — then edits local composition.html) | transcribe narration → animated word-by-word caption cues |
1107
+ | `vidfarm captions style <dir> --style <preset>` / `captions list` / `captions clear` | (edits local composition.html) | restyle / inspect / remove animated captions |
1108
+ | `vidfarm keyframes` / `move`\|`nudge` / `ripple` / `trim` / `restack`\|`zindex` `<dir> …` | (edits local composition.html) | script-free CSS keyframe motion + timeline verbs (see "Script-free keyframe motion & timeline verbs") |
1109
+ | `vidfarm set-text` / `set-style` / `set-visual` / `set-identity` / `duplicate` / `split` / `retime` / `set-composition` `<dir> …` | (edits local composition.html) | named layer-edit verbs — devcli twins of the web `set_layer_*` / `set_composition` (opacity, line-height, letter-spacing, canvas resize/duration/background); see parity table above |
1110
+ | `vidfarm decompose <forkId>` | `POST .../compositions/:forkId/auto-decompose` | split source into scenes |
1111
+ | `vidfarm remove-video-captions <forkId>` (alias: `ghostcut`) | `GET .../compositions/:forkId/remove-video-captions` | subtitle-removal status |
1112
+ | `vidfarm snapshot <forkId>` | `POST .../compositions/:forkId/versions` | save composition fork version |
1113
+ | `vidfarm versions <forkId>` | `GET .../compositions/:forkId/versions` | list versions |
1114
+ | `vidfarm render <forkId> [--dir <dir\|composition.html>] [--out <path>] [--target local\|cloud] [--wait]` | local (default): renders ON YOUR MACHINE via bundled hyperframes, free; `--target cloud`: `PUT/PATCH working files, then POST .../compositions/:forkId/render` (billed) | script-friendly render to MP4; no `--dir` uses the fork's current working state |
1115
+ | `vidfarm render-status <forkId> <renderId>` | `GET .../compositions/:forkId/renders/:renderId` | poll a render |
1116
+ | `vidfarm visibility <forkId> <private\|public>` | `PATCH .../compositions/:forkId/visibility` | set visibility |
1117
+ | `vidfarm clone <forkId>` | `POST .../compositions/:forkId/clone` | clone a fork |
1118
+ | `vidfarm share-link <forkId>` | `POST .../compositions/:forkId/share-links` | mint a share URL |
1119
+ | `vidfarm approve --video <url\|file> --caption "…"` | `POST /api/v1/approved/posts` | approve post (local `--video`/`--media` auto-upload to `temp/`; prints `share_url`) |
1120
+ | `vidfarm posts` / `vidfarm post <id>` | `GET /api/v1/approved/posts[/:id]` | browse approved posts |
1121
+ | `vidfarm schedule <postId> --at <iso> --to <dest>` | `POST /api/v1/approved/posts/:postId/schedules` | schedule a post |
1122
+ | `vidfarm schedules <postId>` | `GET /api/v1/approved/posts/:postId/schedules` | browse scheduled posts |
1123
+ | `vidfarm login <email>` / `vidfarm verify <email> <code>` | `POST /api/v1/user/request-otp` · `verify-otp` | get an API key |
1124
+ | `vidfarm whoami` | `GET /api/v1/user/me` | who am I (also prints the active cost mode) |
1125
+ | `vidfarm cost-mode [minimize\|hybrid\|pure-ai]` | (local `~/.vidfarm/cost-mode.json`) | show/set the money-saving preference every billed command respects; no arg = show + explain the three; `--clear` forgets it |
1126
+ | `vidfarm wallet [--job <id>\|--tracer <t>] [--limit <n>]` | `GET /api/v1/user/me/wallet` | cost log: balance + lifetime spend + recent charges. `--job <renderJobId>` prints **what that one video cost** (sums its charges); `--tracer <t>` sums a tracer. Cloud-only, paid plan. Aliases: `spend`, `costs` |
1127
+ | `vidfarm provider-keys` / `vidfarm add-provider-key <p> <secret>` | `GET`·`POST /api/v1/user/me/provider-keys` | manage AI keys |
1128
+ | `vidfarm upload <file> [--folder <path>]` | `POST /api/v1/user/me/temporary-files/upload` | upload → durable URL (ephemeral; prefer `--folder temp` for scratch) |
1129
+ | `vidfarm download <url> [dest]` | (streams any URL to disk) | download media |
1130
+ | `vidfarm download-post <url> [--quality best\|hd\|full_hd]` | `POST /api/v1/primitives/videos/download` + poll | download a social/media post into a durable MP4 or slideshow |
1131
+ | `vidfarm download-post-audio <url>` | `POST /api/v1/primitives/audio/download` + poll | download a social/media post's audio into a durable audio file |
1132
+ | `vidfarm files [--folder <path>]` | `GET /api/v1/user/me/attachments` | list My Files assets + folders |
1133
+ | `vidfarm files --search "…" [--folder <path>]` | `POST /api/v1/user/me/attachments/search` | find My Files assets by MEANING (keyword + vector over name/folder/notes) |
1134
+ | `vidfarm get-file <id> [dest] [--print]` | (resolve id → view_url, then stream/print) | read one My Files asset |
1135
+ | `vidfarm put-file <file> [--folder <p>] [--as <name>] [--content/--stdin] [--notes "…"]` | `POST /api/v1/user/me/attachments/upload` | write into My Files (persistent) |
1136
+ | `vidfarm annotate-file <id\|name> --notes "…"` | `PATCH /api/v1/user/me/attachments/:id` | set metadata notes on one My Files entry (vector-embedded) |
1137
+ | `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 |
1138
+ | `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) |
1139
+ | `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 |
1140
+ | `vidfarm raws search "…"` / `raws match "…"` / `raws list` / `raws sources` | (local library; NL→criteria via local agent or provider key) | search/reuse the raws library |
1141
+ | `vidfarm raws preset list\|run\|save` / `raws export <ids…> --to <dir>` | (local library) | saved queries; copy raw MP4s out |
1142
+ | `vidfarm lint <dir\|composition.html>` | (local static validation) | pre-publish composition check: timing, overlaps, preset names, media src |
1143
+ | `vidfarm stills <dir> [--at 0,2.5,…]` | (local in-process render of PNG frames) | visually verify an edit without a full render |
1144
+ | `vidfarm doctor` | (local environment triage) | check ffmpeg/node/keys/agent CLI/poisoned env before debugging anything else |
1145
+ | `vidfarm skills list\|add <name>\|update` | `GET /skill-pack/index.json` · `/skill-pack/:name/*` | install/refresh skill packs (see "Skill packs — import on demand") |
1146
+ | `vidfarm tts "…" --engine local` / `vidfarm stt <file> --engine whisper` | (keyless LOCAL engines: Kokoro-82M TTS, whisper.cpp STT) | narration + word-timestamp transcripts with zero keys and zero accounts |
1147
+ | `vidfarm remove-background <video\|image>` | (local ONNX matting — free) | transparent-subject media for occlusion captions/cutouts (arbitrary/messy background; for a FLAT solid background use `remove-background-greenscreen`) |
1148
+ | `vidfarm capture <url>` | (local headless-Chrome capture) | website screenshots/assets for website-to-video flows |
1149
+ | `vidfarm beats <audio>` | (local beat detection → JSON) | music beat timestamps to sync cuts/transitions/captions |
1150
+
1151
+ **Frontend URLs are first-class output.** Every command that has a human-openable page (editor, discover, approved-post preview, share link) prints that prod frontend URL as a highlighted line. `render --wait` polls to completion and prints the final MP4 URL; `approve` prints the approved-post `share_url`. Add `--json` to any command for pure JSON (agent-friendly, no banners).
1152
+
1153
+ **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.
1154
+
1155
+ ## Cost mode — the devcli's money-saving guardrail
1156
+
1157
+ `vidfarm cost-mode <minimize|hybrid|pure-ai>` records a single spend preference (in `~/.vidfarm/cost-mode.json`) that every **billed** command honors: `generate`, `music`, `decompose`, `inspiration-decompose`, `create`, `replicate`, `inpaint`, `create-overlay`, and the cloud paths of `render --target cloud`, `tts --cloud`, `stt --cloud`, `remove-greenscreen` (non-`--local`). FREE local engines never gate (`render` local default, `tts --engine local`, `stt --engine whisper`, `remove-greenscreen --local`, all the file-editing verbs).
1158
+
1159
+ - **minimize** — a billed op is **refused** unless you add `--yes`; the error names the free local alternative. Use this to guarantee no surprise AI spend.
1160
+ - **hybrid** *(default recommendation)* — billed ops run but print a one-line cost notice each.
1161
+ - **pure-ai** — billed ops run without gating; cost is still printed.
1162
+
1163
+ Precedence: `--cost-mode <m>` flag → `VIDFARM_COST_MODE` env → the saved `cost-mode` → default (hybrid, flagged as "not set"). When nothing is saved and a billed op runs, the CLI prints a "no preference set — ask the user" nudge instead of silently spending, so the default posture really is *ask before you spend*.
1164
+
1165
+ **Agent-memory handoff.** After the user picks, offer to remember it across sessions — but the destination depends on the agent, so ask: Claude Code → `CLAUDE.md` (or its memory dir); Codex / OpenCode / most others → `AGENTS.md`; or a note file the user names. `vidfarm cost-mode <choice>` already persists the devcli-side preference; agent memory is the extra step that survives a fresh checkout. In the **web app UI** there is no memory file — ask each time unless the user states a standing preference for the session.
1166
+
1167
+ ## Local dev loop (`vidfarm serve`)
1168
+
1169
+ 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.
1170
+
1171
+ ```bash
1172
+ npx -y @officexapp/vidfarm-devcli serve <template_id>
1173
+ # → boots the full backend on http://localhost:3000 (records + storage on disk)
1174
+ # → pulls that template's default fork (or --fork <id>) from the cloud onto disk
1175
+ # → auto-provisions a local user and opens the editor, pre-authed
1176
+ ```
1177
+
1178
+ The **editor and its data** run locally (`RECORDS_DRIVER=local`, `STORAGE_DRIVER=local`) and rendering happens **in-process on this box for free**. Everything catalog-shaped still mirrors the cloud host ("cloud passthrough"): `/discover`, `/api/v1/videos`, and `/library` list the **cloud** catalog and the cloud account's approved posts next to anything local, opening a cloud template **seeds** its composition onto disk on demand, Add Template ingests into the cloud account, and approved-post actions (schedules, archive, delete) on cloud posts proxy through. Media a seeded composition already references stays on its cloud URLs — but you can also drop your **own local files** onto the timeline without any upload: `vidfarm place --src ./clip.mp4` copies the file into this box's disk store and references it by a `localhost/storage` URL (see "Local file paths as media" above), so the free in-process renderer plays it back with zero S3 involvement. Pass `--no-cloud` for a fully-offline box.
1179
+
1180
+ **Rendering from a serve box** — the editor's **Render** button becomes a popover with two options when a cloud `--api-key` is configured: **Render Local (Free)** (in-process HyperFrames render, no charge) and **Render in Cloud** (hands the render to the cloud renderer, billed to the cloud account's wallet). Over REST, pass `render_target: "cloud"` in the `POST /render` body; the local box resolves (or clones, once) a publishable cloud fork, remembers the mapping in the fork's `upstream-link.json`, and `GET /renders/:renderId` transparently proxies the cloud job status.
1181
+
1182
+ How the loop works:
1183
+ - The composition lives on disk at `<data-dir>/storage/compositions/forks/<forkId>/working/composition.html` (default `<data-dir>` is `./.vidfarm-local`). Point your agent at that file.
1184
+ - When the agent saves it, the server's `fs.watch` fans a `reload` for that fork over **same-origin** SSE (`GET /api/v1/dev/events`) and the open editor tab live-morphs the change — no reload, playback state preserved (keyed DOM morph on `data-hf-id`).
1185
+ - When the human edits in the browser, the editor `PUT`s the composition back to the same file; the server suppresses that self-write so it doesn't echo. Multiple forks can be edited at once under one server.
1186
+
1187
+ Seeding from the cloud: `serve <template_id>` pulls the template's **default fork** (which may be another user's public decomposition); `serve --fork <id>` pulls a specific fork. Only the composition + records are localized — media stays on cloud URLs. Re-serving keeps local edits unless you pass `--refetch`.
1188
+
1189
+ Flags: `--port` (default 3000), `--dir` (default `./.vidfarm-local`), `--key` (bootstrap/browser key, or `VIDFARM_API_KEY`), `--fork <id>`, `--host` (cloud host to mirror + pull from, default `https://vidfarm.cc`), `--api-key` (cloud key for pulls, `/library`, and cloud render — defaults to `VIDFARM_API_KEY`), `--refetch`, `--no-cloud` (fully offline: no cloud catalog, seeding, or cloud render), `--no-open`. `vidfarm <template_id>` is an alias for `serve <template_id>`.
1190
+
1191
+ To *analyze* the source media locally (videos, transcript, recurring cast), read the `video-context.json` / `cast.json` routes, or pull a media URL with `vidfarm download <url>`.
1192
+
1193
+ ## Help docs / tutorials as context
1194
+
1195
+ The customer-facing walkthrough (the "VidFarm Walkthrough Tutorial" course) is published at `/blog` with one chapter per `/blog/<slug>`, and is queryable as structured context — reach for it when a user asks "how do I…" about a VidFarm workflow, or when onboarding someone:
1196
+
1197
+ - `GET /api/v1/help/tutorials` → the ordered chapter index (`{ playlist, count, chapters:[{ slug, title, blurb, section, reading_time, keywords, url }] }`).
1198
+ - `GET /api/v1/help/tutorials/<slug>` → one chapter as readable plain text (`{ slug, title, section, url, keywords, text }`), or `404 {"error":"unknown_tutorial"}`.
1199
+
1200
+ Both are public and read-only (no auth). Prefer these to guessing steps — quote the real chapter and link the reader to its `url`. Chapters cover onboarding/setup, the operating funnel (angles/hooks/awareness), each guided edit demo (recaption, product tease, remix-with-raws, actor replacement, animate-static-book, drama series, product promo, motion explainers), sourcing/clipping raws, the wallet, cancellation/refunds, and the developer devcli/scripting/free-mode chapters.
1201
+
1202
+ ## Skill packs — import on demand (HyperFrames-grade authoring power)
1203
+
1204
+ This skill stays lean on purpose. Deep authoring craft lives in **skill packs** — vidfarm-scrubbed snapshots of the HyperFrames skill suite plus vidfarm's own media pack — vendored on the Vidfarm host and installed only when a task needs them. Never install skills from upstream vendor orgs or third-party registries; the vidfarm mirror is the source (`vidfarm skills add <name>` fetches `GET /skill-pack/:name/*` with hash verification into `.agents/skills/` + a `.claude/skills/` link, pinned in `skills-lock.json`; `vidfarm skills list` shows what is available/installed; `vidfarm skills update` refreshes pins).
1205
+
1206
+ Import by task:
1207
+
1208
+ | When the task needs… | Install |
1209
+ |---|---|
1210
+ | the raw composition HTML contract (data-* attrs, tracks, clips, sub-compositions, determinism rules) | `vidfarm skills add hyperframes-core` |
1211
+ | motion design: animation rules, multi-phase scene blueprints, transition doctrine, runtime adapters (GSAP/Lottie/Three/Anime/CSS/WAAPI/TypeGPU) | `vidfarm skills add hyperframes-animation` |
1212
+ | seek-safe keyframe patterns (FLIP, paths, masks, SVG draw/morph, 3D depth) | `vidfarm skills add hyperframes-keyframes` |
1213
+ | creative direction: design specs, palettes, typography, house style, narration craft | `vidfarm skills add hyperframes-creative` |
1214
+ | the HyperFrames CLI dev loop (init/lint/validate/inspect/snapshot/preview/render) | `vidfarm skills add hyperframes-cli` |
1215
+ | narration/BGM/SFX/media resolution (the shared audio engine) | `vidfarm skills add vidfarm-media` |
1216
+ | embedded/cinematic captions with subject occlusion (32-identity catalog) | `vidfarm skills add embedded-captions` |
1217
+ | graphic overlay cards on existing talking-head footage | `vidfarm skills add talking-head-recut` |
1218
+ | a product launch/promo video end-to-end | `vidfarm skills add product-launch-video` |
1219
+ | a faceless topic explainer end-to-end | `vidfarm skills add faceless-explainer` |
1220
+ | a website tour/showcase video end-to-end | `vidfarm skills add website-to-video` |
1221
+ | a short motion graphic (kinetic type, stat hit, logo sting, lower-third) | `vidfarm skills add motion-graphics` |
1222
+ | an interactive slideshow/deck | `vidfarm skills add slideshow` |
1223
+ | any other multi-scene composition (fallback workflow) | `vidfarm skills add general-video` |
1224
+
1225
+ Ground rules:
1226
+
1227
+ - **Local coding agents** (Claude Code / Codex on a `vidfarm serve` box or a pulled fork) get the FULL packs — install, read, and follow them. Scripted/GSAP compositions authored this way render correctly through `vidfarm render` (local and cloud); note that the **web editor strips `<script>` on save**, so keep compositions that must round-trip through the browser editor declarative (the built-in Ken Burns / transitions / animated-captions vocabulary).
1228
+ - **The web copilot** never installs packs — it has a `load_skill` tool that reads the same content on demand from the vidfarm mirror (served from `.agents/skills/`; the `.claude/skills/*` entries are just Claude Code discovery symlinks to the same dirs). It can `load_skill` any hyperframes pack (`hyperframes`, `hyperframes-core`, `hyperframes-animation`, `hyperframes-keyframes`, `hyperframes-creative`, `hyperframes-cli`) plus the workflow packs (`embedded-captions`, `product-launch-video`, `faceless-explainer`, `website-to-video`, `general-video`, `motion-graphics`, `slideshow`, `talking-head-recut`, `vidfarm-media`). Nothing to install; it is already wired. **In the web editor, apply only the CSS / `@keyframes` + declarative-preset half of `hyperframes-animation` / `hyperframes-keyframes`** — their JS-adapter techniques are devcli-only (script is stripped on save).
1229
+ - Vidfarm-managed environments set `HYPERFRAMES_SKIP_SKILLS=1` and `HYPERFRAMES_NO_TELEMETRY=1`, so `npx hyperframes init` never overwrites the vidfarm-scrubbed packs and the bundled CLI never phones home. Do not run `hyperframes auth`, `hyperframes cloud`, `hyperframes publish`, `hyperframes play`, or `hyperframes feedback` — vidfarm's own render/share/telemetry surfaces cover all of them.
1230
+
1231
+ ## What NOT to do
1232
+
1233
+ - Do **not** manipulate composition HTML by string concatenation. Always parse, edit, and re-serialize the DOM. The editor and runtime rely on `data-start`, `data-duration`, `data-track-index`, `data-hf-id`, `data-composition-id` attributes being well-formed.
1234
+ - Do **not** save AI provider keys in the composition HTML or JSON. They belong in the caller's provider-keys record.
1235
+ - Do **not** call the renderer directly. Publishing goes through `POST /api/v1/compositions/:forkId/render` so billing, retries, cost caps, and Lambda quotas are enforced.
1236
+ - Do **not** re-invent scene manipulation as REST commands (`AddScene`, `RemoveScene`, `UpdateSceneField`). The Trackpad Editor is the surface for that. The pattern is: mutate the composition HTML DOM, PUT it back.
1237
+
1238
+ ## Alpha posture
1239
+
1240
+ Vidfarm is in active development. Endpoints, response shapes, and the editor UI can change. When something behaves unexpectedly:
1241
+
1242
+ - prefer `GET /api/v1/compositions/:forkId` over cached state
1243
+ - treat `latest_version` as the source of truth for what the editor loaded
1244
+ - if a publish fails, check `renders/:renderId` for the error phase and stderr before retrying
1245
+
1246
+ ## Getting started / onboarding a new director
1247
+
1248
+ Only run this flow when the director signals they **don't know where to start** ("getting started", "help me set up", "I don't know where to begin"). If they already know what they want, skip straight to it — never force onboarding. Directors can also jump to any single step (e.g. "just find me a good template for X" → go straight to template discovery).
1249
+
1250
+ The point of onboarding is to build **durable, reusable context** in My Files, namescoped under the director's product/offer folder, so future chats and agents can read it back. Save each artifact as Markdown with `browse_files` write (web copilot) or `vidfarm put-file` (devcli):
1251
+
1252
+ 1. **Product context** → `About.md` (basic offer/product) or a deeper `Interview.md` if they want depth. Drive the interview questions with `brainstorm/coldstart`.
1253
+ 2. **Awareness level** (Eugene Schwartz — problem-aware, solution-unaware, …) → `awareness-levels.md`. If it's genuinely unknown after thinking it through, note that ads for **every** level should be made and tested. Use `brainstorm/awareness_stages`.
1254
+ 3. **Persuasive angles** → `persuasive-angles.md`, via `brainstorm/angles`.
1255
+ 4. **Hooks** → `ad-hooks.md`, via `brainstorm/hooks`.
1256
+ 5. **Brand assets & demos** — ask if they have logos/mascots/themes (suggest a `/brand-assets/` folder, e.g. `/brand-assets/logo.png`) or product demos / screen recordings (suggest a `/product-demos/` folder). `browse_files list` / `vidfarm files` first to see what they already uploaded; filenames should be descriptive and every asset worth finding later should get **notes** (`annotate-file` / `browse_files annotate`) so `files --search` works months from now. If they have a recurring character/mascot, set up its `/files/characters/<slug>/` trio now — `<character_id>.json` (e.g. `character_zara.json`) + `character_sprite_card.png` + `character_about.md` (see "Recurring characters are first-class").
1257
+ 6. **Budget** — ask roughly what they want to spend per video, and map it to the Cost spectrum (free reuse+local render → pennies for cloud render → ~$1 for some AI scenes → $10+ for heavy AI gen). This sets which approach you default to and whether AI **video** generation is on the table (ask permission before using it; image gen is cheap and fine). Budget can also be revisited per editor project.
1258
+ 7. **Recommend & adapt a template** — pair what you now know about the offer against the decomposed template catalog (`GET /discover/feed?q=<offer>`, read each result's `promotions`/`keywords`/`summary`), recommend the best 3-6, then fork and **modify** the winner to fit their offer. Prefer already-decomposed templates so the director skips the ~$0.10 decompose cost.
1259
+
1260
+ **Assume multiple offers.** My Files is multi-offer (see the My Files section) — namescope every onboarding artifact under the right product/offer/region folder (`acme-skincare/About.md`, not a bare `About.md`) so one brand's context never bleeds into another's.
1261
+
1262
+ ## Default assistance pattern
1263
+
1264
+ When a director asks "make me a video", the default sequence is:
1265
+
1266
+ 1. Ask what source material and what template style they want
1267
+ 2. Discover a matching template with `GET /discover/feed`
1268
+ 3. Fork it
1269
+ 4. If they want an entirely new source video, guide them to `auto-decompose` first
1270
+ 5. Open the Trackpad Editor and let them make edits
1271
+ 6. Publish and share
1272
+
1273
+ Prefer specific templates over primitives when a template exists that already captures the desired production pattern.
1274
+
1275
+ ## Primitive: image_remove_background
1276
+
1277
+ Remove the background from any image URL. Result is a transparent PNG/WebP stored at a durable Vidfarm URL you can reuse in compositions or downstream primitives.
1278
+
1279
+ - `POST /api/v1/primitives/images/remove-background`
1280
+ - Body: `{ "source_image_url": "https://...", "output_format": "webp" | "png" | "jpeg" }` (`output_format` defaults to `webp`)
1281
+ - Response: standard primitive job; poll for completion, then read `image.file_url` / `primary_file_url`
1282
+ - Billing: flat pass-through fee via RapidAPI (`rapidapi_remove_background` cost center)
1283
+ - Reach for this only for an **arbitrary photo with a messy background**. If the image already sits on a **flat solid background** (anything you generated on a controlled backdrop), use the far cheaper `image_remove_background_greenscreen` below instead.
1284
+
1285
+ Example:
1286
+
1287
+ ```bash
1288
+ curl -X POST "$VIDFARM_BASE/api/v1/primitives/images/remove-background" \
1289
+ -H "vidfarm-api-key: $VIDFARM_API_KEY" \
1290
+ -H "content-type: application/json" \
1291
+ -d '{"source_image_url": "https://cdn.example.com/photo.jpg"}'
1292
+ ```
1293
+
1294
+ ## Primitive: remove-greenscreen (image **or** video chroma-key)
1295
+
1296
+ **Local chroma-key** background removal for an IMAGE **or** a VIDEO — no third-party API, so it costs a fraction of `image_remove_background`. Use it whenever the source sits on a **flat, solid, evenly-lit background**: a green screen, or any single fill color. An IMAGE returns a transparent PNG/WebP; a VIDEO returns a transparent **WebM (VP9 alpha)** that stays transparent in the `/editor` preview AND the final render (it composites over whatever layers sit behind it).
1297
+
1298
+ - **Unified (auto-detect):** `POST /api/v1/primitives/remove-greenscreen` — detects image vs video from the source and dispatches. Preferred entry point.
1299
+ - Image-only: `POST /api/v1/primitives/images/remove-background-greenscreen` (flat alias: `POST /api/v1/primitives/remove-background-greenscreen`). Video-only: `POST /api/v1/primitives/videos/remove-greenscreen`.
1300
+ - Body: `{ "tracer": "...", "payload": { "source_url": "https://..." (image OR video), "media_type"?: "image" | "video" (skip auto-detect), "preset"?: "green" | "digital-green" | "blue" | "white" | "black" | "magenta", "key_color"?: "#00FF00", "tolerance"?: 0.3, "softness"?: 0.1, "despill"?: true, "output_format"?: "png" | "webp" (image only) }, "webhook_url"?: "..." }`
1301
+ - `preset` — pick a common background color by name; fills `key_color` + tuned defaults. `key_color` (hex / `rgb(...)` / named) overrides the preset for a custom color/range.
1302
+ - `tolerance` (0–1) — how close a pixel must be to key it out. Raise it if fringe survives, lower it if the subject is eaten. `softness` (0–1) feathers the edge; `despill` (default `true`) trims the residual key-color rim (green/blue only).
1303
+ - Response: standard primitive job; poll, then read `primary_file_url` (a `.png/.webp` for image, `.webm` for video). After keying, `set_layer_media` the URL back onto the source layer to swap the transparent cut-out in place.
1304
+ - Billing: the **real measured AWS compute cost × 1.2** (`greenscreen_chroma_key` cost center) — no flat fee. Far cheaper than the RapidAPI matting route.
1305
+
1306
+ ## Primitive: media_overlay ("create media overlay" — Vox-style)
1307
+
1308
+ **The go-to way to mint a transparent overlay graphic to float over a composition.** So-called "Vox-style" animations are really just AI-generated illustrations with transparent backgrounds animated in HTML over the timeline — this primitive is that two-step process fused into one job: it (1) generates an AI image of your subject on a **forced flat key-color background** (using the caller's own BYOK image keys, with the greenscreen instruction injected into the prompt automatically) and (2) chroma-keys that background out, returning a ready-to-composite transparent PNG/WebP. **Prefer this over generate-then-manually-remove**, and reach for it *frequently* whenever a scene wants a floating illustration, prop, icon, sticker, or cut-out character.
1309
+
1310
+ - `POST /api/v1/primitives/images/create-overlay` (flat alias: `POST /api/v1/primitives/create-media-overlay`)
1311
+ - Body: `{ "tracer": "...", "payload": { "prompt": "a cartoon rocket ship, flat vector illustration", "provider"?: "...", "model"?: "...", "prompt_attachments"?: ["https://..."], "aspect_ratio"?: "1:1", "image_size"?: "1K"|"2K"|"4K", "key_color"?: "#00FF00", "tolerance"?: 0.3, "softness"?: 0.1, "despill"?: true, "output_format"?: "png"|"webp" }, "webhook_url"?: "..." }`
1312
+ - Just describe the **subject** in `prompt` — do NOT describe the background; the primitive appends the flat key-color background requirement for you.
1313
+ - `key_color` lets you key against something other than green if your subject is itself green-heavy (e.g. key against `#FF00FF` magenta for a green frog).
1314
+ - The chroma-key params (`tolerance`/`softness`/`despill`) behave exactly as in `image_remove_background_greenscreen`.
1315
+ - Response: `primary_file_url` is the finished transparent overlay; `greenscreen_source_url` is the raw pre-key frame (kept so you can re-key at a different tolerance without paying for another generation).
1316
+ - Billing: small platform wallet fee (`media_overlay` cost center) **plus** the BYOK image-generation cost on the caller's own provider key.
1317
+ - Placement: once the job settles, `add_layer` (or the editor's `generate_layer`/`add_layer`) the `primary_file_url` as an image layer over the video and animate it (Ken Burns, keyframes, entrance transitions) like any other overlay.
1318
+
1319
+ Example:
1320
+
1321
+ ```bash
1322
+ curl -X POST "$VIDFARM_BASE/api/v1/primitives/images/create-overlay" \
1323
+ -H "vidfarm-api-key: $VIDFARM_API_KEY" \
1324
+ -H "content-type: application/json" \
1325
+ -d '{"tracer": "vox-overlay", "payload": {"prompt": "a friendly cartoon lightbulb mascot, flat vector illustration", "aspect_ratio": "1:1"}}'
1326
+ ```
1327
+
1328
+ ## Primitive: videos/download
1329
+
1330
+ Download a supported social/media post URL into a durable Vidfarm-hosted VISUAL asset. This route is intentionally broader than its old name suggests: it returns either a normal MP4 for video posts, or a slideshow payload for photo/carousel posts.
1331
+
1332
+ - `POST /api/v1/primitives/videos/download`
1333
+ - Body: `{ "tracer": "...", "payload": { "source_url": "https://...", "quality"?: "best" | "hd" | "full_hd", "save_manifest"?: true }, "webhook_url"?: "..." }`
1334
+ - Response: standard primitive job. Poll `GET /api/v1/primitives/jobs/:jobId` until `status: "succeeded"`.
1335
+ - Video post: read `primary_file_url` / `video.file_url` / `videoUrl` for the durable MP4.
1336
+ - Photo/carousel post: read `mediaKind: "slideshow"`, ordered `slideImageUrls[]`, optional `slideAudioUrl`, and `primary_file_url` (first slide image).
1337
+ - Billing: RapidAPI pass-through wallet charge (`rapidapi_video_download`) plus the small mirror/extract pass (`video_download_lambda` when MP4 download is involved).
1338
+ - Free-plan / no-spend rule: do **not** use this paid route when the user wants a free path. In the web app, tell them to use the browser to find a downloader instead. Fallback wording is explicit: suggest Googling `"youtube video downloader"` or `"tiktok/twitter/instagram/etc audio/video downloader"`.
1339
+ - devcli wrapper: `vidfarm download-post <url> [--quality best|hd|full_hd]`
1340
+
1341
+ Example:
1342
+
1343
+ ```bash
1344
+ curl -X POST "$VIDFARM_BASE/api/v1/primitives/videos/download" \
1345
+ -H "vidfarm-api-key: $VIDFARM_API_KEY" \
1346
+ -H "content-type: application/json" \
1347
+ -d '{"tracer": "demo-download-post", "payload": {"source_url": "https://www.tiktok.com/@example/video/123"}}'
1348
+ ```
1349
+
1350
+ ## Primitive: audio/download
1351
+
1352
+ Download just the original audio/music/voice track from a supported social/media post URL into a durable Vidfarm-hosted audio file. It reuses the same RapidAPI resolver as `videos/download`, but returns audio instead of a video/slideshow.
1353
+
1354
+ - `POST /api/v1/primitives/audio/download`
1355
+ - Body: `{ "tracer": "...", "payload": { "source_url": "https://...", "save_manifest"?: true }, "webhook_url"?: "..." }`
1356
+ - Response: standard primitive job. Poll `GET /api/v1/primitives/jobs/:jobId` until `status: "succeeded"`, then read `primary_file_url` / `audio.file_url` / `audioUrl`.
1357
+ - Behavior: prefers the provider's direct audio track when present; otherwise resolves the source video and extracts audio server-side.
1358
+ - Billing: same RapidAPI wallet class as `videos/download`.
1359
+ - Free-plan / no-spend rule: same as the visual download route. Use the browser and, if needed, suggest Googling `"youtube audio downloader"` or `"tiktok/twitter/instagram/etc audio/video downloader"` instead of spending wallet credits.
1360
+ - devcli wrapper: `vidfarm download-post-audio <url>`
1361
+
1362
+ Example:
1363
+
1364
+ ```bash
1365
+ curl -X POST "$VIDFARM_BASE/api/v1/primitives/audio/download" \
1366
+ -H "vidfarm-api-key: $VIDFARM_API_KEY" \
1367
+ -H "content-type: application/json" \
1368
+ -d '{"tracer": "demo-download-audio", "payload": {"source_url": "https://www.youtube.com/watch?v=example"}}'
1369
+ ```
1370
+
1371
+ ## Primitive: video_remove_captions
1372
+
1373
+ Remove burned-in captions/subtitles/on-screen text from any video URL (GhostCut-powered — the same pipeline auto-decompose uses for its caption-free mirror, exposed as a standalone reusable primitive). Result is a durable caption-free MP4 stored at a Vidfarm URL.
1374
+
1375
+ - `POST /api/v1/primitives/videos/remove-captions` (alias: `POST /api/v1/primitives/remove-video-captions`)
1376
+ - Body: `{ "tracer": "...", "payload": { "source_video_url": "https://..." }, "webhook_url"?: "..." }`
1377
+ - Response: standard primitive job (`202 { job_id }`). The job probes the source, submits to GhostCut, polls to completion server-side (typically a few minutes), and mirrors the result. Poll `GET /api/v1/primitives/jobs/:jobId` until `status: "succeeded"`, then read `primary_file_url` / `video.file_url` (`captionsRemovedVideoUrl` in the output).
1378
+ - Billing: `~$0.10 per 30 seconds` of source video (per-chunk, 1-chunk minimum; `ghostcut_subtitle_removal` cost center), plus the small probe cost.
1379
+ - For the CURRENT fork's decompose flow, don't re-run this primitive — the caption-free mirror already exists via `GET /api/v1/compositions/:forkId/remove-video-captions`.
1380
+ - **Short clips only — hard cap ~15 minutes** (`GHOSTCUT_MAX_DURATION_SEC`, default 900s). Never point this at a long-form source: for "clips without captions" from a long video, run a raws hunt with `avoid_text: true` (scene selection — see "Raws") and, if needed, apply this primitive to the individual FINISHED raws.
1381
+
1382
+ Example:
1383
+
1384
+ ```bash
1385
+ curl -X POST "$VIDFARM_BASE/api/v1/primitives/videos/remove-captions" \
1386
+ -H "vidfarm-api-key: $VIDFARM_API_KEY" \
1387
+ -H "content-type: application/json" \
1388
+ -d '{"tracer": "demo-remove-captions", "payload": {"source_video_url": "https://cdn.example.com/clip.mp4"}}'
1389
+ ```
1390
+
1391
+ ## Primitive: media_dedupe
1392
+
1393
+ Apply subtle camouflage transforms (zoom, tilt, rotate, saturation, playback speed, contrast, brightness, hue rotate, blur, horizontal flip, tint overlay) to any source **image or video** URL. Useful when reposting existing media and you want small perceptual differences to reduce duplicate-detection.
1394
+
1395
+ - `POST /api/v1/primitives/media/dedupe`
1396
+ - Body: `{ "tracer": "...", "payload": { ...fields... }, "webhook_url"?: "..." }`
1397
+ - Note: webhook delivery is not yet active — `webhook_url` is accepted and persisted on the job but never fired. Poll the job endpoints (`GET /api/v1/primitives/jobs/:jobId`) for completion.
1398
+ - Response: standard primitive job. Poll to completion, then read `primary_file_url` (also `video.file_url` for MP4 or `image.file_url` for stills)
1399
+ - Billing: metered as a HyperFrames render (via `hyperframes_lambda` on prod cloud render; free on a local serve box)
1400
+
1401
+ Payload fields:
1402
+
1403
+ - `source_media_url` (required, URL) — the image or video to transform
1404
+ - `media_type` (`"image" | "video"`, optional) — auto-detected from URL extension if omitted (`.mp4/.mov/.webm/.m4v` → video, else image)
1405
+ - `effects` (optional object). All fields optional; defaults camouflage lightly:
1406
+ - `zoom` (default `1.04`) — scale factor
1407
+ - `tilt` (default `3`) — degrees of 3D X-axis tilt (perspective 1000px)
1408
+ - `rotate` (default `3`) — degrees of 2D rotation
1409
+ - `saturation` (default `1.05`)
1410
+ - `speed` (default `1.05`, video only) — playback rate, also compresses output duration accordingly
1411
+ - `horizontal_flip` (default `false`)
1412
+ - `contrast` (default `1.05`)
1413
+ - `brightness` (default `1.05`)
1414
+ - `hue_rotate` (default `0`) — degrees
1415
+ - `blur` (default `0`) — pixels
1416
+ - `tint_color` (default `"#FF8C00"`), `tint_opacity` (default `0.08`) — subtle color overlay
1417
+ - `width` (default `1080`), `height` (default `1920`) — output canvas
1418
+ - `duration_ms` — output duration for video; if omitted, `fallback_duration_ms` (default `5000`) is used
1419
+ - `object_fit` (`"cover" | "contain" | "fill" | "none" | "scale-down"`, default `"cover"`)
1420
+ - `background_color` (default `"#000000"`) — visible when `object_fit` leaves letterboxing
1421
+ - `muted` (default `false`), `volume` (default `1`) — audio pass-through on video
1422
+ - `output_format` (`"png" | "jpeg" | "webp"`, default `"png"`) — image mode only; video mode always outputs MP4
1423
+
1424
+ Video example (camouflage a reused clip):
1425
+
1426
+ ```bash
1427
+ curl -X POST "$VIDFARM_BASE/api/v1/primitives/media/dedupe" \
1428
+ -H "vidfarm-api-key: $VIDFARM_API_KEY" \
1429
+ -H "content-type: application/json" \
1430
+ -d '{
1431
+ "tracer": "dedupe-2026-07-01-abc",
1432
+ "payload": {
1433
+ "source_media_url": "https://cdn.example.com/reel.mp4",
1434
+ "media_type": "video",
1435
+ "duration_ms": 8500,
1436
+ "effects": {
1437
+ "zoom": 1.05,
1438
+ "tilt": 2,
1439
+ "rotate": -2,
1440
+ "speed": 1.03,
1441
+ "hue_rotate": 4,
1442
+ "horizontal_flip": true
1443
+ },
1444
+ "tint_color": "#00A3FF",
1445
+ "tint_opacity": 0.06
1446
+ }
1447
+ }'
1448
+ ```
1449
+
1450
+ Image example:
1451
+
1452
+ ```bash
1453
+ curl -X POST "$VIDFARM_BASE/api/v1/primitives/media/dedupe" \
1454
+ -H "vidfarm-api-key: $VIDFARM_API_KEY" \
1455
+ -H "content-type: application/json" \
1456
+ -d '{
1457
+ "tracer": "dedupe-still-01",
1458
+ "payload": {
1459
+ "source_media_url": "https://cdn.example.com/photo.jpg",
1460
+ "media_type": "image",
1461
+ "effects": { "zoom": 1.06, "rotate": -1, "hue_rotate": 8 },
1462
+ "output_format": "webp"
1463
+ }
1464
+ }'
1465
+ ```
1466
+
1467
+ ## Primitive: music (text → music)
1468
+
1469
+ Generate music (instrumental, songs with lyrics, background beds, jingles, scores) via ElevenLabs. **Default this on freely — music is a core primitive.** `use_wallet_credits` defaults **true**: it runs on vidfarm's platform ElevenLabs key and bills the customer's wallet. Recommend keeping it on; set it false only to save wallet credits or to use the customer's OWN saved ElevenLabs key.
1470
+
1471
+ - `POST /api/v1/primitives/music/generate` (alias: `POST /api/v1/primitives/music`)
1472
+ - Body: `{ "tracer": "...", "payload": { "prompt": "...", "music_length_ms"?: 30000, "composition_plan"?: {...}, "model"?: "...", "use_wallet_credits"?: true }, "webhook_url"?: "..." }`
1473
+ - `prompt` — describe the vibe/genre/instrumentation. `music_length_ms` ≤ 300000 (5 min), default 30000.
1474
+ - `composition_plan` (optional) — ElevenLabs section-by-section plan for granular lyric/style control (see the loadable `music` skill pack).
1475
+ - Result: durable mp3 (`primary_file_url` / `audio.file_url`). Mount as its own `<audio>` layer ~0.1–0.2 under narration.
1476
+ - devcli: `vidfarm music "<prompt>" --length 30` (`--own-key` to use your ElevenLabs key).
1477
+
1478
+ ## Primitive: tts (text → speech)
1479
+
1480
+ Generate spoken narration audio from text. **`use_wallet_credits` defaults true** → high-quality ElevenLabs narration on vidfarm's platform key, billed to the wallet — the recommended default. Set it false to use the customer's OWN ElevenLabs key or a BYOK OpenAI/Gemini/OpenRouter key (BYOK is never wallet-billed). The **voice style is promptable** via `instructions` (tone/pacing/accent/emotion/persona). Result is a durable Vidfarm audio URL (mp3/wav) ready to drop into a composition as an audio layer.
1481
+
1482
+ - `POST /api/v1/primitives/audio/speech` (alias: `POST /api/v1/primitives/tts`)
1483
+ - Body: `{ "tracer": "...", "payload": { "text": "...", "voice"?: "...", "instructions"?: "...", "use_wallet_credits"?: true, "provider"?: "openai" | "gemini" | "openrouter", "model"?: "...", "output_format"?: "mp3" | "wav" }, "webhook_url"?: "..." }`
1484
+ - `text` (required, ≤8000 chars) — the words to speak, verbatim. Aliases: `input`, `script`.
1485
+ - `voice` (optional) — on the ElevenLabs path, an ElevenLabs **voice_id** (or friendly name george/sarah/…); on the BYOK path a provider preset (OpenAI `alloy`/…; Gemini `Kore`/…). **Default a sensible voice and tell the user they can pick from many** — list them with `GET /api/v1/primitives/audio/voices` (or `vidfarm voices`).
1486
+ - `instructions` (optional) — the voice-STYLE prompt. Aliases: `style`, `style_instructions`, `voice_style`.
1487
+ - `output_format` defaults to `mp3` (Gemini/ElevenLabs may answer in wav — read `audio.content_type`).
1488
+ - Response: standard primitive job. Poll to completion, then read `primary_file_url` / `audio.file_url`.
1489
+ - Billing: wallet-billed on the platform ElevenLabs key (default); free/BYOK when `use_wallet_credits: false` and the caller's own key serves it.
1490
+ - devcli: `vidfarm tts "…" --style "…"` runs LOCAL-FIRST on your own env key (no job); add `--cloud` for the ElevenLabs platform job (`--own-key` = your key). `vidfarm voices` lists voices.
1491
+
1492
+ ## Primitive: audio/voices (list ElevenLabs voices)
1493
+
1494
+ `GET /api/v1/primitives/audio/voices` returns the ElevenLabs voice catalog — `{ scope, default_voice_id, voice_library_url, voices: [{ voice_id, name, category, labels, description, preview_url }] }`. Default scope is vidfarm's platform account; add `?use_wallet_credits=false` to list the voices on the customer's OWN saved ElevenLabs key. Pick a `voice_id` and pass it as `voice` to `/audio/speech`. devcli: `vidfarm voices` (`--own-key` for the user's account).
1495
+
1496
+ Example:
1497
+
1498
+ ```bash
1499
+ curl -X POST "$VIDFARM_BASE/api/v1/primitives/audio/speech" \
1500
+ -H "vidfarm-api-key: $VIDFARM_API_KEY" \
1501
+ -H "content-type: application/json" \
1502
+ -d '{
1503
+ "tracer": "demo-tts",
1504
+ "payload": {
1505
+ "text": "Welcome back. Today we are testing the three most viral hooks.",
1506
+ "voice": "coral",
1507
+ "instructions": "energetic UGC creator, conversational, slightly breathless"
1508
+ }
1509
+ }'
1510
+ ```
1511
+
1512
+ ## Primitive: stt (video/audio → transcript)
1513
+
1514
+ Transcribe speech from **any video or audio URL** on the caller's saved AI provider keys (video sources are demuxed to audio automatically via the platform ffmpeg seam). One job returns **BOTH formats**:
1515
+
1516
+ 1. **Simple subtitle version** — the plain transcript (`output.text`, `transcript.txt`) plus timed SRT cues (`output.subtitles.srt`, `transcript.srt`) with no speaker labels — drop-in captions.
1517
+ 2. **Advanced multi-speaker version** — speaker-attributed, timestamped segments (`output.segments`, each `{ speaker, start_sec, end_sec, text }`, with `output.speakers` listing the cast) for multi-narration sources like podcasts, interviews, and skits — stored durably as `transcript.json`.
1518
+
1519
+ - `POST /api/v1/primitives/audio/transcribe` (alias: `POST /api/v1/primitives/stt`)
1520
+ - Body: `{ "tracer": "...", "payload": { "source_url": "https://...", "diarize"?: true, "word_timestamps"?: false, "use_wallet_credits"?: true, "language"?: "en", "prompt"?: "...", "provider"?: "gemini" | "openai" | "openrouter", "model"?: "...", "media_type"?: "auto" | "video" | "audio" }, "webhook_url"?: "..." }`
1521
+ - `source_url` (required) — video OR audio URL. Aliases: `source_video_url`, `source_audio_url`, `video_url`, `audio_url`, `url`. For a fork's composition, pick the right source via `GET /api/v1/compositions/:forkId/remove-video-captions` first.
1522
+ - `use_wallet_credits` (default `true`) — ElevenLabs Scribe on the platform key (native diarization + real word timestamps), billed to the wallet. Set false to use the customer's OWN ElevenLabs key or a BYOK gemini/openai/openrouter key.
1523
+ - `diarize` (default `true`) — attribute segments to speakers. On the BYOK path speaker labels need a **Gemini key**; OpenAI/OpenRouter degrade to a single-speaker transcript. ElevenLabs Scribe (the default) diarizes natively.
1524
+ - `prompt` (optional) — domain/vocabulary hint; `language` (optional) — expected language code.
1525
+ - Response: standard primitive job. Poll to completion; `output` carries both formats inline (very long transcripts set `text_truncated: true` and keep the full copy in the `transcript.json` artifact), and the artifacts `transcript.json` / `transcript.srt` / `transcript.txt` are durable URLs.
1526
+ - Limits: ~40 minutes of speech per job (20MB extracted audio) — trim longer sources with `/videos/trim` or `/audio/trim` and transcribe in parts.
1527
+ - Billing: wallet-billed on the platform ElevenLabs key (default); free/BYOK when `use_wallet_credits: false`. The tiny audio-demux compute step always bills (`primitive_media_lambda`), like every media primitive.
1528
+ - For the CURRENT fork, the decompose-time transcript may already exist via `video-context.json` — prefer it before paying for a new transcription; use this primitive for other URLs, multi-speaker attribution, or SRT output.
1529
+ - devcli: `vidfarm stt <file|url>` runs LOCAL-FIRST (local ffmpeg + your own key); add `--cloud` to run this route instead.
1530
+
1531
+ Example:
1532
+
1533
+ ```bash
1534
+ curl -X POST "$VIDFARM_BASE/api/v1/primitives/audio/transcribe" \
1535
+ -H "vidfarm-api-key: $VIDFARM_API_KEY" \
1536
+ -H "content-type: application/json" \
1537
+ -d '{
1538
+ "tracer": "demo-stt",
1539
+ "payload": {
1540
+ "source_url": "https://cdn.example.com/podcast-clip.mp4",
1541
+ "diarize": true,
1542
+ "language": "en"
1543
+ }
1544
+ }'
1545
+ ```
1546
+
1547
+ ## Brainstorm primitives
1548
+
1549
+ The `brainstorm/*` primitives are the strategy toolkit. They are reusable, billable AI reasoning steps — the same family the AI Copilot exposes as chip suggestions. Treat **product placement** as a first-class member of this family, right alongside angles and hooks:
1550
+
1551
+ - `POST /api/v1/primitives/brainstorm/coldstart` — `{ payload: { user_message } }` → foundational questionnaire for a customer starting from zero.
1552
+ - `POST /api/v1/primitives/brainstorm/awareness_stages` — `{ payload: { offer_description } }` → which Eugene-Schwartz awareness stages to target first.
1553
+ - `POST /api/v1/primitives/brainstorm/angles` — `{ payload: { offer_description, problem_awareness, solution_awareness } }` → persuasive angles.
1554
+ - `POST /api/v1/primitives/brainstorm/hooks` — `{ payload: { offer_description } }` → many TikTok-native hooks.
1555
+ - `POST /api/v1/primitives/brainstorm/product_placement` — `{ payload: { source_video_url, offer_description } }` → **watches the video** and returns concrete, timestamped opportunities to natively place the product.
1556
+
1557
+ ### Primitive: brainstorm_product_placement
1558
+
1559
+ Analyze a source video and identify where a product can be organically placed so it feels native rather than bolted on. This is a **multimodal** primitive: it feeds the actual video to a vision-capable model, so prefer a saved **Gemini** key (native video understanding). OpenRouter multimodal models also work; plain OpenAI chat keys cannot watch video.
1560
+
1561
+ - `POST /api/v1/primitives/brainstorm/product_placement`
1562
+ - Body: `{ "tracer": "...", "payload": { "source_video_url": "...", "offer_description": "...", "count"?: 8, "provider"?: "gemini" }, "webhook_url"?: "..." }`
1563
+ - `source_video_url` (required, URL) — the exact durable/public video to analyze. Aliases accepted: `video_url`, `source_url`, `url`. For a fork's composition, call `GET /api/v1/compositions/:forkId/remove-video-captions` first and pass the correct URL — the **original video** (`original_source_url`) or the **decomposed video** (`mirrored_url`).
1564
+ - `offer_description` (required) — the product/offer to place. Aliases: `offer`, `description`, `details`.
1565
+ - `count` (optional, 3–30, default 8) — number of opportunities; only send when the user asks for a specific number.
1566
+ - Response: standard primitive job. Poll `GET /api/v1/primitives/jobs/:jobId` to completion, then read `result.json` (also surfaced on the job output as `opportunities[]`), where each entry has `timestamp`, `scene`, `placement_type`, `placement_idea`, and `why_it_works`.
1567
+
1568
+ You can also satisfy a quick, conversational product-placement question by reasoning over an attached video directly instead of calling this primitive — use the primitive when you want a durable, structured, billable artifact the customer can save and reuse.
1569
+
1570
+ ```bash
1571
+ curl -X POST "$VIDFARM_BASE/api/v1/primitives/brainstorm/product_placement" \
1572
+ -H "vidfarm-api-key: $VIDFARM_API_KEY" \
1573
+ -H "content-type: application/json" \
1574
+ -d '{
1575
+ "tracer": "product-placement-01",
1576
+ "payload": {
1577
+ "source_video_url": "https://cdn.example.com/source.mp4",
1578
+ "offer_description": "A $29/mo AI meal-planning app for busy parents."
1579
+ }
1580
+ }'
1581
+ ```
1582
+
1583
+ ## Recipe: Find and Fork a Template
1584
+
1585
+ Use this when the user already knows the offer or video goal and needs the best starting template.
1586
+
1587
+ 1. Read `references/core-workflows.md`.
1588
+ 2. Search the catalog with `GET /discover/feed?q=<offer>&limit=20`.
1589
+ 3. Rank candidates using `promotions`, `keywords`, `summary`, viral DNA fit, and whether the template is already decomposed.
1590
+ 4. Recommend the best 3-6, but bias toward one strong default.
1591
+ 5. Fork the winner with `POST /api/v1/compositions`.
1592
+ 6. Open the fork in the editor or hand the `fork_id` into the next workflow.
1593
+
1594
+ Default heuristics:
1595
+
1596
+ - Prefer already-decomposed templates.
1597
+ - Prefer reuse over a from-scratch primitive build.
1598
+ - If search is empty, broaden terms or decompose the newly ingested inspiration first.
1599
+
1600
+ ## Recipe: Re-theme a Template While Preserving the Viral DNA
1601
+
1602
+ Use this when the user wants to keep the format’s feel but replace the subject matter.
1603
+
1604
+ 1. Read `references/editor-workflows.md`.
1605
+ 2. Fetch `video-context.json` and `editor-harness.json` first.
1606
+ 3. State the plan in the three axes vocabulary: scenes, audio, text; SWAP vs REPLACE for each.
1607
+ 4. Preserve the hook structure, cut rhythm, emotional punch, and important scenes flagged by the harness.
1608
+ 5. Source footage in this order:
1609
+ - My Files or existing raws
1610
+ - a raws hunt from a long-form source
1611
+ - AI image generation
1612
+ - AI video generation only with permission
1613
+ 6. Rebuild captions and narration so timing, cadence, and joke structure survive the subject change.
1614
+ 7. Render, verify, then approve and schedule only after the director is happy.
1615
+
1616
+ Failure mode to avoid: flattening the format by swapping words but losing the timing, sound, or payoff beat.
1617
+
1618
+ ## Recipe: Local Pull, Edit, Render, Approve
1619
+
1620
+ Use this when a coding agent is doing the work locally or the user wants a reproducible filesystem loop.
1621
+
1622
+ 1. Read `references/automation-and-local-dev.md`.
1623
+ 2. Run `vidfarm pull <forkId> --dir ./work`.
1624
+ 3. Read `./work/.harness/agent-guide.md` and `./work/.harness/context.json` before editing.
1625
+ 4. Make deterministic edits to `composition.html` and optionally `composition.json`.
1626
+ 5. Validate with `vidfarm lint` or `vidfarm stills` when useful.
1627
+ 6. Render with `vidfarm render <forkId> --dir ./work --wait`.
1628
+ 7. Approve the finished MP4 with `vidfarm approve --video <url> --caption "..."`.
1629
+
1630
+ Prefer this path for batch work, CI-like edits, or when the user wants free local rendering through `vidfarm serve`.
1631
+
1632
+ ## Recipe: Onboard a New Director
1633
+
1634
+ Use this only when the director signals they do not know where to start.
1635
+
1636
+ 1. Read `references/onboarding.md`.
1637
+ 2. Capture product context and save durable notes into the correct My Files folder.
1638
+ 3. Determine awareness stages, persuasive angles, and hooks with the brainstorm primitives.
1639
+ 4. Ask about brand assets, demos, and recurring characters; organize them in My Files.
1640
+ 5. Ask about budget and map it to the cost spectrum before recommending expensive generation.
1641
+ 6. Search for the best matching templates and fork one strong default.
1642
+ 7. Transition into the ordinary template-editing workflow.
1643
+
1644
+ Do not force onboarding on users who already know what they want.