@vfrey/vfrey 0.0.1 → 1.0.0-beta.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/LICENSE +33 -0
- package/README.md +78 -3
- package/accounts.example.json +61 -0
- package/accounts.first-run.json +41 -0
- package/bin/vfrey.mjs +36 -0
- package/integration-tools/README.md +43 -0
- package/integration-tools/image/pillow.md +32 -0
- package/integration-tools/image/playwright.md +33 -0
- package/integration-tools/image/resvg.md +36 -0
- package/integration-tools/image/satori.md +36 -0
- package/integration-tools/image/sharp.md +38 -0
- package/integration-tools/video/blender.md +34 -0
- package/integration-tools/video/ffmpeg.md +41 -0
- package/integration-tools/video/manim.md +37 -0
- package/integration-tools/video/motion-canvas.md +37 -0
- package/integration-tools/video/remotion.md +50 -0
- package/integration-tools/video/revideo.md +32 -0
- package/package.json +74 -6
- package/skills/vfrey-orchestrator/SKILL.md +15 -0
- package/skills/vfrey-orchestrator/channel-video/SKILL.md +41 -0
- package/skills/vfrey-product/SKILL.md +63 -0
- package/skills/vfrey-product/examples/concurrency-vs-parallelism/framey/cook-one.svg +14 -0
- package/skills/vfrey-product/examples/concurrency-vs-parallelism/framey/cook-two.svg +22 -0
- package/skills/vfrey-product/examples/concurrency-vs-parallelism/framey/index.html +70 -0
- package/skills/vfrey-product/examples/concurrency-vs-parallelism/remotion/cook-one.svg +14 -0
- package/skills/vfrey-product/examples/concurrency-vs-parallelism/remotion/cook-two.svg +22 -0
- package/skills/vfrey-product/examples/concurrency-vs-parallelism/remotion/index.tsx +49 -0
- package/skills/vfrey-product/examples/concurrency-vs-parallelism/script.json +15 -0
- package/skills/vfrey-product/examples/octopus-facts/index.html +60 -0
- package/skills/vfrey-product/examples/octopus-facts/octopus.svg +8 -0
- package/skills/vfrey-product/examples/octopus-facts/script.json +14 -0
- package/skills/vfrey-product/push/SKILL.md +15 -0
- package/skills/vfrey-product/scenes/NOTICE +5 -0
- package/skills/vfrey-product/scenes/blocks.md +57 -0
- package/skills/vfrey-product/scenes/craft.md +118 -0
- package/skills/vfrey-product/scenes/motion.md +88 -0
- package/src/analytics/dashboard.ts +109 -0
- package/src/analytics/index.ts +260 -0
- package/src/api/client.ts +282 -0
- package/src/api/commands.ts +330 -0
- package/src/api/config.ts +34 -0
- package/src/api/creators.ts +463 -0
- package/src/api/credentials.ts +152 -0
- package/src/api/mail.ts +333 -0
- package/src/api/topics.ts +207 -0
- package/src/api/types.ts +205 -0
- package/src/cli.ts +467 -0
- package/src/cloud/executor.ts +281 -0
- package/src/cloud/job.ts +106 -0
- package/src/cloud/render-job.ts +187 -0
- package/src/config.ts +309 -0
- package/src/dashboard-link.ts +180 -0
- package/src/dashboard-start.ts +103 -0
- package/src/desk/channel.ts +64 -0
- package/src/desk/cli.ts +216 -0
- package/src/desk/creators-route.ts +129 -0
- package/src/desk/fonts/JetBrainsMono-OFL.txt +93 -0
- package/src/desk/fonts/google-sans-flex-latin.woff2 +0 -0
- package/src/desk/fonts/jetbrains-mono-medium.woff2 +0 -0
- package/src/desk/http.ts +460 -0
- package/src/desk/mail-route.ts +103 -0
- package/src/desk/overlay.ts +112 -0
- package/src/desk/preview.ts +422 -0
- package/src/desk/screens.ts +149 -0
- package/src/desk/secrets.ts +143 -0
- package/src/desk/series.ts +127 -0
- package/src/desk/state.ts +77 -0
- package/src/desk/studio-route.ts +119 -0
- package/src/desk/tick-runner.ts +66 -0
- package/src/desk/tick.ts +56 -0
- package/src/desk/topics-route.ts +41 -0
- package/src/desk/ui/app.css +505 -0
- package/src/desk/ui/app.js +185 -0
- package/src/desk/ui/apple-icon.png +0 -0
- package/src/desk/ui/components.js +79 -0
- package/src/desk/ui/core.js +156 -0
- package/src/desk/ui/favicon.ico +0 -0
- package/src/desk/ui/icon.svg +1 -0
- package/src/desk/ui/icons.js +49 -0
- package/src/desk/ui/index.html +41 -0
- package/src/desk/ui/metrics-math.d.ts +22 -0
- package/src/desk/ui/metrics-math.js +135 -0
- package/src/desk/ui/theme.js +5 -0
- package/src/desk/ui/views-creators.js +209 -0
- package/src/desk/ui/views-keys.js +114 -0
- package/src/desk/ui/views-mail.js +188 -0
- package/src/desk/ui/views-misc.js +123 -0
- package/src/desk/ui/views-review.js +119 -0
- package/src/desk/ui/views-runs.js +350 -0
- package/src/desk/ui/views-screens.js +163 -0
- package/src/desk/ui/views-series.js +318 -0
- package/src/desk/ui/views-topics.js +87 -0
- package/src/desk/verbs.ts +361 -0
- package/src/doctor.ts +212 -0
- package/src/drain.ts +14 -0
- package/src/engage/index.ts +217 -0
- package/src/generate/index.ts +110 -0
- package/src/generate/script.ts +152 -0
- package/src/generate/title.ts +78 -0
- package/src/help.ts +233 -0
- package/src/home.ts +45 -0
- package/src/llm/client.ts +177 -0
- package/src/log.ts +7 -0
- package/src/oauth/tokens.ts +189 -0
- package/src/oauth/vault.ts +72 -0
- package/src/pipeline/review.ts +642 -0
- package/src/pipeline/run.ts +443 -0
- package/src/pipeline/series-guard.ts +26 -0
- package/src/pipeline/source-fetch.ts +69 -0
- package/src/pipeline/stages.ts +728 -0
- package/src/pipeline/topics-source.ts +90 -0
- package/src/product/commands.ts +296 -0
- package/src/product/config-file.ts +38 -0
- package/src/product/engine-choice.ts +89 -0
- package/src/product/engines.ts +103 -0
- package/src/product/insights.ts +50 -0
- package/src/product/keys.ts +67 -0
- package/src/product/paths.ts +18 -0
- package/src/product/probes.ts +88 -0
- package/src/product/push-flow.ts +90 -0
- package/src/product/render-video.ts +21 -0
- package/src/product/short.ts +62 -0
- package/src/product/update.ts +32 -0
- package/src/project/canonical.ts +14 -0
- package/src/project/commands.ts +177 -0
- package/src/project/dashboard-status.ts +28 -0
- package/src/project/discover.ts +19 -0
- package/src/project/index-file.ts +73 -0
- package/src/project/marker.ts +87 -0
- package/src/project/migrate.ts +201 -0
- package/src/project/names.ts +27 -0
- package/src/project/notice.ts +16 -0
- package/src/project/open.ts +88 -0
- package/src/project/scaffold.ts +112 -0
- package/src/project/templates.ts +42 -0
- package/src/project/videos.ts +64 -0
- package/src/publish/facebook.ts +91 -0
- package/src/publish/index.ts +12 -0
- package/src/publish/instagram.ts +97 -0
- package/src/publish/rate.ts +62 -0
- package/src/publish/session.ts +242 -0
- package/src/publish/tiktok.ts +202 -0
- package/src/publish/youtube.ts +177 -0
- package/src/queue/desk-ops.ts +1170 -0
- package/src/queue/seal.ts +3 -0
- package/src/queue/store.ts +1006 -0
- package/src/remotion/AiLabel.tsx +26 -0
- package/src/remotion/Root.tsx +73 -0
- package/src/remotion/defaults.ts +26 -0
- package/src/remotion/films/claude/ClaudeFilm.tsx +1014 -0
- package/src/remotion/films/engine/EngineMark.tsx +17 -0
- package/src/remotion/films/rea/ReaShort.tsx +552 -0
- package/src/remotion/films/rea/drawings.tsx +907 -0
- package/src/remotion/films/rea/timing.ts +144 -0
- package/src/remotion/index.ts +4 -0
- package/src/remotion/templates/EditorialSplit.tsx +39 -0
- package/src/remotion/templates/KineticType.tsx +39 -0
- package/src/remotion/templates/SignalBoard.tsx +50 -0
- package/src/remotion/timing.ts +22 -0
- package/src/render/chrome.ts +78 -0
- package/src/render/engine-mark.ts +29 -0
- package/src/render/framey-template.ts +130 -0
- package/src/render/html-engine.ts +149 -0
- package/src/render/node-crypto-stub.ts +11 -0
- package/src/render/probe.ts +29 -0
- package/src/render/props.ts +17 -0
- package/src/render/vertical-short.ts +35 -0
- package/src/render/webpack.ts +33 -0
- package/src/render/worker.ts +65 -0
- package/src/scene/assets/fonts/TitanOne-OFL.txt +94 -0
- package/src/scene/assets/fonts/titan-one.woff2 +0 -0
- package/src/scene/assets/gsap/LICENSE.txt +8 -0
- package/src/scene/assets/gsap/gsap.min.js +11 -0
- package/src/scene/audio.ts +54 -0
- package/src/scene/blocks/LICENSE +190 -0
- package/src/scene/blocks/NOTICE +5 -0
- package/src/scene/blocks/bar-chart-race/bar-chart-race.html +585 -0
- package/src/scene/blocks/beat-freeze-cut/beat-freeze-cut.html +874 -0
- package/src/scene/blocks/camcorder-hud/camcorder-hud.html +164 -0
- package/src/scene/blocks/camera-dolly-zoom/camera-dolly-zoom.html +355 -0
- package/src/scene/blocks/code-diff/code-diff.html +817 -0
- package/src/scene/blocks/code-snippet-apple-terminal-pro/code-snippet-apple-terminal-pro.html +248 -0
- package/src/scene/blocks/code-typing/code-typing.html +764 -0
- package/src/scene/blocks/data-chart/data-chart.html +452 -0
- package/src/scene/blocks/flowchart-vertical/flowchart-vertical.html +454 -0
- package/src/scene/blocks/hw-scribble-transition/hw-scribble-transition.html +153 -0
- package/src/scene/blocks/index.json +824 -0
- package/src/scene/blocks/lt-accent-underline/lt-accent-underline.html +107 -0
- package/src/scene/blocks/lt-bold-block/lt-bold-block.html +109 -0
- package/src/scene/blocks/lt-clean-bar/lt-clean-bar.html +112 -0
- package/src/scene/blocks/lt-dark-card/lt-dark-card.html +111 -0
- package/src/scene/blocks/lt-kicker-name/lt-kicker-name.html +114 -0
- package/src/scene/blocks/lt-mask-reveal/lt-mask-reveal.html +120 -0
- package/src/scene/blocks/lt-neon-border/lt-neon-border.html +391 -0
- package/src/scene/blocks/lt-stack-bars/lt-stack-bars.html +109 -0
- package/src/scene/blocks/macos-notification/macos-notification.html +194 -0
- package/src/scene/blocks/mk-background/mk-background.html +236 -0
- package/src/scene/blocks/mk-callout-highlight/mk-callout-highlight.html +183 -0
- package/src/scene/blocks/mk-clone-wall-transition/mk-clone-wall-transition.html +226 -0
- package/src/scene/blocks/mk-line-graph/mk-line-graph.html +293 -0
- package/src/scene/blocks/mk-progress-stat/mk-progress-stat.html +186 -0
- package/src/scene/blocks/mk-specs-list/mk-specs-list.html +223 -0
- package/src/scene/blocks/news-ticker/news-ticker.html +409 -0
- package/src/scene/blocks/organic-light-leak-overlay/organic-light-leak-overlay.html +126 -0
- package/src/scene/blocks/oscilloscope-trace/oscilloscope-trace.html +519 -0
- package/src/scene/blocks/snow/snow.html +205 -0
- package/src/scene/blocks/split-flap-board/split-flap-board.html +347 -0
- package/src/scene/blocks/spotify-card/spotify-card.html +294 -0
- package/src/scene/blocks/starfield/starfield.html +188 -0
- package/src/scene/blocks/thread-message-stack/thread-message-stack.html +239 -0
- package/src/scene/blocks/week-in-merges/week-in-merges.html +470 -0
- package/src/scene/blocks/x-post/x-post.html +437 -0
- package/src/scene/blocks/yt-comment-card/yt-comment-card.html +319 -0
- package/src/scene/blocks/yt-lcd-background/yt-lcd-background.html +307 -0
- package/src/scene/blocks/yt-prism-title/yt-prism-title.html +205 -0
- package/src/scene/blocks.ts +229 -0
- package/src/scene/command.ts +272 -0
- package/src/scene/figure.ts +181 -0
- package/src/scene/glyphs.ts +107 -0
- package/src/scene/init.ts +86 -0
- package/src/scene/inspect/css-tree.d.ts +6 -0
- package/src/scene/inspect/page-scripts.ts +272 -0
- package/src/scene/inspect/report.ts +405 -0
- package/src/scene/inspect/run.ts +200 -0
- package/src/scene/inspect/source.ts +236 -0
- package/src/scene/label.ts +131 -0
- package/src/scene/net-guard.ts +74 -0
- package/src/scene/render.ts +292 -0
- package/src/scene/runtime/browser.ts +199 -0
- package/src/scene/runtime/core.ts +181 -0
- package/src/scene/runtime/react.tsx +115 -0
- package/src/scene/runtime/transitions.ts +133 -0
- package/src/scene/sanitize.ts +181 -0
- package/src/scene/scene.ts +152 -0
- package/src/scene/studio-command.ts +48 -0
- package/src/scene/studio-skin.ts +59 -0
- package/src/scene/studio.ts +337 -0
- package/src/scene/supply.ts +111 -0
- package/src/scene/timing.ts +288 -0
- package/src/scene/transitions/LICENSE +190 -0
- package/src/scene/transitions/NOTICE +5 -0
- package/src/scene/transitions/SOURCE.json +24 -0
- package/src/scene/transitions/transitions.global.js +38 -0
- package/src/scene/transitions.ts +26 -0
- package/src/service/all-projects.ts +153 -0
- package/src/service/commands.ts +256 -0
- package/src/service/due.ts +82 -0
- package/src/service/lock.ts +101 -0
- package/src/service/log-file.ts +56 -0
- package/src/service/state.ts +41 -0
- package/src/service/units.ts +90 -0
- package/src/service/worker.ts +51 -0
- package/src/skill-agents.ts +74 -0
- package/src/skill-install.ts +334 -0
- package/src/sync-write.ts +21 -0
- package/src/tick-report.ts +205 -0
- package/src/tts.ts +124 -0
- package/vendor/@vfrey/contracts/dist/account.d.ts +97 -0
- package/vendor/@vfrey/contracts/dist/account.js +27 -0
- package/vendor/@vfrey/contracts/dist/checks.d.ts +8 -0
- package/vendor/@vfrey/contracts/dist/checks.js +1 -0
- package/vendor/@vfrey/contracts/dist/credentials/crypto.d.ts +4 -0
- package/vendor/@vfrey/contracts/dist/credentials/crypto.js +38 -0
- package/vendor/@vfrey/contracts/dist/domain/advice-persona.d.ts +10 -0
- package/vendor/@vfrey/contracts/dist/domain/advice-persona.js +24 -0
- package/vendor/@vfrey/contracts/dist/domain/backoff.d.ts +13 -0
- package/vendor/@vfrey/contracts/dist/domain/backoff.js +27 -0
- package/vendor/@vfrey/contracts/dist/domain/cadence.d.ts +29 -0
- package/vendor/@vfrey/contracts/dist/domain/cadence.js +95 -0
- package/vendor/@vfrey/contracts/dist/domain/creators.d.ts +146 -0
- package/vendor/@vfrey/contracts/dist/domain/creators.js +2 -0
- package/vendor/@vfrey/contracts/dist/domain/disclosure.d.ts +127 -0
- package/vendor/@vfrey/contracts/dist/domain/disclosure.js +250 -0
- package/vendor/@vfrey/contracts/dist/domain/errors.d.ts +34 -0
- package/vendor/@vfrey/contracts/dist/domain/errors.js +68 -0
- package/vendor/@vfrey/contracts/dist/domain/redact.d.ts +1 -0
- package/vendor/@vfrey/contracts/dist/domain/redact.js +21 -0
- package/vendor/@vfrey/contracts/dist/domain/revenue.d.ts +13 -0
- package/vendor/@vfrey/contracts/dist/domain/revenue.js +16 -0
- package/vendor/@vfrey/contracts/dist/domain/safety.d.ts +20 -0
- package/vendor/@vfrey/contracts/dist/domain/safety.js +61 -0
- package/vendor/@vfrey/contracts/dist/domain/similarity.d.ts +13 -0
- package/vendor/@vfrey/contracts/dist/domain/similarity.js +100 -0
- package/vendor/@vfrey/contracts/dist/domain/templates.d.ts +18 -0
- package/vendor/@vfrey/contracts/dist/domain/templates.js +67 -0
- package/vendor/@vfrey/contracts/dist/domain/tiktok-chunks.d.ts +19 -0
- package/vendor/@vfrey/contracts/dist/domain/tiktok-chunks.js +52 -0
- package/vendor/@vfrey/contracts/dist/domain/topics.d.ts +78 -0
- package/vendor/@vfrey/contracts/dist/domain/topics.js +4 -0
- package/vendor/@vfrey/contracts/dist/domain/types.d.ts +216 -0
- package/vendor/@vfrey/contracts/dist/domain/types.js +1 -0
- package/vendor/@vfrey/contracts/dist/domain/variation.d.ts +57 -0
- package/vendor/@vfrey/contracts/dist/domain/variation.js +278 -0
- package/vendor/@vfrey/contracts/dist/events.d.ts +20 -0
- package/vendor/@vfrey/contracts/dist/events.js +1 -0
- package/vendor/@vfrey/contracts/dist/http.d.ts +11 -0
- package/vendor/@vfrey/contracts/dist/http.js +37 -0
- package/vendor/@vfrey/contracts/dist/index.d.ts +22 -0
- package/vendor/@vfrey/contracts/dist/index.js +22 -0
- package/vendor/@vfrey/contracts/dist/log.d.ts +2 -0
- package/vendor/@vfrey/contracts/dist/log.js +10 -0
- package/vendor/@vfrey/contracts/dist/mail.d.ts +55 -0
- package/vendor/@vfrey/contracts/dist/mail.js +1 -0
- package/vendor/@vfrey/contracts/package.json +18 -0
- package/vendor/@vfrey/ingest/dist/index.d.ts +41 -0
- package/vendor/@vfrey/ingest/dist/index.js +221 -0
- package/vendor/@vfrey/ingest/dist/parse.d.ts +5 -0
- package/vendor/@vfrey/ingest/dist/parse.js +165 -0
- package/vendor/@vfrey/ingest/package.json +21 -0
- package/vendor/framey/LICENSE +190 -0
- package/vendor/framey/NOTICE +15 -0
- package/vendor/framey/SOURCE.json +24 -0
- package/vendor/framey/STUDIO-SOURCE.json +9 -0
- package/vendor/framey/bin/framey-localize-fonts.mjs +12 -0
- package/vendor/framey/bin/framey.mjs +16 -0
- package/vendor/framey/dist/add-VMJD5KBS.js +75 -0
- package/vendor/framey/dist/animatedGifPrep-QPKGJ6PM.js +43 -0
- package/vendor/framey/dist/api.d.ts +211 -0
- package/vendor/framey/dist/api.js +76 -0
- package/vendor/framey/dist/appHistory-WW56FZTT.js +31 -0
- package/vendor/framey/dist/asset-WQKGOELE.js +26 -0
- package/vendor/framey/dist/audio-RSJWVWRA.js +1936 -0
- package/vendor/framey/dist/batchRender-3HPICDCT.js +366 -0
- package/vendor/framey/dist/beats-HL2BDUZI.js +273 -0
- package/vendor/framey/dist/benchmark-RWCWHI5Z.js +230 -0
- package/vendor/framey/dist/browser-ECYET3Q2.js +206 -0
- package/vendor/framey/dist/bundleWithLocalizedFonts-ALYBQEQT.js +52 -0
- package/vendor/framey/dist/captureCompositionFrame-4DA6SEH2.js +94 -0
- package/vendor/framey/dist/catalog-5JVDEUWK.js +979 -0
- package/vendor/framey/dist/catch-up-NEBCL5NO.js +106 -0
- package/vendor/framey/dist/check-F5N544ML.js +111 -0
- package/vendor/framey/dist/checkBrowser-LZ66CMJG.js +110 -0
- package/vendor/framey/dist/chunk-2K7Z2HVP.js +372 -0
- package/vendor/framey/dist/chunk-2UZQHDM7.js +323 -0
- package/vendor/framey/dist/chunk-2X4PQEYM.js +34 -0
- package/vendor/framey/dist/chunk-3422KMTD.js +168 -0
- package/vendor/framey/dist/chunk-3FYD4YEK.js +72 -0
- package/vendor/framey/dist/chunk-3GXQPCJC.js +682 -0
- package/vendor/framey/dist/chunk-3LM6YPBR.js +179 -0
- package/vendor/framey/dist/chunk-442JNXRM.js +20 -0
- package/vendor/framey/dist/chunk-4BNN5FGX.js +182 -0
- package/vendor/framey/dist/chunk-4P7NX5BM.js +11532 -0
- package/vendor/framey/dist/chunk-4UMPIPCQ.js +88 -0
- package/vendor/framey/dist/chunk-4YYJOFKS.js +28 -0
- package/vendor/framey/dist/chunk-54HJ63Q2.js +29 -0
- package/vendor/framey/dist/chunk-5ETJX6WM.js +89 -0
- package/vendor/framey/dist/chunk-5SG54JRS.js +68 -0
- package/vendor/framey/dist/chunk-634HSJ3J.js +58 -0
- package/vendor/framey/dist/chunk-66LREXGS.js +48 -0
- package/vendor/framey/dist/chunk-6BEFEWFB.js +25 -0
- package/vendor/framey/dist/chunk-6BNTGNGD.js +286 -0
- package/vendor/framey/dist/chunk-6DDPOHMK.js +140 -0
- package/vendor/framey/dist/chunk-6GV5XSOR.js +214 -0
- package/vendor/framey/dist/chunk-6JR2XUEE.js +127 -0
- package/vendor/framey/dist/chunk-73QVATIM.js +362 -0
- package/vendor/framey/dist/chunk-7FMUTGOH.js +279 -0
- package/vendor/framey/dist/chunk-7JQFJ6JJ.js +291 -0
- package/vendor/framey/dist/chunk-AHPGAESX.js +113 -0
- package/vendor/framey/dist/chunk-AHX34EFP.js +15 -0
- package/vendor/framey/dist/chunk-AJ5CSAEZ.js +487 -0
- package/vendor/framey/dist/chunk-B7RJV74O.js +5047 -0
- package/vendor/framey/dist/chunk-BL4U6U52.js +254 -0
- package/vendor/framey/dist/chunk-BSQ7XANM.js +210 -0
- package/vendor/framey/dist/chunk-BZ56MTDX.js +6 -0
- package/vendor/framey/dist/chunk-C5BO7PMT.js +282 -0
- package/vendor/framey/dist/chunk-CFUCHO4T.js +358 -0
- package/vendor/framey/dist/chunk-CZGHFATT.js +31 -0
- package/vendor/framey/dist/chunk-DLA44CMS.js +5649 -0
- package/vendor/framey/dist/chunk-DPVXBVLL.js +31292 -0
- package/vendor/framey/dist/chunk-DQBY7FRR.js +397 -0
- package/vendor/framey/dist/chunk-EAMLBAHA.js +154 -0
- package/vendor/framey/dist/chunk-EOVYQ3IG.js +143 -0
- package/vendor/framey/dist/chunk-EVMY7IPL.js +612 -0
- package/vendor/framey/dist/chunk-FG4BBFZI.js +122 -0
- package/vendor/framey/dist/chunk-FI4I7CME.js +56 -0
- package/vendor/framey/dist/chunk-FLREUEHW.js +2081 -0
- package/vendor/framey/dist/chunk-FUFP7A7S.js +1126 -0
- package/vendor/framey/dist/chunk-FV3PIKGH.js +56 -0
- package/vendor/framey/dist/chunk-FXMKHHPO.js +123 -0
- package/vendor/framey/dist/chunk-GGZAM5VH.js +1187 -0
- package/vendor/framey/dist/chunk-GKZEVJIB.js +39 -0
- package/vendor/framey/dist/chunk-GMECXFAN.js +57 -0
- package/vendor/framey/dist/chunk-GNEFDLFB.js +621 -0
- package/vendor/framey/dist/chunk-GPMLSOEG.js +19274 -0
- package/vendor/framey/dist/chunk-GPYVT5EE.js +59 -0
- package/vendor/framey/dist/chunk-H6IEMZEV.js +40 -0
- package/vendor/framey/dist/chunk-HEFQCIXR.js +170 -0
- package/vendor/framey/dist/chunk-HFQ4CWHA.js +118 -0
- package/vendor/framey/dist/chunk-HPDTCOKE.js +304 -0
- package/vendor/framey/dist/chunk-HTUHQKDB.js +548 -0
- package/vendor/framey/dist/chunk-HU2SPFNG.js +103 -0
- package/vendor/framey/dist/chunk-HVW7ABXD.js +19 -0
- package/vendor/framey/dist/chunk-IG7NDKXO.js +26 -0
- package/vendor/framey/dist/chunk-II3J3WXK.js +246 -0
- package/vendor/framey/dist/chunk-IR35BCLK.js +13 -0
- package/vendor/framey/dist/chunk-ISN5GRYI.js +20 -0
- package/vendor/framey/dist/chunk-IUONB55J.js +669 -0
- package/vendor/framey/dist/chunk-IVSUQR2B.js +161 -0
- package/vendor/framey/dist/chunk-IXU6PQSW.js +259 -0
- package/vendor/framey/dist/chunk-IZCW2NKT.js +29 -0
- package/vendor/framey/dist/chunk-JGXJU5PF.js +68 -0
- package/vendor/framey/dist/chunk-JHOCK3DG.js +27 -0
- package/vendor/framey/dist/chunk-JVBRJMOP.js +121 -0
- package/vendor/framey/dist/chunk-K4MJX7SR.js +17 -0
- package/vendor/framey/dist/chunk-K4WKGXRC.js +18 -0
- package/vendor/framey/dist/chunk-KGPLIH7B.js +127 -0
- package/vendor/framey/dist/chunk-KLIKCX6Q.js +63 -0
- package/vendor/framey/dist/chunk-KXTKKBOC.js +61 -0
- package/vendor/framey/dist/chunk-KZI44OPA.js +185 -0
- package/vendor/framey/dist/chunk-LEKZNRKI.js +45 -0
- package/vendor/framey/dist/chunk-LFGJWOVA.js +61 -0
- package/vendor/framey/dist/chunk-LLMSX6V6.js +17 -0
- package/vendor/framey/dist/chunk-LTGTN2TH.js +195 -0
- package/vendor/framey/dist/chunk-M2WBOQ2Q.js +54 -0
- package/vendor/framey/dist/chunk-MI5FTLYK.js +399 -0
- package/vendor/framey/dist/chunk-MLQP2QUF.js +1809 -0
- package/vendor/framey/dist/chunk-MMQ52NIZ.js +956 -0
- package/vendor/framey/dist/chunk-ND46XKK3.js +742 -0
- package/vendor/framey/dist/chunk-NYEXNSHM.js +504 -0
- package/vendor/framey/dist/chunk-O4YRB27L.js +57 -0
- package/vendor/framey/dist/chunk-O7BGSQ5F.js +92 -0
- package/vendor/framey/dist/chunk-OFQWTKPW.js +117 -0
- package/vendor/framey/dist/chunk-P5ZBLXJ6.js +91 -0
- package/vendor/framey/dist/chunk-P6LJ4Y32.js +4662 -0
- package/vendor/framey/dist/chunk-PKZ7RK5N.js +281 -0
- package/vendor/framey/dist/chunk-PURHAXFL.js +15 -0
- package/vendor/framey/dist/chunk-Q5X2B5WT.js +29 -0
- package/vendor/framey/dist/chunk-QAMKOG67.js +248 -0
- package/vendor/framey/dist/chunk-R6YBK4XX.js +514 -0
- package/vendor/framey/dist/chunk-RGLQURIX.js +2997 -0
- package/vendor/framey/dist/chunk-RNV3DM4S.js +3573 -0
- package/vendor/framey/dist/chunk-ROEW726S.js +4638 -0
- package/vendor/framey/dist/chunk-RQVDTPGD.js +6 -0
- package/vendor/framey/dist/chunk-RVZHGOGR.js +26 -0
- package/vendor/framey/dist/chunk-RXATE6FZ.js +385 -0
- package/vendor/framey/dist/chunk-S3S4TGEH.js +424 -0
- package/vendor/framey/dist/chunk-SDYDNHEE.js +11202 -0
- package/vendor/framey/dist/chunk-SSWBU67I.js +173 -0
- package/vendor/framey/dist/chunk-ST35C4K6.js +28 -0
- package/vendor/framey/dist/chunk-SZNTYTIV.js +990 -0
- package/vendor/framey/dist/chunk-T2EUWCJE.js +29 -0
- package/vendor/framey/dist/chunk-T6AGUTZJ.js +444 -0
- package/vendor/framey/dist/chunk-THJALHFJ.js +11131 -0
- package/vendor/framey/dist/chunk-TIQB63MX.js +18128 -0
- package/vendor/framey/dist/chunk-U242RTCT.js +134 -0
- package/vendor/framey/dist/chunk-UJZREAV6.js +3901 -0
- package/vendor/framey/dist/chunk-UMD3V7CU.js +264 -0
- package/vendor/framey/dist/chunk-UOFAE5RF.js +42 -0
- package/vendor/framey/dist/chunk-UTEIUGAZ.js +29 -0
- package/vendor/framey/dist/chunk-VAS6HQX3.js +138 -0
- package/vendor/framey/dist/chunk-VC76R6W2.js +27 -0
- package/vendor/framey/dist/chunk-VFZ7W5W7.js +226 -0
- package/vendor/framey/dist/chunk-VHNLZAE2.js +348 -0
- package/vendor/framey/dist/chunk-VMC4WSH5.js +278 -0
- package/vendor/framey/dist/chunk-VORQIH64.js +108 -0
- package/vendor/framey/dist/chunk-VTXFRIHO.js +186 -0
- package/vendor/framey/dist/chunk-W2LGJSEN.js +182 -0
- package/vendor/framey/dist/chunk-WKBM7NDG.js +32 -0
- package/vendor/framey/dist/chunk-WMQNIQDJ.js +2057 -0
- package/vendor/framey/dist/chunk-WN4XZC5F.js +504 -0
- package/vendor/framey/dist/chunk-WQ6ZXHFQ.js +133 -0
- package/vendor/framey/dist/chunk-X7HBSTCZ.js +53 -0
- package/vendor/framey/dist/chunk-X7MTZ4T2.js +6 -0
- package/vendor/framey/dist/chunk-XPTOJZJF.js +308 -0
- package/vendor/framey/dist/chunk-XT6MXC7A.js +6 -0
- package/vendor/framey/dist/chunk-XT7X6QCP.js +6 -0
- package/vendor/framey/dist/chunk-XTMDWNEC.js +2820 -0
- package/vendor/framey/dist/chunk-XUHOIDKG.js +6 -0
- package/vendor/framey/dist/chunk-XX7LTW5Y.js +138 -0
- package/vendor/framey/dist/chunk-Y3YGTZEG.js +382 -0
- package/vendor/framey/dist/chunk-Y5SBSGN7.js +346 -0
- package/vendor/framey/dist/chunk-YCMVI4MZ.js +625 -0
- package/vendor/framey/dist/chunk-YOR4CTCE.js +261 -0
- package/vendor/framey/dist/chunk-YPRBGITP.js +2748 -0
- package/vendor/framey/dist/chunk-ZKDMAOFU.js +32 -0
- package/vendor/framey/dist/chunk-ZW6DURPF.js +1318 -0
- package/vendor/framey/dist/clean-ZJOGDKDM.js +336 -0
- package/vendor/framey/dist/cli.js +439 -0
- package/vendor/framey/dist/cloudrun-HGJ3B6ZV.js +767 -0
- package/vendor/framey/dist/commands/contrast-audit.browser.js +643 -0
- package/vendor/framey/dist/commands/layout-audit.browser.js +2914 -0
- package/vendor/framey/dist/commands/motion-sample.browser.js +74 -0
- package/vendor/framey/dist/commands/motion-signature.browser.js +475 -0
- package/vendor/framey/dist/compare-W7UY3BAZ.js +463 -0
- package/vendor/framey/dist/compiler-TM3W6GYF.js +136 -0
- package/vendor/framey/dist/component-QF5VGYMU.js +176 -0
- package/vendor/framey/dist/compositions-XT3PMNDF.js +238 -0
- package/vendor/framey/dist/contactSheet-7W6GYHBQ.js +24 -0
- package/vendor/framey/dist/deploy-6FJPWKF2.js +299 -0
- package/vendor/framey/dist/destroy-HE2RIPKZ.js +57 -0
- package/vendor/framey/dist/deterministicFonts-RADD3BSX.js +50 -0
- package/vendor/framey/dist/dist-4S2FGGVY.js +422 -0
- package/vendor/framey/dist/dist-BE4B2EZE.js +32 -0
- package/vendor/framey/dist/dist-EWXJGN63.js +129 -0
- package/vendor/framey/dist/docs-VKIRFEEX.js +131 -0
- package/vendor/framey/dist/doctor-2UNJ2G7U.js +74 -0
- package/vendor/framey/dist/dom-37K6YOH4.js +14 -0
- package/vendor/framey/dist/dotEnv-7PZQTVIM.js +54 -0
- package/vendor/framey/dist/events-ACY3P2PA.js +76 -0
- package/vendor/framey/dist/events-IHB3B2MK.js +69 -0
- package/vendor/framey/dist/ffmpeg-DR5LGGSK.js +63 -0
- package/vendor/framey/dist/figma-BODVUPHB.js +63 -0
- package/vendor/framey/dist/fontData.generated-NXAOI4GH.js +13 -0
- package/vendor/framey/dist/fontLocalizeCli.js +142 -0
- package/vendor/framey/dist/gpuPolicy-XLEXORZY.js +49 -0
- package/vendor/framey/dist/grade-compare-GAXFQ74C.js +661 -0
- package/vendor/framey/dist/gsapParser-IEUSLYDJ.js +2885 -0
- package/vendor/framey/dist/gsapParserAcorn-LZJKCFQI.js +30 -0
- package/vendor/framey/dist/help-VCB5X2HS.js +184 -0
- package/vendor/framey/dist/history-ENGZEVFT.js +471 -0
- package/vendor/framey/dist/hyperframe.manifest.json +22 -0
- package/vendor/framey/dist/hyperframe.runtime.iife.js +1132 -0
- package/vendor/framey/dist/hyperframes-player.global.js +478 -0
- package/vendor/framey/dist/hyperframes-slideshow.global.js +148 -0
- package/vendor/framey/dist/info-WPCJHBMU.js +182 -0
- package/vendor/framey/dist/init-G4WGLYGD.js +1176 -0
- package/vendor/framey/dist/inspect-5IQ6CNEQ.js +106 -0
- package/vendor/framey/dist/installer-ML4HWUV7.js +40 -0
- package/vendor/framey/dist/keyframes-Q6XJO7VW.js +2755 -0
- package/vendor/framey/dist/lambda-A7JC74JF.js +468 -0
- package/vendor/framey/dist/layout-YET6XNXA.js +95 -0
- package/vendor/framey/dist/lint-I7FZN2VJ.js +162 -0
- package/vendor/framey/dist/lintProject-PDB3UN72.js +30 -0
- package/vendor/framey/dist/manager-CD6LJ7ZN.js +45 -0
- package/vendor/framey/dist/manager-WA4F2GST.js +67 -0
- package/vendor/framey/dist/media-treatment-BXYS4WVA.js +1030 -0
- package/vendor/framey/dist/media-use-LWH3MANK.js +113 -0
- package/vendor/framey/dist/models-K363FYM4.js +195 -0
- package/vendor/framey/dist/motionShot-GPAVWXLH.js +640 -0
- package/vendor/framey/dist/normalize-FASIPBUM.js +24 -0
- package/vendor/framey/dist/normalize-audio-VDWYRHN6.js +268 -0
- package/vendor/framey/dist/open-E3TB6P6J.js +128 -0
- package/vendor/framey/dist/parakeetRunner-UP34UOI2.js +58 -0
- package/vendor/framey/dist/pipeline-VXIUYEBU.js +64 -0
- package/vendor/framey/dist/play-BUTDQ4DV.js +353 -0
- package/vendor/framey/dist/policies-OLDYGFK5.js +309 -0
- package/vendor/framey/dist/preflight-2KZ2GWAZ.js +55 -0
- package/vendor/framey/dist/present-GXYSM7NX.js +353 -0
- package/vendor/framey/dist/preview-EKZ33JOJ.js +1910 -0
- package/vendor/framey/dist/producer-SKU5EV3J.js +13 -0
- package/vendor/framey/dist/progress-ZWB4Z4ET.js +76 -0
- package/vendor/framey/dist/projectConfig-3MZAYVTS.js +60 -0
- package/vendor/framey/dist/publication-DTKWNF74.js +33 -0
- package/vendor/framey/dist/remove-background-OY5L7XN2.js +265 -0
- package/vendor/framey/dist/render-CDV4GDFX.js +187 -0
- package/vendor/framey/dist/render-YE5YAMOD.js +2620 -0
- package/vendor/framey/dist/render-batch-ZAMFPCLL.js +274 -0
- package/vendor/framey/dist/renderSetupWorker.js +120 -0
- package/vendor/framey/dist/resolver-D5BIPSEW.js +42 -0
- package/vendor/framey/dist/runtimeVersion.js +18 -0
- package/vendor/framey/dist/sam-WFZXI4F7.js +23 -0
- package/vendor/framey/dist/shaderTransitionWorker.js +44 -0
- package/vendor/framey/dist/sherpa-RQWLV7I3.js +79 -0
- package/vendor/framey/dist/sherpaWorker.js +84 -0
- package/vendor/framey/dist/sites-AMOVABTK.js +54 -0
- package/vendor/framey/dist/skill-ZHVLH7KX.js +15 -0
- package/vendor/framey/dist/slideshow-SJGK4RQS.js +24 -0
- package/vendor/framey/dist/snapshot-TUXL6LBQ.js +1515 -0
- package/vendor/framey/dist/src-JHQGB2S6.js +215 -0
- package/vendor/framey/dist/src-Z5PGEMXB.js +532 -0
- package/vendor/framey/dist/state-B6H3WHCF.js +28 -0
- package/vendor/framey/dist/staticProjectServer-DFVZOUGI.js +63 -0
- package/vendor/framey/dist/studio/assets/hyperframes-player-DLaKjw0L.js +478 -0
- package/vendor/framey/dist/studio/assets/index-BX3KHhGX.js +71 -0
- package/vendor/framey/dist/studio/assets/index-CYbvOVp-.css +1 -0
- package/vendor/framey/dist/studio/assets/index-Dp3QEUGL.js +402 -0
- package/vendor/framey/dist/studio/assets/index-qrTAXbqc.js +531 -0
- package/vendor/framey/dist/studio/favicon.svg +14 -0
- package/vendor/framey/dist/studio/icons/timeline/audio.svg +7 -0
- package/vendor/framey/dist/studio/icons/timeline/captions.svg +5 -0
- package/vendor/framey/dist/studio/icons/timeline/composition.svg +12 -0
- package/vendor/framey/dist/studio/icons/timeline/image.svg +18 -0
- package/vendor/framey/dist/studio/icons/timeline/music.svg +10 -0
- package/vendor/framey/dist/studio/icons/timeline/text.svg +3 -0
- package/vendor/framey/dist/studio/index.html +23 -0
- package/vendor/framey/dist/studio-api-2NECGRMB.js +179 -0
- package/vendor/framey/dist/studioSelectionClient-IUUPKUHZ.js +25 -0
- package/vendor/framey/dist/studioServer-N2OPUMOQ.js +1569 -0
- package/vendor/framey/dist/synthesize-PKTB5ATG.js +183 -0
- package/vendor/framey/dist/system-7F3UEHQT.js +27 -0
- package/vendor/framey/dist/telemetry-DQGPEKM6.js +145 -0
- package/vendor/framey/dist/telemetry-UN55N6U6.js +77 -0
- package/vendor/framey/dist/timeline-LHUUMK63.js +1295 -0
- package/vendor/framey/dist/tokens-OLECWDFK.js +92 -0
- package/vendor/framey/dist/transcribe-O7HQKMIV.js +331 -0
- package/vendor/framey/dist/transcribe-UJESPBT5.js +76 -0
- package/vendor/framey/dist/tts-YCCQHZD4.js +234 -0
- package/vendor/framey/dist/urlDownloader-H2LXLG4U.js +48 -0
- package/vendor/framey/dist/usage-HG4WMM7A.js +2167 -0
- package/vendor/framey/dist/validate-F2XMN6EV.js +87 -0
- package/vendor/framey/dist/vendor/npm/gsap@3.15.0/GSAP-LICENSE.txt +8 -0
- package/vendor/framey/dist/vendor/npm/gsap@3.15.0/dist/CustomEase.min.js +11 -0
- package/vendor/framey/dist/vendor/npm/gsap@3.15.0/dist/MotionPathPlugin.min.js +11 -0
- package/vendor/framey/dist/vendor/npm/gsap@3.15.0/dist/gsap.min.js +11 -0
- package/vendor/framey/dist/warmCheck.js +220 -0
- package/vendor/framey/dist/warmCheckClient.js +139 -0
- package/vendor/framey/dist/workspaceBuildCheck-XYD2YTFW.js +133 -0
- package/vendor/framey-player/LICENSE +190 -0
- package/vendor/framey-player/NOTICE +16 -0
- package/vendor/framey-player/SOURCE.json +6 -0
- package/vendor/framey-player/hyperframes-player.global.js +478 -0
- package/vendor/framey.pin +1 -0
- package/bin/index.js +0 -3
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// The `vfrey-topics` series source (spec 016): the value is a niche id. The source stage reads the ranked topics, skips the ones this series already made a video about,
|
|
2
|
+
// and hands the rest on as candidates with their evidence. Any failure skips the source with a warning on the run; it never stops the tick.
|
|
3
|
+
import { ApiConfigError } from "../api/config.js";
|
|
4
|
+
import { ApiFailure, clean } from "../api/client.js";
|
|
5
|
+
import { fetchTopics } from "../api/topics.js";
|
|
6
|
+
import { insertWarningOnce } from "../queue/desk-ops.js";
|
|
7
|
+
import { log } from "../log.js";
|
|
8
|
+
import type { Candidate } from "@vfrey/contracts";
|
|
9
|
+
import type { Topic } from "@vfrey/contracts";
|
|
10
|
+
import type { JobRecord } from "../queue/store.js";
|
|
11
|
+
import type { Runtime } from "./run.js";
|
|
12
|
+
|
|
13
|
+
/** What the job payload carries under `topic`: enough for the agent or script to cite the evidence. */
|
|
14
|
+
export interface UsedTopic {
|
|
15
|
+
source: "vfrey-topics";
|
|
16
|
+
id: string;
|
|
17
|
+
niche: string;
|
|
18
|
+
title: string;
|
|
19
|
+
keywords: string[];
|
|
20
|
+
platforms: string[];
|
|
21
|
+
score: number;
|
|
22
|
+
evidence: Topic["evidence"];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const topicFingerprint = (niche: string, id: string): string => `vfrey-topic:${niche}:${id}`;
|
|
26
|
+
|
|
27
|
+
const norm = (text: unknown): string => String(text ?? "").trim().toLowerCase().replace(/\s+/g, " ");
|
|
28
|
+
|
|
29
|
+
/** Topic ids and titles this series already used or made a video about, read from its earlier jobs. */
|
|
30
|
+
export function postedTopics(runtime: Pick<Runtime, "store">, accountId: string): { ids: Set<string>; titles: Set<string> } {
|
|
31
|
+
const ids = new Set<string>();
|
|
32
|
+
const titles = new Set<string>();
|
|
33
|
+
const rows = runtime.store.db.prepare(`SELECT payload FROM jobs WHERE account_id = ? ORDER BY created_at DESC LIMIT 500`).all(accountId) as Array<{ payload: string }>;
|
|
34
|
+
for (const row of rows) {
|
|
35
|
+
try {
|
|
36
|
+
const payload = JSON.parse(row.payload) as { topic?: { id?: string; niche?: string; title?: string }; candidates?: Array<{ title?: string }>; content?: { title?: string; candidate?: { title?: string } } | null };
|
|
37
|
+
if (payload.topic?.id) ids.add(`${payload.topic.niche ?? ""}:${payload.topic.id}`);
|
|
38
|
+
if (payload.topic?.title) titles.add(norm(payload.topic.title));
|
|
39
|
+
for (const candidate of payload.candidates ?? []) if (candidate.title) titles.add(norm(candidate.title));
|
|
40
|
+
if (payload.content?.title) titles.add(norm(payload.content.title));
|
|
41
|
+
if (payload.content?.candidate?.title) titles.add(norm(payload.content.candidate.title));
|
|
42
|
+
} catch {
|
|
43
|
+
/* an unreadable payload is not a posted topic */
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return { ids, titles };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function excerptOf(topic: Topic): string {
|
|
50
|
+
const lines = [`Trending on ${topic.platforms.join(", ") || "social platforms"} (score ${Number(topic.score).toFixed(1)}). Keywords: ${topic.keywords.join(", ")}.`];
|
|
51
|
+
for (const item of topic.evidence) lines.push(`Evidence: ${item.url}${typeof item.views === "number" ? ` (${item.views} views)` : ""}${item.why ? ` - ${item.why}` : ""}`);
|
|
52
|
+
return clean(lines.join("\n")).slice(0, 1500);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** The top topics for each `vfrey-topics` source of this series, best first, minus the ones already posted. */
|
|
56
|
+
export async function topicCandidates(runtime: Runtime, account: { id: string }, job?: Pick<JobRecord, "id" | "attempt">): Promise<Array<{ candidate: Candidate; topic: UsedTopic }>> {
|
|
57
|
+
const sources = runtime.store.db.prepare(`SELECT id, value FROM desk_sources WHERE series_id = ? AND kind = 'vfrey-topics' ORDER BY rowid`).all(account.id) as Array<{ id: string; value: string }>;
|
|
58
|
+
if (sources.length === 0) return [];
|
|
59
|
+
const out: Array<{ candidate: Candidate; topic: UsedTopic }> = [];
|
|
60
|
+
const posted = postedTopics(runtime, account.id);
|
|
61
|
+
for (const source of sources) {
|
|
62
|
+
const before = out.length;
|
|
63
|
+
try {
|
|
64
|
+
const answer = await fetchTopics({ env: runtime.env, fetch: (url, init) => runtime.fetchImpl(url, init) }, { niche: source.value, limit: 20 });
|
|
65
|
+
const ranked = [...answer.topics].sort((a, b) => b.score - a.score);
|
|
66
|
+
for (const topic of ranked) {
|
|
67
|
+
if (posted.ids.has(`${topic.niche ?? source.value}:${topic.id}`) || posted.ids.has(`${source.value}:${topic.id}`) || posted.titles.has(norm(topic.title))) continue;
|
|
68
|
+
const url = topic.evidence.map((item) => item.url).find((u) => /^https?:\/\//i.test(u)) ?? `vfrey-topics:${source.value}/${topic.id}`;
|
|
69
|
+
out.push({
|
|
70
|
+
candidate: { fingerprint: topicFingerprint(source.value, topic.id), provider: "manual", title: clean(topic.title), url, excerpt: excerptOf(topic), publishedAt: topic.lastSeen ?? null },
|
|
71
|
+
topic: { source: "vfrey-topics", id: topic.id, niche: topic.niche ?? source.value, title: clean(topic.title), keywords: topic.keywords, platforms: topic.platforms, score: topic.score, evidence: topic.evidence },
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
runtime.store.recordSourceFetch(source.id, { at: runtime.now(), count: out.length - before, code: null, status: "ok" });
|
|
75
|
+
} catch (error) {
|
|
76
|
+
const known = error instanceof ApiFailure || error instanceof ApiConfigError;
|
|
77
|
+
const message = known ? `${error.message} Fix: ${error.fix}` : `vfrey Topics failed: ${error instanceof Error ? clean(error.message) : "unknown error"}`;
|
|
78
|
+
const text = `vfrey Topics source "${source.value}" was skipped. ${message}`;
|
|
79
|
+
log("warn", "topics source skipped", { accountId: account.id, niche: source.value, code: error instanceof ApiFailure ? error.code : undefined });
|
|
80
|
+
runtime.store.recordSourceFetch(source.id, { at: runtime.now(), count: null, code: "topics_unavailable", status: "skipped" });
|
|
81
|
+
if (job) runtime.store.recordEvent(job.id, { stage: "source", status: "warning", code: "topics_unavailable", message: text, durationMs: null, attempt: job.attempt });
|
|
82
|
+
try {
|
|
83
|
+
insertWarningOnce(runtime.store.db, { accountId: account.id, jobId: job?.id ?? null, code: "topics_unavailable", message: text });
|
|
84
|
+
} catch {
|
|
85
|
+
/* a warning that cannot be written must not stop the tick */
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return out;
|
|
90
|
+
}
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { acquireStartLock, canListen, deskOf, findDashboard, pickPort, removeDashboardRecord, writeDashboardRecord } from "../dashboard-link.js";
|
|
4
|
+
import { startDashboard } from "../desk/http.js";
|
|
5
|
+
import { runDeskTick } from "../desk/tick.js";
|
|
6
|
+
import { dataDirFor, resolveHome } from "../home.js";
|
|
7
|
+
import { Store } from "../queue/store.js";
|
|
8
|
+
import { readProductConfig } from "./config-file.js";
|
|
9
|
+
import { chooseEngine, readMachineEngine, readProjectEngine, unavailableMessage } from "./engine-choice.js";
|
|
10
|
+
import { allEngineStatuses, DEFAULT_ENGINE, engineById, engineStatus, statusText, type EngineId, type EngineProbes } from "./engines.js";
|
|
11
|
+
import { defaultProbes } from "./probes.js";
|
|
12
|
+
import * as verbs from "../desk/verbs.js";
|
|
13
|
+
import { forcedFailure, renderHtmlEngine } from "../render/html-engine.js";
|
|
14
|
+
import { appendInsight } from "./insights.js";
|
|
15
|
+
import { configFile, insightFile, productDir } from "./paths.js";
|
|
16
|
+
import { renderVerticalShort } from "../render/vertical-short.js";
|
|
17
|
+
import { failedMessage } from "./engine-choice.js";
|
|
18
|
+
import { renderChosenEngine } from "./render-video.js";
|
|
19
|
+
import { buildShort, parseComposition } from "./short.js";
|
|
20
|
+
import { runSceneCommand, runSceneRender } from "../scene/command.js";
|
|
21
|
+
import { formatUpdatePlan, updateCommands, updateInstallRoot } from "./update.js";
|
|
22
|
+
|
|
23
|
+
export interface CommandIo {
|
|
24
|
+
env: NodeJS.ProcessEnv;
|
|
25
|
+
stdout: (text: string) => void;
|
|
26
|
+
stderr: (text: string) => void;
|
|
27
|
+
renderLocal?: (outputPath: string, props: import("@vfrey/contracts").RenderProps, engine: EngineId) => Promise<void>;
|
|
28
|
+
upload?: () => Promise<void>;
|
|
29
|
+
probes?: EngineProbes;
|
|
30
|
+
/** Test hook. The CLI omits this and the dashboard stays up. */
|
|
31
|
+
onListen?: (port: number, close: () => Promise<void>) => Promise<void>;
|
|
32
|
+
apply?: (command: string, cwd: string) => Promise<void>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function flag(argv: string[], name: string): string | null {
|
|
36
|
+
const index = argv.indexOf(name);
|
|
37
|
+
if (index === -1) return null;
|
|
38
|
+
return argv[index + 1] ?? null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function hasFlag(argv: string[], name: string): boolean {
|
|
42
|
+
return argv.includes(name);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export { defaultProbes };
|
|
46
|
+
|
|
47
|
+
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* `vfrey dashboard`. Without --port it serves this desk once: when this desk's dashboard is already listening it prints
|
|
51
|
+
* that address and exits 0. It picks the recorded port if free, else 4317 (VFREY_DASHBOARD_PORT), else the next free
|
|
52
|
+
* port. --port forces a port (0 for any) and never reuses.
|
|
53
|
+
*/
|
|
54
|
+
async function runDashboard(argv: string[], io: CommandIo): Promise<number> {
|
|
55
|
+
const portFlag = flag(argv, "--port");
|
|
56
|
+
const forced = portFlag !== null;
|
|
57
|
+
const forcedPort = Number(portFlag);
|
|
58
|
+
if (forced && (!Number.isInteger(forcedPort) || forcedPort < 0 || forcedPort > 65535)) {
|
|
59
|
+
io.stderr(`✗ ${portFlag} is not a port.\nFix: pass --port <n> between 0 and 65535.\n`);
|
|
60
|
+
return 1;
|
|
61
|
+
}
|
|
62
|
+
const home = resolveHome(io.env);
|
|
63
|
+
const desk = deskOf(home);
|
|
64
|
+
const dataDir = home.dataDir;
|
|
65
|
+
const ready = (port: number) => {
|
|
66
|
+
// The ready line goes to stdout at once for whoever reads the output, and to stderr for whoever captures logs.
|
|
67
|
+
io.stdout(`vfrey dashboard http://127.0.0.1:${port}\n`);
|
|
68
|
+
io.stderr(`vfrey dashboard http://127.0.0.1:${port}\n`);
|
|
69
|
+
};
|
|
70
|
+
const listening = async (): Promise<number | null> => {
|
|
71
|
+
const origin = await findDashboard(desk, io.env);
|
|
72
|
+
return origin ? Number(new URL(origin).port) : null;
|
|
73
|
+
};
|
|
74
|
+
let release: (() => void) | null = null;
|
|
75
|
+
if (!forced) {
|
|
76
|
+
const up = await listening();
|
|
77
|
+
if (up !== null) {
|
|
78
|
+
ready(up);
|
|
79
|
+
return 0;
|
|
80
|
+
}
|
|
81
|
+
// Another start in flight (a tick and `open` at once) holds the lock: wait for its dashboard instead of making a second.
|
|
82
|
+
release = acquireStartLock(dataDir);
|
|
83
|
+
for (let waited = 0; !release && waited < 12_000; waited += 200) {
|
|
84
|
+
await sleep(200);
|
|
85
|
+
const now = await listening();
|
|
86
|
+
if (now !== null) {
|
|
87
|
+
ready(now);
|
|
88
|
+
return 0;
|
|
89
|
+
}
|
|
90
|
+
release = acquireStartLock(dataDir);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
const probes = io.probes ?? (await defaultProbes(io.env));
|
|
94
|
+
const store = new Store(path.join(dataDir, "pipeline.sqlite"));
|
|
95
|
+
const tried = new Set<number>();
|
|
96
|
+
let server;
|
|
97
|
+
let port = forced ? forcedPort : await pickPort(desk, io.env);
|
|
98
|
+
try {
|
|
99
|
+
for (;;) {
|
|
100
|
+
try {
|
|
101
|
+
server = await startDashboard({ store, dataDir, env: io.env, port, probes, project: desk.project, tick: (tickArgv) => runDeskTick({ store, env: io.env, argv: tickArgv }) });
|
|
102
|
+
break;
|
|
103
|
+
} catch (error) {
|
|
104
|
+
if ((error as NodeJS.ErrnoException).code !== "EADDRINUSE") throw error;
|
|
105
|
+
if (forced || port === 0 || tried.size >= 5) {
|
|
106
|
+
io.stderr(`✗ Port ${port} is in use.\nFix: open http://127.0.0.1:${port} if it is already the dashboard, or pass --port <n>.\n`);
|
|
107
|
+
store.close();
|
|
108
|
+
release?.();
|
|
109
|
+
return 1;
|
|
110
|
+
}
|
|
111
|
+
// Lost a race for an auto-chosen port: take the next one.
|
|
112
|
+
tried.add(port);
|
|
113
|
+
port = await pickPort(desk, io.env, canListen, tried);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
} catch (error) {
|
|
117
|
+
store.close();
|
|
118
|
+
release?.();
|
|
119
|
+
throw error;
|
|
120
|
+
}
|
|
121
|
+
const address = server.address();
|
|
122
|
+
const bound = address && typeof address === "object" ? address.port : port;
|
|
123
|
+
writeDashboardRecord(dataDir, bound, desk.project?.id ?? null);
|
|
124
|
+
release?.();
|
|
125
|
+
ready(bound);
|
|
126
|
+
const close = () =>
|
|
127
|
+
new Promise<void>((resolve, reject) => {
|
|
128
|
+
server.close((error) => (error ? reject(error) : resolve()));
|
|
129
|
+
server.closeAllConnections();
|
|
130
|
+
}).finally(() => {
|
|
131
|
+
removeDashboardRecord(dataDir, bound);
|
|
132
|
+
store.close();
|
|
133
|
+
});
|
|
134
|
+
process.once("exit", () => removeDashboardRecord(dataDir, bound));
|
|
135
|
+
if (!io.onListen) {
|
|
136
|
+
// Drop the record, then let the signal do what it would have done.
|
|
137
|
+
for (const signal of ["SIGINT", "SIGTERM", "SIGHUP"] as const) {
|
|
138
|
+
// A persistent listener: a second signal that arrives while this one is handled must not take the default action first.
|
|
139
|
+
process.on(signal, () => {
|
|
140
|
+
removeDashboardRecord(dataDir, bound);
|
|
141
|
+
process.removeAllListeners(signal);
|
|
142
|
+
process.kill(process.pid, signal);
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
if (io.onListen) {
|
|
147
|
+
await io.onListen(bound, close);
|
|
148
|
+
return 0;
|
|
149
|
+
}
|
|
150
|
+
await new Promise(() => {});
|
|
151
|
+
return 0;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export async function runProductCommand(command: string, argv: string[], io: CommandIo): Promise<number> {
|
|
155
|
+
const dir = productDir(io.env);
|
|
156
|
+
if (command === "update") {
|
|
157
|
+
io.stdout(formatUpdatePlan(hasFlag(argv, "--apply")));
|
|
158
|
+
if (hasFlag(argv, "--apply")) {
|
|
159
|
+
const apply = io.apply ?? ((line: string, cwd: string) => runShell(line, io, cwd));
|
|
160
|
+
const cwd = updateInstallRoot();
|
|
161
|
+
for (const line of updateCommands()) await apply(line, cwd);
|
|
162
|
+
}
|
|
163
|
+
return 0;
|
|
164
|
+
}
|
|
165
|
+
if (command === "dev" || command === "dashboard") return runDashboard(argv, io);
|
|
166
|
+
if (command === "engine") return runEngineCommand(argv, io);
|
|
167
|
+
if (command === "scene") return runSceneCommand(argv, io);
|
|
168
|
+
if (command === "render" && flag(argv, "--scene")) return runSceneRender(argv, io);
|
|
169
|
+
if (command === "render") {
|
|
170
|
+
const config = await readProductConfig(configFile(dir), dir);
|
|
171
|
+
const requested = flag(argv, "--engine");
|
|
172
|
+
let engine;
|
|
173
|
+
try {
|
|
174
|
+
engine = chooseEngine({ command: requested, project: readProjectEngine(), machine: readMachineEngine(dataDirFor(io.env)) ?? config.defaultEngine }).engine;
|
|
175
|
+
} catch {
|
|
176
|
+
io.stderr(`✗ "${requested}" is not an engine.\nFix: choose remotion, hyperframes, vfrey-framey, or comfyui; \`npx vfrey engine list\` shows which are ready.\n`);
|
|
177
|
+
return 1;
|
|
178
|
+
}
|
|
179
|
+
let props;
|
|
180
|
+
try {
|
|
181
|
+
props = buildShort(flag(argv, "--subject") ?? "", parseComposition(flag(argv, "--composition")));
|
|
182
|
+
} catch (error) {
|
|
183
|
+
io.stderr(`${error instanceof Error ? error.message : "Render failed"}\n`);
|
|
184
|
+
return 1;
|
|
185
|
+
}
|
|
186
|
+
const probes = io.probes ?? (await defaultProbes(io.env));
|
|
187
|
+
const status = engineStatus(engine, probes);
|
|
188
|
+
const outputPath = path.resolve(flag(argv, "--output") ?? path.join(config.outputDir, `${engine.id}.mp4`));
|
|
189
|
+
let rendered;
|
|
190
|
+
try {
|
|
191
|
+
rendered = await renderChosenEngine({
|
|
192
|
+
engine,
|
|
193
|
+
availability: status.availability,
|
|
194
|
+
reason: status.reason,
|
|
195
|
+
outputPath,
|
|
196
|
+
props,
|
|
197
|
+
renderLocal: io.renderLocal ?? ((out, shortProps, id) => renderLocalFor(out, shortProps, id, io.env)),
|
|
198
|
+
});
|
|
199
|
+
} catch (error) {
|
|
200
|
+
const message = status.availability === "unavailable" ? unavailableMessage(engine, probes) : failedRender(engine, error, probes);
|
|
201
|
+
const [what, fix] = message.split(" Fix: ");
|
|
202
|
+
io.stderr(`✗ ${what}\nFix: ${fix}\n`);
|
|
203
|
+
return 1;
|
|
204
|
+
}
|
|
205
|
+
await appendInsight(insightFile(dir), {
|
|
206
|
+
engineId: engine.id,
|
|
207
|
+
engineLabel: engine.label,
|
|
208
|
+
outputPath: rendered.outputPath,
|
|
209
|
+
bytes: rendered.bytes,
|
|
210
|
+
createdAt: new Date().toISOString(),
|
|
211
|
+
subject: props.title,
|
|
212
|
+
composition: props.templateId,
|
|
213
|
+
durationSec: props.durationSec,
|
|
214
|
+
});
|
|
215
|
+
io.stdout(`${rendered.outputPath}\n`);
|
|
216
|
+
return 0;
|
|
217
|
+
}
|
|
218
|
+
if (command === "push") {
|
|
219
|
+
// No upload path exists behind this command yet. Publishing runs inside `vfrey tick`.
|
|
220
|
+
io.stderr("✗ push is not built yet.\nFix: publishing runs inside `vfrey tick` once the platform keys are set.\n");
|
|
221
|
+
return 1;
|
|
222
|
+
}
|
|
223
|
+
io.stderr(`Unknown product command ${command}\n`);
|
|
224
|
+
return 1;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function runShell(line: string, io: CommandIo, cwd: string): Promise<void> {
|
|
228
|
+
return new Promise((resolve, reject) => {
|
|
229
|
+
const child = spawn(line, { shell: true, stdio: "inherit", env: io.env, cwd });
|
|
230
|
+
child.on("error", reject);
|
|
231
|
+
child.on("exit", (code) => {
|
|
232
|
+
if (code === 0) resolve();
|
|
233
|
+
else reject(new Error(`Update command failed: ${line}`));
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function failedRender(engine: ReturnType<typeof engineById>, error: unknown, probes: EngineProbes): string {
|
|
239
|
+
return failedMessage(engine, error, probes);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
async function renderLocalFor(
|
|
243
|
+
outputPath: string,
|
|
244
|
+
props: import("@vfrey/contracts").RenderProps,
|
|
245
|
+
engine: EngineId,
|
|
246
|
+
env: NodeJS.ProcessEnv,
|
|
247
|
+
): Promise<void> {
|
|
248
|
+
const forced = forcedFailure(engine, env);
|
|
249
|
+
if (forced) throw new Error(forced);
|
|
250
|
+
if (engine === "vfrey-framey" || engine === "hyperframes") {
|
|
251
|
+
await renderHtmlEngine({ engine, props, voiceoverPath: null, outputPath, workDir: `${outputPath}.work`, env });
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
await renderVerticalShort(outputPath, props);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/** `vfrey engine list` and `vfrey engine use <engine>`. */
|
|
258
|
+
async function runEngineCommand(argv: string[], io: CommandIo): Promise<number> {
|
|
259
|
+
const [action, name] = argv;
|
|
260
|
+
const probes = io.probes ?? (await defaultProbes(io.env));
|
|
261
|
+
const dataDir = dataDirFor(io.env);
|
|
262
|
+
if (action === "list" || action === undefined) {
|
|
263
|
+
const current = engineById(readMachineEngine(dataDir) ?? DEFAULT_ENGINE).id;
|
|
264
|
+
const rows = allEngineStatuses(probes).map((row) => ({ ...row, default: row.id === current }));
|
|
265
|
+
if (hasFlag(argv, "--json")) {
|
|
266
|
+
io.stdout(`${JSON.stringify({ ok: true, default: current, engines: rows }, null, 2)}\n`);
|
|
267
|
+
return 0;
|
|
268
|
+
}
|
|
269
|
+
const width = Math.max(...rows.map((row) => row.id.length));
|
|
270
|
+
const lines = rows.map(
|
|
271
|
+
(row) => `${row.default ? "*" : " "} ${row.id.padEnd(width)} ${statusText(row)}${row.default ? " (default)" : ""}`,
|
|
272
|
+
);
|
|
273
|
+
io.stdout(`${[...lines, "", "Pick one for a run with --engine, for a series with `desk series update --engine`, or for the machine with `engine use <engine>`."].join("\n")}\n`);
|
|
274
|
+
return 0;
|
|
275
|
+
}
|
|
276
|
+
if (action === "use") {
|
|
277
|
+
if (!name || name.startsWith("--")) {
|
|
278
|
+
io.stderr("✗ engine use needs an engine.\nFix: run `npx vfrey engine use vfrey-framey` or `npx vfrey engine use remotion`.\n");
|
|
279
|
+
return 1;
|
|
280
|
+
}
|
|
281
|
+
const store = new Store(path.join(dataDir, "pipeline.sqlite"));
|
|
282
|
+
try {
|
|
283
|
+
const result = verbs.engineUse(store, { engine: name, probes });
|
|
284
|
+
if (!result.ok) {
|
|
285
|
+
io.stderr(`✗ ${result.refusal}\nFix: ${result.fix}\n`);
|
|
286
|
+
return 1;
|
|
287
|
+
}
|
|
288
|
+
io.stdout(`Default engine: ${String(result.data?.label)}\n`);
|
|
289
|
+
return 0;
|
|
290
|
+
} finally {
|
|
291
|
+
store.close();
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
io.stderr(`✗ Unknown engine command "${action}".\nFix: use \`npx vfrey engine list\` or \`npx vfrey engine use <engine>\`.\n`);
|
|
295
|
+
return 1;
|
|
296
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
export interface ProductConfig {
|
|
5
|
+
outputDir: string;
|
|
6
|
+
defaultEngine: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function defaultProductConfig(dir: string): ProductConfig {
|
|
10
|
+
return { outputDir: path.join(dir, "out"), defaultEngine: "remotion" };
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function parseProductConfig(raw: string, fallback: ProductConfig): ProductConfig {
|
|
14
|
+
if (!raw.trim()) return fallback;
|
|
15
|
+
const parsed = JSON.parse(raw) as Partial<ProductConfig>;
|
|
16
|
+
return {
|
|
17
|
+
outputDir: typeof parsed.outputDir === "string" && parsed.outputDir.trim() ? parsed.outputDir : fallback.outputDir,
|
|
18
|
+
defaultEngine:
|
|
19
|
+
typeof parsed.defaultEngine === "string" && parsed.defaultEngine.trim()
|
|
20
|
+
? parsed.defaultEngine
|
|
21
|
+
: fallback.defaultEngine,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function readProductConfig(filePath: string, dir: string): Promise<ProductConfig> {
|
|
26
|
+
const fallback = defaultProductConfig(dir);
|
|
27
|
+
try {
|
|
28
|
+
return parseProductConfig(await readFile(filePath, "utf8"), fallback);
|
|
29
|
+
} catch (error) {
|
|
30
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") return fallback;
|
|
31
|
+
throw error;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export async function writeProductConfig(filePath: string, config: ProductConfig): Promise<void> {
|
|
36
|
+
await mkdir(path.dirname(filePath), { recursive: true });
|
|
37
|
+
await writeFile(filePath, `${JSON.stringify(config)}\n`);
|
|
38
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { DatabaseSync } from "node:sqlite";
|
|
4
|
+
import { findProject } from "../project/discover.js";
|
|
5
|
+
import {
|
|
6
|
+
DEFAULT_ENGINE,
|
|
7
|
+
ENGINES,
|
|
8
|
+
engineById,
|
|
9
|
+
engineStatus,
|
|
10
|
+
otherReadyEngine,
|
|
11
|
+
type EngineProbes,
|
|
12
|
+
type EngineSpec,
|
|
13
|
+
} from "./engines.js";
|
|
14
|
+
|
|
15
|
+
export type ChoiceSource = "command" | "series" | "project" | "default" | "fallback";
|
|
16
|
+
|
|
17
|
+
export interface EngineChoice {
|
|
18
|
+
engine: EngineSpec;
|
|
19
|
+
source: ChoiceSource;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** The most specific choice wins: the command, then the series, then the project (vfrey.json), then the machine default, then Remotion. */
|
|
23
|
+
export function chooseEngine(input: { command?: string | null; series?: string | null; project?: string | null; machine?: string | null }): EngineChoice {
|
|
24
|
+
const pick = (value: string | null | undefined): EngineSpec | null => {
|
|
25
|
+
if (!value || !value.trim()) return null;
|
|
26
|
+
try {
|
|
27
|
+
return engineById(value);
|
|
28
|
+
} catch {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
const command = input.command?.trim() ? engineById(input.command) : null;
|
|
33
|
+
if (command) return { engine: command, source: "command" };
|
|
34
|
+
const series = pick(input.series);
|
|
35
|
+
if (series) return { engine: series, source: "series" };
|
|
36
|
+
const project = pick(input.project);
|
|
37
|
+
if (project) return { engine: project, source: "project" };
|
|
38
|
+
const machine = pick(input.machine);
|
|
39
|
+
if (machine) return { engine: machine, source: "default" };
|
|
40
|
+
return { engine: engineById(DEFAULT_ENGINE), source: "fallback" };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** The machine default, read without creating or changing the database. */
|
|
44
|
+
export function readMachineEngine(dataDir: string): string | null {
|
|
45
|
+
const file = path.join(dataDir, "pipeline.sqlite");
|
|
46
|
+
if (!existsSync(file)) return null;
|
|
47
|
+
let db: DatabaseSync | null = null;
|
|
48
|
+
try {
|
|
49
|
+
db = new DatabaseSync(file, { readOnly: true });
|
|
50
|
+
const row = db.prepare(`SELECT value FROM desk_settings WHERE key = 'engine.default'`).get() as { value: string } | undefined;
|
|
51
|
+
return row?.value ?? null;
|
|
52
|
+
} catch {
|
|
53
|
+
return null;
|
|
54
|
+
} finally {
|
|
55
|
+
db?.close();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** The `Fix:` text for a chosen engine that cannot render: name another ready engine, or point at doctor. */
|
|
60
|
+
export function engineFix(chosen: EngineSpec, probes: EngineProbes): string {
|
|
61
|
+
const other = otherReadyEngine(chosen.id, probes);
|
|
62
|
+
return other
|
|
63
|
+
? `render with ${other.label} instead: pass --engine ${other.id}, or run \`npx vfrey engine use ${other.id}\`, then run it again.`
|
|
64
|
+
: "run `npx vfrey doctor` to see what is missing, then run it again.";
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Message for a chosen engine that is not ready. One sentence, then the fix. */
|
|
68
|
+
export function unavailableMessage(chosen: EngineSpec, probes: EngineProbes): string {
|
|
69
|
+
const status = engineStatus(chosen, probes);
|
|
70
|
+
return `${chosen.label} is unavailable${status.reason ? `: ${status.reason}` : ""}. Fix: ${engineFix(chosen, probes)}`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Message for a chosen engine that was ready and then failed. There is no fallback render. */
|
|
74
|
+
export function failedMessage(chosen: EngineSpec, error: unknown, probes: EngineProbes): string {
|
|
75
|
+
const raw = (error instanceof Error ? error.message : String(error)).replace(/\s+/g, " ").trim();
|
|
76
|
+
// An error that knows its own fix keeps it.
|
|
77
|
+
const [what = raw, own] = raw.split(" Fix: ");
|
|
78
|
+
const text = what.replace(/\.$/, "").slice(0, 400);
|
|
79
|
+
return `${chosen.label} could not render the video: ${text}. Fix: ${own ?? engineFix(chosen, probes)}`;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function engineIds(): string[] {
|
|
83
|
+
return ENGINES.map((engine) => engine.id);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** The engine the nearest vfrey.json names, or null. A broken vfrey.json throws a ProjectError (a refusal with a Fix line). */
|
|
87
|
+
export function readProjectEngine(cwd: string = process.cwd()): string | null {
|
|
88
|
+
return findProject(cwd)?.engine ?? null;
|
|
89
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
export const ENGINE_IDS = ["remotion", "hyperframes", "vfrey-framey", "comfyui"] as const;
|
|
2
|
+
|
|
3
|
+
export type EngineId = (typeof ENGINE_IDS)[number];
|
|
4
|
+
|
|
5
|
+
export interface EngineSpec {
|
|
6
|
+
id: EngineId;
|
|
7
|
+
label: string;
|
|
8
|
+
/** A local render needs no paid API key. */
|
|
9
|
+
localFree: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const ENGINES: readonly EngineSpec[] = [
|
|
13
|
+
{ id: "remotion", label: "Remotion", localFree: true },
|
|
14
|
+
{ id: "hyperframes", label: "HyperFrames", localFree: true },
|
|
15
|
+
{ id: "vfrey-framey", label: "vfrey Framey", localFree: true },
|
|
16
|
+
{ id: "comfyui", label: "ComfyUI", localFree: true },
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
export const DEFAULT_ENGINE: EngineId = "remotion";
|
|
20
|
+
|
|
21
|
+
export type EngineAvailability = "ready" | "unavailable";
|
|
22
|
+
|
|
23
|
+
export interface EngineProbes {
|
|
24
|
+
/** The upstream `hyperframes` CLI answered `--version`. */
|
|
25
|
+
hyperframesCli: boolean;
|
|
26
|
+
comfyReachable: boolean;
|
|
27
|
+
/** Why the vendored Framey renderer cannot run here. Null: it can. Absent: not probed, so unavailable. */
|
|
28
|
+
frameyProblem?: string | null;
|
|
29
|
+
/** Why an HTML engine has no Chrome or ffmpeg here. Null: it has both. */
|
|
30
|
+
renderEnvProblem?: string | null;
|
|
31
|
+
/** Only the Chrome Headless Shell is missing for Framey: its first render downloads it. */
|
|
32
|
+
frameyDownloadsChrome?: boolean;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface EngineStatus {
|
|
36
|
+
id: EngineId;
|
|
37
|
+
label: string;
|
|
38
|
+
availability: EngineAvailability;
|
|
39
|
+
/** Why the engine cannot render. Null when it is ready. */
|
|
40
|
+
reason: string | null;
|
|
41
|
+
/** A caveat on a ready engine, such as a first-render download. Null when there is none. */
|
|
42
|
+
note: string | null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export const FRAMEY_DOWNLOAD_NOTE = "downloads Chrome Headless Shell on first render";
|
|
46
|
+
|
|
47
|
+
/** `ready`, `ready (note)`, or `unavailable: reason`. */
|
|
48
|
+
export function statusText(row: Pick<EngineStatus, "availability" | "reason" | "note">): string {
|
|
49
|
+
if (row.availability === "ready") return row.note ? `ready (${row.note})` : "ready";
|
|
50
|
+
return `unavailable: ${row.reason}`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function engineById(value: string): EngineSpec {
|
|
54
|
+
const needle = value.trim().toLowerCase();
|
|
55
|
+
const found = ENGINES.find((engine) => engine.id === needle || engine.label.toLowerCase() === needle);
|
|
56
|
+
if (!found) {
|
|
57
|
+
throw new Error("Unknown engine. Choose remotion, hyperframes, vfrey-framey, or comfyui.");
|
|
58
|
+
}
|
|
59
|
+
return found;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export const HYPERFRAMES_MISSING = "HyperFrames CLI not found; install it or choose vfrey Framey";
|
|
63
|
+
export const COMFYUI_REASON = "needs a ComfyUI server at COMFYUI_URL; not wired yet";
|
|
64
|
+
|
|
65
|
+
export function engineStatus(engine: EngineSpec, probes: EngineProbes): EngineStatus {
|
|
66
|
+
const base = { id: engine.id, label: engine.label };
|
|
67
|
+
const ready: EngineStatus = { ...base, availability: "ready", reason: null, note: null };
|
|
68
|
+
const no = (reason: string): EngineStatus => ({ ...base, availability: "unavailable", reason, note: null });
|
|
69
|
+
switch (engine.id) {
|
|
70
|
+
case "remotion":
|
|
71
|
+
return ready;
|
|
72
|
+
case "vfrey-framey":
|
|
73
|
+
if (probes.frameyProblem === undefined) return no("the Framey renderer was not probed on this machine");
|
|
74
|
+
if (probes.frameyProblem) return no(probes.frameyProblem);
|
|
75
|
+
return probes.frameyDownloadsChrome ? { ...ready, note: FRAMEY_DOWNLOAD_NOTE } : ready;
|
|
76
|
+
case "hyperframes":
|
|
77
|
+
if (!probes.hyperframesCli) return no(HYPERFRAMES_MISSING);
|
|
78
|
+
return probes.renderEnvProblem ? no(probes.renderEnvProblem) : ready;
|
|
79
|
+
default:
|
|
80
|
+
// A server answering does not make a renderer. ComfyUI is the next slice.
|
|
81
|
+
return no(COMFYUI_REASON);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function allEngineStatuses(probes: EngineProbes): EngineStatus[] {
|
|
86
|
+
return ENGINES.map((engine) => engineStatus(engine, probes));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function availabilityFor(engine: EngineSpec, probes: EngineProbes): EngineAvailability {
|
|
90
|
+
return engineStatus(engine, probes).availability;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** The first ready engine other than `chosen`, Remotion first. */
|
|
94
|
+
export function otherReadyEngine(chosen: EngineId, probes: EngineProbes): EngineSpec | null {
|
|
95
|
+
const order = [ENGINES[0]!, ...ENGINES.filter((engine) => engine.id !== "remotion")];
|
|
96
|
+
return order.find((engine) => engine.id !== chosen && availabilityFor(engine, probes) === "ready") ?? null;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function assertCanRender(engine: EngineSpec, availability: EngineAvailability, reason?: string | null): void {
|
|
100
|
+
if (availability === "unavailable") {
|
|
101
|
+
throw new Error(`${engine.label} is unavailable on this machine${reason ? `: ${reason}` : ""}.`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
export interface InsightRow {
|
|
5
|
+
engineId: string;
|
|
6
|
+
engineLabel: string;
|
|
7
|
+
outputPath: string;
|
|
8
|
+
bytes: number;
|
|
9
|
+
createdAt: string;
|
|
10
|
+
subject?: string;
|
|
11
|
+
composition?: string;
|
|
12
|
+
durationSec?: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function summarizeInsights(rows: InsightRow[]): { count: number; bytes: number; byEngine: Record<string, number> } {
|
|
16
|
+
const byEngine: Record<string, number> = {};
|
|
17
|
+
let bytes = 0;
|
|
18
|
+
for (const row of rows) {
|
|
19
|
+
bytes += row.bytes;
|
|
20
|
+
byEngine[row.engineLabel] = (byEngine[row.engineLabel] ?? 0) + 1;
|
|
21
|
+
}
|
|
22
|
+
return { count: rows.length, bytes, byEngine };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function parseInsights(raw: string): InsightRow[] {
|
|
26
|
+
if (!raw.trim()) return [];
|
|
27
|
+
const parsed = JSON.parse(raw) as unknown;
|
|
28
|
+
if (!Array.isArray(parsed)) return [];
|
|
29
|
+
return parsed.filter((row): row is InsightRow => {
|
|
30
|
+
if (!row || typeof row !== "object") return false;
|
|
31
|
+
const item = row as InsightRow;
|
|
32
|
+
return typeof item.engineId === "string" && typeof item.outputPath === "string" && typeof item.bytes === "number";
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export async function readInsights(filePath: string): Promise<InsightRow[]> {
|
|
37
|
+
try {
|
|
38
|
+
return parseInsights(await readFile(filePath, "utf8"));
|
|
39
|
+
} catch (error) {
|
|
40
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") return [];
|
|
41
|
+
throw error;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export async function appendInsight(filePath: string, row: InsightRow): Promise<InsightRow[]> {
|
|
46
|
+
const rows = [...(await readInsights(filePath)), row];
|
|
47
|
+
await mkdir(path.dirname(filePath), { recursive: true });
|
|
48
|
+
await writeFile(filePath, `${JSON.stringify(rows)}\n`);
|
|
49
|
+
return rows;
|
|
50
|
+
}
|