@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
package/src/help.ts
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
/** Help text. It prints and exits; it never opens the database or starts a job. */
|
|
2
|
+
|
|
3
|
+
interface CommandHelp {
|
|
4
|
+
summary: string;
|
|
5
|
+
usage: string[];
|
|
6
|
+
flags: Array<[string, string]>;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const HELP: Record<string, CommandHelp> = {
|
|
10
|
+
doctor: {
|
|
11
|
+
summary: "Check this machine (Node, home folder, Chrome, voice, skill). Exits 1 when a check fails.",
|
|
12
|
+
usage: ["npx vfrey doctor [--json] [--rules]"],
|
|
13
|
+
flags: [["--json", "print one JSON document; always exits 0, read .ok"], ["--rules", "also print the variation and compliance checklists"]],
|
|
14
|
+
},
|
|
15
|
+
login: {
|
|
16
|
+
summary: "Sign in to your vfrey account (paid features only; free ones never need it).",
|
|
17
|
+
usage: ["npx vfrey login [--api URL] [--no-browser]"],
|
|
18
|
+
flags: [["--api URL", "the vfrey server (default: VFREY_API_URL)"], ["--no-browser", "print the link only; also VFREY_NO_BROWSER=1"]],
|
|
19
|
+
},
|
|
20
|
+
logout: {
|
|
21
|
+
summary: "Sign out: revoke this machine's token on the server, then delete it here.",
|
|
22
|
+
usage: ["npx vfrey logout [--api URL]"],
|
|
23
|
+
flags: [["--api URL", "the vfrey server (default: VFREY_API_URL)"]],
|
|
24
|
+
},
|
|
25
|
+
whoami: {
|
|
26
|
+
summary: "Show your org, plan, entitlements and usage. VFREY_TOKEN signs in without login.",
|
|
27
|
+
usage: ["npx vfrey whoami [--json] [--api URL]"],
|
|
28
|
+
flags: [["--json", "print the server's answer as JSON"], ["--api URL", "the vfrey server (default: VFREY_API_URL)"]],
|
|
29
|
+
},
|
|
30
|
+
org: {
|
|
31
|
+
summary: "List your orgs, or switch the org this machine works in (accounts with several orgs).",
|
|
32
|
+
usage: ["npx vfrey org list [--json] [--api URL]", "npx vfrey org use ORG [--api URL]"],
|
|
33
|
+
flags: [["ORG", "an org name or id from `org list`"], ["--json", "list: print JSON"], ["--api URL", "the vfrey server (default: VFREY_API_URL)"]],
|
|
34
|
+
},
|
|
35
|
+
topics: {
|
|
36
|
+
summary: "What is trending in a niche, with evidence links (needs an account and credits; free features never do).",
|
|
37
|
+
usage: [
|
|
38
|
+
"npx vfrey topics --niche ID [--limit N] [--since ISO] [--json]",
|
|
39
|
+
"npx vfrey topics niches [--json]",
|
|
40
|
+
"npx vfrey topics request --name N --keywords a,b [--hashtags a,b] [--platforms tiktok,youtube]",
|
|
41
|
+
],
|
|
42
|
+
flags: [
|
|
43
|
+
["--niche ID", "the niche to read; list ids with `topics niches`"],
|
|
44
|
+
["--limit N", "how many topics, 1 to 100 (default 20)"],
|
|
45
|
+
["--since ISO", "only topics seen since this date"],
|
|
46
|
+
["--json", "print the server's answer as JSON"],
|
|
47
|
+
["niches", "list the niches; free, needs only the plan"],
|
|
48
|
+
["request", "ask for a new niche from keywords; costs credits"],
|
|
49
|
+
["--api URL", "the vfrey server (default: VFREY_API_URL)"],
|
|
50
|
+
],
|
|
51
|
+
},
|
|
52
|
+
creators: {
|
|
53
|
+
summary: "Find creators in a niche and study their formats and hooks (needs the Research plan and credits; free features never do).",
|
|
54
|
+
usage: [
|
|
55
|
+
"npx vfrey creators search [--niche ID] [--platform tiktok,youtube] [--followers 10k-200k] [--posts-per-week 3] [--engagement 4%] [--sort engagement|growth|views|followers] [--limit N] [--json]",
|
|
56
|
+
"npx vfrey creators show @handle --platform tiktok [--json]",
|
|
57
|
+
"npx vfrey creators watch add|remove @handle --platform tiktok [--watchlist NAME]",
|
|
58
|
+
"npx vfrey creators watch list [--watchlist NAME] [--json]",
|
|
59
|
+
"npx vfrey creators export --watchlist ID > watchlist.csv",
|
|
60
|
+
"npx vfrey creators alerts [--json]",
|
|
61
|
+
],
|
|
62
|
+
flags: [
|
|
63
|
+
["--niche ID", "only creators in this niche; list ids with `topics niches`"],
|
|
64
|
+
["--platform P", "tiktok, instagram or youtube; for search a comma list"],
|
|
65
|
+
["--followers A-B", "a follower range: 10k-200k, 10k- (at least) or -200k (at most)"],
|
|
66
|
+
["--posts-per-week N", "at least this many posts a week"],
|
|
67
|
+
["--engagement 4%", "at least this engagement (4% or 0.04); it is a share of views or of followers, and the result says which"],
|
|
68
|
+
["--sort KEY", "engagement, growth, views or followers (default engagement)"],
|
|
69
|
+
["--limit N", "how many creators, 1 to 100 (default 20)"],
|
|
70
|
+
["--watchlist NAME", "a watchlist by name or id (default for add and remove: Watching)"],
|
|
71
|
+
["--json", "print the server's answer as JSON"],
|
|
72
|
+
["export", "print one watchlist as CSV on stdout, at most 500 rows"],
|
|
73
|
+
["alerts", "posts at 5x a watched creator's median views within 48 hours"],
|
|
74
|
+
["--api URL", "the vfrey server (default: VFREY_API_URL)"],
|
|
75
|
+
],
|
|
76
|
+
},
|
|
77
|
+
mail: {
|
|
78
|
+
summary: "Draft outreach (sponsor, brand, creator, client). A person approves and sends each draft (needs the Mail add-on).",
|
|
79
|
+
usage: [
|
|
80
|
+
"npx vfrey mail draft --kind sponsor|brand|creator_collab|client_update --need TEXT [--creator @handle --platform tiktok] [--to EMAIL] [--to-name N] [--tone T] [--json]",
|
|
81
|
+
"npx vfrey mail drafts [--status draft|approved|sent|failed|discarded] [--json]",
|
|
82
|
+
"npx vfrey mail show --id ID [--json]",
|
|
83
|
+
"npx vfrey mail approve --id ID",
|
|
84
|
+
"npx vfrey mail send --id ID",
|
|
85
|
+
"npx vfrey mail discard --id ID",
|
|
86
|
+
"npx vfrey mail identity [--from-name N --reply-to EMAIL --postal-address A --org NAME] [--json]",
|
|
87
|
+
],
|
|
88
|
+
flags: [
|
|
89
|
+
["--kind K", "sponsor, brand, creator_collab, client_update (or generic)"],
|
|
90
|
+
["--need TEXT", "what the message should ask or say"],
|
|
91
|
+
["--creator @h --platform P", "write to a creator from the index; --to defaults to their public business contact"],
|
|
92
|
+
["draft", "writes a draft and prints it with its id; free, sends nothing"],
|
|
93
|
+
["approve", "a person approves one draft; sends nothing. An agent token is refused"],
|
|
94
|
+
["send", "a person sends one approved draft, once; charges one send. An agent token is refused"],
|
|
95
|
+
["identity", "who mail is from, where replies go, and the postal address in every footer; a new reply-to must be confirmed by link before anything sends"],
|
|
96
|
+
["--json", "print the server's answer as JSON"],
|
|
97
|
+
["--api URL", "the vfrey server (default: VFREY_API_URL)"],
|
|
98
|
+
],
|
|
99
|
+
},
|
|
100
|
+
tick: {
|
|
101
|
+
summary: "Run the pipeline once: source, generate, render, publish. Without --now it follows each series' cadence.",
|
|
102
|
+
usage: [
|
|
103
|
+
'npx vfrey tick --now [--about "<subject>"] [--script FILE] [--series <id>] [--engine <id>] [--dry-run] [--skip-render] [--json]',
|
|
104
|
+
"npx vfrey tick --script-template",
|
|
105
|
+
],
|
|
106
|
+
flags: [
|
|
107
|
+
["--now", "make a video now, ignoring the posting window"],
|
|
108
|
+
["--about \"<subject>\"", "make the video about this subject; no feed is read (implies --now)"],
|
|
109
|
+
["--script FILE", "use the script in this JSON file (title, hook, lines, hashtags) instead of a model; implies --now"],
|
|
110
|
+
["--script-template", "print an example script JSON and exit"],
|
|
111
|
+
["--scene DIR", "render the scene you wrote in DIR (index.html for vfrey-framey or hyperframes, index.tsx for remotion) instead of the default template"],
|
|
112
|
+
["--series <id>", "only this series (desk tick)"],
|
|
113
|
+
["--engine <id>", "render with remotion, vfrey-framey, or hyperframes for this run; beats the series, the project's vfrey.json engine and the machine default"],
|
|
114
|
+
["--dry-run", "run every stage without a real render or upload"],
|
|
115
|
+
["--skip-render", "skip the render; no real mp4"],
|
|
116
|
+
["--json", "print one JSON document instead of the summary"],
|
|
117
|
+
],
|
|
118
|
+
},
|
|
119
|
+
worker: {
|
|
120
|
+
summary: "Tick every 60 seconds until stopped (Ctrl-C).",
|
|
121
|
+
usage: ["npx vfrey worker [--dry-run]", "npx vfrey worker --all-projects [--once] [--dry-run]"],
|
|
122
|
+
flags: [["--dry-run", "no real render or upload"], ["--all-projects", "each cycle, tick every project in the machine index that has a due, unpaused series; logs to ~/.vfrey/logs/worker.log"], ["--once", "with --all-projects: run one cycle and exit"]],
|
|
123
|
+
},
|
|
124
|
+
service: {
|
|
125
|
+
summary: "Run the worker in the background on this machine (launchd on macOS, systemd --user on Linux).",
|
|
126
|
+
usage: ["npx vfrey service install", "npx vfrey service uninstall", "npx vfrey service status [--json]", "npx vfrey service logs [--follow]"],
|
|
127
|
+
flags: [["install", "write the launchd agent or systemd unit and start it; safe to run again"], ["uninstall", "stop it and remove the file"], ["status", "service state, last cycle, each project's last tick and next due time"], ["logs", "the last 50 lines of ~/.vfrey/logs/worker.log; --follow keeps printing"]],
|
|
128
|
+
},
|
|
129
|
+
engage: { summary: "Reply to comments on published videos for every series.", usage: ["npx vfrey engage [--dry-run]"], flags: [["--dry-run", "no real replies"]] },
|
|
130
|
+
analytics: { summary: "Collect view and like counts for published videos.", usage: ["npx vfrey analytics [--dry-run]"], flags: [["--dry-run", "no network reads"]] },
|
|
131
|
+
render: {
|
|
132
|
+
summary: "Render one vertical 1080x1920 short from a subject, without sourcing a job.",
|
|
133
|
+
usage: ['npx vfrey render --subject "<text>" [--engine <id>] [--composition <name>] [--output <file.mp4>]', "npx vfrey render --scene DIR --engine <id> [--script FILE] --frame <seconds> out.png"],
|
|
134
|
+
flags: [["--subject", "required; an empty subject is refused"], ["--engine", "remotion, vfrey-framey, or hyperframes; default is the project's vfrey.json engine, else the machine default (`engine list`)"], ["--composition", "default KineticType"], ["--output", "where to write the mp4"], ["--scene DIR", "render your scene instead; with --frame <seconds> OUT.png it writes one frame (silent, timing estimated from --script) so you can look at it"]],
|
|
135
|
+
},
|
|
136
|
+
scene: {
|
|
137
|
+
summary: "Write a starter scene: the HTML or React composition an agent edits to design a video.",
|
|
138
|
+
usage: ["npx vfrey scene init DIR [--engine framey|hyperframes|remotion]", "npx vfrey scene blocks [--category X]", "npx vfrey scene add BLOCK DIR", "npx vfrey scene transitions [--json]", "npx vfrey scene inspect DIR [--at SECONDS] [--selector CSS | --text \"words\"] [--script FILE] [--json]", "npx vfrey scene studio DIR [--port N] [--no-browser]"],
|
|
139
|
+
flags: [["inspect DIR", "before a \"change just this\" edit: which element, CSS rule and GSAP tween (with file:line) make the thing you name; --at seeks the timeline, --selector or --text narrows to it (default: every visible element); HTML scenes only"], ["studio DIR", "open an HTML scene in the visual editor (timeline, code, live preview) and save edits back into DIR; stays up until Ctrl+C. Binds 127.0.0.1 on a free port from 47000"], ["--port N", "studio only: use this port"], ["--no-browser", "studio only: print the address only (also VFREY_NO_BROWSER=1)"], ["init DIR", "write index.html (index.tsx for remotion) and vfrey's helper files into DIR"], ["blocks", "list the ready-made scene blocks; --category X narrows the list"], ["add BLOCK DIR", "copy a block into the scene in DIR and print the snippet to paste into #root"], ["transitions", "list the shader transitions for vfrey.transition() in HTML scenes (--json for machines)"], ["--engine", "framey (default), hyperframes, or remotion"]],
|
|
140
|
+
},
|
|
141
|
+
engine: {
|
|
142
|
+
summary: "List the render engines and choose the machine default.",
|
|
143
|
+
usage: ["npx vfrey engine list [--json]", "npx vfrey engine use <remotion|vfrey-framey|hyperframes>"],
|
|
144
|
+
flags: [["list", "each engine, ready or unavailable with the reason, and the current default"], ["use <engine>", "make this the default for every run that names no engine"]],
|
|
145
|
+
},
|
|
146
|
+
dashboard: {
|
|
147
|
+
summary: "Serve this desk's dashboard in the foreground (also: dev, desk serve). Prints `vfrey dashboard http://127.0.0.1:<port>` when ready; when this desk's dashboard is already listening it prints that address and exits 0.",
|
|
148
|
+
usage: ["npx vfrey dashboard [--port <n>]"],
|
|
149
|
+
flags: [["--port <n>", "force a port (0 picks any free one) and never reuse; without it: the recorded port if free, else 4317 (VFREY_DASHBOARD_PORT), else the next free port"]],
|
|
150
|
+
},
|
|
151
|
+
dev: { summary: "Same as dashboard.", usage: ["npx vfrey dev [--port <n>]"], flags: [["--port <n>", "default 4317"]] },
|
|
152
|
+
open: {
|
|
153
|
+
summary: "Open this project's dashboard: reuse it when it is listening, else start it detached, then print the URL. The one command to run when the person wants to look.",
|
|
154
|
+
usage: ["npx vfrey open [<run id | video path or title>] [--no-browser] [--json]"],
|
|
155
|
+
flags: [["<run id | video>", "open that run's page; a video is a path or title from `npx vfrey videos`"], ["--no-browser", "print the URL only (also VFREY_NO_BROWSER=1)"], ["--json", "print {url, started, project, runId}"]],
|
|
156
|
+
},
|
|
157
|
+
push: { summary: "Not built yet. Publishing runs inside tick once platform keys are set.", usage: ["npx vfrey push"], flags: [] },
|
|
158
|
+
update: { summary: "Print the commands that update the app and the skills.", usage: ["npx vfrey update [--apply]"], flags: [["--apply", "run them"]] },
|
|
159
|
+
desk: {
|
|
160
|
+
summary: "Read and change the desk database (accounts, series, jobs, secrets).",
|
|
161
|
+
usage: [
|
|
162
|
+
"npx vfrey desk snapshot --json",
|
|
163
|
+
"npx vfrey desk series create|update|pause|resume|delete --id <id> ... (update takes --engine <id>|default, --publish, --engage, --review true|false)",
|
|
164
|
+
"npx vfrey desk series live --id <id> --confirm <channel id> | --off (a new series is dry-run until you go live)",
|
|
165
|
+
"npx vfrey desk job retry|cancel|delete --id <id>",
|
|
166
|
+
"npx vfrey desk job approve|reject --id <id> [--reason <text>] (a job waits for review when its series has --review on; approve renders it on the next tick, reject ends it as rejected: review_rejected)",
|
|
167
|
+
"npx vfrey desk account create|rename|delete, secret set|clear, platform set|remove|disable, source add|update|remove, cadence set, mode set|clear",
|
|
168
|
+
"npx vfrey desk import --from <folder>",
|
|
169
|
+
"npx vfrey desk tick [--now] [--series <id>]",
|
|
170
|
+
],
|
|
171
|
+
flags: [["--json", "snapshot prints JSON"]],
|
|
172
|
+
},
|
|
173
|
+
init: {
|
|
174
|
+
summary: "Make this folder a vfrey project: vfrey.json, package.json, AGENTS.md, CLAUDE.md, the skill for every agent, scenes/, scripts/, videos/ and a private .vfrey/ data folder. Run again to repair; existing files are kept. `npm create vfrey@latest my-channel` does this in a new folder and installs the package. In a folder with no project yet, run it as `npx @vfrey/vfrey init`.",
|
|
175
|
+
usage: ["npx vfrey init [--name <name>] [--engine <id>] [--from DIR]"],
|
|
176
|
+
flags: [
|
|
177
|
+
["--name <name>", "the project name; default is the folder's name"],
|
|
178
|
+
["--engine <id>", "record the project's preferred engine in vfrey.json (remotion, hyperframes, vfrey-framey, comfyui)"],
|
|
179
|
+
["--from DIR", "import a data folder, such as the machine desk ~/.vfrey: its database (run ids kept), renders as videos/<date> <title>.mp4 with transcripts, and scene copies. The source is not changed and a busy source is refused. Running it again adds nothing."],
|
|
180
|
+
],
|
|
181
|
+
},
|
|
182
|
+
videos: {
|
|
183
|
+
summary: "List finished videos, newest first: date, length, title, path. Works in a project or on the machine desk.",
|
|
184
|
+
usage: ["npx vfrey videos [--json] [--limit <n>]"],
|
|
185
|
+
flags: [["--json", "print one JSON document, with the project (or null)"], ["--limit <n>", "show only the newest n"]],
|
|
186
|
+
},
|
|
187
|
+
projects: {
|
|
188
|
+
summary: "List the projects on this machine, with each one's last video. A folder that is gone shows as missing.",
|
|
189
|
+
usage: ["npx vfrey projects [--json] [--prune]"],
|
|
190
|
+
flags: [["--json", "print one JSON document"], ["--prune", "drop projects whose folder is gone from the list"]],
|
|
191
|
+
},
|
|
192
|
+
skill: {
|
|
193
|
+
summary: "Install the vfrey skill where your coding agent finds it (Claude Code, Codex, Grok, Cursor, Gemini CLI).",
|
|
194
|
+
usage: [
|
|
195
|
+
"npx vfrey skill install [--agent claude|codex|grok|cursor|gemini|all] [--project] [--force]",
|
|
196
|
+
"npx vfrey skill install --dir <skills folder>",
|
|
197
|
+
"npx vfrey skill uninstall [--agent <agent>|--dir <folder>] [--project]",
|
|
198
|
+
],
|
|
199
|
+
flags: [
|
|
200
|
+
["--agent <name>", "which agent to install for (default claude): claude, codex, grok, cursor, gemini, or all"],
|
|
201
|
+
["--project", "install into this folder's skills folder for that agent (for example ./.agents/skills for codex)"],
|
|
202
|
+
["--dir <folder>", "any other agent: install into <folder>/vfrey"],
|
|
203
|
+
["--force", "replace a different skill already named vfrey"],
|
|
204
|
+
],
|
|
205
|
+
},
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
const ORDER = ["doctor", "tick", "worker", "service", "engage", "analytics", "render", "scene", "engine", "dashboard", "open", "push", "update", "desk", "skill", "init", "videos", "projects", "login", "logout", "whoami", "org", "topics", "creators", "mail"];
|
|
209
|
+
|
|
210
|
+
export function generalHelp(): string {
|
|
211
|
+
const lines = ["vfrey: make short videos from your agent or the command line.", "", "Usage: npx vfrey <command> [flags]", "", "Commands:"];
|
|
212
|
+
for (const name of ORDER) lines.push(` ${(name === "dashboard" ? "dashboard, dev" : name).padEnd(16)}${HELP[name]!.summary}`);
|
|
213
|
+
lines.push(
|
|
214
|
+
"",
|
|
215
|
+
"Common flags:",
|
|
216
|
+
" tick --now --about \"<subject>\" --dry-run --json --series <id>",
|
|
217
|
+
" open [run|video] --no-browser dashboard --port <n>",
|
|
218
|
+
" desk snapshot --json",
|
|
219
|
+
" skill install --agent codex --project",
|
|
220
|
+
" init --name my-channel videos --limit 5 projects --prune",
|
|
221
|
+
"",
|
|
222
|
+
"Run `npx vfrey <command> --help` for one command's flags.",
|
|
223
|
+
);
|
|
224
|
+
return lines.join("\n") + "\n";
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export function commandHelp(name: string): string | null {
|
|
228
|
+
const help = HELP[name];
|
|
229
|
+
if (!help) return null;
|
|
230
|
+
const lines = [help.summary, "", "Usage:", ...help.usage.map((line) => ` ${line}`)];
|
|
231
|
+
if (help.flags.length) lines.push("", "Flags:", ...help.flags.map(([flag, text]) => ` ${flag.padEnd(24)}${text}`));
|
|
232
|
+
return lines.join("\n") + "\n";
|
|
233
|
+
}
|
package/src/home.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import os from "node:os";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { findProject } from "./project/discover.js";
|
|
4
|
+
import { canonicalPath } from "./project/canonical.js";
|
|
5
|
+
import type { Project } from "./project/marker.js";
|
|
6
|
+
|
|
7
|
+
type Env = Record<string, string | undefined>;
|
|
8
|
+
|
|
9
|
+
/** Where the data folder came from, in precedence order. */
|
|
10
|
+
export type DataSource = "DATA_DIR" | "VFREY_HOME" | "project" | "machine";
|
|
11
|
+
|
|
12
|
+
export interface ResolvedHome {
|
|
13
|
+
dataDir: string;
|
|
14
|
+
source: DataSource;
|
|
15
|
+
/** The project that decided the data folder. Null when an env var or the machine desk did. */
|
|
16
|
+
project: Project | null;
|
|
17
|
+
/** The machine-wide folder: VFREY_HOME, else ~/.vfrey. It holds the project index. */
|
|
18
|
+
machineHome: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** The machine-wide vfrey folder. The project index lives here; it is the data folder only outside a project. */
|
|
22
|
+
export function vfreyHome(env: Env, homedir: string = os.homedir()): string {
|
|
23
|
+
if (env.VFREY_HOME) return path.resolve(env.VFREY_HOME);
|
|
24
|
+
// The background worker runs each project as a child with VFREY_HOME removed (so the project decides its data
|
|
25
|
+
// folder) and names the machine folder here, so the index and logs stay in one place.
|
|
26
|
+
if (env.VFREY_MACHINE_HOME) return path.resolve(env.VFREY_MACHINE_HOME);
|
|
27
|
+
return path.join(homedir, ".vfrey");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The one place the data folder is decided: DATA_DIR > VFREY_HOME > nearest project (`<root>/.vfrey`) > `~/.vfrey`.
|
|
32
|
+
* Every command goes through this (via dataDirFor / loadConfig), with the folder it was run in.
|
|
33
|
+
*/
|
|
34
|
+
export function resolveHome(env: Env, homedir: string = os.homedir(), cwd: string = process.cwd()): ResolvedHome {
|
|
35
|
+
const machineHome = vfreyHome(env, homedir);
|
|
36
|
+
if (env.DATA_DIR) return { dataDir: canonicalPath(env.DATA_DIR), source: "DATA_DIR", project: null, machineHome };
|
|
37
|
+
if (env.VFREY_HOME) return { dataDir: machineHome, source: "VFREY_HOME", project: null, machineHome };
|
|
38
|
+
const project = findProject(cwd);
|
|
39
|
+
if (project) return { dataDir: project.dataDir, source: "project", project, machineHome };
|
|
40
|
+
return { dataDir: machineHome, source: "machine", project: null, machineHome };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function dataDirFor(env: Env, homedir: string = os.homedir(), cwd: string = process.cwd()): string {
|
|
44
|
+
return resolveHome(env, homedir, cwd).dataDir;
|
|
45
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { ConfigError, RetryableError } from "@vfrey/contracts";
|
|
2
|
+
import { requestJson } from "@vfrey/contracts";
|
|
3
|
+
import { cleanTitle, lowerFirst, standInTitle, subjectGist, subjectTag } from "../generate/title.js";
|
|
4
|
+
|
|
5
|
+
export interface LlmRequest {
|
|
6
|
+
system: string;
|
|
7
|
+
user: string;
|
|
8
|
+
temperature: number;
|
|
9
|
+
maxTokens: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface LlmClient {
|
|
13
|
+
complete(input: LlmRequest): Promise<string>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function createLlmClient(input: {
|
|
17
|
+
provider: string;
|
|
18
|
+
baseUrl: string;
|
|
19
|
+
apiKey: string | null;
|
|
20
|
+
model: string;
|
|
21
|
+
fetchImpl?: typeof fetch;
|
|
22
|
+
}): LlmClient {
|
|
23
|
+
const fetchImpl = input.fetchImpl ?? fetch;
|
|
24
|
+
if (input.provider === "mock" || input.provider === "none") return new MockLlm();
|
|
25
|
+
if (input.provider === "anthropic") {
|
|
26
|
+
return {
|
|
27
|
+
async complete(request) {
|
|
28
|
+
if (!input.apiKey) throw new ConfigError("llm_auth", "LLM_API_KEY is not set");
|
|
29
|
+
const result = await requestJson(fetchImpl, `${input.baseUrl}/v1/messages`, {
|
|
30
|
+
method: "POST",
|
|
31
|
+
headers: {
|
|
32
|
+
"content-type": "application/json",
|
|
33
|
+
"x-api-key": input.apiKey,
|
|
34
|
+
"anthropic-version": "2023-06-01",
|
|
35
|
+
...(process.env.ANTHROPIC_WORKSPACE_ID
|
|
36
|
+
? { "anthropic-workspace-id": process.env.ANTHROPIC_WORKSPACE_ID }
|
|
37
|
+
: {}),
|
|
38
|
+
},
|
|
39
|
+
body: JSON.stringify({
|
|
40
|
+
model: input.model,
|
|
41
|
+
max_tokens: request.maxTokens,
|
|
42
|
+
system: request.system,
|
|
43
|
+
messages: [{ role: "user", content: request.user }],
|
|
44
|
+
}),
|
|
45
|
+
});
|
|
46
|
+
if (result.status === 429 || result.status >= 500) {
|
|
47
|
+
throw new RetryableError("llm_rate", `Anthropic ${result.status}`);
|
|
48
|
+
}
|
|
49
|
+
refusedByProvider("Anthropic", result.status);
|
|
50
|
+
if (result.status >= 400) throw new RetryableError("llm_error", `Anthropic ${result.status}`);
|
|
51
|
+
const body = result.json as { content?: Array<{ type?: string; text?: string }> };
|
|
52
|
+
return (
|
|
53
|
+
body.content
|
|
54
|
+
?.filter((part) => part.type === "text" || (part.type === undefined && part.text))
|
|
55
|
+
.map((part) => part.text ?? "")
|
|
56
|
+
.join("\n") ?? ""
|
|
57
|
+
);
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
async complete(request) {
|
|
63
|
+
if (!input.apiKey) throw new ConfigError("llm_auth", "LLM_API_KEY is not set");
|
|
64
|
+
const result = await requestJson(fetchImpl, `${input.baseUrl}/chat/completions`, {
|
|
65
|
+
method: "POST",
|
|
66
|
+
headers: {
|
|
67
|
+
"content-type": "application/json",
|
|
68
|
+
authorization: `Bearer ${input.apiKey}`,
|
|
69
|
+
},
|
|
70
|
+
body: JSON.stringify({
|
|
71
|
+
model: input.model,
|
|
72
|
+
temperature: request.temperature,
|
|
73
|
+
max_tokens: request.maxTokens,
|
|
74
|
+
messages: [
|
|
75
|
+
{ role: "system", content: request.system },
|
|
76
|
+
{ role: "user", content: request.user },
|
|
77
|
+
],
|
|
78
|
+
}),
|
|
79
|
+
});
|
|
80
|
+
if (result.status === 429 || result.status >= 500) {
|
|
81
|
+
throw new RetryableError("llm_rate", `${input.provider} ${result.status}`);
|
|
82
|
+
}
|
|
83
|
+
refusedByProvider(input.provider, result.status);
|
|
84
|
+
if (result.status >= 400) throw new RetryableError("llm_error", `${input.provider} ${result.status}`);
|
|
85
|
+
const body = result.json as { choices?: Array<{ message?: { content?: string } }> };
|
|
86
|
+
return body.choices?.[0]?.message?.content ?? "";
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** A key or model the provider refuses will refuse again. That is configuration, not a retry. */
|
|
92
|
+
function refusedByProvider(provider: string, status: number): void {
|
|
93
|
+
if (status === 401 || status === 403) {
|
|
94
|
+
throw new ConfigError("llm_auth", `${provider} refused the LLM key (HTTP ${status}). Check LLM_API_KEY and its permissions.`);
|
|
95
|
+
}
|
|
96
|
+
if (status === 404) {
|
|
97
|
+
throw new ConfigError("llm_model", `${provider} did not find the model or endpoint (HTTP 404). Check LLM_MODEL and LLM_BASE_URL.`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function parseModelJson(text: string): unknown {
|
|
102
|
+
const fenced = text.match(/```(?:json)?\s*([\s\S]*?)```/);
|
|
103
|
+
const raw = (fenced?.[1] ?? text).trim();
|
|
104
|
+
return JSON.parse(raw) as unknown;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** Deterministic stand-in so a dry run exercises every gate without a network model. */
|
|
108
|
+
export class MockLlm implements LlmClient {
|
|
109
|
+
async complete(input: LlmRequest): Promise<string> {
|
|
110
|
+
if (input.user.includes("TASK:SCORE")) {
|
|
111
|
+
const risky = /diagnose|prescription|as your lawyer|investment advice/i.test(input.user);
|
|
112
|
+
return JSON.stringify({
|
|
113
|
+
score: risky ? 20 : 88,
|
|
114
|
+
safety: risky ? 4 : 24,
|
|
115
|
+
reason: risky ? "advice persona" : "specific and commentable",
|
|
116
|
+
riskClass: risky ? "compliance" : "ok",
|
|
117
|
+
complianceTags: risky ? ["financial_advice_persona"] : [],
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
if (input.user.includes("TASK:PACKAGE")) {
|
|
121
|
+
// An honest placeholder: the subject, cleaned, and lines that say so. It makes no claims about any topic.
|
|
122
|
+
const subject = cleanTitle(extractBetween(input.user, "SOURCE_TITLE:", "\n")) || "A short explainer";
|
|
123
|
+
const said = subject.replace(/[?!]+$/u, "");
|
|
124
|
+
// Inside a sentence the subject is lowercase-initial, and the spoken lines carry only the two words
|
|
125
|
+
// that bind them to it. The full subject lives in the title, so the lines never copy the source.
|
|
126
|
+
const mid = lowerFirst(said);
|
|
127
|
+
const gist = subjectGist(said);
|
|
128
|
+
const pattern = extractBetween(input.user, "HOOK_PATTERN:", "\n") || "question";
|
|
129
|
+
// The wording differs per hook pattern, so two placeholders in a row do not read as one hook.
|
|
130
|
+
const hooks: Record<string, string> = {
|
|
131
|
+
question: `Curious about ${gist}? Here is a quick look.`,
|
|
132
|
+
contrarian: `Take one minute for ${gist}.`,
|
|
133
|
+
story: `The short version of ${gist}, told fast.`,
|
|
134
|
+
statistic: `In under a minute: ${gist}.`,
|
|
135
|
+
"cold-open": `Start here: ${gist}.`,
|
|
136
|
+
};
|
|
137
|
+
const hook = hooks[pattern] ?? hooks.statistic!;
|
|
138
|
+
const tag = subjectTag(subject);
|
|
139
|
+
return JSON.stringify({
|
|
140
|
+
title: standInTitle(subject),
|
|
141
|
+
hook,
|
|
142
|
+
caption: `${hook} #explainer`,
|
|
143
|
+
description: `A placeholder script about ${mid}. Synthetic voice and graphics.`,
|
|
144
|
+
hashtags: ["#explainer", ...(tag ? [tag] : []), "#shorts"],
|
|
145
|
+
thumbnailPrompt: `Vertical poster, large words: ${said}`,
|
|
146
|
+
imagePrompts: [`Abstract diagram of ${mid}`, `Editorial still about ${mid}`],
|
|
147
|
+
beats: [
|
|
148
|
+
{ role: "hook", text: hook, caption: hook, seconds: 3 },
|
|
149
|
+
{ role: "setup", text: `This is a placeholder script. It makes no claims about ${gist}.`, caption: "A placeholder script", seconds: 4 },
|
|
150
|
+
{ role: "turn", text: "Ask your agent to write the real one with --script.", caption: "Ask your agent", seconds: 4 },
|
|
151
|
+
{ role: "payoff", text: "Until then, treat this video as a draft.", caption: "A draft", seconds: 4 },
|
|
152
|
+
{ role: "cta", text: "Then make it again for the real thing.", caption: "Make it again", seconds: 3 },
|
|
153
|
+
],
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
if (input.user.includes("TASK:REPLY")) {
|
|
157
|
+
const tone = extractBetween(input.user, "TONE:", "\n") || "genuine";
|
|
158
|
+
const comment = extractBetween(input.user, "COMMENT:", "\n") || "interesting";
|
|
159
|
+
if (tone === "spicy") {
|
|
160
|
+
return JSON.stringify({ tone, text: `Fair push on "${comment.slice(0, 40)}". The timing still holds.` });
|
|
161
|
+
}
|
|
162
|
+
if (tone === "oneliner") {
|
|
163
|
+
return JSON.stringify({ tone, text: "Glad that landed." });
|
|
164
|
+
}
|
|
165
|
+
return JSON.stringify({ tone, text: "The short version: the timing is the part that moves." });
|
|
166
|
+
}
|
|
167
|
+
return JSON.stringify({ ok: true });
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function extractBetween(value: string, start: string, end: string): string {
|
|
172
|
+
const from = value.indexOf(start);
|
|
173
|
+
if (from < 0) return "";
|
|
174
|
+
const rest = value.slice(from + start.length);
|
|
175
|
+
const to = rest.indexOf(end);
|
|
176
|
+
return (to < 0 ? rest : rest.slice(0, to)).trim();
|
|
177
|
+
}
|
package/src/log.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// Structured log lines go to stderr. Stdout belongs to the command's own output, which agents parse.
|
|
2
|
+
import { redact } from "@vfrey/contracts";
|
|
3
|
+
|
|
4
|
+
export function log(level: string, message: string, fields: Record<string, unknown> = {}): void {
|
|
5
|
+
const line = redact(JSON.stringify({ ts: new Date().toISOString(), level, message, ...fields }));
|
|
6
|
+
process.stderr.write(line + "\n");
|
|
7
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { ConfigError, RetryableError } from "@vfrey/contracts";
|
|
2
|
+
import type { Account, AppConfig } from "@vfrey/contracts";
|
|
3
|
+
import { requestJson } from "@vfrey/contracts";
|
|
4
|
+
import { createHash } from "node:crypto";
|
|
5
|
+
import { TokenVault, vaultKey } from "./vault.js";
|
|
6
|
+
|
|
7
|
+
type Env = Record<string, string | undefined>;
|
|
8
|
+
|
|
9
|
+
/** A Meta token is read from the environment again after this long, so a rotated token is picked up. */
|
|
10
|
+
const META_TOKEN_DAYS = 50;
|
|
11
|
+
|
|
12
|
+
function fingerprint(token: string): string {
|
|
13
|
+
return createHash("sha256").update(token).digest("hex").slice(0, 16);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function tokenEnvName(account: Account, platform: "youtube" | "tiktok" | "instagram" | "facebook"): string | null {
|
|
17
|
+
switch (platform) {
|
|
18
|
+
case "youtube":
|
|
19
|
+
return account.platforms.youtube?.refreshTokenEnv ?? null;
|
|
20
|
+
case "tiktok":
|
|
21
|
+
return account.platforms.tiktok?.refreshTokenEnv ?? null;
|
|
22
|
+
case "instagram":
|
|
23
|
+
return account.platforms.instagram?.accessTokenEnv ?? null;
|
|
24
|
+
default:
|
|
25
|
+
return account.platforms.facebook?.pageTokenEnv ?? null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export async function accessToken(input: {
|
|
30
|
+
config: AppConfig;
|
|
31
|
+
account: Account;
|
|
32
|
+
platform: "youtube" | "tiktok" | "instagram" | "facebook";
|
|
33
|
+
env: Env;
|
|
34
|
+
vault: TokenVault;
|
|
35
|
+
fetchImpl: typeof fetch;
|
|
36
|
+
now: Date;
|
|
37
|
+
}): Promise<string> {
|
|
38
|
+
const key = vaultKey(input.account.id, input.platform);
|
|
39
|
+
const envName = tokenEnvName(input.account, input.platform);
|
|
40
|
+
const envToken = envName ? input.env[envName] : undefined;
|
|
41
|
+
const seed = envToken ? fingerprint(envToken) : null;
|
|
42
|
+
let cached = input.vault.get(key);
|
|
43
|
+
// A token the operator set after the cache was seeded wins over the cache.
|
|
44
|
+
if (cached && seed && cached.seed && cached.seed !== seed) cached = null;
|
|
45
|
+
const context = { ...input, seed, envName };
|
|
46
|
+
|
|
47
|
+
if (input.platform === "instagram" || input.platform === "facebook") {
|
|
48
|
+
return metaToken(context, key, input.platform, cached);
|
|
49
|
+
}
|
|
50
|
+
if (cached?.accessToken && cached.accessExpiresAt) {
|
|
51
|
+
if (new Date(cached.accessExpiresAt).getTime() - input.now.getTime() > 5 * 60_000) {
|
|
52
|
+
return cached.accessToken;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (input.platform === "youtube") return refreshGoogle(context, key, cached?.refreshToken ?? null);
|
|
56
|
+
return refreshTikTok(context, key, cached?.refreshToken ?? null);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function isInvalidGrant(json: unknown): boolean {
|
|
60
|
+
return Boolean(json) && typeof json === "object" && (json as { error?: unknown }).error === "invalid_grant";
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function refreshGoogle(
|
|
64
|
+
input: {
|
|
65
|
+
account: Account;
|
|
66
|
+
env: Env;
|
|
67
|
+
vault: TokenVault;
|
|
68
|
+
fetchImpl: typeof fetch;
|
|
69
|
+
now: Date;
|
|
70
|
+
seed: string | null;
|
|
71
|
+
envName: string | null;
|
|
72
|
+
},
|
|
73
|
+
key: string,
|
|
74
|
+
cachedRefresh: string | null,
|
|
75
|
+
): Promise<string> {
|
|
76
|
+
const youtube = input.account.platforms.youtube;
|
|
77
|
+
if (!youtube) throw new ConfigError("oauth", "YouTube is not configured");
|
|
78
|
+
const refreshToken = cachedRefresh || required(input.env, youtube.refreshTokenEnv);
|
|
79
|
+
const body = new URLSearchParams({
|
|
80
|
+
client_id: required(input.env, youtube.clientIdEnv),
|
|
81
|
+
client_secret: required(input.env, youtube.clientSecretEnv),
|
|
82
|
+
refresh_token: refreshToken,
|
|
83
|
+
grant_type: "refresh_token",
|
|
84
|
+
});
|
|
85
|
+
const result = await requestJson(input.fetchImpl, "https://oauth2.googleapis.com/token", {
|
|
86
|
+
method: "POST",
|
|
87
|
+
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
88
|
+
body,
|
|
89
|
+
});
|
|
90
|
+
if (result.status === 400) {
|
|
91
|
+
if (isInvalidGrant(result.json)) {
|
|
92
|
+
input.vault.drop(key);
|
|
93
|
+
throw new ConfigError("oauth_refresh", `Google refused the refresh token (invalid_grant). Set ${youtube.refreshTokenEnv} to a new refresh token.`);
|
|
94
|
+
}
|
|
95
|
+
throw new ConfigError("oauth_refresh", "Google refused the token request (HTTP 400). Check the client id and secret.");
|
|
96
|
+
}
|
|
97
|
+
if (result.status >= 400) throw new RetryableError("oauth_refresh", `Google token HTTP ${result.status}`);
|
|
98
|
+
const json = result.json as { access_token?: string; expires_in?: number; refresh_token?: string };
|
|
99
|
+
if (!json.access_token) throw new RetryableError("oauth_refresh", "Google token response had no access_token");
|
|
100
|
+
const expires = new Date(input.now.getTime() + (json.expires_in ?? 3600) * 1000).toISOString();
|
|
101
|
+
input.vault.put(key, {
|
|
102
|
+
accessToken: json.access_token,
|
|
103
|
+
accessExpiresAt: expires,
|
|
104
|
+
refreshToken: json.refresh_token || refreshToken,
|
|
105
|
+
seed: input.seed ?? input.vault.get(key)?.seed ?? null,
|
|
106
|
+
});
|
|
107
|
+
return json.access_token;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async function refreshTikTok(
|
|
111
|
+
input: {
|
|
112
|
+
config?: AppConfig;
|
|
113
|
+
account: Account;
|
|
114
|
+
env: Env;
|
|
115
|
+
vault: TokenVault;
|
|
116
|
+
fetchImpl: typeof fetch;
|
|
117
|
+
now: Date;
|
|
118
|
+
configClient?: AppConfig;
|
|
119
|
+
seed: string | null;
|
|
120
|
+
envName: string | null;
|
|
121
|
+
} & { config: AppConfig },
|
|
122
|
+
key: string,
|
|
123
|
+
cachedRefresh: string | null,
|
|
124
|
+
): Promise<string> {
|
|
125
|
+
const tiktok = input.account.platforms.tiktok;
|
|
126
|
+
if (!tiktok) throw new ConfigError("oauth", "TikTok is not configured");
|
|
127
|
+
if (!input.config.tiktok.clientKey || !input.config.tiktok.clientSecret) {
|
|
128
|
+
throw new ConfigError("oauth", "TIKTOK_CLIENT_KEY and TIKTOK_CLIENT_SECRET are required");
|
|
129
|
+
}
|
|
130
|
+
const refreshToken = cachedRefresh || required(input.env, tiktok.refreshTokenEnv);
|
|
131
|
+
const body = new URLSearchParams({
|
|
132
|
+
client_key: input.config.tiktok.clientKey,
|
|
133
|
+
client_secret: input.config.tiktok.clientSecret,
|
|
134
|
+
grant_type: "refresh_token",
|
|
135
|
+
refresh_token: refreshToken,
|
|
136
|
+
});
|
|
137
|
+
const result = await requestJson(input.fetchImpl, "https://open.tiktokapis.com/v2/oauth/token/", {
|
|
138
|
+
method: "POST",
|
|
139
|
+
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
140
|
+
body,
|
|
141
|
+
});
|
|
142
|
+
if (result.status === 400) {
|
|
143
|
+
if (isInvalidGrant(result.json)) {
|
|
144
|
+
input.vault.drop(key);
|
|
145
|
+
throw new ConfigError("oauth_refresh", `TikTok refused the refresh token (invalid_grant). Set ${tiktok.refreshTokenEnv} to a new refresh token.`);
|
|
146
|
+
}
|
|
147
|
+
throw new ConfigError("oauth_refresh", "TikTok refused the token request (HTTP 400). Check TIKTOK_CLIENT_KEY and TIKTOK_CLIENT_SECRET.");
|
|
148
|
+
}
|
|
149
|
+
if (result.status >= 400) throw new RetryableError("oauth_refresh", `TikTok token HTTP ${result.status}`);
|
|
150
|
+
const json = result.json as { access_token?: string; expires_in?: number; refresh_token?: string };
|
|
151
|
+
if (!json.access_token) throw new RetryableError("oauth_refresh", "TikTok token response had no access_token");
|
|
152
|
+
input.vault.put(key, {
|
|
153
|
+
accessToken: json.access_token,
|
|
154
|
+
accessExpiresAt: new Date(input.now.getTime() + (json.expires_in ?? 86400) * 1000).toISOString(),
|
|
155
|
+
refreshToken: json.refresh_token || refreshToken,
|
|
156
|
+
seed: input.seed ?? input.vault.get(key)?.seed ?? null,
|
|
157
|
+
});
|
|
158
|
+
return json.access_token;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function metaToken(
|
|
162
|
+
input: { account: Account; env: Env; vault: TokenVault; now: Date; seed: string | null },
|
|
163
|
+
key: string,
|
|
164
|
+
platform: "instagram" | "facebook",
|
|
165
|
+
cached: { accessToken: string | null; accessExpiresAt: string | null } | null,
|
|
166
|
+
): string {
|
|
167
|
+
const envName =
|
|
168
|
+
platform === "instagram"
|
|
169
|
+
? input.account.platforms.instagram?.accessTokenEnv
|
|
170
|
+
: input.account.platforms.facebook?.pageTokenEnv;
|
|
171
|
+
if (!envName) throw new ConfigError("oauth", `${platform} token env is not configured`);
|
|
172
|
+
const live =
|
|
173
|
+
cached?.accessToken && cached.accessExpiresAt && new Date(cached.accessExpiresAt).getTime() > input.now.getTime();
|
|
174
|
+
if (live) return cached.accessToken as string;
|
|
175
|
+
const token = input.env[envName];
|
|
176
|
+
// A cache from before tokens carried an expiry stands in only while the environment has no token.
|
|
177
|
+
if (!token && cached?.accessToken && !cached.accessExpiresAt) return cached.accessToken;
|
|
178
|
+
if (!token) throw new ConfigError("missing_env", `Environment variable ${envName} is not set`);
|
|
179
|
+
// The token is read from the environment again after META_TOKEN_DAYS, so a rotated token is picked up.
|
|
180
|
+
const expires = new Date(input.now.getTime() + META_TOKEN_DAYS * 86_400_000).toISOString();
|
|
181
|
+
input.vault.put(key, { accessToken: token, accessExpiresAt: expires, refreshToken: null, seed: input.seed });
|
|
182
|
+
return token;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function required(env: Env, name: string): string {
|
|
186
|
+
const value = env[name];
|
|
187
|
+
if (!value) throw new ConfigError("missing_env", `Environment variable ${name} is not set`);
|
|
188
|
+
return value;
|
|
189
|
+
}
|