@officexapp/vidfarm-devcli 0.21.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/dollarplatoon-skill/SKILL.md +1227 -0
- package/.agents/skills/editor-capabilities/SKILL.md +182 -0
- package/.agents/skills/editor-capabilities/references/re-theme-walkthrough.md +58 -0
- 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/farmville-saas-ux/SKILL.md +156 -0
- package/.agents/skills/farmville-saas-ux/assets/starter.html +294 -0
- package/.agents/skills/farmville-saas-ux/references/components.md +340 -0
- package/.agents/skills/farmville-saas-ux/references/porting-guide.md +121 -0
- package/.agents/skills/farmville-saas-ux/references/tokens.md +271 -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/music/SKILL.md +416 -0
- package/.agents/skills/music/references/api_reference.md +519 -0
- package/.agents/skills/music/references/installation.md +65 -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/text-to-speech/SKILL.md +226 -0
- package/.agents/skills/text-to-speech/references/installation.md +90 -0
- package/.agents/skills/text-to-speech/references/streaming.md +307 -0
- package/.agents/skills/text-to-speech/references/voice-settings.md +115 -0
- package/.agents/skills/vidfarm-director/SKILL.md +179 -0
- package/.agents/skills/vidfarm-director/recipes/find-and-fork-template.md +16 -0
- package/.agents/skills/vidfarm-director/recipes/local-edit-render-approve.md +13 -0
- package/.agents/skills/vidfarm-director/recipes/onboard-a-new-director.md +13 -0
- package/.agents/skills/vidfarm-director/recipes/retheme-template.md +17 -0
- package/.agents/skills/vidfarm-director/references/assets-and-sourcing.md +117 -0
- package/.agents/skills/vidfarm-director/references/automation-and-local-dev.md +276 -0
- package/.agents/skills/vidfarm-director/references/core-workflows.md +301 -0
- package/.agents/skills/vidfarm-director/references/editor-workflows.md +367 -0
- package/.agents/skills/vidfarm-director/references/onboarding.md +28 -0
- package/.agents/skills/vidfarm-director/references/primitives.md +307 -0
- package/.agents/skills/vidfarm-director/references/rest-api.md +84 -0
- package/.agents/skills/vidfarm-media/SKILL.md +158 -0
- package/.agents/skills/vidfarm-media/references/tts.md +111 -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/README.md +97 -0
- package/SKILL.director.md +1644 -0
- package/SKILL.md +83 -0
- package/demo/README.md +28 -0
- package/demo/dist/app.css +1 -0
- package/demo/dist/app.js +968 -0
- package/demo/dist/chunks/chunk-3OT2D42H.js +1 -0
- package/demo/dist/chunks/chunk-DXB73IDG.js +1 -0
- package/demo/dist/chunks/domEditingLayers-AT7G6F4L-ZTHJHB7R.js +1 -0
- package/demo/dist/chunks/hyperframes-player-7HKH6NSO.js +459 -0
- package/demo/dist/chunks/lib-XAQ37YOE.js +1 -0
- package/demo/dist/chunks/src-TJ2QYA4U.js +207 -0
- package/demo/dist/favicon.ico +0 -0
- package/demo/dist/icons/timeline/audio.svg +7 -0
- package/demo/dist/icons/timeline/captions.svg +5 -0
- package/demo/dist/icons/timeline/composition.svg +12 -0
- package/demo/dist/icons/timeline/image.svg +18 -0
- package/demo/dist/icons/timeline/music.svg +10 -0
- package/demo/dist/icons/timeline/text.svg +3 -0
- package/demo/dist/index.html +16 -0
- package/dist/src/cli.js +7258 -0
- package/dist/src/devcli/auth-store.js +67 -0
- package/dist/src/devcli/captions.js +310 -0
- package/dist/src/devcli/clip-store.js +368 -0
- package/dist/src/devcli/clips.js +1138 -0
- package/dist/src/devcli/composition-edit.js +1292 -0
- package/dist/src/devcli/cost-mode.js +149 -0
- package/dist/src/devcli/doctor.js +192 -0
- package/dist/src/devcli/hyperframes-cli.js +118 -0
- package/dist/src/devcli/local-backend.js +167 -0
- package/dist/src/devcli/local-frontend-server.js +681 -0
- package/dist/src/devcli/local-render.js +136 -0
- package/dist/src/devcli/skills.js +476 -0
- package/dist/src/devcli/speech.js +178 -0
- package/dist/src/devcli/stills.js +249 -0
- package/dist/src/devcli/telemetry.js +236 -0
- package/dist/src/devcli/timeline-edit.js +490 -0
- package/dist/src/devcli/transitions.js +205 -0
- package/dist/src/hyperframes/composition.js +780 -0
- package/dist/src/lib/crypto.js +45 -0
- package/dist/src/lib/dev-log.js +54 -0
- package/dist/src/lib/display-name.js +11 -0
- package/dist/src/lib/ids.js +24 -0
- package/dist/src/lib/images.js +19 -0
- package/dist/src/lib/json.js +15 -0
- package/dist/src/lib/package-root.js +47 -0
- package/dist/src/lib/render-media-prep.js +293 -0
- package/dist/src/lib/template-paths.js +28 -0
- package/dist/src/lib/time.js +7 -0
- package/dist/src/lib/url-clean.js +85 -0
- package/dist/src/services/captions.js +123 -0
- package/dist/src/services/clip-curation/cost.js +116 -0
- package/dist/src/services/clip-curation/ffmpeg.js +352 -0
- package/dist/src/services/clip-curation/gemini.js +418 -0
- package/dist/src/services/clip-curation/hunt.js +470 -0
- package/dist/src/services/clip-curation/index.js +19 -0
- package/dist/src/services/clip-curation/local-agent.js +248 -0
- package/dist/src/services/clip-curation/media-select.js +152 -0
- package/dist/src/services/clip-curation/presets.js +20 -0
- package/dist/src/services/clip-curation/presets.v1.json +59 -0
- package/dist/src/services/clip-curation/query.js +167 -0
- package/dist/src/services/clip-curation/refine.js +165 -0
- package/dist/src/services/clip-curation/scan.js +189 -0
- package/dist/src/services/clip-curation/source-naming.js +131 -0
- package/dist/src/services/clip-curation/taxonomy.js +73 -0
- package/dist/src/services/clip-curation/taxonomy.v1.json +102 -0
- package/dist/src/services/clip-curation/types.js +7 -0
- package/dist/src/services/composition-lint.js +199 -0
- package/dist/src/services/provider-errors.js +128 -0
- package/dist/src/services/speech.js +629 -0
- package/package.json +154 -0
- package/public/assets/discover-client-app.js +1 -0
- package/public/assets/favicon.ico +0 -0
- package/public/assets/file-directory-app.js +89 -0
- package/public/assets/homepage-app.js +54 -0
- package/public/assets/homepage-client-app.js +80 -0
- package/public/assets/landing-page-client-app.js +87 -0
- package/public/assets/logo-vidfarm.png +0 -0
- package/public/assets/page-runtime-client-app.js +94 -0
- package/public/assets/placeholders/scene-placeholder.png +0 -0
- package/public/serve-shells/editor.html +3146 -0
- package/public/serve-shells/library-files.html +3450 -0
- package/public/serve-shells/library-raws.html +4404 -0
- package/public/serve-shells/tools-clipper.html +3991 -0
- package/public/serve-shells/tools-image.html +5584 -0
- package/public/serve-shells/tools-video.html +4404 -0
|
@@ -0,0 +1,681 @@
|
|
|
1
|
+
// Backend-free curated local frontend for the PUBLISHED cloud-only CLI.
|
|
2
|
+
//
|
|
3
|
+
// The full `vidfarm serve` boots the real Hono `app` in-process (records +
|
|
4
|
+
// storage on disk, free local render) — but that requires shipping the 26k-line
|
|
5
|
+
// backend, which the published package deliberately omits. This module is the
|
|
6
|
+
// published-package fallback: a tiny Node http server that ships ZERO backend.
|
|
7
|
+
// It only
|
|
8
|
+
// 1. serves BUILD-TIME HTML SNAPSHOTS of the client-hydrated pages
|
|
9
|
+
// (/editor, /tools/*, /library/{raws,files}) from public/serve-shells,
|
|
10
|
+
// 2. serves their prebuilt static bundles (demo/dist editor + public/assets),
|
|
11
|
+
// 3. reverse-proxies their data/API calls (/api, /raws, /clips, /composition,
|
|
12
|
+
// /auto-login, /editor/*/composition) to the logged-in cloud host,
|
|
13
|
+
// 4. intercepts the editor's render POST and runs it ON THIS MACHINE via the
|
|
14
|
+
// bundled hyperframes CLI (free local render — no backend needed; an
|
|
15
|
+
// explicit render_target:"cloud" still proxies to the billed renderer),
|
|
16
|
+
// 5. serves the same live-reload SSE the full backend does, driven by
|
|
17
|
+
// polling the cloud working copy of watched forks, so open editor tabs
|
|
18
|
+
// live-morph when `vidfarm publish` / another tab changes the fork, and
|
|
19
|
+
// 6. for every OTHER route, shows a small "this page lives in the cloud"
|
|
20
|
+
// interstitial with an open-in-new-tab button (browser page loads), so
|
|
21
|
+
// users aren't silently auto-forwarded off localhost; non-HTML requests
|
|
22
|
+
// still 302 to https://vidfarm.cc/* so programmatic fetches keep working.
|
|
23
|
+
//
|
|
24
|
+
// It imports only Node built-ins + the CLI's own backend-free helpers
|
|
25
|
+
// (auth-store, lib/package-root), so it stays inside the public static closure
|
|
26
|
+
// and never drags a backend module into the tarball.
|
|
27
|
+
import { createServer } from "node:http";
|
|
28
|
+
import { createReadStream, existsSync, readFileSync, statSync } from "node:fs";
|
|
29
|
+
import { mkdir } from "node:fs/promises";
|
|
30
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
31
|
+
import os from "node:os";
|
|
32
|
+
import path from "node:path";
|
|
33
|
+
import { Readable } from "node:stream";
|
|
34
|
+
import { resolveRootFileCandidates } from "../lib/package-root.js";
|
|
35
|
+
const DEFAULT_HOST = "https://vidfarm.cc";
|
|
36
|
+
// ── snapshot + asset resolution ─────────────────────────────────────────────
|
|
37
|
+
function readRootFile(relativePath) {
|
|
38
|
+
for (const candidate of resolveRootFileCandidates(relativePath)) {
|
|
39
|
+
if (existsSync(candidate)) {
|
|
40
|
+
try {
|
|
41
|
+
return readFileSync(candidate);
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
// keep trying other candidates
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
/** True when the shipped snapshots exist next to this CLI (or in the cwd). */
|
|
51
|
+
export function serveShellsPresent() {
|
|
52
|
+
return readRootFile("public/serve-shells/editor.html") !== null;
|
|
53
|
+
}
|
|
54
|
+
// The client-hydrated pages we can serve locally, mapped to their snapshot file.
|
|
55
|
+
// SSR-data pages (/library/approved, /library/logs) are intentionally absent —
|
|
56
|
+
// they render per-user data server-side, so a static snapshot would be stale;
|
|
57
|
+
// those get the cloud-only interstitial like every other non-served route.
|
|
58
|
+
function shellFileForPath(pathname) {
|
|
59
|
+
const p = pathname.replace(/\/+$/, "") || "/";
|
|
60
|
+
// Editor: any /editor or /editor/... EXCEPT the composition data sub-route.
|
|
61
|
+
if ((p === "/editor" || p.startsWith("/editor/")) && !p.endsWith("/composition")) {
|
|
62
|
+
return "editor.html";
|
|
63
|
+
}
|
|
64
|
+
// Bare /library mirrors the cloud default (Files view).
|
|
65
|
+
if (p === "/library" || p === "/library/files")
|
|
66
|
+
return "library-files.html";
|
|
67
|
+
if (p === "/library/raws" || p === "/library/clips")
|
|
68
|
+
return "library-raws.html";
|
|
69
|
+
if (p === "/tools" || p === "/tools/image" || p === "/inpaint" || p === "/inpaint/image")
|
|
70
|
+
return "tools-image.html";
|
|
71
|
+
if (p === "/tools/video" || p === "/inpaint/video")
|
|
72
|
+
return "tools-video.html";
|
|
73
|
+
if (p === "/tools/clipper" || p === "/inpaint/clipper")
|
|
74
|
+
return "tools-clipper.html";
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
// Prefixes whose requests are reverse-proxied to the cloud host (the pages'
|
|
78
|
+
// data plane). Everything the served shells fetch at runtime lives here.
|
|
79
|
+
const PROXY_PREFIXES = ["/api/", "/raws", "/clips", "/composition", "/auto-login", "/me/"];
|
|
80
|
+
function isProxyPath(pathname) {
|
|
81
|
+
if (pathname.startsWith("/editor/") && pathname.endsWith("/composition"))
|
|
82
|
+
return true;
|
|
83
|
+
return PROXY_PREFIXES.some((prefix) => prefix.endsWith("/") ? pathname.startsWith(prefix) : pathname === prefix || pathname.startsWith(prefix + "/"));
|
|
84
|
+
}
|
|
85
|
+
// ── default-fork resolution (editor) ────────────────────────────────────────
|
|
86
|
+
// The cloud's GET /editor/<templateId> resolves the caller's per-user fork
|
|
87
|
+
// (most-recent own fork → template default fork → mint one) and 302s to
|
|
88
|
+
// /editor/<tid>/fork/<forkId>. The SPA reads its fork id ONLY off that URL
|
|
89
|
+
// segment — without it, it loads the template's raw ORIGINAL composition and
|
|
90
|
+
// the decomposed default fork never appears. So a bare /editor/<templateId>
|
|
91
|
+
// page load asks the cloud where it WOULD redirect (redirect:"manual" + the
|
|
92
|
+
// stored api key, same trick as services/upstream.ts resolveUpstreamForkId)
|
|
93
|
+
// and replays that redirect locally. Best-effort: any failure falls through
|
|
94
|
+
// to serving the shell exactly as before.
|
|
95
|
+
const FORK_RESOLVE_TIMEOUT_MS = 15_000;
|
|
96
|
+
async function resolveEditorForkRedirect(host, pathname, search, auth) {
|
|
97
|
+
if (!auth?.apiKey)
|
|
98
|
+
return null;
|
|
99
|
+
// Only bare /editor/<template_...> (no /fork/ segment) needs resolution.
|
|
100
|
+
const match = pathname.match(/^\/editor\/(template_[^/]+)\/?$/);
|
|
101
|
+
if (!match)
|
|
102
|
+
return null;
|
|
103
|
+
try {
|
|
104
|
+
const res = await fetch(`${host}${pathname}${search}`, {
|
|
105
|
+
method: "GET",
|
|
106
|
+
redirect: "manual",
|
|
107
|
+
headers: { accept: "text/html", "vidfarm-api-key": auth.apiKey },
|
|
108
|
+
signal: AbortSignal.timeout(FORK_RESOLVE_TIMEOUT_MS)
|
|
109
|
+
});
|
|
110
|
+
if (res.status < 300 || res.status >= 400)
|
|
111
|
+
return null;
|
|
112
|
+
const location = res.headers.get("location");
|
|
113
|
+
if (!location)
|
|
114
|
+
return null;
|
|
115
|
+
const target = new URL(location, host);
|
|
116
|
+
// Follow only the fork-form editor redirect (never e.g. /login).
|
|
117
|
+
if (!/^\/editor\/[^/]+\/fork\/[^/]+\/?$/.test(target.pathname))
|
|
118
|
+
return null;
|
|
119
|
+
return `${target.pathname}${target.search}`;
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
// ── boot injection (editor) ─────────────────────────────────────────────────
|
|
126
|
+
/** Escape a JSON string for safe embedding inside a <script> element. */
|
|
127
|
+
function escapeJsonForHtml(value) {
|
|
128
|
+
return JSON.stringify(value)
|
|
129
|
+
.replace(/</g, "\\u003c")
|
|
130
|
+
.replace(/>/g, "\\u003e")
|
|
131
|
+
.replace(/&/g, "\\u0026");
|
|
132
|
+
}
|
|
133
|
+
function editorBoot(pathname, query, auth) {
|
|
134
|
+
const loggedIn = Boolean(auth?.apiKey);
|
|
135
|
+
const accountId = auth?.customerId ?? "";
|
|
136
|
+
// /editor/<templateId>[/fork/<forkId>] — a bare /editor (or /editor/original)
|
|
137
|
+
// is a fresh blank project the SPA persists client-side.
|
|
138
|
+
const match = pathname.match(/^\/editor\/([^/]+)/);
|
|
139
|
+
const templateId = match?.[1] && match[1] !== "original" ? decodeURIComponent(match[1]) : "original";
|
|
140
|
+
const isBlank = templateId === "original";
|
|
141
|
+
return {
|
|
142
|
+
compositionId: templateId,
|
|
143
|
+
inspirationId: null,
|
|
144
|
+
slugId: templateId,
|
|
145
|
+
templateId,
|
|
146
|
+
title: isBlank ? "Untitled project" : "VidFarm Studio",
|
|
147
|
+
origin: isBlank ? "raw" : null,
|
|
148
|
+
originalUrl: null,
|
|
149
|
+
songName: null,
|
|
150
|
+
songUrl: null,
|
|
151
|
+
initialThreadId: query.get("thread") || null,
|
|
152
|
+
jobRunsUrl: accountId ? `/job-runs?account=${encodeURIComponent(accountId)}` : "/job-runs",
|
|
153
|
+
// Logged out → free-tier client-only editor (IndexedDB autosave, no cloud
|
|
154
|
+
// persist/render). Logged in → the cloud account is paid, so drop the gate
|
|
155
|
+
// and let persist/chat/render proxy to the cloud host.
|
|
156
|
+
freeTier: !loggedIn,
|
|
157
|
+
editorChatApiUrl: "/api/v1/editor-chat",
|
|
158
|
+
vidfarmApiKey: auth?.apiKey ?? null,
|
|
159
|
+
// Renders run locally (the shell intercepts the render POST and drives the
|
|
160
|
+
// bundled hyperframes CLI — free; with a login the Render button becomes
|
|
161
|
+
// the local/cloud picker), and live-reload is on: the shell polls the
|
|
162
|
+
// cloud working copy of watched forks and pushes the same `reload` SSE the
|
|
163
|
+
// full-backend serve emits, so `vidfarm publish` / other tabs live-morph
|
|
164
|
+
// into an open editor instead of requiring a new tab.
|
|
165
|
+
liveReload: true,
|
|
166
|
+
localRender: true,
|
|
167
|
+
cloudRenderAvailable: loggedIn
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
function fillShell(shellFile, html, pathname, query, auth) {
|
|
171
|
+
const accountId = auth?.customerId ?? "";
|
|
172
|
+
let out = html.split("{{ACCOUNT_ID}}").join(accountId);
|
|
173
|
+
out = out.split("{{API_KEY}}").join(auth?.apiKey ?? "");
|
|
174
|
+
if (shellFile === "editor.html") {
|
|
175
|
+
const boot = editorBoot(pathname, query, auth);
|
|
176
|
+
out = out.split("{{HF_BOOT_JSON}}").join(escapeJsonForHtml(boot));
|
|
177
|
+
out = out.split("{{PAGE_TITLE}}").join(`${boot.title} | VidFarm Studio`);
|
|
178
|
+
}
|
|
179
|
+
return out;
|
|
180
|
+
}
|
|
181
|
+
// ── static asset serving ────────────────────────────────────────────────────
|
|
182
|
+
const MIME = {
|
|
183
|
+
".js": "text/javascript; charset=utf-8",
|
|
184
|
+
".mjs": "text/javascript; charset=utf-8",
|
|
185
|
+
".css": "text/css; charset=utf-8",
|
|
186
|
+
".map": "application/json; charset=utf-8",
|
|
187
|
+
".json": "application/json; charset=utf-8",
|
|
188
|
+
".html": "text/html; charset=utf-8",
|
|
189
|
+
".ico": "image/x-icon",
|
|
190
|
+
".png": "image/png",
|
|
191
|
+
".svg": "image/svg+xml",
|
|
192
|
+
".woff": "font/woff",
|
|
193
|
+
".woff2": "font/woff2"
|
|
194
|
+
};
|
|
195
|
+
/** Resolve an /assets/* URL to a repo-relative file, or null if unknown. */
|
|
196
|
+
function assetRelativePath(pathname) {
|
|
197
|
+
// The hyperframes trackpad editor bundle lives in demo/dist and is served
|
|
198
|
+
// under /assets/editor/*; everything else is a public/assets bundle.
|
|
199
|
+
if (pathname.startsWith("/assets/editor/")) {
|
|
200
|
+
const rest = pathname.slice("/assets/editor/".length);
|
|
201
|
+
if (rest.includes(".."))
|
|
202
|
+
return null;
|
|
203
|
+
return path.posix.join("demo/dist", rest);
|
|
204
|
+
}
|
|
205
|
+
if (pathname.startsWith("/assets/")) {
|
|
206
|
+
const rest = pathname.slice("/assets/".length);
|
|
207
|
+
if (rest.includes(".."))
|
|
208
|
+
return null;
|
|
209
|
+
return path.posix.join("public/assets", rest);
|
|
210
|
+
}
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
function serveAsset(res, pathname) {
|
|
214
|
+
// Sentry is backend-generated; a no-op stub keeps the shell's <script> from 404ing.
|
|
215
|
+
if (pathname === "/assets/sentry-config.js") {
|
|
216
|
+
res.statusCode = 200;
|
|
217
|
+
res.setHeader("content-type", MIME[".js"]);
|
|
218
|
+
res.setHeader("cache-control", "no-cache");
|
|
219
|
+
res.end("// sentry disabled (local frontend)\n");
|
|
220
|
+
return true;
|
|
221
|
+
}
|
|
222
|
+
const rel = assetRelativePath(pathname);
|
|
223
|
+
if (!rel)
|
|
224
|
+
return false;
|
|
225
|
+
const buf = readRootFile(rel);
|
|
226
|
+
if (!buf)
|
|
227
|
+
return false;
|
|
228
|
+
res.statusCode = 200;
|
|
229
|
+
res.setHeader("content-type", MIME[path.extname(rel)] ?? "application/octet-stream");
|
|
230
|
+
res.setHeader("cache-control", "no-cache");
|
|
231
|
+
res.end(buf);
|
|
232
|
+
return true;
|
|
233
|
+
}
|
|
234
|
+
// ── reverse proxy to the cloud host ─────────────────────────────────────────
|
|
235
|
+
// Hop-by-hop headers are stripped so we forward a clean request/response.
|
|
236
|
+
const HOP_BY_HOP = new Set([
|
|
237
|
+
"connection", "keep-alive", "proxy-authenticate", "proxy-authorization",
|
|
238
|
+
"te", "trailer", "transfer-encoding", "upgrade", "host", "content-length"
|
|
239
|
+
]);
|
|
240
|
+
function readBody(req) {
|
|
241
|
+
return new Promise((resolve, reject) => {
|
|
242
|
+
const chunks = [];
|
|
243
|
+
req.on("data", (chunk) => chunks.push(chunk));
|
|
244
|
+
req.on("end", () => resolve(Buffer.concat(chunks)));
|
|
245
|
+
req.on("error", reject);
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
async function proxyToCloud(req, res, host, pathWithSearch, auth, bodyOverride) {
|
|
249
|
+
const target = new URL(pathWithSearch, host);
|
|
250
|
+
const headers = {};
|
|
251
|
+
for (const [key, value] of Object.entries(req.headers)) {
|
|
252
|
+
if (value === undefined)
|
|
253
|
+
continue;
|
|
254
|
+
if (HOP_BY_HOP.has(key.toLowerCase()))
|
|
255
|
+
continue;
|
|
256
|
+
headers[key] = Array.isArray(value) ? value.join(", ") : value;
|
|
257
|
+
}
|
|
258
|
+
// Inject the persisted login so proxied data calls authenticate as the
|
|
259
|
+
// logged-in account even if the browser didn't attach the key itself.
|
|
260
|
+
if (auth?.apiKey) {
|
|
261
|
+
headers["vidfarm-api-key"] = auth.apiKey;
|
|
262
|
+
headers["authorization"] = `Bearer ${auth.apiKey}`;
|
|
263
|
+
}
|
|
264
|
+
const method = req.method ?? "GET";
|
|
265
|
+
const body = method === "GET" || method === "HEAD" ? undefined : (bodyOverride ?? await readBody(req));
|
|
266
|
+
let upstream;
|
|
267
|
+
try {
|
|
268
|
+
upstream = await fetch(target, { method, headers, body: body, redirect: "manual" });
|
|
269
|
+
}
|
|
270
|
+
catch (error) {
|
|
271
|
+
res.statusCode = 502;
|
|
272
|
+
res.setHeader("content-type", "application/json");
|
|
273
|
+
res.end(JSON.stringify({ error: "upstream_unreachable", detail: error instanceof Error ? error.message : String(error), host }));
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
res.statusCode = upstream.status;
|
|
277
|
+
upstream.headers.forEach((value, key) => {
|
|
278
|
+
if (HOP_BY_HOP.has(key.toLowerCase()))
|
|
279
|
+
return;
|
|
280
|
+
if (key.toLowerCase() === "content-encoding")
|
|
281
|
+
return; // fetch already decoded the body
|
|
282
|
+
res.setHeader(key, value);
|
|
283
|
+
});
|
|
284
|
+
res.setHeader("x-vidfarm-upstream", host);
|
|
285
|
+
if (upstream.body) {
|
|
286
|
+
// Stream the response (covers SSE from /api/v1/editor-chat too).
|
|
287
|
+
Readable.fromWeb(upstream.body).pipe(res);
|
|
288
|
+
}
|
|
289
|
+
else {
|
|
290
|
+
res.end();
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
// ── live reload (cloud-poll edition) ────────────────────────────────────────
|
|
294
|
+
// The full-backend serve watches the working composition ON DISK and pushes a
|
|
295
|
+
// `reload` SSE so an open editor tab live-morphs when an agent edits the file.
|
|
296
|
+
// This shell has no disk copy — the working state lives on the cloud host — so
|
|
297
|
+
// an open tab used to go stale the moment `vidfarm publish` (or another tab)
|
|
298
|
+
// updated the fork, and users learned to open new tabs. Same SSE contract
|
|
299
|
+
// (`/api/v1/dev/events`, event `reload`, data `{forkId}`), different change
|
|
300
|
+
// source: while at least one tab is subscribed, poll the cloud working copy of
|
|
301
|
+
// every fork a tab has touched and broadcast when its content hash moves.
|
|
302
|
+
// Self-saves are suppressed by pre-seeding the hash from proxied PUT bodies —
|
|
303
|
+
// the shell's equivalent of the backend's compositionWatch.suppressFork.
|
|
304
|
+
const LIVE_POLL_INTERVAL_MS = 3_000;
|
|
305
|
+
const LIVE_WATCH_TTL_MS = 30 * 60_000;
|
|
306
|
+
const LIVE_WATCH_MAX_FORKS = 8;
|
|
307
|
+
const watchedForks = new Map();
|
|
308
|
+
const sseClients = new Set();
|
|
309
|
+
let livePollTimer = null;
|
|
310
|
+
let livePollInFlight = false;
|
|
311
|
+
function contentHash(body) {
|
|
312
|
+
return createHash("sha256").update(body).digest("hex");
|
|
313
|
+
}
|
|
314
|
+
/** Record that a tab uses this fork; an explicit hash pre-seeds/suppresses. */
|
|
315
|
+
function noteForkSeen(forkId, hash) {
|
|
316
|
+
const existing = watchedForks.get(forkId);
|
|
317
|
+
watchedForks.set(forkId, { hash: hash ?? existing?.hash ?? null, lastSeen: Date.now() });
|
|
318
|
+
// Keep the watch list tiny — drop the stalest fork beyond the cap.
|
|
319
|
+
if (watchedForks.size > LIVE_WATCH_MAX_FORKS) {
|
|
320
|
+
let oldest = null;
|
|
321
|
+
let oldestSeen = Infinity;
|
|
322
|
+
for (const [id, entry] of watchedForks) {
|
|
323
|
+
if (entry.lastSeen < oldestSeen) {
|
|
324
|
+
oldest = id;
|
|
325
|
+
oldestSeen = entry.lastSeen;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
if (oldest)
|
|
329
|
+
watchedForks.delete(oldest);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
function broadcastReload(forkId) {
|
|
333
|
+
const frame = `event: reload\ndata: ${JSON.stringify({ forkId })}\n\n`;
|
|
334
|
+
for (const client of sseClients) {
|
|
335
|
+
try {
|
|
336
|
+
client.write(frame);
|
|
337
|
+
}
|
|
338
|
+
catch { /* dropped client — close handler prunes it */ }
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
async function pollWatchedForks(host, auth) {
|
|
342
|
+
if (livePollInFlight)
|
|
343
|
+
return;
|
|
344
|
+
livePollInFlight = true;
|
|
345
|
+
try {
|
|
346
|
+
const now = Date.now();
|
|
347
|
+
for (const [forkId, entry] of watchedForks) {
|
|
348
|
+
if (now - entry.lastSeen > LIVE_WATCH_TTL_MS) {
|
|
349
|
+
watchedForks.delete(forkId);
|
|
350
|
+
continue;
|
|
351
|
+
}
|
|
352
|
+
try {
|
|
353
|
+
const res = await fetch(`${host}/api/v1/compositions/${encodeURIComponent(forkId)}/composition.html`, {
|
|
354
|
+
headers: auth?.apiKey ? { "vidfarm-api-key": auth.apiKey } : {},
|
|
355
|
+
signal: AbortSignal.timeout(10_000)
|
|
356
|
+
});
|
|
357
|
+
if (!res.ok)
|
|
358
|
+
continue;
|
|
359
|
+
const hash = contentHash(await res.text());
|
|
360
|
+
if (entry.hash !== null && entry.hash !== hash)
|
|
361
|
+
broadcastReload(forkId);
|
|
362
|
+
entry.hash = hash;
|
|
363
|
+
}
|
|
364
|
+
catch { /* transient — retry next tick */ }
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
finally {
|
|
368
|
+
livePollInFlight = false;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
// Poll only while a tab is actually listening; idle shells make zero requests.
|
|
372
|
+
function syncLivePollLoop(host, auth) {
|
|
373
|
+
if (sseClients.size > 0 && !livePollTimer) {
|
|
374
|
+
livePollTimer = setInterval(() => { void pollWatchedForks(host, auth); }, LIVE_POLL_INTERVAL_MS);
|
|
375
|
+
livePollTimer.unref?.();
|
|
376
|
+
void pollWatchedForks(host, auth); // immediate baseline
|
|
377
|
+
}
|
|
378
|
+
else if (sseClients.size === 0 && livePollTimer) {
|
|
379
|
+
clearInterval(livePollTimer);
|
|
380
|
+
livePollTimer = null;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
function handleDevEventsStream(req, res, host, auth) {
|
|
384
|
+
res.statusCode = 200;
|
|
385
|
+
res.setHeader("content-type", "text/event-stream");
|
|
386
|
+
res.setHeader("cache-control", "no-store");
|
|
387
|
+
res.setHeader("connection", "keep-alive");
|
|
388
|
+
res.write(": connected\n\n");
|
|
389
|
+
sseClients.add(res);
|
|
390
|
+
syncLivePollLoop(host, auth);
|
|
391
|
+
// Keep intermediaries from timing the stream out.
|
|
392
|
+
const ping = setInterval(() => {
|
|
393
|
+
try {
|
|
394
|
+
res.write(": ping\n\n");
|
|
395
|
+
}
|
|
396
|
+
catch { /* close handler cleans up */ }
|
|
397
|
+
}, 25_000);
|
|
398
|
+
ping.unref?.();
|
|
399
|
+
req.on("close", () => {
|
|
400
|
+
clearInterval(ping);
|
|
401
|
+
sseClients.delete(res);
|
|
402
|
+
syncLivePollLoop(host, auth);
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
const localRenders = new Map();
|
|
406
|
+
const localRenderDir = path.join(os.tmpdir(), "vidfarm-serve-renders");
|
|
407
|
+
// Synthetic progress: hyperframes runs captured, so creep toward 93% on an
|
|
408
|
+
// elapsed-time estimate and snap to 1 on completion. UI-only.
|
|
409
|
+
const LOCAL_RENDER_ESTIMATE_MS = 3 * 60_000;
|
|
410
|
+
function localRenderStatusJson(renderId, record) {
|
|
411
|
+
const progress = record.status === "SUCCEEDED"
|
|
412
|
+
? 1
|
|
413
|
+
: record.status === "FAILED"
|
|
414
|
+
? 0
|
|
415
|
+
: Math.min(0.93, 0.04 + ((Date.now() - record.startedAt) / LOCAL_RENDER_ESTIMATE_MS) * 0.9);
|
|
416
|
+
return {
|
|
417
|
+
ok: true,
|
|
418
|
+
renderId,
|
|
419
|
+
render_target: "local",
|
|
420
|
+
title: record.title,
|
|
421
|
+
status: record.status,
|
|
422
|
+
progress,
|
|
423
|
+
cost: "$0.00 (local render)",
|
|
424
|
+
outputUrl: record.status === "SUCCEEDED" ? `/local-renders/${encodeURIComponent(renderId)}.mp4` : undefined,
|
|
425
|
+
error: record.error,
|
|
426
|
+
fatalErrorEncountered: record.status === "FAILED" ? true : undefined
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
function sendJson(res, status, payload) {
|
|
430
|
+
res.statusCode = status;
|
|
431
|
+
res.setHeader("content-type", "application/json; charset=utf-8");
|
|
432
|
+
res.setHeader("cache-control", "no-store");
|
|
433
|
+
res.end(JSON.stringify(payload));
|
|
434
|
+
}
|
|
435
|
+
async function startLocalRender(input) {
|
|
436
|
+
const submittedHtml = typeof input.body.html === "string" && input.body.html.includes("data-composition-id=")
|
|
437
|
+
? input.body.html
|
|
438
|
+
: null;
|
|
439
|
+
let compositionHtml = submittedHtml;
|
|
440
|
+
if (!compositionHtml) {
|
|
441
|
+
const res = await fetch(`${input.host}/api/v1/compositions/${encodeURIComponent(input.forkId)}/composition.html`, {
|
|
442
|
+
headers: input.auth?.apiKey ? { "vidfarm-api-key": input.auth.apiKey } : {}
|
|
443
|
+
});
|
|
444
|
+
if (!res.ok)
|
|
445
|
+
throw new Error(`Could not load composition.html for ${input.forkId} (${res.status}). Log in with \`vidfarm login\` or render from the editor (which submits the HTML).`);
|
|
446
|
+
compositionHtml = await res.text();
|
|
447
|
+
if (!compositionHtml.includes("data-composition-id=")) {
|
|
448
|
+
throw new Error(`Composition for ${input.forkId} is missing data-composition-id — not renderable.`);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
else if (input.auth?.apiKey) {
|
|
452
|
+
// The cloud route persists submitted HTML to the fork's working copy as a
|
|
453
|
+
// side effect of render; mirror that so the account's saved state matches
|
|
454
|
+
// what was rendered. Best-effort — the local render proceeds regardless.
|
|
455
|
+
void fetch(`${input.host}/api/v1/compositions/${encodeURIComponent(input.forkId)}/composition.html`, {
|
|
456
|
+
method: "PUT",
|
|
457
|
+
headers: { "content-type": "text/html; charset=utf-8", "vidfarm-api-key": input.auth.apiKey },
|
|
458
|
+
body: submittedHtml
|
|
459
|
+
}).catch(() => { });
|
|
460
|
+
}
|
|
461
|
+
const renderId = `local-${Date.now().toString(36)}-${randomUUID().slice(0, 8)}`;
|
|
462
|
+
const title = typeof input.body.title === "string" && input.body.title.trim() ? input.body.title.trim() : input.forkId;
|
|
463
|
+
const record = { status: "RUNNING", title, startedAt: Date.now() };
|
|
464
|
+
localRenders.set(renderId, record);
|
|
465
|
+
await mkdir(localRenderDir, { recursive: true });
|
|
466
|
+
const outputPath = path.join(localRenderDir, `${renderId}.mp4`);
|
|
467
|
+
void (async () => {
|
|
468
|
+
try {
|
|
469
|
+
const { renderCompositionLocally } = await import("./local-render.js");
|
|
470
|
+
await renderCompositionLocally({
|
|
471
|
+
compositionHtml: compositionHtml,
|
|
472
|
+
outputPath,
|
|
473
|
+
stdio: "capture"
|
|
474
|
+
});
|
|
475
|
+
record.status = "SUCCEEDED";
|
|
476
|
+
record.outputPath = outputPath;
|
|
477
|
+
record.endedAt = Date.now();
|
|
478
|
+
console.log(`[vidfarm] local render ${renderId} finished (${((record.endedAt - record.startedAt) / 1000).toFixed(1)}s) → ${outputPath}`);
|
|
479
|
+
}
|
|
480
|
+
catch (error) {
|
|
481
|
+
record.status = "FAILED";
|
|
482
|
+
record.error = error instanceof Error ? error.message : String(error);
|
|
483
|
+
record.endedAt = Date.now();
|
|
484
|
+
console.error(`[vidfarm] local render ${renderId} failed: ${record.error}`);
|
|
485
|
+
}
|
|
486
|
+
})();
|
|
487
|
+
return { renderId, record };
|
|
488
|
+
}
|
|
489
|
+
// ── cloud-only interstitial ─────────────────────────────────────────────────
|
|
490
|
+
// Shown instead of an auto-forwarding 302 when a browser navigates to a route
|
|
491
|
+
// the local frontend doesn't serve. Keeps the user oriented on localhost and
|
|
492
|
+
// opens the cloud page in a new tab.
|
|
493
|
+
function escapeHtml(value) {
|
|
494
|
+
return value
|
|
495
|
+
.replace(/&/g, "&")
|
|
496
|
+
.replace(/</g, "<")
|
|
497
|
+
.replace(/>/g, ">")
|
|
498
|
+
.replace(/"/g, """);
|
|
499
|
+
}
|
|
500
|
+
function renderCloudOnlyPage(cloudUrl, pathname) {
|
|
501
|
+
const safeUrl = escapeHtml(cloudUrl);
|
|
502
|
+
const safePath = escapeHtml(pathname);
|
|
503
|
+
return `<!doctype html>
|
|
504
|
+
<html lang="en">
|
|
505
|
+
<head>
|
|
506
|
+
<meta charset="utf-8">
|
|
507
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
508
|
+
<title>Available in cloud | VidFarm</title>
|
|
509
|
+
<style>
|
|
510
|
+
body{margin:0;min-height:100vh;display:flex;align-items:center;justify-content:center;background:#fbfbfb;color:#171717;font:15px/1.55 -apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}
|
|
511
|
+
.card{max-width:440px;margin:24px;padding:36px 32px;background:#fff;border:1px solid #ededed;border-radius:16px;box-shadow:0 4px 24px -8px #00000026,0 1px 2px #0000000d;text-align:center}
|
|
512
|
+
h1{margin:0 0 8px;font-size:19px}
|
|
513
|
+
p{margin:0 0 20px;color:#737373}
|
|
514
|
+
code{font-size:13px;background:#f5f5f5;border-radius:6px;padding:2px 6px}
|
|
515
|
+
.open{display:inline-block;padding:10px 22px;border-radius:999px;background:#ffc738;color:#171717;font-weight:600;text-decoration:none}
|
|
516
|
+
.open:hover{background:#fcb900}
|
|
517
|
+
.hint{margin-top:16px;font-size:13px;color:#a3a3a3}
|
|
518
|
+
</style>
|
|
519
|
+
</head>
|
|
520
|
+
<body>
|
|
521
|
+
<div class="card">
|
|
522
|
+
<h1>This page is available in cloud</h1>
|
|
523
|
+
<p><code>${safePath}</code> isn't served by the local devcli frontend — open it on VidFarm cloud with the link below.</p>
|
|
524
|
+
<a class="open" href="${safeUrl}" target="_blank" rel="noopener noreferrer">Open in VidFarm cloud</a>
|
|
525
|
+
<div class="hint">Local pages: /editor, /tools/*, /library, /library/raws, /library/files</div>
|
|
526
|
+
</div>
|
|
527
|
+
</body>
|
|
528
|
+
</html>
|
|
529
|
+
`;
|
|
530
|
+
}
|
|
531
|
+
export function startLocalFrontendServer(opts) {
|
|
532
|
+
const host = opts.host.replace(/\/+$/, "");
|
|
533
|
+
const server = createServer((req, res) => {
|
|
534
|
+
void handleRequest(req, res, host, opts.auth).catch((error) => {
|
|
535
|
+
if (!res.headersSent) {
|
|
536
|
+
res.statusCode = 500;
|
|
537
|
+
res.setHeader("content-type", "text/plain");
|
|
538
|
+
}
|
|
539
|
+
res.end(`Local frontend error: ${error instanceof Error ? error.message : String(error)}`);
|
|
540
|
+
});
|
|
541
|
+
});
|
|
542
|
+
return new Promise((resolve, reject) => {
|
|
543
|
+
server.once("error", reject);
|
|
544
|
+
server.listen(opts.port, () => resolve({ port: opts.port, host }));
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
async function handleRequest(req, res, host, auth) {
|
|
548
|
+
const rawUrl = req.url ?? "/";
|
|
549
|
+
const parsed = new URL(rawUrl, "http://localhost");
|
|
550
|
+
const pathname = parsed.pathname;
|
|
551
|
+
// 1. Static bundles (editor + public/assets).
|
|
552
|
+
if (pathname.startsWith("/assets/")) {
|
|
553
|
+
if (serveAsset(res, pathname))
|
|
554
|
+
return;
|
|
555
|
+
res.statusCode = 404;
|
|
556
|
+
res.end("Not found");
|
|
557
|
+
return;
|
|
558
|
+
}
|
|
559
|
+
// 2. Live-reload SSE for open editor tabs (see the live-reload section).
|
|
560
|
+
if (pathname === "/api/v1/dev/events" && req.method === "GET") {
|
|
561
|
+
handleDevEventsStream(req, res, host, auth);
|
|
562
|
+
return;
|
|
563
|
+
}
|
|
564
|
+
// Track which forks tabs are using, and pre-seed hashes from saves so a
|
|
565
|
+
// tab's own PUT never bounces back as a reload.
|
|
566
|
+
const compositionFile = pathname.match(/^\/api\/v1\/compositions\/([^/]+)\/composition\.html\/?$/);
|
|
567
|
+
if (compositionFile && (req.method === "PUT" || req.method === "POST")) {
|
|
568
|
+
const forkId = decodeURIComponent(compositionFile[1]);
|
|
569
|
+
const bodyBuf = await readBody(req);
|
|
570
|
+
noteForkSeen(forkId, contentHash(bodyBuf));
|
|
571
|
+
await proxyToCloud(req, res, host, pathname + parsed.search, auth, bodyBuf);
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
if (compositionFile && req.method === "GET") {
|
|
575
|
+
noteForkSeen(decodeURIComponent(compositionFile[1]));
|
|
576
|
+
}
|
|
577
|
+
const editorForkPage = req.method === "GET" ? pathname.match(/^\/editor\/[^/]+\/fork\/([^/]+)\/?$/) : null;
|
|
578
|
+
if (editorForkPage)
|
|
579
|
+
noteForkSeen(decodeURIComponent(editorForkPage[1]));
|
|
580
|
+
// 3. Render endpoints → run ON THIS MACHINE via the bundled hyperframes CLI
|
|
581
|
+
// (free), unless the caller explicitly asked for the billed cloud renderer.
|
|
582
|
+
const renderPost = req.method === "POST" ? pathname.match(/^\/api\/v1\/compositions\/([^/]+)\/render\/?$/) : null;
|
|
583
|
+
if (renderPost) {
|
|
584
|
+
const bodyBuf = await readBody(req);
|
|
585
|
+
let body = {};
|
|
586
|
+
try {
|
|
587
|
+
body = JSON.parse(bodyBuf.toString("utf8") || "{}");
|
|
588
|
+
}
|
|
589
|
+
catch { /* tolerate empty body */ }
|
|
590
|
+
const target = body.render_target ?? body.target;
|
|
591
|
+
if (target === "cloud") {
|
|
592
|
+
await proxyToCloud(req, res, host, pathname + parsed.search, auth, bodyBuf);
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
595
|
+
try {
|
|
596
|
+
const renderForkId = decodeURIComponent(renderPost[1]);
|
|
597
|
+
// The render intercept PUTs the submitted html back to the cloud working
|
|
598
|
+
// copy — pre-seed the hash so that save doesn't echo back as a reload.
|
|
599
|
+
if (typeof body.html === "string" && body.html)
|
|
600
|
+
noteForkSeen(renderForkId, contentHash(body.html));
|
|
601
|
+
const { renderId, record } = await startLocalRender({ host, auth, forkId: renderForkId, body });
|
|
602
|
+
sendJson(res, 202, localRenderStatusJson(renderId, record));
|
|
603
|
+
}
|
|
604
|
+
catch (error) {
|
|
605
|
+
sendJson(res, 400, { ok: false, error: error instanceof Error ? error.message : String(error) });
|
|
606
|
+
}
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
609
|
+
// Status polls for local renders; unknown render ids (cloud renders) proxy on.
|
|
610
|
+
const renderPoll = req.method === "GET" ? pathname.match(/^\/api\/v1\/compositions\/[^/]+\/renders\/([^/]+)\/?$/) : null;
|
|
611
|
+
if (renderPoll) {
|
|
612
|
+
const renderId = decodeURIComponent(renderPoll[1]);
|
|
613
|
+
const record = localRenders.get(renderId);
|
|
614
|
+
if (record) {
|
|
615
|
+
sendJson(res, 200, localRenderStatusJson(renderId, record));
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
// Finished local render MP4s.
|
|
620
|
+
const renderFile = req.method === "GET" ? pathname.match(/^\/local-renders\/([^/]+)\.mp4$/) : null;
|
|
621
|
+
if (renderFile) {
|
|
622
|
+
const record = localRenders.get(decodeURIComponent(renderFile[1]));
|
|
623
|
+
if (!record?.outputPath || !existsSync(record.outputPath)) {
|
|
624
|
+
res.statusCode = 404;
|
|
625
|
+
res.end("Render output not found");
|
|
626
|
+
return;
|
|
627
|
+
}
|
|
628
|
+
res.statusCode = 200;
|
|
629
|
+
res.setHeader("content-type", "video/mp4");
|
|
630
|
+
res.setHeader("content-length", String(statSync(record.outputPath).size));
|
|
631
|
+
res.setHeader("content-disposition", `inline; filename="vidfarm-${path.basename(record.outputPath)}"`);
|
|
632
|
+
createReadStream(record.outputPath).pipe(res);
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
635
|
+
// 4. Data plane → reverse-proxy to the cloud host.
|
|
636
|
+
if (isProxyPath(pathname)) {
|
|
637
|
+
await proxyToCloud(req, res, host, pathname + parsed.search, auth);
|
|
638
|
+
return;
|
|
639
|
+
}
|
|
640
|
+
// 5. Bare /editor/<templateId> → mirror the cloud's per-user default-fork
|
|
641
|
+
// resolution so the SPA lands on /editor/<tid>/fork/<forkId> (the decomposed
|
|
642
|
+
// fork) instead of the template's raw original composition.
|
|
643
|
+
if (req.method === "GET") {
|
|
644
|
+
const forkPath = await resolveEditorForkRedirect(host, pathname, parsed.search, auth);
|
|
645
|
+
if (forkPath) {
|
|
646
|
+
res.statusCode = 302;
|
|
647
|
+
res.setHeader("location", forkPath);
|
|
648
|
+
res.setHeader("cache-control", "no-store");
|
|
649
|
+
res.end();
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
// 6. Locally-served page shells (client-hydrated) → snapshot + boot fill.
|
|
654
|
+
const shellFile = shellFileForPath(pathname);
|
|
655
|
+
if (shellFile && req.method === "GET") {
|
|
656
|
+
const html = readRootFile(`public/serve-shells/${shellFile}`);
|
|
657
|
+
if (html) {
|
|
658
|
+
res.statusCode = 200;
|
|
659
|
+
res.setHeader("content-type", "text/html; charset=utf-8");
|
|
660
|
+
res.setHeader("cache-control", "no-cache");
|
|
661
|
+
res.end(fillShell(shellFile, html.toString("utf8"), pathname, parsed.searchParams, auth));
|
|
662
|
+
return;
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
// 7. Everything else lives on the cloud host. Browser page loads get an
|
|
666
|
+
// explicit interstitial (no confusing silent auto-forward off localhost);
|
|
667
|
+
// anything programmatic (non-GET, or not asking for HTML) still 302s.
|
|
668
|
+
const cloudUrl = `${host}${pathname}${parsed.search}`;
|
|
669
|
+
const wantsHtml = (req.headers.accept ?? "").includes("text/html");
|
|
670
|
+
if (req.method === "GET" && wantsHtml) {
|
|
671
|
+
res.statusCode = 200;
|
|
672
|
+
res.setHeader("content-type", "text/html; charset=utf-8");
|
|
673
|
+
res.setHeader("cache-control", "no-cache");
|
|
674
|
+
res.end(renderCloudOnlyPage(cloudUrl, pathname));
|
|
675
|
+
return;
|
|
676
|
+
}
|
|
677
|
+
res.statusCode = 302;
|
|
678
|
+
res.setHeader("location", cloudUrl);
|
|
679
|
+
res.end();
|
|
680
|
+
}
|
|
681
|
+
//# sourceMappingURL=local-frontend-server.js.map
|