@mevdragon/vidfarm-devcli 0.14.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 (418) 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 +37 -0
  405. package/SKILL.platform.md +6 -1
  406. package/demo/dist/app.js +295 -11
  407. package/dist/src/app.js +317 -133
  408. package/dist/src/cli.js +529 -12
  409. package/dist/src/composition-runtime.js +262 -3
  410. package/dist/src/devcli/doctor.js +192 -0
  411. package/dist/src/devcli/hyperframes-cli.js +118 -0
  412. package/dist/src/devcli/skills.js +476 -0
  413. package/dist/src/devcli/stills.js +249 -0
  414. package/dist/src/editor-chat.js +255 -0
  415. package/dist/src/hyperframes/composition.js +5 -5
  416. package/dist/src/services/composition-lint.js +199 -0
  417. package/dist/src/services/hyperframes.js +67 -13
  418. package/package.json +2 -1
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";
@@ -13,7 +13,13 @@ import { runCaptionsCommand } from "./devcli/captions.js";
13
13
  import { runClipsCommand } from "./devcli/clips.js";
14
14
  import { runTransitionsCommand } from "./devcli/transitions.js";
15
15
  import { MAX_LOCAL_TRANSCRIBE_AUDIO_BYTES, loadSpeechAudioLocally, localGenerateSpeech, localTranscribeSpeech, resolveLocalSpeechAuth } from "./devcli/speech.js";
16
- 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";
17
23
  import { initTelemetry, reportCliCrash } from "./devcli/telemetry.js";
18
24
  // vidfarm-devcli — command-line bridge for the Vidfarm video studio. The
19
25
  // `serve` command boots the FULL editor locally (single origin, disk-backed
@@ -189,6 +195,9 @@ Speech (TTS/STT) — LOCAL-FIRST on your own AI key; --cloud is the explicit bac
189
195
  a voice from the wrong provider family is rejected with a clear error.
190
196
  --out <file> Output audio path (default tts-<id>.mp3|wav)
191
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.
192
201
  --cloud BACKUP: saved-key platform job → POST /api/v1/primitives/audio/speech (+ poll)
193
202
  stt <file|url> Video or audio → transcript (alias: transcribe). LOCAL: local ffmpeg demux +
194
203
  Returns BOTH formats: the simple subtitle your provider key (gemini labels
@@ -200,6 +209,10 @@ Speech (TTS/STT) — LOCAL-FIRST on your own AI key; --cloud is the explicit bac
200
209
  --language <code> Expected language hint (e.g. en, es)
201
210
  --prompt "<context>" Domain/vocabulary hint for the transcriber
202
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).
203
216
  --cloud BACKUP: saved-key platform job → POST /api/v1/primitives/audio/transcribe (+ poll)
204
217
  (a local file uploads to your temp folder first — 30-day TTL)
205
218
 
@@ -233,6 +246,37 @@ Scene transitions (between clips on a pulled/served composition — local file w
233
246
  clone <forkId> Clone a fork as a new starting point → POST .../compositions/:forkId/clone
234
247
  share-link <forkId> Mint a share-token URL for a fork → POST .../compositions/:forkId/share-links
235
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
+
236
280
  Approve & schedule (publish a finished MP4 as a shareable post):
237
281
  approve Approve media into a preview/share page → POST /api/v1/approved/posts
238
282
  --video <url|file> Primary MP4 (URL, or a local file auto-uploaded to temp/)
@@ -493,6 +537,30 @@ async function main() {
493
537
  case "transitions":
494
538
  await runTransitionsCommand(rest);
495
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;
496
564
  default:
497
565
  if (!command.startsWith("-")) {
498
566
  // Positional template id → default to booting the local editor.
@@ -669,6 +737,10 @@ async function runServeCommand(argv) {
669
737
  process.env.PORT = String(port);
670
738
  if (!process.env.NODE_ENV)
671
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";
672
744
  // Cloud passthrough: only the editor is local — /discover and /library keep
673
745
  // listing the upstream (cloud) catalog, templates seed onto disk on demand,
674
746
  // and the editor's Render button can hand off to the cloud renderer. The
@@ -2218,6 +2290,7 @@ async function runTtsCommand(argv) {
2218
2290
  model: { type: "string" },
2219
2291
  format: { type: "string" },
2220
2292
  out: { type: "string" },
2293
+ engine: { type: "string" },
2221
2294
  cloud: { type: "boolean", default: false },
2222
2295
  "gemini-key": { type: "string" },
2223
2296
  "openai-key": { type: "string" },
@@ -2230,19 +2303,48 @@ async function runTtsCommand(argv) {
2230
2303
  if (!text)
2231
2304
  throw new Error('tts requires text: vidfarm tts "Welcome back to the channel" --style "calm, warm narrator".');
2232
2305
  const style = (parsed.values.instructions ?? parsed.values.style)?.trim() || undefined;
2233
- const rawFormat = String(parsed.values.format ?? "mp3").toLowerCase();
2306
+ const explicitFormat = parsed.values.format?.trim().toLowerCase();
2307
+ const rawFormat = explicitFormat ?? "mp3";
2234
2308
  if (rawFormat !== "mp3" && rawFormat !== "wav")
2235
2309
  throw new Error("tts --format must be mp3 or wav.");
2236
2310
  const format = rawFormat;
2237
2311
  const voice = parsed.values.voice;
2238
2312
  const json = Boolean(parsed.values.json);
2239
- 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) {
2240
2326
  // A Gemini voice preset ("Kore", "Puck", …) should find a Gemini-capable
2241
2327
  // key first — openrouter's default TTS model is Gemini-family too.
2242
- 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"];
2243
2330
  const auth = resolveLocalSpeechAuth(parsed.values, keyPreference, parsed.values.provider);
2244
- if (!auth)
2245
- 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
+ }
2246
2348
  if (!json)
2247
2349
  console.log(`${DIM}Generating speech locally on your ${auth.provider} key…${RESET}`);
2248
2350
  const result = await localGenerateSpeech({
@@ -2312,6 +2414,52 @@ async function runTtsCommand(argv) {
2312
2414
  console.log(`${GREEN}Wrote ${outPath}${RESET} ${DIM}(${mediaUrl})${RESET}`);
2313
2415
  }
2314
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
+ }
2315
2463
  function formatSpeechClock(totalSeconds) {
2316
2464
  const minutes = Math.floor(totalSeconds / 60);
2317
2465
  const seconds = Math.floor(totalSeconds % 60);
@@ -2339,7 +2487,8 @@ function emitSttResult(input) {
2339
2487
  diarization: input.result.diarization,
2340
2488
  text: input.result.text,
2341
2489
  speakers: [...new Set((input.result.segments ?? []).map((segment) => segment.speaker))],
2342
- segments: input.result.segments ?? []
2490
+ segments: input.result.segments ?? [],
2491
+ ...(input.result.words ? { words: input.result.words } : {})
2343
2492
  }, null, 2)}\n`);
2344
2493
  written.push(dest);
2345
2494
  }
@@ -2372,6 +2521,7 @@ function emitSttResult(input) {
2372
2521
  diarization: input.result.diarization,
2373
2522
  text: input.result.text,
2374
2523
  segments: input.result.segments ?? [],
2524
+ ...(input.result.words ? { words: input.result.words } : {}),
2375
2525
  srt: input.result.srt,
2376
2526
  written
2377
2527
  });
@@ -2402,6 +2552,7 @@ async function runSttCommand(argv) {
2402
2552
  provider: { type: "string" },
2403
2553
  model: { type: "string" },
2404
2554
  out: { type: "string" },
2555
+ engine: { type: "string" },
2405
2556
  cloud: { type: "boolean", default: false },
2406
2557
  "gemini-key": { type: "string" },
2407
2558
  "openai-key": { type: "string" },
@@ -2418,10 +2569,42 @@ async function runSttCommand(argv) {
2418
2569
  const json = Boolean(parsed.values.json);
2419
2570
  const language = parsed.values.language?.trim() || undefined;
2420
2571
  const prompt = parsed.values.prompt?.trim() || undefined;
2421
- 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) {
2422
2591
  const auth = resolveLocalSpeechAuth(parsed.values, ["gemini", "openai", "openrouter"], parsed.values.provider);
2423
- if (!auth)
2424
- 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
+ }
2425
2608
  if (diarize && auth.provider !== "gemini" && !json) {
2426
2609
  console.log(`${DIM}Note: speaker labels need a Gemini key; ${auth.provider} returns a plain single-speaker transcript.${RESET}`);
2427
2610
  }
@@ -2534,6 +2717,108 @@ async function runSttCommand(argv) {
2534
2717
  }
2535
2718
  });
2536
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
+ }
2537
2822
  async function runPlaceCommand(argv) {
2538
2823
  const parsed = parseArgs({
2539
2824
  args: argv,
@@ -3092,6 +3377,238 @@ async function runPutFileCommand(argv) {
3092
3377
  assertApiOk(result, "put-file");
3093
3378
  emitResult(result, ctx.json, [["My Files URL", result.json?.attachment?.viewUrl]]);
3094
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
+ }
3095
3612
  // ── Agent skill ───────────────────────────────────────────────────────────────
3096
3613
  // Install the latest director skill onto disk as a Claude Code / agent skill so
3097
3614
  // the user's AI agent can read SKILL.director.md and act. We pull the freshest