@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.
- package/.agents/skills/embedded-captions/CATALOG.md +93 -0
- package/.agents/skills/embedded-captions/SKILL.md +286 -0
- package/.agents/skills/embedded-captions/assets/brand/CDPR-fankit-terms.txt +35 -0
- package/.agents/skills/embedded-captions/assets/brand/cyberpunk-widths.json +115 -0
- package/.agents/skills/embedded-captions/assets/fonts/char-widths.json +2211 -0
- package/.agents/skills/embedded-captions/assets/strokefonts/HersheyScript1.svg +242 -0
- package/.agents/skills/embedded-captions/assets/strokefonts/HersheyScriptMed.svg +242 -0
- package/.agents/skills/embedded-captions/dna/README.md +148 -0
- package/.agents/skills/embedded-captions/dna/chrome.json +68 -0
- package/.agents/skills/embedded-captions/dna/cream.json +69 -0
- package/.agents/skills/embedded-captions/dna/documentary.json +62 -0
- package/.agents/skills/embedded-captions/dna/editorial.json +70 -0
- package/.agents/skills/embedded-captions/dna/glitch.json +82 -0
- package/.agents/skills/embedded-captions/dna/ink.json +65 -0
- package/.agents/skills/embedded-captions/dna/keynote.json +62 -0
- package/.agents/skills/embedded-captions/dna/loud.json +75 -0
- package/.agents/skills/embedded-captions/dna/neon.json +58 -0
- package/.agents/skills/embedded-captions/dna/velocity.json +89 -0
- package/.agents/skills/embedded-captions/modes/cinematic/README.md +48 -0
- package/.agents/skills/embedded-captions/modes/cinematic/_archive/champion/spec.md +98 -0
- package/.agents/skills/embedded-captions/modes/cinematic/_archive/champion/template.html +188 -0
- package/.agents/skills/embedded-captions/modes/cinematic/_archive/memory-wall/spec.md +134 -0
- package/.agents/skills/embedded-captions/modes/cinematic/_archive/memory-wall/template.html +179 -0
- package/.agents/skills/embedded-captions/modes/cinematic/_archive/portrait-header/spec.md +81 -0
- package/.agents/skills/embedded-captions/modes/cinematic/_archive/portrait-header/template.html +170 -0
- package/.agents/skills/embedded-captions/modes/cinematic/cinematic-cream/spec.md +16 -0
- package/.agents/skills/embedded-captions/modes/cinematic/cinematic-cream/template.html +226 -0
- package/.agents/skills/embedded-captions/modes/cinematic/engine.html +355 -0
- package/.agents/skills/embedded-captions/modes/standard/_anatomy.md +233 -0
- package/.agents/skills/embedded-captions/modes/standard/_motion.md +192 -0
- package/.agents/skills/embedded-captions/modes/standard/fonts/build-fonts-css.cjs +97 -0
- package/.agents/skills/embedded-captions/references/aesthetic-principles.md +168 -0
- package/.agents/skills/embedded-captions/references/anti-patterns.md +188 -0
- package/.agents/skills/embedded-captions/references/bespoke-vs-presets.md +178 -0
- package/.agents/skills/embedded-captions/references/caption-grouping.md +130 -0
- package/.agents/skills/embedded-captions/references/composition-craft.md +522 -0
- package/.agents/skills/embedded-captions/references/direction-catalog.md +138 -0
- package/.agents/skills/embedded-captions/references/example-renders/champion.html +371 -0
- package/.agents/skills/embedded-captions/references/example-renders/memory-wall.html +334 -0
- package/.agents/skills/embedded-captions/references/failure-modes.md +145 -0
- package/.agents/skills/embedded-captions/references/layout-heuristics.md +238 -0
- package/.agents/skills/embedded-captions/references/motion-vocabulary.md +148 -0
- package/.agents/skills/embedded-captions/references/rail.md +68 -0
- package/.agents/skills/embedded-captions/references/reference-bar.md +47 -0
- package/.agents/skills/embedded-captions/references/scene-types.md +94 -0
- package/.agents/skills/embedded-captions/references/test-set.md +66 -0
- package/.agents/skills/embedded-captions/references/typographic-moves.md +266 -0
- package/.agents/skills/embedded-captions/references/typography-presets.md +63 -0
- package/.agents/skills/embedded-captions/scripts/audio-envelope.cjs +95 -0
- package/.agents/skills/embedded-captions/scripts/check-occlusion.cjs +250 -0
- package/.agents/skills/embedded-captions/scripts/check-overflow.cjs +194 -0
- package/.agents/skills/embedded-captions/scripts/check-rail-climax.cjs +233 -0
- package/.agents/skills/embedded-captions/scripts/check-timing.cjs +173 -0
- package/.agents/skills/embedded-captions/scripts/fill-timings.cjs +124 -0
- package/.agents/skills/embedded-captions/scripts/fit-fonts.cjs +158 -0
- package/.agents/skills/embedded-captions/scripts/gen-stroke-path.py +46 -0
- package/.agents/skills/embedded-captions/scripts/inject-fonts.cjs +169 -0
- package/.agents/skills/embedded-captions/scripts/lib-dna.cjs +201 -0
- package/.agents/skills/embedded-captions/scripts/make-cinematic.cjs +1205 -0
- package/.agents/skills/embedded-captions/scripts/make-composition.cjs +404 -0
- package/.agents/skills/embedded-captions/scripts/make-theme.cjs +8781 -0
- package/.agents/skills/embedded-captions/scripts/matte.cjs +294 -0
- package/.agents/skills/embedded-captions/scripts/measure-layout.cjs +293 -0
- package/.agents/skills/embedded-captions/scripts/prepare.sh +39 -0
- package/.agents/skills/embedded-captions/scripts/preview-frames.cjs +268 -0
- package/.agents/skills/embedded-captions/scripts/render-and-composite.sh +509 -0
- package/.agents/skills/embedded-captions/scripts/render-theme.sh +34 -0
- package/.agents/skills/embedded-captions/scripts/safe-zones.cjs +813 -0
- package/.agents/skills/embedded-captions/scripts/transcribe.cjs +349 -0
- package/.agents/skills/embedded-captions/themes/PORTING.md +49 -0
- package/.agents/skills/embedded-captions/themes/README.md +244 -0
- package/.agents/skills/embedded-captions/themes/anchor.json +27 -0
- package/.agents/skills/embedded-captions/themes/arcade.json +51 -0
- package/.agents/skills/embedded-captions/themes/aurora.json +41 -0
- package/.agents/skills/embedded-captions/themes/biolume.json +35 -0
- package/.agents/skills/embedded-captions/themes/brush.json +74 -0
- package/.agents/skills/embedded-captions/themes/chalkboard.json +52 -0
- package/.agents/skills/embedded-captions/themes/dossier.json +56 -0
- package/.agents/skills/embedded-captions/themes/graffiti.json +62 -0
- package/.agents/skills/embedded-captions/themes/hologram.json +41 -0
- package/.agents/skills/embedded-captions/themes/inkwater.json +43 -0
- package/.agents/skills/embedded-captions/themes/laser.json +46 -0
- package/.agents/skills/embedded-captions/themes/lastpage.json +33 -0
- package/.agents/skills/embedded-captions/themes/neonsign.json +53 -0
- package/.agents/skills/embedded-captions/themes/nightcity.json +56 -0
- package/.agents/skills/embedded-captions/themes/ordnance.json +54 -0
- package/.agents/skills/embedded-captions/themes/papercut.json +37 -0
- package/.agents/skills/embedded-captions/themes/popup.json +46 -0
- package/.agents/skills/embedded-captions/themes/ransom.json +62 -0
- package/.agents/skills/embedded-captions/themes/scoreboard.json +54 -0
- package/.agents/skills/embedded-captions/themes/spectrum.json +46 -0
- package/.agents/skills/embedded-captions/themes/stardust.json +29 -0
- package/.agents/skills/embedded-captions/themes/stomp.json +43 -0
- package/.agents/skills/embedded-captions/themes/terminal.json +49 -0
- package/.agents/skills/embedded-captions/themes/thunder.json +44 -0
- package/.agents/skills/embedded-captions/themes/transit.json +44 -0
- package/.agents/skills/embedded-captions/themes/vhs.json +47 -0
- package/.agents/skills/faceless-explainer/SKILL.md +209 -0
- package/.agents/skills/faceless-explainer/references/cut-catalog.md +215 -0
- package/.agents/skills/faceless-explainer/references/motion-language.md +156 -0
- package/.agents/skills/faceless-explainer/references/story-design.md +248 -0
- package/.agents/skills/faceless-explainer/references/visual-design.md +146 -0
- package/.agents/skills/faceless-explainer/scripts/assemble-index.mjs +570 -0
- package/.agents/skills/faceless-explainer/scripts/audio.mjs +260 -0
- package/.agents/skills/faceless-explainer/scripts/build-frame.mjs +536 -0
- package/.agents/skills/faceless-explainer/scripts/captions.mjs +508 -0
- package/.agents/skills/faceless-explainer/scripts/lib/assets.mjs +55 -0
- package/.agents/skills/faceless-explainer/scripts/lib/dimensions.mjs +45 -0
- package/.agents/skills/faceless-explainer/scripts/lib/storyboard.mjs +249 -0
- package/.agents/skills/faceless-explainer/scripts/lib/tokens.mjs +204 -0
- package/.agents/skills/faceless-explainer/scripts/lib/transition-registry.mjs +38 -0
- package/.agents/skills/faceless-explainer/scripts/lib/transitions.json +71 -0
- package/.agents/skills/faceless-explainer/scripts/transitions.mjs +320 -0
- package/.agents/skills/faceless-explainer/sub-agents/frame-worker.md +75 -0
- package/.agents/skills/flockposter/LICENSE +17 -0
- package/.agents/skills/flockposter/README.md +72 -0
- package/.agents/skills/flockposter/SKILL.md +562 -0
- package/.agents/skills/flockposter/examples/cli-instagram-reel.sh +21 -0
- package/.agents/skills/flockposter/examples/cli-multi-platform.json +69 -0
- package/.agents/skills/flockposter/examples/cli-tiktok-upload-only.sh +22 -0
- package/.agents/skills/flockposter/examples/cli-x-thread.sh +17 -0
- package/.agents/skills/flockposter/examples/cli-youtube-short.sh +21 -0
- package/.agents/skills/flockposter/examples/public-api-instagram-reel.json +28 -0
- package/.agents/skills/general-video/SKILL.md +143 -0
- package/.agents/skills/hyperframes/SKILL.md +160 -0
- package/.agents/skills/hyperframes-animation/SKILL.md +84 -0
- package/.agents/skills/hyperframes-animation/adapters/animate-text.md +64 -0
- package/.agents/skills/hyperframes-animation/adapters/animejs.md +114 -0
- package/.agents/skills/hyperframes-animation/adapters/css-animations.md +143 -0
- package/.agents/skills/hyperframes-animation/adapters/gsap-easing-and-stagger.md +118 -0
- package/.agents/skills/hyperframes-animation/adapters/gsap-timeline-and-labels.md +96 -0
- package/.agents/skills/hyperframes-animation/adapters/gsap-transforms-and-perf.md +96 -0
- package/.agents/skills/hyperframes-animation/adapters/gsap.md +105 -0
- package/.agents/skills/hyperframes-animation/adapters/html-in-canvas-patterns.md +507 -0
- package/.agents/skills/hyperframes-animation/adapters/lottie.md +117 -0
- package/.agents/skills/hyperframes-animation/adapters/three.md +131 -0
- package/.agents/skills/hyperframes-animation/adapters/typegpu.md +178 -0
- package/.agents/skills/hyperframes-animation/adapters/waapi.md +101 -0
- package/.agents/skills/hyperframes-animation/blueprints/comparison-split.md +27 -0
- package/.agents/skills/hyperframes-animation/blueprints/constellation-hub.md +44 -0
- package/.agents/skills/hyperframes-animation/blueprints/cta-morph-press.md +28 -0
- package/.agents/skills/hyperframes-animation/blueprints/cursor-ui-demo.md +57 -0
- package/.agents/skills/hyperframes-animation/blueprints/dataviz-countup.md +46 -0
- package/.agents/skills/hyperframes-animation/blueprints/device-surface-showcase.md +53 -0
- package/.agents/skills/hyperframes-animation/blueprints/grid-card-assemble.md +66 -0
- package/.agents/skills/hyperframes-animation/blueprints/kinetic-type-beats.md +78 -0
- package/.agents/skills/hyperframes-animation/blueprints/logo-assemble-lockup.md +69 -0
- package/.agents/skills/hyperframes-animation/blueprints/overwhelm-surround.md +28 -0
- package/.agents/skills/hyperframes-animation/blueprints/spatial-pan-stations.md +37 -0
- package/.agents/skills/hyperframes-animation/blueprints/ticker-takeover.md +29 -0
- package/.agents/skills/hyperframes-animation/blueprints/titlecard-reveal.md +42 -0
- package/.agents/skills/hyperframes-animation/blueprints/typewriter-reveal.md +51 -0
- package/.agents/skills/hyperframes-animation/blueprints/video-text-pivot.md +30 -0
- package/.agents/skills/hyperframes-animation/blueprints-index.md +146 -0
- package/.agents/skills/hyperframes-animation/examples/assets/avatars/02.avif +0 -0
- package/.agents/skills/hyperframes-animation/examples/assets/brands/github.avif +0 -0
- package/.agents/skills/hyperframes-animation/examples/assets/brands/nvidia.avif +0 -0
- package/.agents/skills/hyperframes-animation/examples/assets/brands/visa.avif +0 -0
- package/.agents/skills/hyperframes-animation/examples/assets/brands/zoominfo.avif +0 -0
- package/.agents/skills/hyperframes-animation/examples/brand-reveal-assemble-zoom.html +382 -0
- package/.agents/skills/hyperframes-animation/examples/comparison-split-cards.html +649 -0
- package/.agents/skills/hyperframes-animation/examples/concept-demo-decode-pan.html +520 -0
- package/.agents/skills/hyperframes-animation/examples/cta-morph-press.html +468 -0
- package/.agents/skills/hyperframes-animation/examples/cta-orbit-collapse.html +1298 -0
- package/.agents/skills/hyperframes-animation/examples/demo-page-scroll-spotlight.html +759 -0
- package/.agents/skills/hyperframes-animation/examples/hook-counter-burst.html +729 -0
- package/.agents/skills/hyperframes-animation/examples/messaging-multi-phrase.html +352 -0
- package/.agents/skills/hyperframes-animation/examples/metric-video-text-pivot.html +779 -0
- package/.agents/skills/hyperframes-animation/examples/problem-mockup-overwhelm.html +1374 -0
- package/.agents/skills/hyperframes-animation/examples/proof-logo-chain.html +861 -0
- package/.agents/skills/hyperframes-animation/examples/takeover-ticker-displace.html +347 -0
- package/.agents/skills/hyperframes-animation/examples/workflow-approve-press.html +606 -0
- package/.agents/skills/hyperframes-animation/rules/3d-page-scroll.md +227 -0
- package/.agents/skills/hyperframes-animation/rules/3d-text-depth-layers.md +297 -0
- package/.agents/skills/hyperframes-animation/rules/ai-tracking-box.md +382 -0
- package/.agents/skills/hyperframes-animation/rules/ambient-glow-bloom.md +305 -0
- package/.agents/skills/hyperframes-animation/rules/asr-keyword-glow.md +286 -0
- package/.agents/skills/hyperframes-animation/rules/avatar-cloud-network.md +371 -0
- package/.agents/skills/hyperframes-animation/rules/camera-cursor-tracking.md +246 -0
- package/.agents/skills/hyperframes-animation/rules/card-morph-anchor.md +267 -0
- package/.agents/skills/hyperframes-animation/rules/center-outward-expansion.md +227 -0
- package/.agents/skills/hyperframes-animation/rules/context-sensitive-cursor.md +257 -0
- package/.agents/skills/hyperframes-animation/rules/coordinate-target-zoom.md +332 -0
- package/.agents/skills/hyperframes-animation/rules/counting-dynamic-scale.md +283 -0
- package/.agents/skills/hyperframes-animation/rules/css-marker-patterns.md +373 -0
- package/.agents/skills/hyperframes-animation/rules/cursor-click-ripple.md +262 -0
- package/.agents/skills/hyperframes-animation/rules/depth-of-field-blur.md +313 -0
- package/.agents/skills/hyperframes-animation/rules/depth-scatter-assemble.md +303 -0
- package/.agents/skills/hyperframes-animation/rules/discrete-text-sequence.md +273 -0
- package/.agents/skills/hyperframes-animation/rules/dynamic-content-sequencing.md +307 -0
- package/.agents/skills/hyperframes-animation/rules/gsap-effects.md +299 -0
- package/.agents/skills/hyperframes-animation/rules/hacker-flip-3d.md +223 -0
- package/.agents/skills/hyperframes-animation/rules/kinetic-beat-slam.md +183 -0
- package/.agents/skills/hyperframes-animation/rules/motion-blur-streak.md +328 -0
- package/.agents/skills/hyperframes-animation/rules/multi-phase-camera.md +273 -0
- package/.agents/skills/hyperframes-animation/rules/orbit-3d-entry.md +301 -0
- package/.agents/skills/hyperframes-animation/rules/physics-press-reaction.md +350 -0
- package/.agents/skills/hyperframes-animation/rules/press-release-spring.md +296 -0
- package/.agents/skills/hyperframes-animation/rules/reactive-displacement.md +277 -0
- package/.agents/skills/hyperframes-animation/rules/scale-swap-transition.md +298 -0
- package/.agents/skills/hyperframes-animation/rules/sine-wave-loop.md +278 -0
- package/.agents/skills/hyperframes-animation/rules/split-tilt-cards.md +277 -0
- package/.agents/skills/hyperframes-animation/rules/spring-pop-entrance.md +273 -0
- package/.agents/skills/hyperframes-animation/rules/stat-bars-and-fills.md +156 -0
- package/.agents/skills/hyperframes-animation/rules/svg-icon-enrichment.md +329 -0
- package/.agents/skills/hyperframes-animation/rules/svg-path-draw.md +274 -0
- package/.agents/skills/hyperframes-animation/rules/vertical-spring-ticker.md +239 -0
- package/.agents/skills/hyperframes-animation/rules/viewport-change.md +349 -0
- package/.agents/skills/hyperframes-animation/rules-index.md +86 -0
- package/.agents/skills/hyperframes-animation/scripts/animation-map.mjs +606 -0
- package/.agents/skills/hyperframes-animation/scripts/package-loader.mjs +288 -0
- package/.agents/skills/hyperframes-animation/scripts/package-loader.test.mjs +62 -0
- package/.agents/skills/hyperframes-animation/techniques.md +507 -0
- package/.agents/skills/hyperframes-animation/transitions/TRANSITION-REGISTRY.md +167 -0
- package/.agents/skills/hyperframes-animation/transitions/catalog.md +127 -0
- package/.agents/skills/hyperframes-animation/transitions/css-3d.md +12 -0
- package/.agents/skills/hyperframes-animation/transitions/css-blur.md +51 -0
- package/.agents/skills/hyperframes-animation/transitions/css-cover.md +43 -0
- package/.agents/skills/hyperframes-animation/transitions/css-destruction.md +95 -0
- package/.agents/skills/hyperframes-animation/transitions/css-dissolve.md +66 -0
- package/.agents/skills/hyperframes-animation/transitions/css-distortion.md +45 -0
- package/.agents/skills/hyperframes-animation/transitions/css-grid.md +10 -0
- package/.agents/skills/hyperframes-animation/transitions/css-light.md +49 -0
- package/.agents/skills/hyperframes-animation/transitions/css-mechanical.md +30 -0
- package/.agents/skills/hyperframes-animation/transitions/css-other.md +25 -0
- package/.agents/skills/hyperframes-animation/transitions/css-push.md +41 -0
- package/.agents/skills/hyperframes-animation/transitions/css-radial.md +37 -0
- package/.agents/skills/hyperframes-animation/transitions/css-scale.md +24 -0
- package/.agents/skills/hyperframes-animation/transitions/overview.md +153 -0
- package/.agents/skills/hyperframes-cli/SKILL.md +113 -0
- package/.agents/skills/hyperframes-cli/references/doctor-browser.md +45 -0
- package/.agents/skills/hyperframes-cli/references/init-and-scaffold.md +55 -0
- package/.agents/skills/hyperframes-cli/references/lambda.md +132 -0
- package/.agents/skills/hyperframes-cli/references/lint-validate-inspect.md +121 -0
- package/.agents/skills/hyperframes-cli/references/preview-render.md +149 -0
- package/.agents/skills/hyperframes-cli/references/upgrade-info-misc.md +75 -0
- package/.agents/skills/hyperframes-core/SKILL.md +78 -0
- package/.agents/skills/hyperframes-core/references/composition-patterns.md +263 -0
- package/.agents/skills/hyperframes-core/references/data-attributes.md +70 -0
- package/.agents/skills/hyperframes-core/references/determinism-rules.md +68 -0
- package/.agents/skills/hyperframes-core/references/full-screen-motion.md +62 -0
- package/.agents/skills/hyperframes-core/references/minimal-composition.md +66 -0
- package/.agents/skills/hyperframes-core/references/script-format.md +49 -0
- package/.agents/skills/hyperframes-core/references/storyboard-format.md +95 -0
- package/.agents/skills/hyperframes-core/references/sub-compositions.md +237 -0
- package/.agents/skills/hyperframes-core/references/subagent-dispatch.md +41 -0
- package/.agents/skills/hyperframes-core/references/tailwind.md +125 -0
- package/.agents/skills/hyperframes-core/references/tracks-and-clips.md +76 -0
- package/.agents/skills/hyperframes-core/references/variables-and-media.md +90 -0
- package/.agents/skills/hyperframes-creative/SKILL.md +70 -0
- package/.agents/skills/hyperframes-creative/frame-presets/biennale-yellow/FRAME.md +289 -0
- package/.agents/skills/hyperframes-creative/frame-presets/biennale-yellow/caption-skin.html +221 -0
- package/.agents/skills/hyperframes-creative/frame-presets/biennale-yellow/frame-showcase.html +1214 -0
- package/.agents/skills/hyperframes-creative/frame-presets/blockframe/FRAME.md +275 -0
- package/.agents/skills/hyperframes-creative/frame-presets/blockframe/caption-skin.html +224 -0
- package/.agents/skills/hyperframes-creative/frame-presets/blockframe/frame-showcase.html +1347 -0
- package/.agents/skills/hyperframes-creative/frame-presets/blue-professional/FRAME.md +307 -0
- package/.agents/skills/hyperframes-creative/frame-presets/blue-professional/caption-skin.html +223 -0
- package/.agents/skills/hyperframes-creative/frame-presets/blue-professional/frame-showcase.html +1372 -0
- package/.agents/skills/hyperframes-creative/frame-presets/bold-poster/FRAME.md +275 -0
- package/.agents/skills/hyperframes-creative/frame-presets/bold-poster/caption-skin.html +230 -0
- package/.agents/skills/hyperframes-creative/frame-presets/bold-poster/frame-showcase.html +1140 -0
- package/.agents/skills/hyperframes-creative/frame-presets/broadside/FRAME.md +284 -0
- package/.agents/skills/hyperframes-creative/frame-presets/broadside/caption-skin.html +225 -0
- package/.agents/skills/hyperframes-creative/frame-presets/broadside/frame-showcase.html +1166 -0
- package/.agents/skills/hyperframes-creative/frame-presets/capsule/FRAME.md +284 -0
- package/.agents/skills/hyperframes-creative/frame-presets/capsule/caption-skin.html +224 -0
- package/.agents/skills/hyperframes-creative/frame-presets/capsule/frame-showcase.html +1465 -0
- package/.agents/skills/hyperframes-creative/frame-presets/cartesian/FRAME.md +278 -0
- package/.agents/skills/hyperframes-creative/frame-presets/cartesian/caption-skin.html +222 -0
- package/.agents/skills/hyperframes-creative/frame-presets/cartesian/frame-showcase.html +1263 -0
- package/.agents/skills/hyperframes-creative/frame-presets/claude/FRAME.md +277 -0
- package/.agents/skills/hyperframes-creative/frame-presets/claude/caption-skin.html +224 -0
- package/.agents/skills/hyperframes-creative/frame-presets/claude/frame-showcase.html +1439 -0
- package/.agents/skills/hyperframes-creative/frame-presets/cobalt-grid/FRAME.md +265 -0
- package/.agents/skills/hyperframes-creative/frame-presets/cobalt-grid/caption-skin.html +238 -0
- package/.agents/skills/hyperframes-creative/frame-presets/cobalt-grid/frame-showcase.html +1468 -0
- package/.agents/skills/hyperframes-creative/frame-presets/coral/FRAME.md +314 -0
- package/.agents/skills/hyperframes-creative/frame-presets/coral/caption-skin.html +226 -0
- package/.agents/skills/hyperframes-creative/frame-presets/coral/frame-showcase.html +1268 -0
- package/.agents/skills/hyperframes-creative/frame-presets/creative-mode/FRAME.md +333 -0
- package/.agents/skills/hyperframes-creative/frame-presets/creative-mode/caption-skin.html +227 -0
- package/.agents/skills/hyperframes-creative/frame-presets/creative-mode/frame-showcase.html +1330 -0
- package/.agents/skills/hyperframes-creative/frame-presets/daisy-days/FRAME.md +274 -0
- package/.agents/skills/hyperframes-creative/frame-presets/daisy-days/caption-skin.html +230 -0
- package/.agents/skills/hyperframes-creative/frame-presets/daisy-days/frame-showcase.html +1444 -0
- package/.agents/skills/hyperframes-creative/frame-presets/editorial-forest/FRAME.md +265 -0
- package/.agents/skills/hyperframes-creative/frame-presets/editorial-forest/caption-skin.html +223 -0
- package/.agents/skills/hyperframes-creative/frame-presets/editorial-forest/frame-showcase.html +1273 -0
- package/.agents/skills/hyperframes-creative/palettes/bold-energetic.md +14 -0
- package/.agents/skills/hyperframes-creative/palettes/clean-corporate.md +14 -0
- package/.agents/skills/hyperframes-creative/palettes/dark-premium.md +14 -0
- package/.agents/skills/hyperframes-creative/palettes/jewel-rich.md +14 -0
- package/.agents/skills/hyperframes-creative/palettes/monochrome.md +14 -0
- package/.agents/skills/hyperframes-creative/palettes/nature-earth.md +14 -0
- package/.agents/skills/hyperframes-creative/palettes/neon-electric.md +14 -0
- package/.agents/skills/hyperframes-creative/palettes/pastel-soft.md +14 -0
- package/.agents/skills/hyperframes-creative/palettes/warm-editorial.md +14 -0
- package/.agents/skills/hyperframes-creative/references/audio-reactive.md +76 -0
- package/.agents/skills/hyperframes-creative/references/beat-direction.md +164 -0
- package/.agents/skills/hyperframes-creative/references/composition-patterns.md +199 -0
- package/.agents/skills/hyperframes-creative/references/data-in-motion.md +19 -0
- package/.agents/skills/hyperframes-creative/references/design-adherence.md +19 -0
- package/.agents/skills/hyperframes-creative/references/design-picker.md +123 -0
- package/.agents/skills/hyperframes-creative/references/design-spec.md +55 -0
- package/.agents/skills/hyperframes-creative/references/house-style.md +73 -0
- package/.agents/skills/hyperframes-creative/references/motion-principles.md +150 -0
- package/.agents/skills/hyperframes-creative/references/narration.md +92 -0
- package/.agents/skills/hyperframes-creative/references/prompt-expansion.md +68 -0
- package/.agents/skills/hyperframes-creative/references/typography.md +218 -0
- package/.agents/skills/hyperframes-creative/references/video-composition.md +63 -0
- package/.agents/skills/hyperframes-creative/references/visual-styles.md +457 -0
- package/.agents/skills/hyperframes-creative/scripts/contrast-report.mjs +353 -0
- package/.agents/skills/hyperframes-creative/scripts/extract-audio-data.py +188 -0
- package/.agents/skills/hyperframes-creative/scripts/package-loader.mjs +288 -0
- package/.agents/skills/hyperframes-creative/scripts/package-loader.test.mjs +62 -0
- package/.agents/skills/hyperframes-creative/templates/design-picker.html +1432 -0
- package/.agents/skills/hyperframes-keyframes/SKILL.md +237 -0
- package/.agents/skills/hyperframes-keyframes/agents/openai.yaml +4 -0
- package/.agents/skills/hyperframes-keyframes/references/keyframe-patterns.md +106 -0
- package/.agents/skills/motion-graphics/SKILL.md +172 -0
- package/.agents/skills/motion-graphics/agents/builder.md +40 -0
- package/.agents/skills/motion-graphics/agents/director.md +53 -0
- package/.agents/skills/motion-graphics/agents/finalize.md +17 -0
- package/.agents/skills/motion-graphics/catalog-map.md +40 -0
- package/.agents/skills/motion-graphics/categories/asset-fusion/module.md +37 -0
- package/.agents/skills/motion-graphics/categories/charts/module.md +21 -0
- package/.agents/skills/motion-graphics/categories/kinetic-type/module.md +19 -0
- package/.agents/skills/motion-graphics/categories/logo-reveal/module.md +17 -0
- package/.agents/skills/motion-graphics/categories/lower-thirds/module.md +16 -0
- package/.agents/skills/motion-graphics/categories/maps/bake-basemap.mjs +273 -0
- package/.agents/skills/motion-graphics/categories/maps/module.md +61 -0
- package/.agents/skills/motion-graphics/categories/news/module.md +56 -0
- package/.agents/skills/motion-graphics/categories/stat/module.md +21 -0
- package/.agents/skills/motion-graphics/categories/tweet/module.md +16 -0
- package/.agents/skills/motion-graphics/categories/webpage/module.md +21 -0
- package/.agents/skills/motion-graphics/grounding/PROTOCOL.md +64 -0
- package/.agents/skills/motion-graphics/grounding/locate.mjs +227 -0
- package/.agents/skills/motion-graphics/phases/source/guide.md +21 -0
- package/.agents/skills/motion-graphics/references/builder-contract.md +38 -0
- package/.agents/skills/motion-graphics/references/motion-vocabulary.md +43 -0
- package/.agents/skills/motion-graphics/references/shot-plan-ir.md +54 -0
- package/.agents/skills/motion-graphics/samples/asset-fusion/_ref-circle-highlight.html +306 -0
- package/.agents/skills/product-launch-video/SKILL.md +208 -0
- package/.agents/skills/product-launch-video/references/cut-catalog.md +220 -0
- package/.agents/skills/product-launch-video/references/motion-language.md +156 -0
- package/.agents/skills/product-launch-video/references/story-design.md +373 -0
- package/.agents/skills/product-launch-video/references/visual-design.md +126 -0
- package/.agents/skills/product-launch-video/scripts/assemble-index.mjs +570 -0
- package/.agents/skills/product-launch-video/scripts/audio.mjs +260 -0
- package/.agents/skills/product-launch-video/scripts/build-frame.mjs +536 -0
- package/.agents/skills/product-launch-video/scripts/captions.mjs +508 -0
- package/.agents/skills/product-launch-video/scripts/lib/assets.mjs +55 -0
- package/.agents/skills/product-launch-video/scripts/lib/dimensions.mjs +45 -0
- package/.agents/skills/product-launch-video/scripts/lib/pad-frame-duration.mjs +36 -0
- package/.agents/skills/product-launch-video/scripts/lib/pad-frame-duration.test.mjs +76 -0
- package/.agents/skills/product-launch-video/scripts/lib/storyboard.mjs +249 -0
- package/.agents/skills/product-launch-video/scripts/lib/tokens.mjs +204 -0
- package/.agents/skills/product-launch-video/scripts/lib/transition-registry.mjs +38 -0
- package/.agents/skills/product-launch-video/scripts/lib/transitions.json +71 -0
- package/.agents/skills/product-launch-video/scripts/stage-assets.mjs +39 -0
- package/.agents/skills/product-launch-video/scripts/transitions.mjs +327 -0
- package/.agents/skills/product-launch-video/sub-agents/frame-worker.md +75 -0
- package/.agents/skills/slideshow/SKILL.md +550 -0
- package/.agents/skills/slideshow/references/standalone-harness.md +1020 -0
- package/.agents/skills/talking-head-recut/NOTICE.md +39 -0
- package/.agents/skills/talking-head-recut/SKILL.md +1195 -0
- package/.agents/skills/talking-head-recut/assets/vendor/gsap.min.js +10 -0
- package/.agents/skills/talking-head-recut/references/DESIGN_INDEX.md +145 -0
- package/.agents/skills/talking-head-recut/references/frames/clean.html +92 -0
- package/.agents/skills/talking-head-recut/references/frames/hairline.html +171 -0
- package/.agents/skills/talking-head-recut/references/frames/polaroid.html +131 -0
- package/.agents/skills/talking-head-recut/references/layouts/overlay.html +164 -0
- package/.agents/skills/talking-head-recut/references/layouts/pip.html +143 -0
- package/.agents/skills/talking-head-recut/references/layouts/split.html +121 -0
- package/.agents/skills/talking-head-recut/references/layouts/stack.html +109 -0
- package/.agents/skills/talking-head-recut/references/styles/academic.html +172 -0
- package/.agents/skills/talking-head-recut/references/styles/audit.html +170 -0
- package/.agents/skills/talking-head-recut/references/styles/editorial.html +189 -0
- package/.agents/skills/talking-head-recut/references/styles/geom.html +181 -0
- package/.agents/skills/talking-head-recut/references/styles/minimal.html +130 -0
- package/.agents/skills/talking-head-recut/references/styles/spotlight.html +142 -0
- package/.agents/skills/talking-head-recut/references/styles/swiss.html +188 -0
- package/.agents/skills/talking-head-recut/references/styles/terminal.html +196 -0
- package/.agents/skills/talking-head-recut/references/styles/whiteboard.html +170 -0
- package/.agents/skills/talking-head-recut/references/styles/xhs.html +206 -0
- package/.agents/skills/vidfarm-media/SKILL.md +85 -0
- package/.agents/skills/vidfarm-media/references/tts.md +54 -0
- package/.agents/skills/vidfarm-media/scripts/audio.mjs +628 -0
- package/.agents/skills/vidfarm-media/scripts/wait-bgm.mjs +31 -0
- package/.agents/skills/website-to-video/SKILL.md +145 -0
- package/.agents/skills/website-to-video/assets/sfx/CREDITS.md +35 -0
- package/.agents/skills/website-to-video/assets/sfx/manifest.json +97 -0
- package/.agents/skills/website-to-video/references/beat-builder-guide.md +263 -0
- package/.agents/skills/website-to-video/references/capabilities.md +713 -0
- package/.agents/skills/website-to-video/references/step-0-capture.md +55 -0
- package/.agents/skills/website-to-video/references/step-1-design.md +333 -0
- package/.agents/skills/website-to-video/references/step-2-brief.md +178 -0
- package/.agents/skills/website-to-video/references/step-3-storyboard.md +572 -0
- package/.agents/skills/website-to-video/references/step-4-vo.md +225 -0
- package/.agents/skills/website-to-video/references/step-5-build.md +483 -0
- package/.agents/skills/website-to-video/references/step-6-validate.md +341 -0
- package/.agents/skills/website-to-video/scripts/w2h-verify.mjs +759 -0
- package/SKILL.director.md +60 -4
- package/SKILL.platform.md +7 -2
- package/demo/dist/app.css +1 -1
- package/demo/dist/app.js +485 -86
- package/dist/src/account-pages-legacy.js +9 -14
- package/dist/src/app.js +659 -159
- package/dist/src/cli.js +618 -17
- package/dist/src/composition-runtime.js +286 -6
- package/dist/src/devcli/composition-edit.js +176 -8
- package/dist/src/devcli/doctor.js +192 -0
- package/dist/src/devcli/hyperframes-cli.js +118 -0
- package/dist/src/devcli/skills.js +476 -0
- package/dist/src/devcli/stills.js +249 -0
- package/dist/src/devcli/transitions.js +205 -0
- package/dist/src/editor-chat.js +260 -4
- package/dist/src/frontend/template-editor-chat.js +6 -5
- package/dist/src/homepage.js +2 -48
- package/dist/src/hyperframes/composition.js +188 -7
- package/dist/src/services/composition-lint.js +199 -0
- package/dist/src/services/hyperframes.js +127 -28
- package/dist/src/services/serverless-records.js +7 -0
- package/dist/src/template-editor-shell.js +87 -48
- package/package.json +2 -1
- 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|
|
|
152
|
-
slide-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|