@mevdragon/vidfarm-devcli 0.13.0 → 0.15.0

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 (427) hide show
  1. package/.agents/skills/embedded-captions/CATALOG.md +93 -0
  2. package/.agents/skills/embedded-captions/SKILL.md +286 -0
  3. package/.agents/skills/embedded-captions/assets/brand/CDPR-fankit-terms.txt +35 -0
  4. package/.agents/skills/embedded-captions/assets/brand/cyberpunk-widths.json +115 -0
  5. package/.agents/skills/embedded-captions/assets/fonts/char-widths.json +2211 -0
  6. package/.agents/skills/embedded-captions/assets/strokefonts/HersheyScript1.svg +242 -0
  7. package/.agents/skills/embedded-captions/assets/strokefonts/HersheyScriptMed.svg +242 -0
  8. package/.agents/skills/embedded-captions/dna/README.md +148 -0
  9. package/.agents/skills/embedded-captions/dna/chrome.json +68 -0
  10. package/.agents/skills/embedded-captions/dna/cream.json +69 -0
  11. package/.agents/skills/embedded-captions/dna/documentary.json +62 -0
  12. package/.agents/skills/embedded-captions/dna/editorial.json +70 -0
  13. package/.agents/skills/embedded-captions/dna/glitch.json +82 -0
  14. package/.agents/skills/embedded-captions/dna/ink.json +65 -0
  15. package/.agents/skills/embedded-captions/dna/keynote.json +62 -0
  16. package/.agents/skills/embedded-captions/dna/loud.json +75 -0
  17. package/.agents/skills/embedded-captions/dna/neon.json +58 -0
  18. package/.agents/skills/embedded-captions/dna/velocity.json +89 -0
  19. package/.agents/skills/embedded-captions/modes/cinematic/README.md +48 -0
  20. package/.agents/skills/embedded-captions/modes/cinematic/_archive/champion/spec.md +98 -0
  21. package/.agents/skills/embedded-captions/modes/cinematic/_archive/champion/template.html +188 -0
  22. package/.agents/skills/embedded-captions/modes/cinematic/_archive/memory-wall/spec.md +134 -0
  23. package/.agents/skills/embedded-captions/modes/cinematic/_archive/memory-wall/template.html +179 -0
  24. package/.agents/skills/embedded-captions/modes/cinematic/_archive/portrait-header/spec.md +81 -0
  25. package/.agents/skills/embedded-captions/modes/cinematic/_archive/portrait-header/template.html +170 -0
  26. package/.agents/skills/embedded-captions/modes/cinematic/cinematic-cream/spec.md +16 -0
  27. package/.agents/skills/embedded-captions/modes/cinematic/cinematic-cream/template.html +226 -0
  28. package/.agents/skills/embedded-captions/modes/cinematic/engine.html +355 -0
  29. package/.agents/skills/embedded-captions/modes/standard/_anatomy.md +233 -0
  30. package/.agents/skills/embedded-captions/modes/standard/_motion.md +192 -0
  31. package/.agents/skills/embedded-captions/modes/standard/fonts/build-fonts-css.cjs +97 -0
  32. package/.agents/skills/embedded-captions/references/aesthetic-principles.md +168 -0
  33. package/.agents/skills/embedded-captions/references/anti-patterns.md +188 -0
  34. package/.agents/skills/embedded-captions/references/bespoke-vs-presets.md +178 -0
  35. package/.agents/skills/embedded-captions/references/caption-grouping.md +130 -0
  36. package/.agents/skills/embedded-captions/references/composition-craft.md +522 -0
  37. package/.agents/skills/embedded-captions/references/direction-catalog.md +138 -0
  38. package/.agents/skills/embedded-captions/references/example-renders/champion.html +371 -0
  39. package/.agents/skills/embedded-captions/references/example-renders/memory-wall.html +334 -0
  40. package/.agents/skills/embedded-captions/references/failure-modes.md +145 -0
  41. package/.agents/skills/embedded-captions/references/layout-heuristics.md +238 -0
  42. package/.agents/skills/embedded-captions/references/motion-vocabulary.md +148 -0
  43. package/.agents/skills/embedded-captions/references/rail.md +68 -0
  44. package/.agents/skills/embedded-captions/references/reference-bar.md +47 -0
  45. package/.agents/skills/embedded-captions/references/scene-types.md +94 -0
  46. package/.agents/skills/embedded-captions/references/test-set.md +66 -0
  47. package/.agents/skills/embedded-captions/references/typographic-moves.md +266 -0
  48. package/.agents/skills/embedded-captions/references/typography-presets.md +63 -0
  49. package/.agents/skills/embedded-captions/scripts/audio-envelope.cjs +95 -0
  50. package/.agents/skills/embedded-captions/scripts/check-occlusion.cjs +250 -0
  51. package/.agents/skills/embedded-captions/scripts/check-overflow.cjs +194 -0
  52. package/.agents/skills/embedded-captions/scripts/check-rail-climax.cjs +233 -0
  53. package/.agents/skills/embedded-captions/scripts/check-timing.cjs +173 -0
  54. package/.agents/skills/embedded-captions/scripts/fill-timings.cjs +124 -0
  55. package/.agents/skills/embedded-captions/scripts/fit-fonts.cjs +158 -0
  56. package/.agents/skills/embedded-captions/scripts/gen-stroke-path.py +46 -0
  57. package/.agents/skills/embedded-captions/scripts/inject-fonts.cjs +169 -0
  58. package/.agents/skills/embedded-captions/scripts/lib-dna.cjs +201 -0
  59. package/.agents/skills/embedded-captions/scripts/make-cinematic.cjs +1205 -0
  60. package/.agents/skills/embedded-captions/scripts/make-composition.cjs +404 -0
  61. package/.agents/skills/embedded-captions/scripts/make-theme.cjs +8781 -0
  62. package/.agents/skills/embedded-captions/scripts/matte.cjs +294 -0
  63. package/.agents/skills/embedded-captions/scripts/measure-layout.cjs +293 -0
  64. package/.agents/skills/embedded-captions/scripts/prepare.sh +39 -0
  65. package/.agents/skills/embedded-captions/scripts/preview-frames.cjs +268 -0
  66. package/.agents/skills/embedded-captions/scripts/render-and-composite.sh +509 -0
  67. package/.agents/skills/embedded-captions/scripts/render-theme.sh +34 -0
  68. package/.agents/skills/embedded-captions/scripts/safe-zones.cjs +813 -0
  69. package/.agents/skills/embedded-captions/scripts/transcribe.cjs +349 -0
  70. package/.agents/skills/embedded-captions/themes/PORTING.md +49 -0
  71. package/.agents/skills/embedded-captions/themes/README.md +244 -0
  72. package/.agents/skills/embedded-captions/themes/anchor.json +27 -0
  73. package/.agents/skills/embedded-captions/themes/arcade.json +51 -0
  74. package/.agents/skills/embedded-captions/themes/aurora.json +41 -0
  75. package/.agents/skills/embedded-captions/themes/biolume.json +35 -0
  76. package/.agents/skills/embedded-captions/themes/brush.json +74 -0
  77. package/.agents/skills/embedded-captions/themes/chalkboard.json +52 -0
  78. package/.agents/skills/embedded-captions/themes/dossier.json +56 -0
  79. package/.agents/skills/embedded-captions/themes/graffiti.json +62 -0
  80. package/.agents/skills/embedded-captions/themes/hologram.json +41 -0
  81. package/.agents/skills/embedded-captions/themes/inkwater.json +43 -0
  82. package/.agents/skills/embedded-captions/themes/laser.json +46 -0
  83. package/.agents/skills/embedded-captions/themes/lastpage.json +33 -0
  84. package/.agents/skills/embedded-captions/themes/neonsign.json +53 -0
  85. package/.agents/skills/embedded-captions/themes/nightcity.json +56 -0
  86. package/.agents/skills/embedded-captions/themes/ordnance.json +54 -0
  87. package/.agents/skills/embedded-captions/themes/papercut.json +37 -0
  88. package/.agents/skills/embedded-captions/themes/popup.json +46 -0
  89. package/.agents/skills/embedded-captions/themes/ransom.json +62 -0
  90. package/.agents/skills/embedded-captions/themes/scoreboard.json +54 -0
  91. package/.agents/skills/embedded-captions/themes/spectrum.json +46 -0
  92. package/.agents/skills/embedded-captions/themes/stardust.json +29 -0
  93. package/.agents/skills/embedded-captions/themes/stomp.json +43 -0
  94. package/.agents/skills/embedded-captions/themes/terminal.json +49 -0
  95. package/.agents/skills/embedded-captions/themes/thunder.json +44 -0
  96. package/.agents/skills/embedded-captions/themes/transit.json +44 -0
  97. package/.agents/skills/embedded-captions/themes/vhs.json +47 -0
  98. package/.agents/skills/faceless-explainer/SKILL.md +209 -0
  99. package/.agents/skills/faceless-explainer/references/cut-catalog.md +215 -0
  100. package/.agents/skills/faceless-explainer/references/motion-language.md +156 -0
  101. package/.agents/skills/faceless-explainer/references/story-design.md +248 -0
  102. package/.agents/skills/faceless-explainer/references/visual-design.md +146 -0
  103. package/.agents/skills/faceless-explainer/scripts/assemble-index.mjs +570 -0
  104. package/.agents/skills/faceless-explainer/scripts/audio.mjs +260 -0
  105. package/.agents/skills/faceless-explainer/scripts/build-frame.mjs +536 -0
  106. package/.agents/skills/faceless-explainer/scripts/captions.mjs +508 -0
  107. package/.agents/skills/faceless-explainer/scripts/lib/assets.mjs +55 -0
  108. package/.agents/skills/faceless-explainer/scripts/lib/dimensions.mjs +45 -0
  109. package/.agents/skills/faceless-explainer/scripts/lib/storyboard.mjs +249 -0
  110. package/.agents/skills/faceless-explainer/scripts/lib/tokens.mjs +204 -0
  111. package/.agents/skills/faceless-explainer/scripts/lib/transition-registry.mjs +38 -0
  112. package/.agents/skills/faceless-explainer/scripts/lib/transitions.json +71 -0
  113. package/.agents/skills/faceless-explainer/scripts/transitions.mjs +320 -0
  114. package/.agents/skills/faceless-explainer/sub-agents/frame-worker.md +75 -0
  115. package/.agents/skills/flockposter/LICENSE +17 -0
  116. package/.agents/skills/flockposter/README.md +72 -0
  117. package/.agents/skills/flockposter/SKILL.md +562 -0
  118. package/.agents/skills/flockposter/examples/cli-instagram-reel.sh +21 -0
  119. package/.agents/skills/flockposter/examples/cli-multi-platform.json +69 -0
  120. package/.agents/skills/flockposter/examples/cli-tiktok-upload-only.sh +22 -0
  121. package/.agents/skills/flockposter/examples/cli-x-thread.sh +17 -0
  122. package/.agents/skills/flockposter/examples/cli-youtube-short.sh +21 -0
  123. package/.agents/skills/flockposter/examples/public-api-instagram-reel.json +28 -0
  124. package/.agents/skills/general-video/SKILL.md +143 -0
  125. package/.agents/skills/hyperframes/SKILL.md +160 -0
  126. package/.agents/skills/hyperframes-animation/SKILL.md +84 -0
  127. package/.agents/skills/hyperframes-animation/adapters/animate-text.md +64 -0
  128. package/.agents/skills/hyperframes-animation/adapters/animejs.md +114 -0
  129. package/.agents/skills/hyperframes-animation/adapters/css-animations.md +143 -0
  130. package/.agents/skills/hyperframes-animation/adapters/gsap-easing-and-stagger.md +118 -0
  131. package/.agents/skills/hyperframes-animation/adapters/gsap-timeline-and-labels.md +96 -0
  132. package/.agents/skills/hyperframes-animation/adapters/gsap-transforms-and-perf.md +96 -0
  133. package/.agents/skills/hyperframes-animation/adapters/gsap.md +105 -0
  134. package/.agents/skills/hyperframes-animation/adapters/html-in-canvas-patterns.md +507 -0
  135. package/.agents/skills/hyperframes-animation/adapters/lottie.md +117 -0
  136. package/.agents/skills/hyperframes-animation/adapters/three.md +131 -0
  137. package/.agents/skills/hyperframes-animation/adapters/typegpu.md +178 -0
  138. package/.agents/skills/hyperframes-animation/adapters/waapi.md +101 -0
  139. package/.agents/skills/hyperframes-animation/blueprints/comparison-split.md +27 -0
  140. package/.agents/skills/hyperframes-animation/blueprints/constellation-hub.md +44 -0
  141. package/.agents/skills/hyperframes-animation/blueprints/cta-morph-press.md +28 -0
  142. package/.agents/skills/hyperframes-animation/blueprints/cursor-ui-demo.md +57 -0
  143. package/.agents/skills/hyperframes-animation/blueprints/dataviz-countup.md +46 -0
  144. package/.agents/skills/hyperframes-animation/blueprints/device-surface-showcase.md +53 -0
  145. package/.agents/skills/hyperframes-animation/blueprints/grid-card-assemble.md +66 -0
  146. package/.agents/skills/hyperframes-animation/blueprints/kinetic-type-beats.md +78 -0
  147. package/.agents/skills/hyperframes-animation/blueprints/logo-assemble-lockup.md +69 -0
  148. package/.agents/skills/hyperframes-animation/blueprints/overwhelm-surround.md +28 -0
  149. package/.agents/skills/hyperframes-animation/blueprints/spatial-pan-stations.md +37 -0
  150. package/.agents/skills/hyperframes-animation/blueprints/ticker-takeover.md +29 -0
  151. package/.agents/skills/hyperframes-animation/blueprints/titlecard-reveal.md +42 -0
  152. package/.agents/skills/hyperframes-animation/blueprints/typewriter-reveal.md +51 -0
  153. package/.agents/skills/hyperframes-animation/blueprints/video-text-pivot.md +30 -0
  154. package/.agents/skills/hyperframes-animation/blueprints-index.md +146 -0
  155. package/.agents/skills/hyperframes-animation/examples/assets/avatars/02.avif +0 -0
  156. package/.agents/skills/hyperframes-animation/examples/assets/brands/github.avif +0 -0
  157. package/.agents/skills/hyperframes-animation/examples/assets/brands/nvidia.avif +0 -0
  158. package/.agents/skills/hyperframes-animation/examples/assets/brands/visa.avif +0 -0
  159. package/.agents/skills/hyperframes-animation/examples/assets/brands/zoominfo.avif +0 -0
  160. package/.agents/skills/hyperframes-animation/examples/brand-reveal-assemble-zoom.html +382 -0
  161. package/.agents/skills/hyperframes-animation/examples/comparison-split-cards.html +649 -0
  162. package/.agents/skills/hyperframes-animation/examples/concept-demo-decode-pan.html +520 -0
  163. package/.agents/skills/hyperframes-animation/examples/cta-morph-press.html +468 -0
  164. package/.agents/skills/hyperframes-animation/examples/cta-orbit-collapse.html +1298 -0
  165. package/.agents/skills/hyperframes-animation/examples/demo-page-scroll-spotlight.html +759 -0
  166. package/.agents/skills/hyperframes-animation/examples/hook-counter-burst.html +729 -0
  167. package/.agents/skills/hyperframes-animation/examples/messaging-multi-phrase.html +352 -0
  168. package/.agents/skills/hyperframes-animation/examples/metric-video-text-pivot.html +779 -0
  169. package/.agents/skills/hyperframes-animation/examples/problem-mockup-overwhelm.html +1374 -0
  170. package/.agents/skills/hyperframes-animation/examples/proof-logo-chain.html +861 -0
  171. package/.agents/skills/hyperframes-animation/examples/takeover-ticker-displace.html +347 -0
  172. package/.agents/skills/hyperframes-animation/examples/workflow-approve-press.html +606 -0
  173. package/.agents/skills/hyperframes-animation/rules/3d-page-scroll.md +227 -0
  174. package/.agents/skills/hyperframes-animation/rules/3d-text-depth-layers.md +297 -0
  175. package/.agents/skills/hyperframes-animation/rules/ai-tracking-box.md +382 -0
  176. package/.agents/skills/hyperframes-animation/rules/ambient-glow-bloom.md +305 -0
  177. package/.agents/skills/hyperframes-animation/rules/asr-keyword-glow.md +286 -0
  178. package/.agents/skills/hyperframes-animation/rules/avatar-cloud-network.md +371 -0
  179. package/.agents/skills/hyperframes-animation/rules/camera-cursor-tracking.md +246 -0
  180. package/.agents/skills/hyperframes-animation/rules/card-morph-anchor.md +267 -0
  181. package/.agents/skills/hyperframes-animation/rules/center-outward-expansion.md +227 -0
  182. package/.agents/skills/hyperframes-animation/rules/context-sensitive-cursor.md +257 -0
  183. package/.agents/skills/hyperframes-animation/rules/coordinate-target-zoom.md +332 -0
  184. package/.agents/skills/hyperframes-animation/rules/counting-dynamic-scale.md +283 -0
  185. package/.agents/skills/hyperframes-animation/rules/css-marker-patterns.md +373 -0
  186. package/.agents/skills/hyperframes-animation/rules/cursor-click-ripple.md +262 -0
  187. package/.agents/skills/hyperframes-animation/rules/depth-of-field-blur.md +313 -0
  188. package/.agents/skills/hyperframes-animation/rules/depth-scatter-assemble.md +303 -0
  189. package/.agents/skills/hyperframes-animation/rules/discrete-text-sequence.md +273 -0
  190. package/.agents/skills/hyperframes-animation/rules/dynamic-content-sequencing.md +307 -0
  191. package/.agents/skills/hyperframes-animation/rules/gsap-effects.md +299 -0
  192. package/.agents/skills/hyperframes-animation/rules/hacker-flip-3d.md +223 -0
  193. package/.agents/skills/hyperframes-animation/rules/kinetic-beat-slam.md +183 -0
  194. package/.agents/skills/hyperframes-animation/rules/motion-blur-streak.md +328 -0
  195. package/.agents/skills/hyperframes-animation/rules/multi-phase-camera.md +273 -0
  196. package/.agents/skills/hyperframes-animation/rules/orbit-3d-entry.md +301 -0
  197. package/.agents/skills/hyperframes-animation/rules/physics-press-reaction.md +350 -0
  198. package/.agents/skills/hyperframes-animation/rules/press-release-spring.md +296 -0
  199. package/.agents/skills/hyperframes-animation/rules/reactive-displacement.md +277 -0
  200. package/.agents/skills/hyperframes-animation/rules/scale-swap-transition.md +298 -0
  201. package/.agents/skills/hyperframes-animation/rules/sine-wave-loop.md +278 -0
  202. package/.agents/skills/hyperframes-animation/rules/split-tilt-cards.md +277 -0
  203. package/.agents/skills/hyperframes-animation/rules/spring-pop-entrance.md +273 -0
  204. package/.agents/skills/hyperframes-animation/rules/stat-bars-and-fills.md +156 -0
  205. package/.agents/skills/hyperframes-animation/rules/svg-icon-enrichment.md +329 -0
  206. package/.agents/skills/hyperframes-animation/rules/svg-path-draw.md +274 -0
  207. package/.agents/skills/hyperframes-animation/rules/vertical-spring-ticker.md +239 -0
  208. package/.agents/skills/hyperframes-animation/rules/viewport-change.md +349 -0
  209. package/.agents/skills/hyperframes-animation/rules-index.md +86 -0
  210. package/.agents/skills/hyperframes-animation/scripts/animation-map.mjs +606 -0
  211. package/.agents/skills/hyperframes-animation/scripts/package-loader.mjs +288 -0
  212. package/.agents/skills/hyperframes-animation/scripts/package-loader.test.mjs +62 -0
  213. package/.agents/skills/hyperframes-animation/techniques.md +507 -0
  214. package/.agents/skills/hyperframes-animation/transitions/TRANSITION-REGISTRY.md +167 -0
  215. package/.agents/skills/hyperframes-animation/transitions/catalog.md +127 -0
  216. package/.agents/skills/hyperframes-animation/transitions/css-3d.md +12 -0
  217. package/.agents/skills/hyperframes-animation/transitions/css-blur.md +51 -0
  218. package/.agents/skills/hyperframes-animation/transitions/css-cover.md +43 -0
  219. package/.agents/skills/hyperframes-animation/transitions/css-destruction.md +95 -0
  220. package/.agents/skills/hyperframes-animation/transitions/css-dissolve.md +66 -0
  221. package/.agents/skills/hyperframes-animation/transitions/css-distortion.md +45 -0
  222. package/.agents/skills/hyperframes-animation/transitions/css-grid.md +10 -0
  223. package/.agents/skills/hyperframes-animation/transitions/css-light.md +49 -0
  224. package/.agents/skills/hyperframes-animation/transitions/css-mechanical.md +30 -0
  225. package/.agents/skills/hyperframes-animation/transitions/css-other.md +25 -0
  226. package/.agents/skills/hyperframes-animation/transitions/css-push.md +41 -0
  227. package/.agents/skills/hyperframes-animation/transitions/css-radial.md +37 -0
  228. package/.agents/skills/hyperframes-animation/transitions/css-scale.md +24 -0
  229. package/.agents/skills/hyperframes-animation/transitions/overview.md +153 -0
  230. package/.agents/skills/hyperframes-cli/SKILL.md +113 -0
  231. package/.agents/skills/hyperframes-cli/references/doctor-browser.md +45 -0
  232. package/.agents/skills/hyperframes-cli/references/init-and-scaffold.md +55 -0
  233. package/.agents/skills/hyperframes-cli/references/lambda.md +132 -0
  234. package/.agents/skills/hyperframes-cli/references/lint-validate-inspect.md +121 -0
  235. package/.agents/skills/hyperframes-cli/references/preview-render.md +149 -0
  236. package/.agents/skills/hyperframes-cli/references/upgrade-info-misc.md +75 -0
  237. package/.agents/skills/hyperframes-core/SKILL.md +78 -0
  238. package/.agents/skills/hyperframes-core/references/composition-patterns.md +263 -0
  239. package/.agents/skills/hyperframes-core/references/data-attributes.md +70 -0
  240. package/.agents/skills/hyperframes-core/references/determinism-rules.md +68 -0
  241. package/.agents/skills/hyperframes-core/references/full-screen-motion.md +62 -0
  242. package/.agents/skills/hyperframes-core/references/minimal-composition.md +66 -0
  243. package/.agents/skills/hyperframes-core/references/script-format.md +49 -0
  244. package/.agents/skills/hyperframes-core/references/storyboard-format.md +95 -0
  245. package/.agents/skills/hyperframes-core/references/sub-compositions.md +237 -0
  246. package/.agents/skills/hyperframes-core/references/subagent-dispatch.md +41 -0
  247. package/.agents/skills/hyperframes-core/references/tailwind.md +125 -0
  248. package/.agents/skills/hyperframes-core/references/tracks-and-clips.md +76 -0
  249. package/.agents/skills/hyperframes-core/references/variables-and-media.md +90 -0
  250. package/.agents/skills/hyperframes-creative/SKILL.md +70 -0
  251. package/.agents/skills/hyperframes-creative/frame-presets/biennale-yellow/FRAME.md +289 -0
  252. package/.agents/skills/hyperframes-creative/frame-presets/biennale-yellow/caption-skin.html +221 -0
  253. package/.agents/skills/hyperframes-creative/frame-presets/biennale-yellow/frame-showcase.html +1214 -0
  254. package/.agents/skills/hyperframes-creative/frame-presets/blockframe/FRAME.md +275 -0
  255. package/.agents/skills/hyperframes-creative/frame-presets/blockframe/caption-skin.html +224 -0
  256. package/.agents/skills/hyperframes-creative/frame-presets/blockframe/frame-showcase.html +1347 -0
  257. package/.agents/skills/hyperframes-creative/frame-presets/blue-professional/FRAME.md +307 -0
  258. package/.agents/skills/hyperframes-creative/frame-presets/blue-professional/caption-skin.html +223 -0
  259. package/.agents/skills/hyperframes-creative/frame-presets/blue-professional/frame-showcase.html +1372 -0
  260. package/.agents/skills/hyperframes-creative/frame-presets/bold-poster/FRAME.md +275 -0
  261. package/.agents/skills/hyperframes-creative/frame-presets/bold-poster/caption-skin.html +230 -0
  262. package/.agents/skills/hyperframes-creative/frame-presets/bold-poster/frame-showcase.html +1140 -0
  263. package/.agents/skills/hyperframes-creative/frame-presets/broadside/FRAME.md +284 -0
  264. package/.agents/skills/hyperframes-creative/frame-presets/broadside/caption-skin.html +225 -0
  265. package/.agents/skills/hyperframes-creative/frame-presets/broadside/frame-showcase.html +1166 -0
  266. package/.agents/skills/hyperframes-creative/frame-presets/capsule/FRAME.md +284 -0
  267. package/.agents/skills/hyperframes-creative/frame-presets/capsule/caption-skin.html +224 -0
  268. package/.agents/skills/hyperframes-creative/frame-presets/capsule/frame-showcase.html +1465 -0
  269. package/.agents/skills/hyperframes-creative/frame-presets/cartesian/FRAME.md +278 -0
  270. package/.agents/skills/hyperframes-creative/frame-presets/cartesian/caption-skin.html +222 -0
  271. package/.agents/skills/hyperframes-creative/frame-presets/cartesian/frame-showcase.html +1263 -0
  272. package/.agents/skills/hyperframes-creative/frame-presets/claude/FRAME.md +277 -0
  273. package/.agents/skills/hyperframes-creative/frame-presets/claude/caption-skin.html +224 -0
  274. package/.agents/skills/hyperframes-creative/frame-presets/claude/frame-showcase.html +1439 -0
  275. package/.agents/skills/hyperframes-creative/frame-presets/cobalt-grid/FRAME.md +265 -0
  276. package/.agents/skills/hyperframes-creative/frame-presets/cobalt-grid/caption-skin.html +238 -0
  277. package/.agents/skills/hyperframes-creative/frame-presets/cobalt-grid/frame-showcase.html +1468 -0
  278. package/.agents/skills/hyperframes-creative/frame-presets/coral/FRAME.md +314 -0
  279. package/.agents/skills/hyperframes-creative/frame-presets/coral/caption-skin.html +226 -0
  280. package/.agents/skills/hyperframes-creative/frame-presets/coral/frame-showcase.html +1268 -0
  281. package/.agents/skills/hyperframes-creative/frame-presets/creative-mode/FRAME.md +333 -0
  282. package/.agents/skills/hyperframes-creative/frame-presets/creative-mode/caption-skin.html +227 -0
  283. package/.agents/skills/hyperframes-creative/frame-presets/creative-mode/frame-showcase.html +1330 -0
  284. package/.agents/skills/hyperframes-creative/frame-presets/daisy-days/FRAME.md +274 -0
  285. package/.agents/skills/hyperframes-creative/frame-presets/daisy-days/caption-skin.html +230 -0
  286. package/.agents/skills/hyperframes-creative/frame-presets/daisy-days/frame-showcase.html +1444 -0
  287. package/.agents/skills/hyperframes-creative/frame-presets/editorial-forest/FRAME.md +265 -0
  288. package/.agents/skills/hyperframes-creative/frame-presets/editorial-forest/caption-skin.html +223 -0
  289. package/.agents/skills/hyperframes-creative/frame-presets/editorial-forest/frame-showcase.html +1273 -0
  290. package/.agents/skills/hyperframes-creative/palettes/bold-energetic.md +14 -0
  291. package/.agents/skills/hyperframes-creative/palettes/clean-corporate.md +14 -0
  292. package/.agents/skills/hyperframes-creative/palettes/dark-premium.md +14 -0
  293. package/.agents/skills/hyperframes-creative/palettes/jewel-rich.md +14 -0
  294. package/.agents/skills/hyperframes-creative/palettes/monochrome.md +14 -0
  295. package/.agents/skills/hyperframes-creative/palettes/nature-earth.md +14 -0
  296. package/.agents/skills/hyperframes-creative/palettes/neon-electric.md +14 -0
  297. package/.agents/skills/hyperframes-creative/palettes/pastel-soft.md +14 -0
  298. package/.agents/skills/hyperframes-creative/palettes/warm-editorial.md +14 -0
  299. package/.agents/skills/hyperframes-creative/references/audio-reactive.md +76 -0
  300. package/.agents/skills/hyperframes-creative/references/beat-direction.md +164 -0
  301. package/.agents/skills/hyperframes-creative/references/composition-patterns.md +199 -0
  302. package/.agents/skills/hyperframes-creative/references/data-in-motion.md +19 -0
  303. package/.agents/skills/hyperframes-creative/references/design-adherence.md +19 -0
  304. package/.agents/skills/hyperframes-creative/references/design-picker.md +123 -0
  305. package/.agents/skills/hyperframes-creative/references/design-spec.md +55 -0
  306. package/.agents/skills/hyperframes-creative/references/house-style.md +73 -0
  307. package/.agents/skills/hyperframes-creative/references/motion-principles.md +150 -0
  308. package/.agents/skills/hyperframes-creative/references/narration.md +92 -0
  309. package/.agents/skills/hyperframes-creative/references/prompt-expansion.md +68 -0
  310. package/.agents/skills/hyperframes-creative/references/typography.md +218 -0
  311. package/.agents/skills/hyperframes-creative/references/video-composition.md +63 -0
  312. package/.agents/skills/hyperframes-creative/references/visual-styles.md +457 -0
  313. package/.agents/skills/hyperframes-creative/scripts/contrast-report.mjs +353 -0
  314. package/.agents/skills/hyperframes-creative/scripts/extract-audio-data.py +188 -0
  315. package/.agents/skills/hyperframes-creative/scripts/package-loader.mjs +288 -0
  316. package/.agents/skills/hyperframes-creative/scripts/package-loader.test.mjs +62 -0
  317. package/.agents/skills/hyperframes-creative/templates/design-picker.html +1432 -0
  318. package/.agents/skills/hyperframes-keyframes/SKILL.md +237 -0
  319. package/.agents/skills/hyperframes-keyframes/agents/openai.yaml +4 -0
  320. package/.agents/skills/hyperframes-keyframes/references/keyframe-patterns.md +106 -0
  321. package/.agents/skills/motion-graphics/SKILL.md +172 -0
  322. package/.agents/skills/motion-graphics/agents/builder.md +40 -0
  323. package/.agents/skills/motion-graphics/agents/director.md +53 -0
  324. package/.agents/skills/motion-graphics/agents/finalize.md +17 -0
  325. package/.agents/skills/motion-graphics/catalog-map.md +40 -0
  326. package/.agents/skills/motion-graphics/categories/asset-fusion/module.md +37 -0
  327. package/.agents/skills/motion-graphics/categories/charts/module.md +21 -0
  328. package/.agents/skills/motion-graphics/categories/kinetic-type/module.md +19 -0
  329. package/.agents/skills/motion-graphics/categories/logo-reveal/module.md +17 -0
  330. package/.agents/skills/motion-graphics/categories/lower-thirds/module.md +16 -0
  331. package/.agents/skills/motion-graphics/categories/maps/bake-basemap.mjs +273 -0
  332. package/.agents/skills/motion-graphics/categories/maps/module.md +61 -0
  333. package/.agents/skills/motion-graphics/categories/news/module.md +56 -0
  334. package/.agents/skills/motion-graphics/categories/stat/module.md +21 -0
  335. package/.agents/skills/motion-graphics/categories/tweet/module.md +16 -0
  336. package/.agents/skills/motion-graphics/categories/webpage/module.md +21 -0
  337. package/.agents/skills/motion-graphics/grounding/PROTOCOL.md +64 -0
  338. package/.agents/skills/motion-graphics/grounding/locate.mjs +227 -0
  339. package/.agents/skills/motion-graphics/phases/source/guide.md +21 -0
  340. package/.agents/skills/motion-graphics/references/builder-contract.md +38 -0
  341. package/.agents/skills/motion-graphics/references/motion-vocabulary.md +43 -0
  342. package/.agents/skills/motion-graphics/references/shot-plan-ir.md +54 -0
  343. package/.agents/skills/motion-graphics/samples/asset-fusion/_ref-circle-highlight.html +306 -0
  344. package/.agents/skills/product-launch-video/SKILL.md +208 -0
  345. package/.agents/skills/product-launch-video/references/cut-catalog.md +220 -0
  346. package/.agents/skills/product-launch-video/references/motion-language.md +156 -0
  347. package/.agents/skills/product-launch-video/references/story-design.md +373 -0
  348. package/.agents/skills/product-launch-video/references/visual-design.md +126 -0
  349. package/.agents/skills/product-launch-video/scripts/assemble-index.mjs +570 -0
  350. package/.agents/skills/product-launch-video/scripts/audio.mjs +260 -0
  351. package/.agents/skills/product-launch-video/scripts/build-frame.mjs +536 -0
  352. package/.agents/skills/product-launch-video/scripts/captions.mjs +508 -0
  353. package/.agents/skills/product-launch-video/scripts/lib/assets.mjs +55 -0
  354. package/.agents/skills/product-launch-video/scripts/lib/dimensions.mjs +45 -0
  355. package/.agents/skills/product-launch-video/scripts/lib/pad-frame-duration.mjs +36 -0
  356. package/.agents/skills/product-launch-video/scripts/lib/pad-frame-duration.test.mjs +76 -0
  357. package/.agents/skills/product-launch-video/scripts/lib/storyboard.mjs +249 -0
  358. package/.agents/skills/product-launch-video/scripts/lib/tokens.mjs +204 -0
  359. package/.agents/skills/product-launch-video/scripts/lib/transition-registry.mjs +38 -0
  360. package/.agents/skills/product-launch-video/scripts/lib/transitions.json +71 -0
  361. package/.agents/skills/product-launch-video/scripts/stage-assets.mjs +39 -0
  362. package/.agents/skills/product-launch-video/scripts/transitions.mjs +327 -0
  363. package/.agents/skills/product-launch-video/sub-agents/frame-worker.md +75 -0
  364. package/.agents/skills/slideshow/SKILL.md +550 -0
  365. package/.agents/skills/slideshow/references/standalone-harness.md +1020 -0
  366. package/.agents/skills/talking-head-recut/NOTICE.md +39 -0
  367. package/.agents/skills/talking-head-recut/SKILL.md +1195 -0
  368. package/.agents/skills/talking-head-recut/assets/vendor/gsap.min.js +10 -0
  369. package/.agents/skills/talking-head-recut/references/DESIGN_INDEX.md +145 -0
  370. package/.agents/skills/talking-head-recut/references/frames/clean.html +92 -0
  371. package/.agents/skills/talking-head-recut/references/frames/hairline.html +171 -0
  372. package/.agents/skills/talking-head-recut/references/frames/polaroid.html +131 -0
  373. package/.agents/skills/talking-head-recut/references/layouts/overlay.html +164 -0
  374. package/.agents/skills/talking-head-recut/references/layouts/pip.html +143 -0
  375. package/.agents/skills/talking-head-recut/references/layouts/split.html +121 -0
  376. package/.agents/skills/talking-head-recut/references/layouts/stack.html +109 -0
  377. package/.agents/skills/talking-head-recut/references/styles/academic.html +172 -0
  378. package/.agents/skills/talking-head-recut/references/styles/audit.html +170 -0
  379. package/.agents/skills/talking-head-recut/references/styles/editorial.html +189 -0
  380. package/.agents/skills/talking-head-recut/references/styles/geom.html +181 -0
  381. package/.agents/skills/talking-head-recut/references/styles/minimal.html +130 -0
  382. package/.agents/skills/talking-head-recut/references/styles/spotlight.html +142 -0
  383. package/.agents/skills/talking-head-recut/references/styles/swiss.html +188 -0
  384. package/.agents/skills/talking-head-recut/references/styles/terminal.html +196 -0
  385. package/.agents/skills/talking-head-recut/references/styles/whiteboard.html +170 -0
  386. package/.agents/skills/talking-head-recut/references/styles/xhs.html +206 -0
  387. package/.agents/skills/vidfarm-media/SKILL.md +85 -0
  388. package/.agents/skills/vidfarm-media/references/tts.md +54 -0
  389. package/.agents/skills/vidfarm-media/scripts/audio.mjs +628 -0
  390. package/.agents/skills/vidfarm-media/scripts/wait-bgm.mjs +31 -0
  391. package/.agents/skills/website-to-video/SKILL.md +145 -0
  392. package/.agents/skills/website-to-video/assets/sfx/CREDITS.md +35 -0
  393. package/.agents/skills/website-to-video/assets/sfx/manifest.json +97 -0
  394. package/.agents/skills/website-to-video/references/beat-builder-guide.md +263 -0
  395. package/.agents/skills/website-to-video/references/capabilities.md +713 -0
  396. package/.agents/skills/website-to-video/references/step-0-capture.md +55 -0
  397. package/.agents/skills/website-to-video/references/step-1-design.md +333 -0
  398. package/.agents/skills/website-to-video/references/step-2-brief.md +178 -0
  399. package/.agents/skills/website-to-video/references/step-3-storyboard.md +572 -0
  400. package/.agents/skills/website-to-video/references/step-4-vo.md +225 -0
  401. package/.agents/skills/website-to-video/references/step-5-build.md +483 -0
  402. package/.agents/skills/website-to-video/references/step-6-validate.md +341 -0
  403. package/.agents/skills/website-to-video/scripts/w2h-verify.mjs +759 -0
  404. package/SKILL.director.md +60 -4
  405. package/SKILL.platform.md +7 -2
  406. package/demo/dist/app.css +1 -1
  407. package/demo/dist/app.js +485 -86
  408. package/dist/src/account-pages-legacy.js +9 -14
  409. package/dist/src/app.js +659 -159
  410. package/dist/src/cli.js +618 -17
  411. package/dist/src/composition-runtime.js +286 -6
  412. package/dist/src/devcli/composition-edit.js +176 -8
  413. package/dist/src/devcli/doctor.js +192 -0
  414. package/dist/src/devcli/hyperframes-cli.js +118 -0
  415. package/dist/src/devcli/skills.js +476 -0
  416. package/dist/src/devcli/stills.js +249 -0
  417. package/dist/src/devcli/transitions.js +205 -0
  418. package/dist/src/editor-chat.js +260 -4
  419. package/dist/src/frontend/template-editor-chat.js +6 -5
  420. package/dist/src/homepage.js +2 -48
  421. package/dist/src/hyperframes/composition.js +188 -7
  422. package/dist/src/services/composition-lint.js +199 -0
  423. package/dist/src/services/hyperframes.js +127 -28
  424. package/dist/src/services/serverless-records.js +7 -0
  425. package/dist/src/template-editor-shell.js +87 -48
  426. package/package.json +2 -1
  427. package/public/assets/page-runtime-client-app.js +31 -31
package/dist/src/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
- import { copyFileSync, createWriteStream, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
2
+ import { copyFileSync, createWriteStream, existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
3
3
  import path from "node:path";
4
- import { homedir } from "node:os";
4
+ import { homedir, tmpdir } from "node:os";
5
5
  import { randomUUID } from "node:crypto";
6
6
  import { fileURLToPath } from "node:url";
7
7
  import { parseArgs } from "node:util";
@@ -11,8 +11,15 @@ import { pipeline } from "node:stream/promises";
11
11
  import { computeCompositionGaps, insertMediaLayer, inspectComposition, replaceLayerWithMedia } from "./devcli/composition-edit.js";
12
12
  import { runCaptionsCommand } from "./devcli/captions.js";
13
13
  import { runClipsCommand } from "./devcli/clips.js";
14
+ import { runTransitionsCommand } from "./devcli/transitions.js";
14
15
  import { MAX_LOCAL_TRANSCRIBE_AUDIO_BYTES, loadSpeechAudioLocally, localGenerateSpeech, localTranscribeSpeech, resolveLocalSpeechAuth } from "./devcli/speech.js";
15
- import { inferSpeechProviderForVoice } from "./services/speech.js";
16
+ import { buildSrtFromSegments, inferSpeechProviderForVoice } from "./services/speech.js";
17
+ import { formatCompositionLintIssues, lintCompositionHtml } from "./services/composition-lint.js";
18
+ import { resolveFfmpeg } from "./services/clip-curation/ffmpeg.js";
19
+ import { parseHyperframesJson, runHyperframesCommand } from "./devcli/hyperframes-cli.js";
20
+ import { renderCompositionStills } from "./devcli/stills.js";
21
+ import { runDoctorCommand } from "./devcli/doctor.js";
22
+ import { runSkillsCommand } from "./devcli/skills.js";
16
23
  import { initTelemetry, reportCliCrash } from "./devcli/telemetry.js";
17
24
  // vidfarm-devcli — command-line bridge for the Vidfarm video studio. The
18
25
  // `serve` command boots the FULL editor locally (single origin, disk-backed
@@ -148,10 +155,15 @@ Generate AI media and drop it on the timeline (for local coding agents):
148
155
  --ken-burns-intensity <n> Travel/zoom strength 0.04-0.5 (default 0.18)
149
156
  --transition <preset> Scene transition INTO this clip (the previous
150
157
  butt-cut clip is held beneath it automatically):
151
- crossfade|fade-black|slide-left|slide-right|
152
- slide-up|slide-down|wipe-left|wipe-right|wipe-up|
158
+ crossfade|fade-black|fade-white|flash|smoke|blur|
159
+ slide-left|slide-right|slide-up|slide-down|
160
+ whip-left|whip-right|wipe-left|wipe-right|wipe-up|
153
161
  wipe-down|zoom-in|zoom-out|circle-open
154
162
  --transition-duration <s> Transition length in seconds 0.1-2.5 (default 0.5)
163
+ --transition-out <preset> Exit transition OUT of this clip at its end:
164
+ fade|fade-black|fade-white|flash|smoke|blur|
165
+ slide-*|wipe-*|zoom-in|zoom-out|circle-close
166
+ --transition-out-duration <s> Exit length in seconds 0.1-2.5 (default 0.5)
155
167
  remove-video-captions <forkId> Read the two video sources: → GET .../compositions/:forkId/remove-video-captions
156
168
  original vs decomposed (caption-free),
157
169
  non-billing (alias: ghostcut)
@@ -183,6 +195,9 @@ Speech (TTS/STT) — LOCAL-FIRST on your own AI key; --cloud is the explicit bac
183
195
  a voice from the wrong provider family is rejected with a clear error.
184
196
  --out <file> Output audio path (default tts-<id>.mp3|wav)
185
197
  --provider <p> --model <m> --format mp3|wav
198
+ --engine local KEYLESS local Kokoro-82M engine (free, no account) — fixed voice
199
+ presets (af_heart, am_adam, bf_emma, …), no --style. When NO
200
+ provider key is found the default byok engine auto-falls back here.
186
201
  --cloud BACKUP: saved-key platform job → POST /api/v1/primitives/audio/speech (+ poll)
187
202
  stt <file|url> Video or audio → transcript (alias: transcribe). LOCAL: local ffmpeg demux +
188
203
  Returns BOTH formats: the simple subtitle your provider key (gemini labels
@@ -194,6 +209,10 @@ Speech (TTS/STT) — LOCAL-FIRST on your own AI key; --cloud is the explicit bac
194
209
  --language <code> Expected language hint (e.g. en, es)
195
210
  --prompt "<context>" Domain/vocabulary hint for the transcriber
196
211
  --provider <p> --model <m>
212
+ --engine whisper KEYLESS local whisper.cpp engine (free, no account) — WORD-level
213
+ timestamps (words[] in the .json/--json output), no speaker labels.
214
+ When NO provider key is found the default byok engine auto-falls
215
+ back here. --model then picks the whisper model (tiny.en…large-v3).
197
216
  --cloud BACKUP: saved-key platform job → POST /api/v1/primitives/audio/transcribe (+ poll)
198
217
  (a local file uploads to your temp folder first — 30-day TTL)
199
218
 
@@ -207,6 +226,16 @@ Animated captions (word-by-word TikTok/CapCut styles on a pulled/served composit
207
226
  captions style <dir> Restyle existing cues (same text/timings)
208
227
  captions list <dir> Show the current animated caption cues [--json]
209
228
  captions clear <dir> Remove all animated caption layers
229
+
230
+ Scene transitions (between clips on a pulled/served composition — local file write):
231
+ transitions apply <dir> One pass over the whole timeline:
232
+ --preset <p> Junction preset at EVERY cut ("none" clears)
233
+ --duration <s> 0.1-2.5 (default 0.5)
234
+ --intro <p> --outro <p> First clip's entrance / last clip's exit
235
+ transitions set <dir> --layer <key> --in <p> --out <p> Configure one clip
236
+ transitions list <dir> Show transitions on every scene clip [--json]
237
+ transitions clear <dir> Remove all transitions
238
+ (run 'vidfarm transitions' for presets & all flags)
210
239
  versions <forkId> List immutable version snapshots → GET .../compositions/:forkId/versions
211
240
  snapshot <forkId> Save the working state as a new version → POST .../compositions/:forkId/versions
212
241
  render <forkId> Render the fork to MP4 (--wait to poll) → POST .../compositions/:forkId/render
@@ -217,6 +246,37 @@ Animated captions (word-by-word TikTok/CapCut styles on a pulled/served composit
217
246
  clone <forkId> Clone a fork as a new starting point → POST .../compositions/:forkId/clone
218
247
  share-link <forkId> Mint a share-token URL for a fork → POST .../compositions/:forkId/share-links
219
248
 
249
+ Local media engines & toolchain (all local, free, no account — no cloud key needed):
250
+ remove-background <file|url> Cut the background out of a video/image (local ONNX — free, no account)
251
+ → transparent WebM (video) / PNG (image)
252
+ --out <path> Output path .webm|.mov|.png (default <name>.transparent.webm|.png)
253
+ --background-out <path> Also write the inverse background plate (.webm|.mov)
254
+ capture <url> Capture a website: screenshots + assets + (local headless Chrome — free,
255
+ video manifest, for site-to-video work no account)
256
+ --out <dir> Output directory (default ./capture)
257
+ --max-screenshots <n> --timeout <ms> --skip-assets
258
+ beats <audioFile|url> Detect musical beats → timestamps JSON to (local — free, no account)
259
+ sync transitions/captions timing against
260
+ --out <path> Output JSON path (default <audio>.beats.json)
261
+ lint <dir|composition.html> Validate a composition on disk (same (local)
262
+ checks the /editor chat preflights); prints
263
+ errors/warnings, exit 1 on errors [--json]
264
+ stills <dir|composition.html> Render PNG stills of the CURRENT (local in-process render)
265
+ composition — the visual self-check loop
266
+ ("did my edit look right") without a full render
267
+ --at 0,2.5,7 Timestamps (default: midpoint of each scene clip, cap 8)
268
+ --out <dir> Output dir (default <dir>/stills)
269
+ doctor Health-check the local toolchain: node, (local)
270
+ ffmpeg, hyperframes engines, Chrome, API key
271
+ (whoami), provider keys, agent CLI, poisoned
272
+ env, port 3000, installed skills [--json]
273
+ skills <list|add <names…>|update> Install/refresh agent skills into → GET /skill-pack/* (or the
274
+ <root>/.agents/skills/<name> + a relative copies bundled in this repo)
275
+ .claude/skills symlink; skills-lock.json is
276
+ merged, never clobbered [--json]
277
+ --dir <root> Project root (default: walk up from cwd to .agents/skills or .git)
278
+ --host <url> Skill-pack host (default VIDFARM_SKILLS_HOST/VIDFARM_HOST or ${DEFAULT_HOST})
279
+
220
280
  Approve & schedule (publish a finished MP4 as a shareable post):
221
281
  approve Approve media into a preview/share page → POST /api/v1/approved/posts
222
282
  --video <url|file> Primary MP4 (URL, or a local file auto-uploaded to temp/)
@@ -251,15 +311,22 @@ Files (multi-step flows the devcli handles for you):
251
311
  --folder <path> Namescope under a product/offer folder (e.g. acme-skincare)
252
312
  --as <name> Name the saved file (required for --content/--stdin)
253
313
  --content <text> Inline text to save (needs --as); or pipe stdin with --stdin
314
+ --notes <text> Metadata notes (what it is / when to use it) — vector-embedded for search
254
315
  e.g. vidfarm put-file About.md --folder acme-skincare
255
316
  echo "..." | vidfarm put-file --stdin --as About.md --folder acme
256
317
  download <url> [dest] Stream any Vidfarm/media URL to disk
257
318
  files List My Files assets + folders → GET /api/v1/user/me/attachments
258
319
  --folder <path> Only files under this folder (e.g. a product/offer)
320
+ --search <query> Find files by MEANING (keyword + vector over → POST /api/v1/user/me/attachments/search
321
+ name/folder/notes) instead of listing everything
259
322
  get-file <id> [dest] Resolve a My Files id to its URL and download it
260
323
  --print Print text contents (md/txt/csv/json) instead of saving
261
- put-file / get-file / files are the My Files (persistent) read+write set;
324
+ annotate-file <id|name> Set metadata notes on one My Files entry → PATCH /api/v1/user/me/attachments/:id
325
+ --notes <text> The notes to attach (empty string clears); embedded for vector search
326
+ put-file / get-file / files / annotate-file are the My Files (persistent) set;
262
327
  upload is the throwaway temp store for dropping media into a composition.
328
+ Annotate assets you'll want back — esp. character refs (character_sprite_card.png
329
+ + about_character.md) — so 'files --search "the mascot"' finds them later.
263
330
  Local file paths: to avoid uploading assets to Vidfarm S3 at all, reference
264
331
  them straight from disk with 'place --src ./clip.mp4' on a 'serve' box (copied
265
332
  to that box's local disk store, free local render). When you DO need a durable
@@ -458,12 +525,42 @@ async function main() {
458
525
  case "save-file":
459
526
  await runPutFileCommand(rest);
460
527
  return;
528
+ case "annotate-file":
529
+ await runAnnotateFileCommand(rest);
530
+ return;
461
531
  case "clips":
462
532
  await runClipsCommand(rest);
463
533
  return;
464
534
  case "captions":
465
535
  await runCaptionsCommand(rest);
466
536
  return;
537
+ case "transitions":
538
+ await runTransitionsCommand(rest);
539
+ return;
540
+ // Local media engines + toolchain (bundled HyperFrames local commands —
541
+ // free, no account — plus vidfarm-native lint/stills/doctor/skills).
542
+ case "remove-background":
543
+ case "rmbg":
544
+ await runRemoveBackgroundCommand(rest);
545
+ return;
546
+ case "capture":
547
+ await runCaptureCommand(rest);
548
+ return;
549
+ case "beats":
550
+ await runBeatsCommand(rest);
551
+ return;
552
+ case "lint":
553
+ await runLintCommand(rest);
554
+ return;
555
+ case "stills":
556
+ await runStillsCommand(rest);
557
+ return;
558
+ case "doctor":
559
+ await runDoctorCommand(rest);
560
+ return;
561
+ case "skills":
562
+ await runSkillsCommand(rest);
563
+ return;
467
564
  default:
468
565
  if (!command.startsWith("-")) {
469
566
  // Positional template id → default to booting the local editor.
@@ -640,6 +737,10 @@ async function runServeCommand(argv) {
640
737
  process.env.PORT = String(port);
641
738
  if (!process.env.NODE_ENV)
642
739
  process.env.NODE_ENV = "development";
740
+ // Harden any hyperframes tooling the serve runtime (or its children) may
741
+ // shell out to: no telemetry pings, no skill-install prompts.
742
+ process.env.HYPERFRAMES_NO_TELEMETRY ||= "1";
743
+ process.env.HYPERFRAMES_SKIP_SKILLS ||= "1";
643
744
  // Cloud passthrough: only the editor is local — /discover and /library keep
644
745
  // listing the upstream (cloud) catalog, templates seed onto disk on demand,
645
746
  // and the editor's Render button can hand off to the cloud renderer. The
@@ -2026,6 +2127,8 @@ function placeMediaOnDisk(input) {
2026
2127
  kenBurnsIntensity: input.kenBurnsIntensity,
2027
2128
  transition: input.transition,
2028
2129
  transitionDuration: input.transitionDuration,
2130
+ transitionOut: input.transitionOut,
2131
+ transitionOutDuration: input.transitionOutDuration,
2029
2132
  slug: input.slug
2030
2133
  };
2031
2134
  const result = input.replace
@@ -2059,6 +2162,8 @@ async function runGenerateCommand(argv) {
2059
2162
  "ken-burns-intensity": { type: "string" },
2060
2163
  transition: { type: "string" },
2061
2164
  "transition-duration": { type: "string" },
2165
+ "transition-out": { type: "string" },
2166
+ "transition-out-duration": { type: "string" },
2062
2167
  "layer-key": { type: "string" }
2063
2168
  }
2064
2169
  });
@@ -2134,6 +2239,8 @@ async function runGenerateCommand(argv) {
2134
2239
  kenBurnsIntensity: parsed.values["ken-burns-intensity"] ? Number(parsed.values["ken-burns-intensity"]) : undefined,
2135
2240
  transition: parsed.values.transition,
2136
2241
  transitionDuration: parsed.values["transition-duration"] ? Number(parsed.values["transition-duration"]) : undefined,
2242
+ transitionOut: parsed.values["transition-out"],
2243
+ transitionOutDuration: parsed.values["transition-out-duration"] ? Number(parsed.values["transition-out-duration"]) : undefined,
2137
2244
  layerKey: parsed.values["layer-key"]
2138
2245
  });
2139
2246
  if (!ctx.json)
@@ -2183,6 +2290,7 @@ async function runTtsCommand(argv) {
2183
2290
  model: { type: "string" },
2184
2291
  format: { type: "string" },
2185
2292
  out: { type: "string" },
2293
+ engine: { type: "string" },
2186
2294
  cloud: { type: "boolean", default: false },
2187
2295
  "gemini-key": { type: "string" },
2188
2296
  "openai-key": { type: "string" },
@@ -2195,19 +2303,48 @@ async function runTtsCommand(argv) {
2195
2303
  if (!text)
2196
2304
  throw new Error('tts requires text: vidfarm tts "Welcome back to the channel" --style "calm, warm narrator".');
2197
2305
  const style = (parsed.values.instructions ?? parsed.values.style)?.trim() || undefined;
2198
- const rawFormat = String(parsed.values.format ?? "mp3").toLowerCase();
2306
+ const explicitFormat = parsed.values.format?.trim().toLowerCase();
2307
+ const rawFormat = explicitFormat ?? "mp3";
2199
2308
  if (rawFormat !== "mp3" && rawFormat !== "wav")
2200
2309
  throw new Error("tts --format must be mp3 or wav.");
2201
2310
  const format = rawFormat;
2202
2311
  const voice = parsed.values.voice;
2203
2312
  const json = Boolean(parsed.values.json);
2204
- if (!parsed.values.cloud) {
2313
+ // Engine selection: byok (default — the user's own provider key), local
2314
+ // (keyless Kokoro-82M via the bundled hyperframes engine — free, no
2315
+ // account), cloud (the platform primitive job, same as --cloud).
2316
+ const engine = parsed.values.engine?.trim().toLowerCase();
2317
+ if (engine && engine !== "local" && engine !== "byok" && engine !== "cloud") {
2318
+ throw new Error("tts --engine must be local (keyless Kokoro), byok (your provider key — default), or cloud.");
2319
+ }
2320
+ const wantCloud = Boolean(parsed.values.cloud) || engine === "cloud";
2321
+ if (engine === "local") {
2322
+ await runLocalKokoroTts({ text, voice, style, out: parsed.values.out, format: explicitFormat, json });
2323
+ return;
2324
+ }
2325
+ if (!wantCloud) {
2205
2326
  // A Gemini voice preset ("Kore", "Puck", …) should find a Gemini-capable
2206
2327
  // key first — openrouter's default TTS model is Gemini-family too.
2207
- const keyPreference = inferSpeechProviderForVoice(voice) === "gemini" ? ["gemini", "openrouter", "openai"] : ["openai", "gemini", "openrouter"];
2328
+ const voiceProvider = inferSpeechProviderForVoice(voice);
2329
+ const keyPreference = voiceProvider === "gemini" ? ["gemini", "openrouter", "openai"] : ["openai", "gemini", "openrouter"];
2208
2330
  const auth = resolveLocalSpeechAuth(parsed.values, keyPreference, parsed.values.provider);
2209
- if (!auth)
2210
- throw new Error(speechKeyHint("tts"));
2331
+ if (!auth) {
2332
+ // EXPLICIT intent (--provider, or a provider voice preset like "alloy"/
2333
+ // "Kore") must never be silently downgraded to Kokoro — hard-error with
2334
+ // the key hint so the user knows the named provider was not used.
2335
+ const requestedProvider = parsed.values.provider?.trim();
2336
+ if (requestedProvider || voiceProvider) {
2337
+ const wanted = requestedProvider ?? `${voiceProvider} (implied by --voice "${voice}")`;
2338
+ throw new Error(`tts: provider ${wanted} was requested but no matching key is configured. ${speechKeyHint("tts")}`);
2339
+ }
2340
+ // Keyless fallback (no explicit provider/voice): rather than erroring
2341
+ // (speechKeyHint), speak through the local Kokoro engine so agents
2342
+ // always get audio out of the box.
2343
+ if (!json)
2344
+ console.log(`${DIM}No provider key found — using the local Kokoro engine; pass --engine cloud + a key for BYOK voices. (${speechKeyHint("tts")})${RESET}`);
2345
+ await runLocalKokoroTts({ text, voice, style, out: parsed.values.out, format: explicitFormat, json, fallback: true });
2346
+ return;
2347
+ }
2211
2348
  if (!json)
2212
2349
  console.log(`${DIM}Generating speech locally on your ${auth.provider} key…${RESET}`);
2213
2350
  const result = await localGenerateSpeech({
@@ -2277,6 +2414,52 @@ async function runTtsCommand(argv) {
2277
2414
  console.log(`${GREEN}Wrote ${outPath}${RESET} ${DIM}(${mediaUrl})${RESET}`);
2278
2415
  }
2279
2416
  }
2417
+ // Keyless local TTS via the bundled hyperframes engine (Kokoro-82M ONNX —
2418
+ // free, no account, no HeyGen). Used by `--engine local` and as the automatic
2419
+ // fallback when no provider key is configured. Kokoro emits WAV natively; an
2420
+ // mp3 request transcodes through the bundled ffmpeg.
2421
+ async function runLocalKokoroTts(input) {
2422
+ const wantMp3 = input.format === "mp3" || /\.mp3(\?|#|$)/i.test(input.out ?? "");
2423
+ // Kokoro voice ids look like af_heart / am_adam / bf_emma. A provider voice
2424
+ // (openai "alloy", gemini "Kore") passed through the fallback path would
2425
+ // just error — drop it to the default with a note instead.
2426
+ let voice = input.voice?.trim() || undefined;
2427
+ if (voice && !/^[a-z]{2}_[a-z0-9]+$/i.test(voice)) {
2428
+ if (!input.json)
2429
+ console.log(`${DIM}Voice "${voice}" is a provider voice — the local Kokoro engine has its own presets (af_heart, af_nova, am_adam, bf_emma, …). Using the default voice.${RESET}`);
2430
+ voice = undefined;
2431
+ }
2432
+ if (input.style && !input.json) {
2433
+ console.log(`${DIM}Note: --style voice direction is BYOK-only; the local Kokoro engine uses fixed voice presets.${RESET}`);
2434
+ }
2435
+ const outPath = path.resolve(process.cwd(), input.out ?? `tts-${Date.now().toString(36)}.${wantMp3 ? "mp3" : "wav"}`);
2436
+ mkdirSync(path.dirname(outPath), { recursive: true });
2437
+ const wavPath = wantMp3 ? path.join(mkdtempSync(path.join(tmpdir(), "vidfarm-tts-")), "speech.wav") : outPath;
2438
+ if (!input.json)
2439
+ console.log(`${DIM}Generating speech with the local Kokoro-82M engine (free, no account)…${RESET}`);
2440
+ const run = await runHyperframesCommand("tts", [input.text, "-o", wavPath, ...(voice ? ["-v", voice] : [])], { stdio: input.json ? "capture" : "inherit" });
2441
+ if (run.code !== 0 || !existsSync(wavPath)) {
2442
+ const detail = run.stderr.trim().split("\n").pop() ?? "";
2443
+ throw new Error(`local Kokoro tts failed (exit ${run.code})${detail ? `: ${detail}` : " — see the engine output above"}. ` +
2444
+ "First use needs `pip install kokoro-onnx soundfile` and downloads the model (~340MB); or pass a provider key for BYOK tts.");
2445
+ }
2446
+ if (wantMp3) {
2447
+ const ffmpeg = await resolveFfmpeg();
2448
+ const transcode = spawnSync(ffmpeg, ["-y", "-hide_banner", "-loglevel", "error", "-i", wavPath, "-b:a", "128k", outPath], { stdio: "ignore" });
2449
+ rmSync(path.dirname(wavPath), { recursive: true, force: true });
2450
+ if (transcode.status !== 0 || !existsSync(outPath)) {
2451
+ throw new Error("ffmpeg mp3 transcode failed after local TTS — pass --format wav to keep the native WAV.");
2452
+ }
2453
+ }
2454
+ const bytes = safeSize(outPath);
2455
+ if (input.json) {
2456
+ printJson({ ok: true, mode: "local-engine", engine: "kokoro-82m", provider: null, voice: voice ?? "af_heart", fallback: Boolean(input.fallback), content_type: wantMp3 ? "audio/mpeg" : "audio/wav", bytes, out: outPath });
2457
+ }
2458
+ else {
2459
+ console.log(`${GREEN}Wrote ${outPath}${RESET} ${DIM}(local Kokoro-82M — free, no account, ${formatBytes(bytes)})${RESET}`);
2460
+ console.log(`${DIM}Get a shareable URL with: vidfarm upload "${outPath}" — then add it to a composition as an audio layer.${RESET}`);
2461
+ }
2462
+ }
2280
2463
  function formatSpeechClock(totalSeconds) {
2281
2464
  const minutes = Math.floor(totalSeconds / 60);
2282
2465
  const seconds = Math.floor(totalSeconds % 60);
@@ -2304,7 +2487,8 @@ function emitSttResult(input) {
2304
2487
  diarization: input.result.diarization,
2305
2488
  text: input.result.text,
2306
2489
  speakers: [...new Set((input.result.segments ?? []).map((segment) => segment.speaker))],
2307
- segments: input.result.segments ?? []
2490
+ segments: input.result.segments ?? [],
2491
+ ...(input.result.words ? { words: input.result.words } : {})
2308
2492
  }, null, 2)}\n`);
2309
2493
  written.push(dest);
2310
2494
  }
@@ -2337,6 +2521,7 @@ function emitSttResult(input) {
2337
2521
  diarization: input.result.diarization,
2338
2522
  text: input.result.text,
2339
2523
  segments: input.result.segments ?? [],
2524
+ ...(input.result.words ? { words: input.result.words } : {}),
2340
2525
  srt: input.result.srt,
2341
2526
  written
2342
2527
  });
@@ -2367,6 +2552,7 @@ async function runSttCommand(argv) {
2367
2552
  provider: { type: "string" },
2368
2553
  model: { type: "string" },
2369
2554
  out: { type: "string" },
2555
+ engine: { type: "string" },
2370
2556
  cloud: { type: "boolean", default: false },
2371
2557
  "gemini-key": { type: "string" },
2372
2558
  "openai-key": { type: "string" },
@@ -2383,10 +2569,42 @@ async function runSttCommand(argv) {
2383
2569
  const json = Boolean(parsed.values.json);
2384
2570
  const language = parsed.values.language?.trim() || undefined;
2385
2571
  const prompt = parsed.values.prompt?.trim() || undefined;
2386
- if (!parsed.values.cloud) {
2572
+ // Engine selection: byok (default — the user's own provider key), whisper
2573
+ // (keyless local whisper.cpp via the bundled hyperframes engine — word-level
2574
+ // timestamps, free, no account), cloud (the platform primitive job).
2575
+ const engine = parsed.values.engine?.trim().toLowerCase();
2576
+ if (engine && engine !== "whisper" && engine !== "byok" && engine !== "cloud") {
2577
+ throw new Error("stt --engine must be whisper (keyless local whisper.cpp), byok (your provider key — default), or cloud.");
2578
+ }
2579
+ const wantCloud = Boolean(parsed.values.cloud) || engine === "cloud";
2580
+ if (engine === "whisper") {
2581
+ await runLocalWhisperStt({
2582
+ target,
2583
+ out: parsed.values.out,
2584
+ json,
2585
+ language,
2586
+ model: parsed.values.model
2587
+ });
2588
+ return;
2589
+ }
2590
+ if (!wantCloud) {
2387
2591
  const auth = resolveLocalSpeechAuth(parsed.values, ["gemini", "openai", "openrouter"], parsed.values.provider);
2388
- if (!auth)
2389
- throw new Error(speechKeyHint("stt"));
2592
+ if (!auth) {
2593
+ // EXPLICIT intent (--provider) must never be silently downgraded to the
2594
+ // local whisper engine (no speaker labels, different quality) — hard-error
2595
+ // with the key hint so the user knows the named provider was not used.
2596
+ const requestedProvider = parsed.values.provider?.trim();
2597
+ if (requestedProvider) {
2598
+ throw new Error(`stt: provider ${requestedProvider} was requested but no matching key is configured. ${speechKeyHint("stt")}`);
2599
+ }
2600
+ // Keyless fallback (no explicit provider): rather than erroring
2601
+ // (speechKeyHint), transcribe with the local whisper.cpp engine (word
2602
+ // timestamps, no speaker labels).
2603
+ if (!json)
2604
+ console.log(`${DIM}No provider key found — using the local whisper.cpp engine; pass --engine cloud + a key for BYOK transcription (gemini adds speaker labels). (${speechKeyHint("stt")})${RESET}`);
2605
+ await runLocalWhisperStt({ target, out: parsed.values.out, json, language, fallback: true });
2606
+ return;
2607
+ }
2390
2608
  if (diarize && auth.provider !== "gemini" && !json) {
2391
2609
  console.log(`${DIM}Note: speaker labels need a Gemini key; ${auth.provider} returns a plain single-speaker transcript.${RESET}`);
2392
2610
  }
@@ -2499,6 +2717,108 @@ async function runSttCommand(argv) {
2499
2717
  }
2500
2718
  });
2501
2719
  }
2720
+ // Group whisper's word-level timings into subtitle-sized segments (new cue on
2721
+ // a >0.8s silence gap, sentence-ending punctuation, or 12 words) so the
2722
+ // existing SttEmitInput/SRT plumbing consumes whisper output unchanged.
2723
+ function groupWhisperWordsIntoSegments(words) {
2724
+ const segments = [];
2725
+ let current = null;
2726
+ const flush = () => {
2727
+ if (!current || current.parts.length === 0)
2728
+ return;
2729
+ segments.push({
2730
+ speaker: "speaker_1",
2731
+ start_sec: Number(current.start.toFixed(3)),
2732
+ end_sec: Number(current.end.toFixed(3)),
2733
+ text: current.parts.join(" ").replace(/\s+/g, " ").trim()
2734
+ });
2735
+ current = null;
2736
+ };
2737
+ for (const word of words) {
2738
+ if (!word || typeof word.text !== "string" || !Number.isFinite(word.start) || !Number.isFinite(word.end))
2739
+ continue;
2740
+ const endsSentence = current ? /[.!?]["')\]]?$/.test(current.parts[current.parts.length - 1] ?? "") : false;
2741
+ if (current && (word.start - current.end > 0.8 || current.parts.length >= 12 || endsSentence))
2742
+ flush();
2743
+ if (!current)
2744
+ current = { start: word.start, end: word.end, parts: [] };
2745
+ current.parts.push(word.text.trim());
2746
+ current.end = Math.max(current.end, word.end);
2747
+ }
2748
+ flush();
2749
+ return segments;
2750
+ }
2751
+ // Keyless local STT via the bundled hyperframes engine (whisper.cpp — word-level
2752
+ // timestamps, free, no account, no HeyGen). Used by `--engine whisper` and as
2753
+ // the automatic fallback when no provider key is configured. IMPORTANT: the
2754
+ // hyperframes transcribe command writes transcript.json into --dir AND patches
2755
+ // `const script = [...]` blocks in any HTML under that dir, so it always runs
2756
+ // against a fresh temp dir here — never the user's own directory.
2757
+ async function runLocalWhisperStt(input) {
2758
+ let localPath = input.target;
2759
+ let downloadDir = null;
2760
+ if (/^https?:\/\//i.test(input.target)) {
2761
+ downloadDir = mkdtempSync(path.join(tmpdir(), "vidfarm-whisper-dl-"));
2762
+ localPath = path.join(downloadDir, path.basename(new URL(input.target).pathname) || "media.bin");
2763
+ if (!input.json)
2764
+ console.log(`${DIM}Downloading source for the local whisper engine…${RESET}`);
2765
+ await downloadUrlToFile(input.target, localPath);
2766
+ }
2767
+ const absPath = path.resolve(process.cwd(), localPath);
2768
+ if (!existsSync(absPath))
2769
+ throw new Error(`No such local file: ${absPath}`);
2770
+ const workDir = mkdtempSync(path.join(tmpdir(), "vidfarm-whisper-"));
2771
+ try {
2772
+ if (!input.json)
2773
+ console.log(`${DIM}Transcribing with the local whisper.cpp engine (word-level timestamps — free, no account)…${RESET}`);
2774
+ const run = await runHyperframesCommand("transcribe", [
2775
+ absPath,
2776
+ "--dir", workDir,
2777
+ "--json",
2778
+ ...(input.language ? ["--language", input.language] : []),
2779
+ ...(input.model ? ["--model", input.model] : [])
2780
+ ], { stdio: "capture" });
2781
+ const summary = parseHyperframesJson(run.stdout) ?? parseHyperframesJson(run.stderr);
2782
+ if (run.code !== 0 || summary?.ok === false) {
2783
+ const reason = (typeof summary?.error === "string" && summary.error)
2784
+ || (typeof summary?.reason === "string" && summary.reason)
2785
+ || run.stderr.trim().split("\n").pop()
2786
+ || "";
2787
+ throw new Error(`local whisper transcription failed${reason ? `: ${reason}` : ""}. First run downloads whisper.cpp + the model — check network/disk, or pass a provider key for BYOK stt.`);
2788
+ }
2789
+ const transcriptPath = typeof summary?.transcriptPath === "string" && existsSync(summary.transcriptPath)
2790
+ ? summary.transcriptPath
2791
+ : path.join(workDir, "transcript.json");
2792
+ if (!existsSync(transcriptPath))
2793
+ throw new Error("local whisper transcription produced no transcript.json.");
2794
+ const words = JSON.parse(readFileSync(transcriptPath, "utf8"))
2795
+ .filter((word) => word && typeof word.text === "string" && Number.isFinite(word.start) && Number.isFinite(word.end))
2796
+ .map((word) => ({ text: String(word.text).trim(), start: Number(word.start), end: Number(word.end) }));
2797
+ const segments = groupWhisperWordsIntoSegments(words);
2798
+ const text = words.map((word) => word.text).join(" ").replace(/\s+/g, " ").trim();
2799
+ emitSttResult({
2800
+ json: input.json,
2801
+ out: input.out,
2802
+ source: input.target,
2803
+ mode: "local",
2804
+ provider: "whisper.cpp",
2805
+ model: (typeof summary?.model === "string" && summary.model) || input.model || "small.en",
2806
+ result: {
2807
+ text,
2808
+ language: input.language ?? null,
2809
+ diarization: "none",
2810
+ segments,
2811
+ srt: buildSrtFromSegments(segments),
2812
+ words
2813
+ }
2814
+ });
2815
+ }
2816
+ finally {
2817
+ rmSync(workDir, { recursive: true, force: true });
2818
+ if (downloadDir)
2819
+ rmSync(downloadDir, { recursive: true, force: true });
2820
+ }
2821
+ }
2502
2822
  async function runPlaceCommand(argv) {
2503
2823
  const parsed = parseArgs({
2504
2824
  args: argv,
@@ -2520,6 +2840,8 @@ async function runPlaceCommand(argv) {
2520
2840
  "ken-burns-intensity": { type: "string" },
2521
2841
  transition: { type: "string" },
2522
2842
  "transition-duration": { type: "string" },
2843
+ "transition-out": { type: "string" },
2844
+ "transition-out-duration": { type: "string" },
2523
2845
  "layer-key": { type: "string" },
2524
2846
  slug: { type: "string" },
2525
2847
  // Local file support: where a serve box serves /storage from, and which
@@ -2564,6 +2886,8 @@ async function runPlaceCommand(argv) {
2564
2886
  kenBurnsIntensity: num(parsed.values["ken-burns-intensity"]),
2565
2887
  transition: parsed.values.transition,
2566
2888
  transitionDuration: num(parsed.values["transition-duration"]),
2889
+ transitionOut: parsed.values["transition-out"],
2890
+ transitionOutDuration: num(parsed.values["transition-out-duration"]),
2567
2891
  layerKey: parsed.values["layer-key"],
2568
2892
  slug: parsed.values.slug
2569
2893
  });
@@ -2889,11 +3213,25 @@ async function runDownloadCommand(argv) {
2889
3213
  // This is the same filesystem the /editor AI agent browses via the browse_files
2890
3214
  // tool, so an agent CLI (Claude Code / Codex) can find assets the same way.
2891
3215
  async function runFilesCommand(argv) {
2892
- const parsed = parseArgs({ args: argv, allowPositionals: true, options: { ...commonOptions(), folder: { type: "string" } } });
3216
+ const parsed = parseArgs({ args: argv, allowPositionals: true, options: { ...commonOptions(), folder: { type: "string" }, search: { type: "string" } } });
2893
3217
  const ctx = commonContext(parsed.values);
3218
+ const folderFilter = parsed.values.folder ? String(parsed.values.folder).replace(/^\/+|\/+$/g, "") : undefined;
3219
+ if (parsed.values.search) {
3220
+ // Meaning-first lookup: keyword + vector search over every file's
3221
+ // name/folder/notes (BYOK embeddings; keyword-only without a saved key).
3222
+ const result = await apiRequest({
3223
+ method: "POST",
3224
+ host: ctx.host,
3225
+ path: "/api/v1/user/me/attachments/search",
3226
+ auth: ctx.auth,
3227
+ body: { query: String(parsed.values.search), ...(folderFilter ? { folder_path: folderFilter } : {}) }
3228
+ });
3229
+ assertApiOk(result, "files --search");
3230
+ emitResult(result, ctx.json);
3231
+ return;
3232
+ }
2894
3233
  const result = await apiRequest({ method: "GET", host: ctx.host, path: "/api/v1/user/me/attachments", auth: ctx.auth });
2895
3234
  assertApiOk(result, "files");
2896
- const folderFilter = parsed.values.folder ? String(parsed.values.folder).replace(/^\/+|\/+$/g, "") : undefined;
2897
3235
  if (folderFilter && result.json && Array.isArray(result.json.attachments)) {
2898
3236
  result.json = {
2899
3237
  ...result.json,
@@ -2902,6 +3240,34 @@ async function runFilesCommand(argv) {
2902
3240
  }
2903
3241
  emitResult(result, ctx.json);
2904
3242
  }
3243
+ // Attach metadata notes to one My Files entry (by id, or file name as a
3244
+ // fallback). Notes describe what the file IS and when to use it; the server
3245
+ // vector-embeds them so `files --search` finds the asset by meaning later —
3246
+ // the My Files analog of the clip library's semantic search.
3247
+ async function runAnnotateFileCommand(argv) {
3248
+ const parsed = parseArgs({ args: argv, allowPositionals: true, options: { ...commonOptions(), notes: { type: "string" } } });
3249
+ const fileRef = parsed.positionals[0];
3250
+ if (!fileRef)
3251
+ throw new Error("annotate-file requires a file id or name (run `files` to list ids).");
3252
+ if (parsed.values.notes == null)
3253
+ throw new Error("annotate-file requires --notes <text> (empty string clears existing notes).");
3254
+ const ctx = commonContext(parsed.values);
3255
+ const list = await apiRequest({ method: "GET", host: ctx.host, path: "/api/v1/user/me/attachments", auth: ctx.auth });
3256
+ assertApiOk(list, "annotate-file");
3257
+ const attachments = Array.isArray(list.json?.attachments) ? list.json.attachments : [];
3258
+ const match = attachments.find((file) => file?.id === fileRef) ?? attachments.find((file) => file?.fileName === fileRef);
3259
+ if (!match)
3260
+ throw new Error(`No file with id or name "${fileRef}" in My Files. Run \`files\` to list ids.`);
3261
+ const result = await apiRequest({
3262
+ method: "PATCH",
3263
+ host: ctx.host,
3264
+ path: `/api/v1/user/me/attachments/${encodeURIComponent(match.id)}`,
3265
+ auth: ctx.auth,
3266
+ body: { notes: String(parsed.values.notes) }
3267
+ });
3268
+ assertApiOk(result, "annotate-file");
3269
+ emitResult(result, ctx.json, [["Notes embedded", result.json?.notes_embedded ? "yes (vector-searchable)" : "no (keyword-searchable only — add a gemini/openai provider key to embed)"]]);
3270
+ }
2905
3271
  async function runGetFileCommand(argv) {
2906
3272
  const parsed = parseArgs({ args: argv, allowPositionals: true, options: { ...commonOptions(), print: { type: "boolean", default: false } } });
2907
3273
  const fileId = parsed.positionals[0];
@@ -2955,6 +3321,7 @@ async function runPutFileCommand(argv) {
2955
3321
  folder: { type: "string" },
2956
3322
  as: { type: "string" },
2957
3323
  content: { type: "string" },
3324
+ notes: { type: "string" },
2958
3325
  stdin: { type: "boolean", default: false }
2959
3326
  }
2960
3327
  });
@@ -2991,6 +3358,8 @@ async function runPutFileCommand(argv) {
2991
3358
  form.append("file", new Blob([new Uint8Array(buffer)], contentType ? { type: contentType } : undefined), fileName);
2992
3359
  if (parsed.values.folder)
2993
3360
  form.append("folder_path", String(parsed.values.folder));
3361
+ if (parsed.values.notes)
3362
+ form.append("notes", String(parsed.values.notes));
2994
3363
  const res = await fetch(new URL("/api/v1/user/me/attachments/upload", ctx.host), {
2995
3364
  method: "POST",
2996
3365
  headers: buildAuthHeaders(ctx.auth),
@@ -3008,6 +3377,238 @@ async function runPutFileCommand(argv) {
3008
3377
  assertApiOk(result, "put-file");
3009
3378
  emitResult(result, ctx.json, [["My Files URL", result.json?.attachment?.viewUrl]]);
3010
3379
  }
3380
+ // ── Local media engines + toolchain ─────────────────────────────────────────
3381
+ // Wrappers over the bundled hyperframes LOCAL commands (free, no account, no
3382
+ // HeyGen — see src/devcli/hyperframes-cli.ts for the account/telemetry guard)
3383
+ // plus vidfarm-native lint/stills. These give a local coding agent the full
3384
+ // media toolchain without any cloud credential.
3385
+ // `vidfarm remove-background <file|url>` → transparent WebM (video) or PNG
3386
+ // (image) via the local ONNX matting model.
3387
+ async function runRemoveBackgroundCommand(argv) {
3388
+ const parsed = parseArgs({
3389
+ args: argv,
3390
+ allowPositionals: true,
3391
+ options: {
3392
+ out: { type: "string" },
3393
+ "background-out": { type: "string" },
3394
+ device: { type: "string" },
3395
+ quality: { type: "string" },
3396
+ json: { type: "boolean", default: false }
3397
+ }
3398
+ });
3399
+ const target = parsed.positionals[0];
3400
+ if (!target)
3401
+ throw new Error("remove-background requires a source: `vidfarm remove-background <video-or-image file|url> [--out <path>]`.");
3402
+ const json = Boolean(parsed.values.json);
3403
+ let localPath = target;
3404
+ let downloadDir = null;
3405
+ if (/^https?:\/\//i.test(target)) {
3406
+ downloadDir = mkdtempSync(path.join(tmpdir(), "vidfarm-rmbg-dl-"));
3407
+ localPath = path.join(downloadDir, path.basename(new URL(target).pathname) || "media.bin");
3408
+ if (!json)
3409
+ console.log(`${DIM}Downloading source…${RESET}`);
3410
+ await downloadUrlToFile(target, localPath);
3411
+ }
3412
+ const absPath = path.resolve(process.cwd(), localPath);
3413
+ if (!existsSync(absPath))
3414
+ throw new Error(`No such local file: ${absPath}`);
3415
+ const isImage = /\.(png|jpe?g|webp)(\?|#|$)/i.test(absPath);
3416
+ const stem = path.basename(absPath).replace(/\.[^.]+$/, "");
3417
+ const outPath = path.resolve(process.cwd(), parsed.values.out ?? `${stem}.transparent.${isImage ? "png" : "webm"}`);
3418
+ mkdirSync(path.dirname(outPath), { recursive: true });
3419
+ try {
3420
+ if (!json)
3421
+ console.log(`${DIM}Removing background with the local ONNX matting model (free, no account)…${RESET}`);
3422
+ const run = await runHyperframesCommand("remove-background", [
3423
+ absPath,
3424
+ "-o", outPath,
3425
+ ...(parsed.values["background-out"] ? ["--background-output", path.resolve(process.cwd(), String(parsed.values["background-out"]))] : []),
3426
+ ...(parsed.values.device ? ["--device", String(parsed.values.device)] : []),
3427
+ ...(parsed.values.quality ? ["--quality", String(parsed.values.quality)] : []),
3428
+ ...(json ? ["--json"] : [])
3429
+ ], { stdio: json ? "capture" : "inherit" });
3430
+ if (run.code !== 0 || !existsSync(outPath)) {
3431
+ const summary = parseHyperframesJson(run.stdout) ?? parseHyperframesJson(run.stderr);
3432
+ const reason = (typeof summary?.error === "string" && summary.error) || run.stderr.trim().split("\n").pop() || "";
3433
+ throw new Error(`remove-background failed (exit ${run.code})${reason ? `: ${reason}` : ""}.`);
3434
+ }
3435
+ if (json) {
3436
+ const summary = parseHyperframesJson(run.stdout);
3437
+ printJson({ ok: true, mode: "local-engine", source: target, out: outPath, bytes: safeSize(outPath), ...(summary ?? {}) });
3438
+ }
3439
+ else {
3440
+ console.log(`${GREEN}Wrote ${outPath}${RESET} ${DIM}(${formatBytes(safeSize(outPath))} — drop it on a composition with \`vidfarm place <dir> --src ${outPath}\`)${RESET}`);
3441
+ }
3442
+ }
3443
+ finally {
3444
+ if (downloadDir)
3445
+ rmSync(downloadDir, { recursive: true, force: true });
3446
+ }
3447
+ }
3448
+ // `vidfarm capture <url>` → screenshots + assets + video manifest of a website
3449
+ // via local headless Chrome (for website-to-video style workflows).
3450
+ async function runCaptureCommand(argv) {
3451
+ const parsed = parseArgs({
3452
+ args: argv,
3453
+ allowPositionals: true,
3454
+ options: {
3455
+ out: { type: "string" },
3456
+ "max-screenshots": { type: "string" },
3457
+ timeout: { type: "string" },
3458
+ "skip-assets": { type: "boolean", default: false },
3459
+ json: { type: "boolean", default: false }
3460
+ }
3461
+ });
3462
+ const url = parsed.positionals[0];
3463
+ if (!url || !/^https?:\/\//i.test(url))
3464
+ throw new Error("capture requires a website URL: `vidfarm capture <url> [--out <dir>]`.");
3465
+ const json = Boolean(parsed.values.json);
3466
+ const outDir = parsed.values.out ? path.resolve(process.cwd(), String(parsed.values.out)) : undefined;
3467
+ const run = await runHyperframesCommand("capture", [
3468
+ url,
3469
+ ...(outDir ? ["-o", outDir] : []),
3470
+ ...(parsed.values["max-screenshots"] ? ["--max-screenshots", String(parsed.values["max-screenshots"])] : []),
3471
+ ...(parsed.values.timeout ? ["--timeout", String(parsed.values.timeout)] : []),
3472
+ ...(parsed.values["skip-assets"] ? ["--skip-assets"] : []),
3473
+ ...(json ? ["--json"] : [])
3474
+ ], { stdio: json ? "capture" : "inherit" });
3475
+ if (json) {
3476
+ const summary = parseHyperframesJson(run.stdout);
3477
+ printJson(summary ?? { ok: run.code === 0, out: outDir ?? null });
3478
+ }
3479
+ if (run.code !== 0) {
3480
+ process.exitCode = 1;
3481
+ if (!json)
3482
+ console.log(`${RED}capture failed (exit ${run.code}).${RESET}`);
3483
+ }
3484
+ }
3485
+ // `vidfarm beats <audioFile>` → beat-timestamp JSON an agent can feed into
3486
+ // transition/caption timing. The hyperframes beats command analyzes a PROJECT
3487
+ // dir's music track, so stage a throwaway project around the audio file.
3488
+ async function runBeatsCommand(argv) {
3489
+ const parsed = parseArgs({
3490
+ args: argv,
3491
+ allowPositionals: true,
3492
+ options: {
3493
+ out: { type: "string" },
3494
+ json: { type: "boolean", default: false }
3495
+ }
3496
+ });
3497
+ const target = parsed.positionals[0];
3498
+ if (!target)
3499
+ throw new Error("beats requires an audio file: `vidfarm beats <audioFile> [--out <path>]`.");
3500
+ const json = Boolean(parsed.values.json);
3501
+ let localPath = target;
3502
+ let downloadDir = null;
3503
+ if (/^https?:\/\//i.test(target)) {
3504
+ downloadDir = mkdtempSync(path.join(tmpdir(), "vidfarm-beats-dl-"));
3505
+ localPath = path.join(downloadDir, path.basename(new URL(target).pathname) || "audio.mp3");
3506
+ if (!json)
3507
+ console.log(`${DIM}Downloading audio…${RESET}`);
3508
+ await downloadUrlToFile(target, localPath);
3509
+ }
3510
+ const absPath = path.resolve(process.cwd(), localPath);
3511
+ if (!existsSync(absPath))
3512
+ throw new Error(`No such local file: ${absPath}`);
3513
+ const audioName = path.basename(absPath);
3514
+ const stem = audioName.replace(/\.[^.]+$/, "");
3515
+ const projectDir = mkdtempSync(path.join(tmpdir(), "vidfarm-beats-"));
3516
+ try {
3517
+ copyFileSync(absPath, path.join(projectDir, audioName));
3518
+ // Minimal project whose music track is the given audio file (the beats
3519
+ // command locates it via data-timeline-role="music").
3520
+ writeFileSync(path.join(projectDir, "index.html"), `<!doctype html>\n<html><body data-composition-id="vidfarm-beats" data-width="720" data-height="1280" data-duration="1">` +
3521
+ `<audio id="music" data-timeline-role="music" src="${audioName}" data-start="0" data-track-index="0"></audio>` +
3522
+ `</body></html>\n`, "utf8");
3523
+ if (!json)
3524
+ console.log(`${DIM}Detecting beats locally (free, no account)…${RESET}`);
3525
+ const run = await runHyperframesCommand("beats", [projectDir, "--json"], { stdio: "capture" });
3526
+ const beatsDir = path.join(projectDir, "beats");
3527
+ const beatsFile = existsSync(beatsDir)
3528
+ ? readdirSync(beatsDir).filter((name) => name.toLowerCase().endsWith(".json")).map((name) => path.join(beatsDir, name))[0]
3529
+ : undefined;
3530
+ if (run.code !== 0 || !beatsFile) {
3531
+ const summary = parseHyperframesJson(run.stdout) ?? parseHyperframesJson(run.stderr);
3532
+ const reason = (typeof summary?.error === "string" && summary.error) || run.stderr.trim().split("\n").pop() || run.stdout.trim().split("\n").pop() || "";
3533
+ throw new Error(`beat detection failed (exit ${run.code})${reason ? `: ${reason}` : ""}.`);
3534
+ }
3535
+ const beatsJson = JSON.parse(readFileSync(beatsFile, "utf8"));
3536
+ const outPath = path.resolve(process.cwd(), parsed.values.out ?? `${stem}.beats.json`);
3537
+ mkdirSync(path.dirname(outPath), { recursive: true });
3538
+ writeFileSync(outPath, `${JSON.stringify(beatsJson, null, 2)}\n`, "utf8");
3539
+ const beatCount = Array.isArray(beatsJson?.beats) ? beatsJson.beats.length : Array.isArray(beatsJson) ? beatsJson.length : null;
3540
+ if (json) {
3541
+ printJson({ ok: true, mode: "local-engine", source: target, out: outPath, beat_count: beatCount, beats: beatsJson });
3542
+ }
3543
+ else {
3544
+ console.log(`${GREEN}Wrote ${outPath}${RESET} ${DIM}(${beatCount ?? "?"} beats — sync transitions/captions to these timestamps)${RESET}`);
3545
+ }
3546
+ }
3547
+ finally {
3548
+ rmSync(projectDir, { recursive: true, force: true });
3549
+ if (downloadDir)
3550
+ rmSync(downloadDir, { recursive: true, force: true });
3551
+ }
3552
+ }
3553
+ // `vidfarm lint <dir|composition.html>` — static composition validation (the
3554
+ // same checks the /editor chat runs before accepting replacement HTML).
3555
+ async function runLintCommand(argv) {
3556
+ const parsed = parseArgs({
3557
+ args: argv,
3558
+ allowPositionals: true,
3559
+ options: { json: { type: "boolean", default: false } }
3560
+ });
3561
+ const target = parsed.positionals[0];
3562
+ if (!target)
3563
+ throw new Error("lint requires a composition path: `vidfarm lint <dir-or-composition.html>`.");
3564
+ const htmlPath = resolveCompositionHtmlPath(target);
3565
+ const result = lintCompositionHtml(readFileSync(htmlPath, "utf8"));
3566
+ if (parsed.values.json) {
3567
+ printJson(result);
3568
+ if (!result.ok)
3569
+ process.exitCode = 1;
3570
+ return;
3571
+ }
3572
+ const formatted = formatCompositionLintIssues(result);
3573
+ if (formatted)
3574
+ console.log(formatted);
3575
+ const mark = result.ok ? `${GREEN}✓${RESET}` : `${RED}✗${RESET}`;
3576
+ console.log(`${mark} ${result.errors.length} error(s), ${result.warnings.length} warning(s) ${DIM}— ${htmlPath}${RESET}`);
3577
+ if (!result.ok)
3578
+ process.exitCode = 1;
3579
+ }
3580
+ // `vidfarm stills <dir|composition.html>` — render PNG stills of the CURRENT
3581
+ // composition in-process (the agent's visual self-verification loop).
3582
+ async function runStillsCommand(argv) {
3583
+ const parsed = parseArgs({
3584
+ args: argv,
3585
+ allowPositionals: true,
3586
+ options: {
3587
+ at: { type: "string" },
3588
+ out: { type: "string" },
3589
+ json: { type: "boolean", default: false }
3590
+ }
3591
+ });
3592
+ const target = parsed.positionals[0];
3593
+ if (!target)
3594
+ throw new Error("stills requires a composition path: `vidfarm stills <dir-or-composition.html> [--at 0,2.5,7] [--out <dir>]`.");
3595
+ const htmlPath = resolveCompositionHtmlPath(target);
3596
+ const json = Boolean(parsed.values.json);
3597
+ const at = parsed.values.at
3598
+ ? String(parsed.values.at).split(",").map((value) => parseTimeToSeconds(value.trim())).filter((value) => Number.isFinite(value))
3599
+ : undefined;
3600
+ if (!json)
3601
+ console.log(`${DIM}Rendering stills in-process (free, local Chrome capture)…${RESET}`);
3602
+ const result = await renderCompositionStills({ htmlPath, at, outDir: parsed.values.out, quiet: json });
3603
+ if (json) {
3604
+ printJson(result);
3605
+ return;
3606
+ }
3607
+ for (const still of result.stills) {
3608
+ console.log(` ${GREEN}${still.path}${RESET} ${DIM}(requested ${still.requested_sec}s → captured ${still.captured_sec}s)${RESET}`);
3609
+ }
3610
+ console.log(`${DIM}${result.stills.length} still(s) at ${result.grid_fps}fps grid accuracy — open them to verify the edit before rendering.${RESET}`);
3611
+ }
3011
3612
  // ── Agent skill ───────────────────────────────────────────────────────────────
3012
3613
  // Install the latest director skill onto disk as a Claude Code / agent skill so
3013
3614
  // the user's AI agent can read SKILL.director.md and act. We pull the freshest