@vfrey/vfrey 0.0.1 → 1.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (610) hide show
  1. package/LICENSE +33 -0
  2. package/README.md +78 -3
  3. package/accounts.example.json +61 -0
  4. package/accounts.first-run.json +41 -0
  5. package/bin/vfrey.mjs +36 -0
  6. package/integration-tools/README.md +43 -0
  7. package/integration-tools/image/pillow.md +32 -0
  8. package/integration-tools/image/playwright.md +33 -0
  9. package/integration-tools/image/resvg.md +36 -0
  10. package/integration-tools/image/satori.md +36 -0
  11. package/integration-tools/image/sharp.md +38 -0
  12. package/integration-tools/video/blender.md +34 -0
  13. package/integration-tools/video/ffmpeg.md +41 -0
  14. package/integration-tools/video/manim.md +37 -0
  15. package/integration-tools/video/motion-canvas.md +37 -0
  16. package/integration-tools/video/remotion.md +50 -0
  17. package/integration-tools/video/revideo.md +32 -0
  18. package/package.json +74 -6
  19. package/skills/vfrey-orchestrator/SKILL.md +15 -0
  20. package/skills/vfrey-orchestrator/channel-video/SKILL.md +41 -0
  21. package/skills/vfrey-product/SKILL.md +63 -0
  22. package/skills/vfrey-product/examples/concurrency-vs-parallelism/framey/cook-one.svg +14 -0
  23. package/skills/vfrey-product/examples/concurrency-vs-parallelism/framey/cook-two.svg +22 -0
  24. package/skills/vfrey-product/examples/concurrency-vs-parallelism/framey/index.html +70 -0
  25. package/skills/vfrey-product/examples/concurrency-vs-parallelism/remotion/cook-one.svg +14 -0
  26. package/skills/vfrey-product/examples/concurrency-vs-parallelism/remotion/cook-two.svg +22 -0
  27. package/skills/vfrey-product/examples/concurrency-vs-parallelism/remotion/index.tsx +49 -0
  28. package/skills/vfrey-product/examples/concurrency-vs-parallelism/script.json +15 -0
  29. package/skills/vfrey-product/examples/octopus-facts/index.html +60 -0
  30. package/skills/vfrey-product/examples/octopus-facts/octopus.svg +8 -0
  31. package/skills/vfrey-product/examples/octopus-facts/script.json +14 -0
  32. package/skills/vfrey-product/push/SKILL.md +15 -0
  33. package/skills/vfrey-product/scenes/NOTICE +5 -0
  34. package/skills/vfrey-product/scenes/blocks.md +57 -0
  35. package/skills/vfrey-product/scenes/craft.md +118 -0
  36. package/skills/vfrey-product/scenes/motion.md +88 -0
  37. package/src/analytics/dashboard.ts +109 -0
  38. package/src/analytics/index.ts +260 -0
  39. package/src/api/client.ts +361 -0
  40. package/src/api/cloud-files.ts +197 -0
  41. package/src/api/cloud-runs.ts +75 -0
  42. package/src/api/cloud.ts +413 -0
  43. package/src/api/commands.ts +330 -0
  44. package/src/api/config.ts +34 -0
  45. package/src/api/creators.ts +463 -0
  46. package/src/api/credentials.ts +152 -0
  47. package/src/api/mail.ts +333 -0
  48. package/src/api/topics.ts +207 -0
  49. package/src/api/types.ts +273 -0
  50. package/src/cli.ts +468 -0
  51. package/src/cloud/bundle.ts +70 -0
  52. package/src/cloud/cloudrun-executor.ts +237 -0
  53. package/src/cloud/executor.ts +281 -0
  54. package/src/cloud/gcs-sign.ts +76 -0
  55. package/src/cloud/job.ts +106 -0
  56. package/src/cloud/remote-job.ts +67 -0
  57. package/src/cloud/render-job.ts +191 -0
  58. package/src/config.ts +309 -0
  59. package/src/dashboard-link.ts +180 -0
  60. package/src/dashboard-start.ts +103 -0
  61. package/src/desk/channel.ts +64 -0
  62. package/src/desk/cli.ts +216 -0
  63. package/src/desk/creators-route.ts +129 -0
  64. package/src/desk/fonts/JetBrainsMono-OFL.txt +93 -0
  65. package/src/desk/fonts/google-sans-flex-latin.woff2 +0 -0
  66. package/src/desk/fonts/jetbrains-mono-medium.woff2 +0 -0
  67. package/src/desk/http.ts +460 -0
  68. package/src/desk/mail-route.ts +103 -0
  69. package/src/desk/overlay.ts +112 -0
  70. package/src/desk/preview.ts +422 -0
  71. package/src/desk/screens.ts +149 -0
  72. package/src/desk/secrets.ts +143 -0
  73. package/src/desk/series.ts +127 -0
  74. package/src/desk/state.ts +77 -0
  75. package/src/desk/studio-route.ts +119 -0
  76. package/src/desk/tick-runner.ts +66 -0
  77. package/src/desk/tick.ts +56 -0
  78. package/src/desk/topics-route.ts +41 -0
  79. package/src/desk/ui/app.css +505 -0
  80. package/src/desk/ui/app.js +185 -0
  81. package/src/desk/ui/apple-icon.png +0 -0
  82. package/src/desk/ui/components.js +79 -0
  83. package/src/desk/ui/core.js +156 -0
  84. package/src/desk/ui/favicon.ico +0 -0
  85. package/src/desk/ui/icon.svg +1 -0
  86. package/src/desk/ui/icons.js +49 -0
  87. package/src/desk/ui/index.html +41 -0
  88. package/src/desk/ui/metrics-math.d.ts +22 -0
  89. package/src/desk/ui/metrics-math.js +135 -0
  90. package/src/desk/ui/theme.js +5 -0
  91. package/src/desk/ui/views-creators.js +209 -0
  92. package/src/desk/ui/views-keys.js +114 -0
  93. package/src/desk/ui/views-mail.js +188 -0
  94. package/src/desk/ui/views-misc.js +123 -0
  95. package/src/desk/ui/views-review.js +119 -0
  96. package/src/desk/ui/views-runs.js +350 -0
  97. package/src/desk/ui/views-screens.js +163 -0
  98. package/src/desk/ui/views-series.js +318 -0
  99. package/src/desk/ui/views-topics.js +87 -0
  100. package/src/desk/verbs.ts +361 -0
  101. package/src/doctor.ts +212 -0
  102. package/src/drain.ts +14 -0
  103. package/src/engage/index.ts +217 -0
  104. package/src/generate/index.ts +110 -0
  105. package/src/generate/script.ts +152 -0
  106. package/src/generate/title.ts +78 -0
  107. package/src/help.ts +252 -0
  108. package/src/home.ts +45 -0
  109. package/src/llm/client.ts +177 -0
  110. package/src/log.ts +7 -0
  111. package/src/oauth/tokens.ts +189 -0
  112. package/src/oauth/vault.ts +72 -0
  113. package/src/pipeline/review.ts +642 -0
  114. package/src/pipeline/run.ts +443 -0
  115. package/src/pipeline/series-guard.ts +26 -0
  116. package/src/pipeline/source-fetch.ts +69 -0
  117. package/src/pipeline/stages.ts +728 -0
  118. package/src/pipeline/topics-source.ts +90 -0
  119. package/src/product/commands.ts +296 -0
  120. package/src/product/config-file.ts +38 -0
  121. package/src/product/engine-choice.ts +89 -0
  122. package/src/product/engines.ts +103 -0
  123. package/src/product/insights.ts +50 -0
  124. package/src/product/keys.ts +67 -0
  125. package/src/product/paths.ts +18 -0
  126. package/src/product/probes.ts +88 -0
  127. package/src/product/push-flow.ts +90 -0
  128. package/src/product/render-video.ts +21 -0
  129. package/src/product/short.ts +62 -0
  130. package/src/product/update.ts +32 -0
  131. package/src/project/canonical.ts +14 -0
  132. package/src/project/commands.ts +177 -0
  133. package/src/project/dashboard-status.ts +28 -0
  134. package/src/project/discover.ts +19 -0
  135. package/src/project/index-file.ts +73 -0
  136. package/src/project/marker.ts +92 -0
  137. package/src/project/migrate.ts +201 -0
  138. package/src/project/names.ts +27 -0
  139. package/src/project/notice.ts +16 -0
  140. package/src/project/open.ts +88 -0
  141. package/src/project/scaffold.ts +112 -0
  142. package/src/project/templates.ts +42 -0
  143. package/src/project/videos.ts +64 -0
  144. package/src/publish/facebook.ts +91 -0
  145. package/src/publish/index.ts +12 -0
  146. package/src/publish/instagram.ts +97 -0
  147. package/src/publish/rate.ts +62 -0
  148. package/src/publish/session.ts +242 -0
  149. package/src/publish/tiktok.ts +202 -0
  150. package/src/publish/youtube.ts +177 -0
  151. package/src/queue/desk-ops.ts +1170 -0
  152. package/src/queue/seal.ts +3 -0
  153. package/src/queue/store.ts +1006 -0
  154. package/src/remotion/AiLabel.tsx +26 -0
  155. package/src/remotion/Root.tsx +73 -0
  156. package/src/remotion/defaults.ts +26 -0
  157. package/src/remotion/films/claude/ClaudeFilm.tsx +1014 -0
  158. package/src/remotion/films/engine/EngineMark.tsx +17 -0
  159. package/src/remotion/films/rea/ReaShort.tsx +552 -0
  160. package/src/remotion/films/rea/drawings.tsx +907 -0
  161. package/src/remotion/films/rea/timing.ts +144 -0
  162. package/src/remotion/index.ts +4 -0
  163. package/src/remotion/templates/EditorialSplit.tsx +39 -0
  164. package/src/remotion/templates/KineticType.tsx +39 -0
  165. package/src/remotion/templates/SignalBoard.tsx +50 -0
  166. package/src/remotion/timing.ts +22 -0
  167. package/src/render/chrome.ts +78 -0
  168. package/src/render/engine-mark.ts +29 -0
  169. package/src/render/framey-template.ts +130 -0
  170. package/src/render/html-engine.ts +149 -0
  171. package/src/render/node-crypto-stub.ts +11 -0
  172. package/src/render/probe.ts +29 -0
  173. package/src/render/props.ts +17 -0
  174. package/src/render/vertical-short.ts +35 -0
  175. package/src/render/webpack.ts +33 -0
  176. package/src/render/worker.ts +65 -0
  177. package/src/scene/assets/fonts/TitanOne-OFL.txt +94 -0
  178. package/src/scene/assets/fonts/titan-one.woff2 +0 -0
  179. package/src/scene/assets/gsap/LICENSE.txt +8 -0
  180. package/src/scene/assets/gsap/gsap.min.js +11 -0
  181. package/src/scene/audio.ts +54 -0
  182. package/src/scene/blocks/LICENSE +190 -0
  183. package/src/scene/blocks/NOTICE +5 -0
  184. package/src/scene/blocks/bar-chart-race/bar-chart-race.html +585 -0
  185. package/src/scene/blocks/beat-freeze-cut/beat-freeze-cut.html +874 -0
  186. package/src/scene/blocks/camcorder-hud/camcorder-hud.html +164 -0
  187. package/src/scene/blocks/camera-dolly-zoom/camera-dolly-zoom.html +355 -0
  188. package/src/scene/blocks/code-diff/code-diff.html +817 -0
  189. package/src/scene/blocks/code-snippet-apple-terminal-pro/code-snippet-apple-terminal-pro.html +248 -0
  190. package/src/scene/blocks/code-typing/code-typing.html +764 -0
  191. package/src/scene/blocks/data-chart/data-chart.html +452 -0
  192. package/src/scene/blocks/flowchart-vertical/flowchart-vertical.html +454 -0
  193. package/src/scene/blocks/hw-scribble-transition/hw-scribble-transition.html +153 -0
  194. package/src/scene/blocks/index.json +824 -0
  195. package/src/scene/blocks/lt-accent-underline/lt-accent-underline.html +107 -0
  196. package/src/scene/blocks/lt-bold-block/lt-bold-block.html +109 -0
  197. package/src/scene/blocks/lt-clean-bar/lt-clean-bar.html +112 -0
  198. package/src/scene/blocks/lt-dark-card/lt-dark-card.html +111 -0
  199. package/src/scene/blocks/lt-kicker-name/lt-kicker-name.html +114 -0
  200. package/src/scene/blocks/lt-mask-reveal/lt-mask-reveal.html +120 -0
  201. package/src/scene/blocks/lt-neon-border/lt-neon-border.html +391 -0
  202. package/src/scene/blocks/lt-stack-bars/lt-stack-bars.html +109 -0
  203. package/src/scene/blocks/macos-notification/macos-notification.html +194 -0
  204. package/src/scene/blocks/mk-background/mk-background.html +236 -0
  205. package/src/scene/blocks/mk-callout-highlight/mk-callout-highlight.html +183 -0
  206. package/src/scene/blocks/mk-clone-wall-transition/mk-clone-wall-transition.html +226 -0
  207. package/src/scene/blocks/mk-line-graph/mk-line-graph.html +293 -0
  208. package/src/scene/blocks/mk-progress-stat/mk-progress-stat.html +186 -0
  209. package/src/scene/blocks/mk-specs-list/mk-specs-list.html +223 -0
  210. package/src/scene/blocks/news-ticker/news-ticker.html +409 -0
  211. package/src/scene/blocks/organic-light-leak-overlay/organic-light-leak-overlay.html +126 -0
  212. package/src/scene/blocks/oscilloscope-trace/oscilloscope-trace.html +519 -0
  213. package/src/scene/blocks/snow/snow.html +205 -0
  214. package/src/scene/blocks/split-flap-board/split-flap-board.html +347 -0
  215. package/src/scene/blocks/spotify-card/spotify-card.html +294 -0
  216. package/src/scene/blocks/starfield/starfield.html +188 -0
  217. package/src/scene/blocks/thread-message-stack/thread-message-stack.html +239 -0
  218. package/src/scene/blocks/week-in-merges/week-in-merges.html +470 -0
  219. package/src/scene/blocks/x-post/x-post.html +437 -0
  220. package/src/scene/blocks/yt-comment-card/yt-comment-card.html +319 -0
  221. package/src/scene/blocks/yt-lcd-background/yt-lcd-background.html +307 -0
  222. package/src/scene/blocks/yt-prism-title/yt-prism-title.html +205 -0
  223. package/src/scene/blocks.ts +229 -0
  224. package/src/scene/command.ts +272 -0
  225. package/src/scene/figure.ts +181 -0
  226. package/src/scene/glyphs.ts +107 -0
  227. package/src/scene/init.ts +86 -0
  228. package/src/scene/inspect/css-tree.d.ts +6 -0
  229. package/src/scene/inspect/page-scripts.ts +272 -0
  230. package/src/scene/inspect/report.ts +405 -0
  231. package/src/scene/inspect/run.ts +200 -0
  232. package/src/scene/inspect/source.ts +236 -0
  233. package/src/scene/label.ts +131 -0
  234. package/src/scene/net-guard.ts +74 -0
  235. package/src/scene/render.ts +292 -0
  236. package/src/scene/runtime/browser.ts +199 -0
  237. package/src/scene/runtime/core.ts +181 -0
  238. package/src/scene/runtime/react.tsx +115 -0
  239. package/src/scene/runtime/transitions.ts +133 -0
  240. package/src/scene/sanitize.ts +181 -0
  241. package/src/scene/scene.ts +152 -0
  242. package/src/scene/studio-command.ts +48 -0
  243. package/src/scene/studio-skin.ts +59 -0
  244. package/src/scene/studio.ts +337 -0
  245. package/src/scene/supply.ts +111 -0
  246. package/src/scene/timing.ts +288 -0
  247. package/src/scene/transitions/LICENSE +190 -0
  248. package/src/scene/transitions/NOTICE +5 -0
  249. package/src/scene/transitions/SOURCE.json +24 -0
  250. package/src/scene/transitions/transitions.global.js +38 -0
  251. package/src/scene/transitions.ts +26 -0
  252. package/src/service/all-projects.ts +153 -0
  253. package/src/service/commands.ts +256 -0
  254. package/src/service/due.ts +82 -0
  255. package/src/service/lock.ts +101 -0
  256. package/src/service/log-file.ts +56 -0
  257. package/src/service/state.ts +41 -0
  258. package/src/service/units.ts +90 -0
  259. package/src/service/worker.ts +51 -0
  260. package/src/skill-agents.ts +74 -0
  261. package/src/skill-install.ts +334 -0
  262. package/src/sync-write.ts +21 -0
  263. package/src/tick-report.ts +205 -0
  264. package/src/tts.ts +124 -0
  265. package/vendor/@vfrey/contracts/dist/account.d.ts +97 -0
  266. package/vendor/@vfrey/contracts/dist/account.js +27 -0
  267. package/vendor/@vfrey/contracts/dist/checks.d.ts +8 -0
  268. package/vendor/@vfrey/contracts/dist/checks.js +1 -0
  269. package/vendor/@vfrey/contracts/dist/credentials/crypto.d.ts +4 -0
  270. package/vendor/@vfrey/contracts/dist/credentials/crypto.js +38 -0
  271. package/vendor/@vfrey/contracts/dist/domain/advice-persona.d.ts +10 -0
  272. package/vendor/@vfrey/contracts/dist/domain/advice-persona.js +24 -0
  273. package/vendor/@vfrey/contracts/dist/domain/backoff.d.ts +13 -0
  274. package/vendor/@vfrey/contracts/dist/domain/backoff.js +27 -0
  275. package/vendor/@vfrey/contracts/dist/domain/cadence.d.ts +29 -0
  276. package/vendor/@vfrey/contracts/dist/domain/cadence.js +95 -0
  277. package/vendor/@vfrey/contracts/dist/domain/creators.d.ts +146 -0
  278. package/vendor/@vfrey/contracts/dist/domain/creators.js +2 -0
  279. package/vendor/@vfrey/contracts/dist/domain/disclosure.d.ts +127 -0
  280. package/vendor/@vfrey/contracts/dist/domain/disclosure.js +250 -0
  281. package/vendor/@vfrey/contracts/dist/domain/errors.d.ts +34 -0
  282. package/vendor/@vfrey/contracts/dist/domain/errors.js +68 -0
  283. package/vendor/@vfrey/contracts/dist/domain/redact.d.ts +1 -0
  284. package/vendor/@vfrey/contracts/dist/domain/redact.js +21 -0
  285. package/vendor/@vfrey/contracts/dist/domain/revenue.d.ts +13 -0
  286. package/vendor/@vfrey/contracts/dist/domain/revenue.js +16 -0
  287. package/vendor/@vfrey/contracts/dist/domain/safety.d.ts +20 -0
  288. package/vendor/@vfrey/contracts/dist/domain/safety.js +61 -0
  289. package/vendor/@vfrey/contracts/dist/domain/similarity.d.ts +13 -0
  290. package/vendor/@vfrey/contracts/dist/domain/similarity.js +100 -0
  291. package/vendor/@vfrey/contracts/dist/domain/templates.d.ts +18 -0
  292. package/vendor/@vfrey/contracts/dist/domain/templates.js +67 -0
  293. package/vendor/@vfrey/contracts/dist/domain/tiktok-chunks.d.ts +19 -0
  294. package/vendor/@vfrey/contracts/dist/domain/tiktok-chunks.js +52 -0
  295. package/vendor/@vfrey/contracts/dist/domain/topics.d.ts +78 -0
  296. package/vendor/@vfrey/contracts/dist/domain/topics.js +4 -0
  297. package/vendor/@vfrey/contracts/dist/domain/types.d.ts +216 -0
  298. package/vendor/@vfrey/contracts/dist/domain/types.js +1 -0
  299. package/vendor/@vfrey/contracts/dist/domain/variation.d.ts +57 -0
  300. package/vendor/@vfrey/contracts/dist/domain/variation.js +278 -0
  301. package/vendor/@vfrey/contracts/dist/events.d.ts +20 -0
  302. package/vendor/@vfrey/contracts/dist/events.js +1 -0
  303. package/vendor/@vfrey/contracts/dist/http.d.ts +11 -0
  304. package/vendor/@vfrey/contracts/dist/http.js +37 -0
  305. package/vendor/@vfrey/contracts/dist/index.d.ts +22 -0
  306. package/vendor/@vfrey/contracts/dist/index.js +22 -0
  307. package/vendor/@vfrey/contracts/dist/log.d.ts +2 -0
  308. package/vendor/@vfrey/contracts/dist/log.js +10 -0
  309. package/vendor/@vfrey/contracts/dist/mail.d.ts +55 -0
  310. package/vendor/@vfrey/contracts/dist/mail.js +1 -0
  311. package/vendor/@vfrey/contracts/package.json +18 -0
  312. package/vendor/@vfrey/ingest/dist/index.d.ts +41 -0
  313. package/vendor/@vfrey/ingest/dist/index.js +221 -0
  314. package/vendor/@vfrey/ingest/dist/parse.d.ts +5 -0
  315. package/vendor/@vfrey/ingest/dist/parse.js +165 -0
  316. package/vendor/@vfrey/ingest/package.json +21 -0
  317. package/vendor/framey/LICENSE +190 -0
  318. package/vendor/framey/NOTICE +15 -0
  319. package/vendor/framey/SOURCE.json +24 -0
  320. package/vendor/framey/STUDIO-SOURCE.json +9 -0
  321. package/vendor/framey/bin/framey-localize-fonts.mjs +12 -0
  322. package/vendor/framey/bin/framey.mjs +16 -0
  323. package/vendor/framey/dist/add-VMJD5KBS.js +75 -0
  324. package/vendor/framey/dist/animatedGifPrep-QPKGJ6PM.js +43 -0
  325. package/vendor/framey/dist/api.d.ts +211 -0
  326. package/vendor/framey/dist/api.js +76 -0
  327. package/vendor/framey/dist/appHistory-WW56FZTT.js +31 -0
  328. package/vendor/framey/dist/asset-WQKGOELE.js +26 -0
  329. package/vendor/framey/dist/audio-RSJWVWRA.js +1936 -0
  330. package/vendor/framey/dist/batchRender-3HPICDCT.js +366 -0
  331. package/vendor/framey/dist/beats-HL2BDUZI.js +273 -0
  332. package/vendor/framey/dist/benchmark-RWCWHI5Z.js +230 -0
  333. package/vendor/framey/dist/browser-ECYET3Q2.js +206 -0
  334. package/vendor/framey/dist/bundleWithLocalizedFonts-ALYBQEQT.js +52 -0
  335. package/vendor/framey/dist/captureCompositionFrame-4DA6SEH2.js +94 -0
  336. package/vendor/framey/dist/catalog-5JVDEUWK.js +979 -0
  337. package/vendor/framey/dist/catch-up-NEBCL5NO.js +106 -0
  338. package/vendor/framey/dist/check-F5N544ML.js +111 -0
  339. package/vendor/framey/dist/checkBrowser-LZ66CMJG.js +110 -0
  340. package/vendor/framey/dist/chunk-2K7Z2HVP.js +372 -0
  341. package/vendor/framey/dist/chunk-2UZQHDM7.js +323 -0
  342. package/vendor/framey/dist/chunk-2X4PQEYM.js +34 -0
  343. package/vendor/framey/dist/chunk-3422KMTD.js +168 -0
  344. package/vendor/framey/dist/chunk-3FYD4YEK.js +72 -0
  345. package/vendor/framey/dist/chunk-3GXQPCJC.js +682 -0
  346. package/vendor/framey/dist/chunk-3LM6YPBR.js +179 -0
  347. package/vendor/framey/dist/chunk-442JNXRM.js +20 -0
  348. package/vendor/framey/dist/chunk-4BNN5FGX.js +182 -0
  349. package/vendor/framey/dist/chunk-4P7NX5BM.js +11532 -0
  350. package/vendor/framey/dist/chunk-4UMPIPCQ.js +88 -0
  351. package/vendor/framey/dist/chunk-4YYJOFKS.js +28 -0
  352. package/vendor/framey/dist/chunk-54HJ63Q2.js +29 -0
  353. package/vendor/framey/dist/chunk-5ETJX6WM.js +89 -0
  354. package/vendor/framey/dist/chunk-5SG54JRS.js +68 -0
  355. package/vendor/framey/dist/chunk-634HSJ3J.js +58 -0
  356. package/vendor/framey/dist/chunk-66LREXGS.js +48 -0
  357. package/vendor/framey/dist/chunk-6BEFEWFB.js +25 -0
  358. package/vendor/framey/dist/chunk-6BNTGNGD.js +286 -0
  359. package/vendor/framey/dist/chunk-6DDPOHMK.js +140 -0
  360. package/vendor/framey/dist/chunk-6GV5XSOR.js +214 -0
  361. package/vendor/framey/dist/chunk-6JR2XUEE.js +127 -0
  362. package/vendor/framey/dist/chunk-73QVATIM.js +362 -0
  363. package/vendor/framey/dist/chunk-7FMUTGOH.js +279 -0
  364. package/vendor/framey/dist/chunk-7JQFJ6JJ.js +291 -0
  365. package/vendor/framey/dist/chunk-AHPGAESX.js +113 -0
  366. package/vendor/framey/dist/chunk-AHX34EFP.js +15 -0
  367. package/vendor/framey/dist/chunk-AJ5CSAEZ.js +487 -0
  368. package/vendor/framey/dist/chunk-B7RJV74O.js +5047 -0
  369. package/vendor/framey/dist/chunk-BL4U6U52.js +254 -0
  370. package/vendor/framey/dist/chunk-BSQ7XANM.js +210 -0
  371. package/vendor/framey/dist/chunk-BZ56MTDX.js +6 -0
  372. package/vendor/framey/dist/chunk-C5BO7PMT.js +282 -0
  373. package/vendor/framey/dist/chunk-CFUCHO4T.js +358 -0
  374. package/vendor/framey/dist/chunk-CZGHFATT.js +31 -0
  375. package/vendor/framey/dist/chunk-DLA44CMS.js +5649 -0
  376. package/vendor/framey/dist/chunk-DPVXBVLL.js +31292 -0
  377. package/vendor/framey/dist/chunk-DQBY7FRR.js +397 -0
  378. package/vendor/framey/dist/chunk-EAMLBAHA.js +154 -0
  379. package/vendor/framey/dist/chunk-EOVYQ3IG.js +143 -0
  380. package/vendor/framey/dist/chunk-EVMY7IPL.js +612 -0
  381. package/vendor/framey/dist/chunk-FG4BBFZI.js +122 -0
  382. package/vendor/framey/dist/chunk-FI4I7CME.js +56 -0
  383. package/vendor/framey/dist/chunk-FLREUEHW.js +2081 -0
  384. package/vendor/framey/dist/chunk-FUFP7A7S.js +1126 -0
  385. package/vendor/framey/dist/chunk-FV3PIKGH.js +56 -0
  386. package/vendor/framey/dist/chunk-FXMKHHPO.js +123 -0
  387. package/vendor/framey/dist/chunk-GGZAM5VH.js +1187 -0
  388. package/vendor/framey/dist/chunk-GKZEVJIB.js +39 -0
  389. package/vendor/framey/dist/chunk-GMECXFAN.js +57 -0
  390. package/vendor/framey/dist/chunk-GNEFDLFB.js +621 -0
  391. package/vendor/framey/dist/chunk-GPMLSOEG.js +19274 -0
  392. package/vendor/framey/dist/chunk-GPYVT5EE.js +59 -0
  393. package/vendor/framey/dist/chunk-H6IEMZEV.js +40 -0
  394. package/vendor/framey/dist/chunk-HEFQCIXR.js +170 -0
  395. package/vendor/framey/dist/chunk-HFQ4CWHA.js +118 -0
  396. package/vendor/framey/dist/chunk-HPDTCOKE.js +304 -0
  397. package/vendor/framey/dist/chunk-HTUHQKDB.js +548 -0
  398. package/vendor/framey/dist/chunk-HU2SPFNG.js +103 -0
  399. package/vendor/framey/dist/chunk-HVW7ABXD.js +19 -0
  400. package/vendor/framey/dist/chunk-IG7NDKXO.js +26 -0
  401. package/vendor/framey/dist/chunk-II3J3WXK.js +246 -0
  402. package/vendor/framey/dist/chunk-IR35BCLK.js +13 -0
  403. package/vendor/framey/dist/chunk-ISN5GRYI.js +20 -0
  404. package/vendor/framey/dist/chunk-IUONB55J.js +669 -0
  405. package/vendor/framey/dist/chunk-IVSUQR2B.js +161 -0
  406. package/vendor/framey/dist/chunk-IXU6PQSW.js +259 -0
  407. package/vendor/framey/dist/chunk-IZCW2NKT.js +29 -0
  408. package/vendor/framey/dist/chunk-JGXJU5PF.js +68 -0
  409. package/vendor/framey/dist/chunk-JHOCK3DG.js +27 -0
  410. package/vendor/framey/dist/chunk-JVBRJMOP.js +121 -0
  411. package/vendor/framey/dist/chunk-K4MJX7SR.js +17 -0
  412. package/vendor/framey/dist/chunk-K4WKGXRC.js +18 -0
  413. package/vendor/framey/dist/chunk-KGPLIH7B.js +127 -0
  414. package/vendor/framey/dist/chunk-KLIKCX6Q.js +63 -0
  415. package/vendor/framey/dist/chunk-KXTKKBOC.js +61 -0
  416. package/vendor/framey/dist/chunk-KZI44OPA.js +185 -0
  417. package/vendor/framey/dist/chunk-LEKZNRKI.js +45 -0
  418. package/vendor/framey/dist/chunk-LFGJWOVA.js +61 -0
  419. package/vendor/framey/dist/chunk-LLMSX6V6.js +17 -0
  420. package/vendor/framey/dist/chunk-LTGTN2TH.js +195 -0
  421. package/vendor/framey/dist/chunk-M2WBOQ2Q.js +54 -0
  422. package/vendor/framey/dist/chunk-MI5FTLYK.js +399 -0
  423. package/vendor/framey/dist/chunk-MLQP2QUF.js +1809 -0
  424. package/vendor/framey/dist/chunk-MMQ52NIZ.js +956 -0
  425. package/vendor/framey/dist/chunk-ND46XKK3.js +742 -0
  426. package/vendor/framey/dist/chunk-NYEXNSHM.js +504 -0
  427. package/vendor/framey/dist/chunk-O4YRB27L.js +57 -0
  428. package/vendor/framey/dist/chunk-O7BGSQ5F.js +92 -0
  429. package/vendor/framey/dist/chunk-OFQWTKPW.js +117 -0
  430. package/vendor/framey/dist/chunk-P5ZBLXJ6.js +91 -0
  431. package/vendor/framey/dist/chunk-P6LJ4Y32.js +4662 -0
  432. package/vendor/framey/dist/chunk-PKZ7RK5N.js +281 -0
  433. package/vendor/framey/dist/chunk-PURHAXFL.js +15 -0
  434. package/vendor/framey/dist/chunk-Q5X2B5WT.js +29 -0
  435. package/vendor/framey/dist/chunk-QAMKOG67.js +248 -0
  436. package/vendor/framey/dist/chunk-R6YBK4XX.js +514 -0
  437. package/vendor/framey/dist/chunk-RGLQURIX.js +2997 -0
  438. package/vendor/framey/dist/chunk-RNV3DM4S.js +3573 -0
  439. package/vendor/framey/dist/chunk-ROEW726S.js +4638 -0
  440. package/vendor/framey/dist/chunk-RQVDTPGD.js +6 -0
  441. package/vendor/framey/dist/chunk-RVZHGOGR.js +26 -0
  442. package/vendor/framey/dist/chunk-RXATE6FZ.js +385 -0
  443. package/vendor/framey/dist/chunk-S3S4TGEH.js +424 -0
  444. package/vendor/framey/dist/chunk-SDYDNHEE.js +11202 -0
  445. package/vendor/framey/dist/chunk-SSWBU67I.js +173 -0
  446. package/vendor/framey/dist/chunk-ST35C4K6.js +28 -0
  447. package/vendor/framey/dist/chunk-SZNTYTIV.js +990 -0
  448. package/vendor/framey/dist/chunk-T2EUWCJE.js +29 -0
  449. package/vendor/framey/dist/chunk-T6AGUTZJ.js +444 -0
  450. package/vendor/framey/dist/chunk-THJALHFJ.js +11131 -0
  451. package/vendor/framey/dist/chunk-TIQB63MX.js +18128 -0
  452. package/vendor/framey/dist/chunk-U242RTCT.js +134 -0
  453. package/vendor/framey/dist/chunk-UJZREAV6.js +3901 -0
  454. package/vendor/framey/dist/chunk-UMD3V7CU.js +264 -0
  455. package/vendor/framey/dist/chunk-UOFAE5RF.js +42 -0
  456. package/vendor/framey/dist/chunk-UTEIUGAZ.js +29 -0
  457. package/vendor/framey/dist/chunk-VAS6HQX3.js +138 -0
  458. package/vendor/framey/dist/chunk-VC76R6W2.js +27 -0
  459. package/vendor/framey/dist/chunk-VFZ7W5W7.js +226 -0
  460. package/vendor/framey/dist/chunk-VHNLZAE2.js +348 -0
  461. package/vendor/framey/dist/chunk-VMC4WSH5.js +278 -0
  462. package/vendor/framey/dist/chunk-VORQIH64.js +108 -0
  463. package/vendor/framey/dist/chunk-VTXFRIHO.js +186 -0
  464. package/vendor/framey/dist/chunk-W2LGJSEN.js +182 -0
  465. package/vendor/framey/dist/chunk-WKBM7NDG.js +32 -0
  466. package/vendor/framey/dist/chunk-WMQNIQDJ.js +2057 -0
  467. package/vendor/framey/dist/chunk-WN4XZC5F.js +504 -0
  468. package/vendor/framey/dist/chunk-WQ6ZXHFQ.js +133 -0
  469. package/vendor/framey/dist/chunk-X7HBSTCZ.js +53 -0
  470. package/vendor/framey/dist/chunk-X7MTZ4T2.js +6 -0
  471. package/vendor/framey/dist/chunk-XPTOJZJF.js +308 -0
  472. package/vendor/framey/dist/chunk-XT6MXC7A.js +6 -0
  473. package/vendor/framey/dist/chunk-XT7X6QCP.js +6 -0
  474. package/vendor/framey/dist/chunk-XTMDWNEC.js +2820 -0
  475. package/vendor/framey/dist/chunk-XUHOIDKG.js +6 -0
  476. package/vendor/framey/dist/chunk-XX7LTW5Y.js +138 -0
  477. package/vendor/framey/dist/chunk-Y3YGTZEG.js +382 -0
  478. package/vendor/framey/dist/chunk-Y5SBSGN7.js +346 -0
  479. package/vendor/framey/dist/chunk-YCMVI4MZ.js +625 -0
  480. package/vendor/framey/dist/chunk-YOR4CTCE.js +261 -0
  481. package/vendor/framey/dist/chunk-YPRBGITP.js +2748 -0
  482. package/vendor/framey/dist/chunk-ZKDMAOFU.js +32 -0
  483. package/vendor/framey/dist/chunk-ZW6DURPF.js +1318 -0
  484. package/vendor/framey/dist/clean-ZJOGDKDM.js +336 -0
  485. package/vendor/framey/dist/cli.js +439 -0
  486. package/vendor/framey/dist/cloudrun-HGJ3B6ZV.js +767 -0
  487. package/vendor/framey/dist/commands/contrast-audit.browser.js +643 -0
  488. package/vendor/framey/dist/commands/layout-audit.browser.js +2914 -0
  489. package/vendor/framey/dist/commands/motion-sample.browser.js +74 -0
  490. package/vendor/framey/dist/commands/motion-signature.browser.js +475 -0
  491. package/vendor/framey/dist/compare-W7UY3BAZ.js +463 -0
  492. package/vendor/framey/dist/compiler-TM3W6GYF.js +136 -0
  493. package/vendor/framey/dist/component-QF5VGYMU.js +176 -0
  494. package/vendor/framey/dist/compositions-XT3PMNDF.js +238 -0
  495. package/vendor/framey/dist/contactSheet-7W6GYHBQ.js +24 -0
  496. package/vendor/framey/dist/deploy-6FJPWKF2.js +299 -0
  497. package/vendor/framey/dist/destroy-HE2RIPKZ.js +57 -0
  498. package/vendor/framey/dist/deterministicFonts-RADD3BSX.js +50 -0
  499. package/vendor/framey/dist/dist-4S2FGGVY.js +422 -0
  500. package/vendor/framey/dist/dist-BE4B2EZE.js +32 -0
  501. package/vendor/framey/dist/dist-EWXJGN63.js +129 -0
  502. package/vendor/framey/dist/docs-VKIRFEEX.js +131 -0
  503. package/vendor/framey/dist/doctor-2UNJ2G7U.js +74 -0
  504. package/vendor/framey/dist/dom-37K6YOH4.js +14 -0
  505. package/vendor/framey/dist/dotEnv-7PZQTVIM.js +54 -0
  506. package/vendor/framey/dist/events-ACY3P2PA.js +76 -0
  507. package/vendor/framey/dist/events-IHB3B2MK.js +69 -0
  508. package/vendor/framey/dist/ffmpeg-DR5LGGSK.js +63 -0
  509. package/vendor/framey/dist/figma-BODVUPHB.js +63 -0
  510. package/vendor/framey/dist/fontData.generated-NXAOI4GH.js +13 -0
  511. package/vendor/framey/dist/fontLocalizeCli.js +142 -0
  512. package/vendor/framey/dist/gpuPolicy-XLEXORZY.js +49 -0
  513. package/vendor/framey/dist/grade-compare-GAXFQ74C.js +661 -0
  514. package/vendor/framey/dist/gsapParser-IEUSLYDJ.js +2885 -0
  515. package/vendor/framey/dist/gsapParserAcorn-LZJKCFQI.js +30 -0
  516. package/vendor/framey/dist/help-VCB5X2HS.js +184 -0
  517. package/vendor/framey/dist/history-ENGZEVFT.js +471 -0
  518. package/vendor/framey/dist/hyperframe.manifest.json +22 -0
  519. package/vendor/framey/dist/hyperframe.runtime.iife.js +1132 -0
  520. package/vendor/framey/dist/hyperframes-player.global.js +478 -0
  521. package/vendor/framey/dist/hyperframes-slideshow.global.js +148 -0
  522. package/vendor/framey/dist/info-WPCJHBMU.js +182 -0
  523. package/vendor/framey/dist/init-G4WGLYGD.js +1176 -0
  524. package/vendor/framey/dist/inspect-5IQ6CNEQ.js +106 -0
  525. package/vendor/framey/dist/installer-ML4HWUV7.js +40 -0
  526. package/vendor/framey/dist/keyframes-Q6XJO7VW.js +2755 -0
  527. package/vendor/framey/dist/lambda-A7JC74JF.js +468 -0
  528. package/vendor/framey/dist/layout-YET6XNXA.js +95 -0
  529. package/vendor/framey/dist/lint-I7FZN2VJ.js +162 -0
  530. package/vendor/framey/dist/lintProject-PDB3UN72.js +30 -0
  531. package/vendor/framey/dist/manager-CD6LJ7ZN.js +45 -0
  532. package/vendor/framey/dist/manager-WA4F2GST.js +67 -0
  533. package/vendor/framey/dist/media-treatment-BXYS4WVA.js +1030 -0
  534. package/vendor/framey/dist/media-use-LWH3MANK.js +113 -0
  535. package/vendor/framey/dist/models-K363FYM4.js +195 -0
  536. package/vendor/framey/dist/motionShot-GPAVWXLH.js +640 -0
  537. package/vendor/framey/dist/normalize-FASIPBUM.js +24 -0
  538. package/vendor/framey/dist/normalize-audio-VDWYRHN6.js +268 -0
  539. package/vendor/framey/dist/open-E3TB6P6J.js +128 -0
  540. package/vendor/framey/dist/parakeetRunner-UP34UOI2.js +58 -0
  541. package/vendor/framey/dist/pipeline-VXIUYEBU.js +64 -0
  542. package/vendor/framey/dist/play-BUTDQ4DV.js +353 -0
  543. package/vendor/framey/dist/policies-OLDYGFK5.js +309 -0
  544. package/vendor/framey/dist/preflight-2KZ2GWAZ.js +55 -0
  545. package/vendor/framey/dist/present-GXYSM7NX.js +353 -0
  546. package/vendor/framey/dist/preview-EKZ33JOJ.js +1910 -0
  547. package/vendor/framey/dist/producer-SKU5EV3J.js +13 -0
  548. package/vendor/framey/dist/progress-ZWB4Z4ET.js +76 -0
  549. package/vendor/framey/dist/projectConfig-3MZAYVTS.js +60 -0
  550. package/vendor/framey/dist/publication-DTKWNF74.js +33 -0
  551. package/vendor/framey/dist/remove-background-OY5L7XN2.js +265 -0
  552. package/vendor/framey/dist/render-CDV4GDFX.js +187 -0
  553. package/vendor/framey/dist/render-YE5YAMOD.js +2620 -0
  554. package/vendor/framey/dist/render-batch-ZAMFPCLL.js +274 -0
  555. package/vendor/framey/dist/renderSetupWorker.js +120 -0
  556. package/vendor/framey/dist/resolver-D5BIPSEW.js +42 -0
  557. package/vendor/framey/dist/runtimeVersion.js +18 -0
  558. package/vendor/framey/dist/sam-WFZXI4F7.js +23 -0
  559. package/vendor/framey/dist/shaderTransitionWorker.js +44 -0
  560. package/vendor/framey/dist/sherpa-RQWLV7I3.js +79 -0
  561. package/vendor/framey/dist/sherpaWorker.js +84 -0
  562. package/vendor/framey/dist/sites-AMOVABTK.js +54 -0
  563. package/vendor/framey/dist/skill-ZHVLH7KX.js +15 -0
  564. package/vendor/framey/dist/slideshow-SJGK4RQS.js +24 -0
  565. package/vendor/framey/dist/snapshot-TUXL6LBQ.js +1515 -0
  566. package/vendor/framey/dist/src-JHQGB2S6.js +215 -0
  567. package/vendor/framey/dist/src-Z5PGEMXB.js +532 -0
  568. package/vendor/framey/dist/state-B6H3WHCF.js +28 -0
  569. package/vendor/framey/dist/staticProjectServer-DFVZOUGI.js +63 -0
  570. package/vendor/framey/dist/studio/assets/hyperframes-player-DLaKjw0L.js +478 -0
  571. package/vendor/framey/dist/studio/assets/index-BX3KHhGX.js +71 -0
  572. package/vendor/framey/dist/studio/assets/index-CYbvOVp-.css +1 -0
  573. package/vendor/framey/dist/studio/assets/index-Dp3QEUGL.js +402 -0
  574. package/vendor/framey/dist/studio/assets/index-qrTAXbqc.js +531 -0
  575. package/vendor/framey/dist/studio/favicon.svg +14 -0
  576. package/vendor/framey/dist/studio/icons/timeline/audio.svg +7 -0
  577. package/vendor/framey/dist/studio/icons/timeline/captions.svg +5 -0
  578. package/vendor/framey/dist/studio/icons/timeline/composition.svg +12 -0
  579. package/vendor/framey/dist/studio/icons/timeline/image.svg +18 -0
  580. package/vendor/framey/dist/studio/icons/timeline/music.svg +10 -0
  581. package/vendor/framey/dist/studio/icons/timeline/text.svg +3 -0
  582. package/vendor/framey/dist/studio/index.html +23 -0
  583. package/vendor/framey/dist/studio-api-2NECGRMB.js +179 -0
  584. package/vendor/framey/dist/studioSelectionClient-IUUPKUHZ.js +25 -0
  585. package/vendor/framey/dist/studioServer-N2OPUMOQ.js +1569 -0
  586. package/vendor/framey/dist/synthesize-PKTB5ATG.js +183 -0
  587. package/vendor/framey/dist/system-7F3UEHQT.js +27 -0
  588. package/vendor/framey/dist/telemetry-DQGPEKM6.js +145 -0
  589. package/vendor/framey/dist/telemetry-UN55N6U6.js +77 -0
  590. package/vendor/framey/dist/timeline-LHUUMK63.js +1295 -0
  591. package/vendor/framey/dist/tokens-OLECWDFK.js +92 -0
  592. package/vendor/framey/dist/transcribe-O7HQKMIV.js +331 -0
  593. package/vendor/framey/dist/transcribe-UJESPBT5.js +76 -0
  594. package/vendor/framey/dist/tts-YCCQHZD4.js +234 -0
  595. package/vendor/framey/dist/urlDownloader-H2LXLG4U.js +48 -0
  596. package/vendor/framey/dist/usage-HG4WMM7A.js +2167 -0
  597. package/vendor/framey/dist/validate-F2XMN6EV.js +87 -0
  598. package/vendor/framey/dist/vendor/npm/gsap@3.15.0/GSAP-LICENSE.txt +8 -0
  599. package/vendor/framey/dist/vendor/npm/gsap@3.15.0/dist/CustomEase.min.js +11 -0
  600. package/vendor/framey/dist/vendor/npm/gsap@3.15.0/dist/MotionPathPlugin.min.js +11 -0
  601. package/vendor/framey/dist/vendor/npm/gsap@3.15.0/dist/gsap.min.js +11 -0
  602. package/vendor/framey/dist/warmCheck.js +220 -0
  603. package/vendor/framey/dist/warmCheckClient.js +139 -0
  604. package/vendor/framey/dist/workspaceBuildCheck-XYD2YTFW.js +133 -0
  605. package/vendor/framey-player/LICENSE +190 -0
  606. package/vendor/framey-player/NOTICE +16 -0
  607. package/vendor/framey-player/SOURCE.json +6 -0
  608. package/vendor/framey-player/hyperframes-player.global.js +478 -0
  609. package/vendor/framey.pin +1 -0
  610. package/bin/index.js +0 -3
@@ -0,0 +1,64 @@
1
+ import { existsSync, statSync } from "node:fs";
2
+ import path from "node:path";
3
+ import type { DatabaseSync } from "node:sqlite";
4
+
5
+ export interface VideoRow {
6
+ id: string;
7
+ /** ISO time the run was created. */
8
+ date: string;
9
+ title: string | null;
10
+ durationSec: number | null;
11
+ path: string;
12
+ }
13
+
14
+ /**
15
+ * The finished videos in a data folder's database, newest first. A video is a run whose mp4 is on disk: the path the
16
+ * run stored (a readable name in a project), else `renders/<id>.mp4` of the machine desk.
17
+ */
18
+ export function listVideos(db: DatabaseSync, dataDir: string, limit?: number): VideoRow[] {
19
+ const rows = db.prepare(`SELECT id, payload, created_at FROM jobs WHERE kind = 'pipeline' ORDER BY created_at DESC, id DESC`).all() as Array<{ id: string; payload: string; created_at: string }>;
20
+ const out: VideoRow[] = [];
21
+ for (const row of rows) {
22
+ let payload: { content?: { title?: unknown; outputPath?: unknown } | null; rendered?: { durationSec?: unknown } | null } = {};
23
+ try {
24
+ payload = JSON.parse(row.payload);
25
+ } catch {
26
+ continue;
27
+ }
28
+ const file = videoFile(dataDir, row.id, payload.content?.outputPath);
29
+ if (!file) continue;
30
+ const length = payload.rendered?.durationSec;
31
+ out.push({
32
+ id: row.id,
33
+ date: row.created_at,
34
+ title: typeof payload.content?.title === "string" ? payload.content.title : null,
35
+ durationSec: typeof length === "number" ? length : null,
36
+ path: file,
37
+ });
38
+ if (limit !== undefined && out.length >= limit) break;
39
+ }
40
+ return out;
41
+ }
42
+
43
+ /** Where a run's mp4 is: the stored path if it exists, else the machine desk's `renders/<id>.mp4`, else null. */
44
+ export function videoFile(dataDir: string, jobId: string, stored: unknown): string | null {
45
+ if (typeof stored === "string" && stored.endsWith(".mp4")) {
46
+ const file = storedVideoPath(dataDir, stored);
47
+ if (existsSync(file) && statSync(file).isFile()) return file;
48
+ }
49
+ const legacy = path.join(dataDir, "renders", `${jobId}.mp4`);
50
+ return existsSync(legacy) ? legacy : null;
51
+ }
52
+
53
+ /** The run's mp4 path for serving: the stored one if it exists, else the machine desk's `renders/<id>.mp4` (which may not exist yet). */
54
+ export function mp4For(dataDir: string, jobId: string, stored: unknown): string {
55
+ return videoFile(dataDir, jobId, stored) ?? path.join(dataDir, "renders", `${jobId}.mp4`);
56
+ }
57
+
58
+ /**
59
+ * A stored video path as a real path. Inside a project it is relative to the project root (`videos/<name>.mp4`), which is
60
+ * the folder above the data folder, so the project can move; a machine-desk path is absolute and stays as it is.
61
+ */
62
+ export function storedVideoPath(dataDir: string, stored: string): string {
63
+ return path.isAbsolute(stored) ? stored : path.resolve(dataDir, "..", stored);
64
+ }
@@ -0,0 +1,91 @@
1
+ import { ComplianceError, RetryableError } from "@vfrey/contracts";
2
+ import { buildFacebookFinish } from "@vfrey/contracts";
3
+ import type { PlatformSession } from "@vfrey/contracts";
4
+ import { requestJson } from "@vfrey/contracts";
5
+ import { fileSize, idle, isMetaRate, metaFailure, putFile, requiredEnv, skipped, type PublishContext } from "./session.js";
6
+
7
+ export async function publishFacebook(ctx: PublishContext): Promise<PlatformSession> {
8
+ const account = ctx.account.platforms.facebook;
9
+ if (!account?.enabled) return skipped();
10
+ const session = idle(ctx.session);
11
+ if (ctx.dryRun) {
12
+ const built = buildFacebookFinish({
13
+ videoId: "dry-run",
14
+ title: ctx.content.title,
15
+ description: `${ctx.content.description}\n\n${ctx.content.hashtags.join(" ")}`,
16
+ });
17
+ session.disclosure = built.proof;
18
+ session.phase = "done";
19
+ session.externalId = "dry-run";
20
+ return session;
21
+ }
22
+ const pageId = requiredEnv(ctx.env, account.pageIdEnv);
23
+ const version = ctx.config.meta.graphVersion;
24
+ if (!session.externalId) {
25
+ const started = await requestJson(
26
+ ctx.fetchImpl,
27
+ `https://graph.facebook.com/${version}/${pageId}/video_reels`,
28
+ {
29
+ method: "POST",
30
+ headers: { "content-type": "application/x-www-form-urlencoded", authorization: `Bearer ${ctx.token}` },
31
+ body: new URLSearchParams({ upload_phase: "start" }),
32
+ },
33
+ );
34
+ if (isMetaRate(started.status, started.json)) throw new RetryableError("meta_rate", "Facebook rate limit");
35
+ if (started.status >= 400) throw metaFailure(started.status, started.text, "facebook");
36
+ const videoId = (started.json as { video_id?: string }).video_id;
37
+ const uploadUrl = (started.json as { upload_url?: string }).upload_url;
38
+ if (!videoId || !uploadUrl) throw new RetryableError("facebook_start", "Facebook start returned no video_id");
39
+ session.externalId = videoId;
40
+ session.uploadUrl = uploadUrl;
41
+ session.phase = "initialized";
42
+ ctx.checkpoint?.(session);
43
+ }
44
+ if (session.phase === "initialized" && session.uploadUrl) {
45
+ const size = fileSize(ctx.filePath);
46
+ const uploaded = await putFile(ctx.fetchImpl, session.uploadUrl, ctx.filePath, {
47
+ method: "POST",
48
+ headers: {
49
+ authorization: `OAuth ${ctx.token}`,
50
+ offset: "0",
51
+ file_size: String(size),
52
+ "content-type": "application/octet-stream",
53
+ "content-length": String(size),
54
+ },
55
+ offset: 0,
56
+ length: size,
57
+ idleMs: ctx.uploadIdleMs,
58
+ });
59
+ if (uploaded.status >= 400) throw new RetryableError("facebook_upload", `Facebook upload HTTP ${uploaded.status}`);
60
+ session.phase = "uploaded";
61
+ session.uploadedBytes = size;
62
+ ctx.checkpoint?.(session);
63
+ }
64
+ const built = buildFacebookFinish({
65
+ videoId: session.externalId ?? "",
66
+ title: ctx.content.title,
67
+ description: `${ctx.content.description}\n\n${ctx.content.hashtags.join(" ")}`,
68
+ });
69
+ session.disclosure = built.proof;
70
+ const finished = await requestJson(ctx.fetchImpl, `https://graph.facebook.com/${version}/${pageId}/video_reels`, {
71
+ method: "POST",
72
+ headers: { "content-type": "application/x-www-form-urlencoded", authorization: `Bearer ${ctx.token}` },
73
+ body: new URLSearchParams(built.form),
74
+ });
75
+ if (finished.status >= 400) {
76
+ const text = finished.text.toLowerCase();
77
+ if (text.includes("already published") || text.includes("video_already")) {
78
+ session.phase = "done";
79
+ return session;
80
+ }
81
+ if (text.includes("is_ai_generated")) {
82
+ throw new ComplianceError(
83
+ "facebook_disclosure_rejected",
84
+ "Facebook rejected is_ai_generated. The video was not published. Check the Graph version in META_GRAPH_VERSION.",
85
+ );
86
+ }
87
+ throw metaFailure(finished.status, finished.text, "facebook");
88
+ }
89
+ session.phase = "done";
90
+ return session;
91
+ }
@@ -0,0 +1,12 @@
1
+ export {
2
+ MAX_FREE_DEFERRALS,
3
+ POLL_WAIT_MS,
4
+ deferralOf,
5
+ proofOf,
6
+ type PublishContext,
7
+ type StoredSession,
8
+ } from "./session.js";
9
+ export { publishYouTube } from "./youtube.js";
10
+ export { publishTikTok } from "./tiktok.js";
11
+ export { publishInstagram } from "./instagram.js";
12
+ export { publishFacebook } from "./facebook.js";
@@ -0,0 +1,97 @@
1
+ import { ComplianceError, RetryableError } from "@vfrey/contracts";
2
+ import { buildInstagramContainer } from "@vfrey/contracts";
3
+ import type { PlatformSession } from "@vfrey/contracts";
4
+ import { requestJson } from "@vfrey/contracts";
5
+ import { assertBucket, markBucket } from "./rate.js";
6
+ import { fileSize, idle, isMetaRate, metaFailure, putFile, requiredEnv, skipped, type PublishContext } from "./session.js";
7
+
8
+ export async function publishInstagram(ctx: PublishContext): Promise<PlatformSession> {
9
+ const account = ctx.account.platforms.instagram;
10
+ if (!account?.enabled) return skipped();
11
+ const session = idle(ctx.session);
12
+ const built = buildInstagramContainer({
13
+ caption: `${ctx.content.caption}\n\n${ctx.content.hashtags.join(" ")}`.trim(),
14
+ shareToFeed: account.shareToFeed,
15
+ });
16
+ session.disclosure = built.proof;
17
+ if (ctx.dryRun) {
18
+ session.phase = "done";
19
+ session.externalId = "dry-run";
20
+ return session;
21
+ }
22
+ const igUserId = requiredEnv(ctx.env, account.igUserIdEnv);
23
+ const version = ctx.config.meta.graphVersion;
24
+ if (!session.creationId) {
25
+ assertBucket({
26
+ store: ctx.store,
27
+ bucket: `instagram:publish:${ctx.account.id}`,
28
+ limit: 25,
29
+ windowMs: 24 * 60 * 60 * 1000,
30
+ now: ctx.now,
31
+ code: "instagram_daily_cap",
32
+ });
33
+ const params = new URLSearchParams(built.form);
34
+ const created = await requestJson(
35
+ ctx.fetchImpl,
36
+ `https://graph.facebook.com/${version}/${igUserId}/media?${params.toString()}`,
37
+ { method: "POST", headers: { authorization: `Bearer ${ctx.token}` } },
38
+ );
39
+ if (isMetaRate(created.status, created.json)) throw new RetryableError("meta_rate", "Instagram rate limit");
40
+ if (created.status >= 400) throw metaFailure(created.status, created.text, "instagram");
41
+ const id = (created.json as { id?: string }).id;
42
+ const uri = (created.json as { uri?: string }).uri;
43
+ if (!id || !uri) throw new RetryableError("instagram_container", "Instagram container response had no id or uri");
44
+ session.creationId = id;
45
+ session.uploadUrl = uri;
46
+ session.phase = "initialized";
47
+ markBucket(ctx.store, `instagram:publish:${ctx.account.id}`, ctx.now);
48
+ ctx.checkpoint?.(session);
49
+ }
50
+ if (session.phase === "initialized" && session.uploadUrl) {
51
+ const size = fileSize(ctx.filePath);
52
+ const uploaded = await putFile(ctx.fetchImpl, session.uploadUrl, ctx.filePath, {
53
+ method: "POST",
54
+ headers: {
55
+ authorization: `OAuth ${ctx.token}`,
56
+ offset: "0",
57
+ file_size: String(size),
58
+ "content-type": "application/octet-stream",
59
+ "content-length": String(size),
60
+ },
61
+ offset: 0,
62
+ length: size,
63
+ idleMs: ctx.uploadIdleMs,
64
+ });
65
+ if (uploaded.status >= 400) throw new RetryableError("instagram_upload", `Instagram upload HTTP ${uploaded.status}`);
66
+ session.phase = "uploaded";
67
+ session.uploadedBytes = size;
68
+ ctx.checkpoint?.(session);
69
+ }
70
+ const container = await requestJson(
71
+ ctx.fetchImpl,
72
+ `https://graph.facebook.com/${version}/${session.creationId}?fields=status_code`,
73
+ { headers: { authorization: `Bearer ${ctx.token}` } },
74
+ );
75
+ const statusCode = (container.json as { status_code?: string }).status_code;
76
+ if (statusCode === "ERROR" || statusCode === "EXPIRED") {
77
+ throw new ComplianceError("instagram_container", `Container status ${statusCode}`);
78
+ }
79
+ if (statusCode === "PUBLISHED") {
80
+ // A crash after media_publish left the container PUBLISHED. It is already live; publishing again would post twice.
81
+ session.externalId = session.externalId ?? session.creationId;
82
+ session.phase = "done";
83
+ return session;
84
+ }
85
+ if (statusCode !== "FINISHED") {
86
+ throw new RetryableError("instagram_processing", `Container status ${statusCode ?? "unknown"}`, 15_000);
87
+ }
88
+ const published = await requestJson(ctx.fetchImpl, `https://graph.facebook.com/${version}/${igUserId}/media_publish`, {
89
+ method: "POST",
90
+ headers: { "content-type": "application/x-www-form-urlencoded", authorization: `Bearer ${ctx.token}` },
91
+ body: new URLSearchParams({ creation_id: session.creationId ?? "" }),
92
+ });
93
+ if (published.status >= 400) throw metaFailure(published.status, published.text, "instagram");
94
+ session.externalId = (published.json as { id?: string }).id ?? session.creationId;
95
+ session.phase = "done";
96
+ return session;
97
+ }
@@ -0,0 +1,62 @@
1
+ import { RetryableError } from "@vfrey/contracts";
2
+ import { log } from "../log.js";
3
+ import type { Store } from "../queue/store.js";
4
+
5
+ export function assertBucket(input: {
6
+ store: Store;
7
+ bucket: string;
8
+ limit: number;
9
+ windowMs: number;
10
+ now: Date;
11
+ code: string;
12
+ /** How long the pause lasts when the bucket is full. Defaults to the window. */
13
+ retryAfterMs?: number;
14
+ }): void {
15
+ // An invalid cap must close the gate. NaN compares false with everything, so it would never trip.
16
+ const valid = Number.isFinite(input.limit) && input.limit >= 0;
17
+ if (!valid) log("warn", "rate cap is not a finite number; treating it as 0", { bucket: input.bucket, limit: String(input.limit) });
18
+ const limit = valid ? input.limit : 0;
19
+ const since = new Date(input.now.getTime() - input.windowMs).toISOString();
20
+ const used = input.store.countRate(input.bucket, since);
21
+ if (used >= limit) {
22
+ throw new RetryableError(
23
+ input.code,
24
+ `Local rate limit ${input.bucket} is ${used}/${limit}. This pause is ours, inside the platform limit.`,
25
+ input.retryAfterMs ?? input.windowMs,
26
+ );
27
+ }
28
+ }
29
+
30
+ export function markBucket(store: Store, bucket: string, now: Date): void {
31
+ store.recordRate(bucket, now);
32
+ }
33
+
34
+ export function utcDay(now: Date): string {
35
+ return now.toISOString().slice(0, 10);
36
+ }
37
+
38
+ const PACIFIC = new Intl.DateTimeFormat("en-CA", {
39
+ timeZone: "America/Los_Angeles",
40
+ year: "numeric",
41
+ month: "2-digit",
42
+ day: "2-digit",
43
+ });
44
+
45
+ /** The calendar day in Pacific time. YouTube's quota resets at Pacific midnight. */
46
+ export function pacificDay(now: Date): string {
47
+ return PACIFIC.format(now);
48
+ }
49
+
50
+ /** Milliseconds from `now` to the next Pacific midnight. */
51
+ export function msUntilPacificMidnight(now: Date): number {
52
+ const start = now.getTime();
53
+ const day = pacificDay(now);
54
+ let low = start;
55
+ let high = start + 26 * 60 * 60_000;
56
+ while (high - low > 1) {
57
+ const mid = Math.floor((low + high) / 2);
58
+ if (pacificDay(new Date(mid)) === day) low = mid;
59
+ else high = mid;
60
+ }
61
+ return high - start;
62
+ }
@@ -0,0 +1,242 @@
1
+ import { open } from "node:fs/promises";
2
+ import { statSync } from "node:fs";
3
+ import { ComplianceError, ConfigError, QualityError, RetryableError } from "@vfrey/contracts";
4
+ import { redact } from "@vfrey/contracts";
5
+ import type { Account, AppConfig, ContentPackage, DisclosureProof, PlatformSession } from "@vfrey/contracts";
6
+ import type { Store } from "../queue/store.js";
7
+
8
+ type Env = Record<string, string | undefined>;
9
+
10
+ /** A stored session plus the pause the pipeline keeps beside it. Extra keys ride in the job payload JSON. */
11
+ export type StoredSession = PlatformSession & {
12
+ /** The platform may be tried again from this instant. */
13
+ resumeAt?: string | null;
14
+ /** True when the wait was a poll or a server-directed pause, so it spends no attempt. */
15
+ resumeFree?: boolean;
16
+ /** How many free waits this platform has had. Past MAX_FREE_DEFERRALS a wait spends an attempt. */
17
+ deferrals?: number;
18
+ };
19
+
20
+ /** How long to wait between processing polls when the platform gave no time. */
21
+ export const POLL_WAIT_MS = 20_000;
22
+ export const MAX_FREE_DEFERRALS = 60;
23
+ const POLL_CODES = new Set(["tiktok_processing", "instagram_processing"]);
24
+
25
+ /**
26
+ * A processing poll and a pause the server or our own limiter set are not failures, so they spend no attempt.
27
+ * Any other retryable error is a failure.
28
+ */
29
+ export function deferralOf(error: { code: string; retryAfterMs?: number }): { free: boolean; retryAfterMs: number | undefined } {
30
+ const retryAfterMs = error.retryAfterMs && error.retryAfterMs > 0 ? error.retryAfterMs : undefined;
31
+ return { free: POLL_CODES.has(error.code) || retryAfterMs !== undefined, retryAfterMs };
32
+ }
33
+
34
+ export interface PublishContext {
35
+ config: AppConfig;
36
+ account: Account;
37
+ content: ContentPackage;
38
+ filePath: string;
39
+ fetchImpl: typeof fetch;
40
+ store: Store;
41
+ now: Date;
42
+ env: Env;
43
+ token: string;
44
+ dryRun: boolean;
45
+ session: PlatformSession;
46
+ checkpoint?: (session: PlatformSession) => void;
47
+ /** Abort a binary upload that moves no bytes for this long. Default 120 s. */
48
+ uploadIdleMs?: number;
49
+ }
50
+
51
+ export function idle(session: PlatformSession | undefined): PlatformSession {
52
+ return (
53
+ session ?? {
54
+ phase: "idle",
55
+ externalId: null,
56
+ uploadUrl: null,
57
+ creationId: null,
58
+ logId: null,
59
+ disclosure: null,
60
+ errorCode: null,
61
+ errorMessage: null,
62
+ uploadedBytes: 0,
63
+ }
64
+ );
65
+ }
66
+
67
+ export function skipped(): PlatformSession {
68
+ return { ...idle(undefined), phase: "skipped" };
69
+ }
70
+
71
+ export function fileSize(filePath: string): number {
72
+ return statSync(filePath).size;
73
+ }
74
+
75
+ /**
76
+ * Sends one byte range. The body is a stream read in chunks, so progress is visible: when no chunk is
77
+ * taken for `idleMs` (default 120 s) the request is aborted with a retryable error and the caller's
78
+ * stored session resumes it.
79
+ */
80
+ export async function putFile(
81
+ fetchImpl: typeof fetch,
82
+ url: string,
83
+ filePath: string,
84
+ init: {
85
+ method: string;
86
+ headers: Record<string, string>;
87
+ offset: number;
88
+ length: number;
89
+ idleMs?: number;
90
+ chunkBytes?: number;
91
+ },
92
+ ): Promise<{ status: number; text: string }> {
93
+ const idleMs = init.idleMs ?? UPLOAD_IDLE_MS;
94
+ const chunkBytes = init.chunkBytes ?? 1024 * 1024;
95
+ const handle = await open(filePath, "r");
96
+ const controller = new AbortController();
97
+ let idled = false;
98
+ const abort = () => {
99
+ idled = true;
100
+ controller.abort();
101
+ };
102
+ let timer = setTimeout(abort, idleMs);
103
+ const poke = () => {
104
+ clearTimeout(timer);
105
+ timer = setTimeout(abort, idleMs);
106
+ };
107
+ let sent = 0;
108
+ const body = new ReadableStream<Uint8Array>({
109
+ async pull(stream) {
110
+ if (sent >= init.length) {
111
+ stream.close();
112
+ return;
113
+ }
114
+ const size = Math.min(chunkBytes, init.length - sent);
115
+ const buffer = Buffer.alloc(size);
116
+ const { bytesRead } = await handle.read(buffer, 0, size, init.offset + sent);
117
+ if (bytesRead === 0) {
118
+ stream.error(new Error("The render file is shorter than the upload range."));
119
+ return;
120
+ }
121
+ sent += bytesRead;
122
+ poke();
123
+ stream.enqueue(new Uint8Array(buffer.buffer, buffer.byteOffset, bytesRead));
124
+ },
125
+ });
126
+ try {
127
+ const response = await fetchImpl(url, {
128
+ method: init.method,
129
+ headers: init.headers,
130
+ body,
131
+ duplex: "half",
132
+ signal: controller.signal,
133
+ } as RequestInit);
134
+ const text = await response.text();
135
+ return { status: response.status, text };
136
+ } catch (error) {
137
+ if (idled) {
138
+ throw new RetryableError("upload_idle", `No upload progress for ${Math.round(idleMs / 1000)} s. The stored session resumes it.`);
139
+ }
140
+ throw error;
141
+ } finally {
142
+ clearTimeout(timer);
143
+ await handle.close();
144
+ }
145
+ }
146
+
147
+ export const UPLOAD_IDLE_MS = 120_000;
148
+
149
+ export type FailureClass = "quality" | "config" | "compliance";
150
+
151
+ const QUALITY_REASON =
152
+ /^(invalid(Title|Description|Tags|VideoMetadata|Filename|Duration)|mediaBodyRequired|videoTooLong|(file_format|duration|frame_rate|picture_size)_check_failed)$/i;
153
+ const CONFIG_REASON =
154
+ /(token|credential|unauthori|authError|invalid_client|invalid_grant|scope|insufficientPermissions|forbidden|youtubeSignupRequired|auth_removed|url_ownership|no_creator_permission)/i;
155
+
156
+ /**
157
+ * Quality is the file. Config is the operator's keys and permissions. Anything else is compliance:
158
+ * an unknown refusal fails closed, because a policy or disclosure refusal must never be retried or hidden.
159
+ */
160
+ export function classifyReason(reason: string): FailureClass {
161
+ if (QUALITY_REASON.test(reason)) return "quality";
162
+ if (CONFIG_REASON.test(reason)) return "config";
163
+ return "compliance";
164
+ }
165
+
166
+ function failureOf(klass: FailureClass, code: string, message: string): Error {
167
+ if (klass === "quality") return new QualityError(code, message);
168
+ if (klass === "config") return new ConfigError(code, message);
169
+ return new ComplianceError(code, message);
170
+ }
171
+
172
+ /** The platform's own reason from an error body: Google `errors[0].reason`, TikTok `code`, Meta `code`. */
173
+ export function reasonOf(text: string): { reason: string | null; message: string | null; metaCode: number | null } {
174
+ const json = safeJson(text) as {
175
+ error?: { errors?: Array<{ reason?: string }>; code?: string | number; status?: string; message?: string; type?: string };
176
+ } | null;
177
+ const error = json?.error;
178
+ if (!error || typeof error !== "object") return { reason: null, message: null, metaCode: null };
179
+ const reason = error.errors?.[0]?.reason ?? (typeof error.code === "string" ? error.code : null) ?? error.status ?? error.type ?? null;
180
+ return {
181
+ reason: reason ? String(reason).replace(/[^\w-]/g, "").slice(0, 60) || null : null,
182
+ message: error.message ? String(error.message) : null,
183
+ metaCode: typeof error.code === "number" ? error.code : null,
184
+ };
185
+ }
186
+
187
+ export function uploadFailure(platform: string, status: number, text: string): Error {
188
+ const body = redact(text).slice(0, 300);
189
+ if (status === 429 || status >= 500) return new RetryableError(`${platform}_http`, `${platform} HTTP ${status}`);
190
+ if (status === 401) {
191
+ return new ConfigError(`${platform}_token`, `${platform} HTTP 401: the access token is expired or revoked. ${body}`);
192
+ }
193
+ if (status === 413) {
194
+ return new QualityError(`${platform}_too_large`, `${platform} HTTP 413: the platform will not take this file. ${body}`);
195
+ }
196
+ const { reason } = reasonOf(text);
197
+ const klass = reason ? classifyReason(reason) : "compliance";
198
+ const code = reason ? `${platform}_${reason}` : `${platform}_rejected`;
199
+ return failureOf(klass, code, `${platform} HTTP ${status}${reason ? ` (${reason})` : ""}: ${body}`);
200
+ }
201
+
202
+ export function metaFailure(status: number, text: string, platform: string): Error {
203
+ const body = redact(text).slice(0, 300);
204
+ const { metaCode } = reasonOf(text);
205
+ if (isMetaRate(status, safeJson(text))) return new RetryableError("meta_rate", body);
206
+ if (status >= 500) return new RetryableError("meta_http", body);
207
+ if (status === 401 || metaCode === 190 || metaCode === 102) {
208
+ return new ConfigError("meta_token", `${platform}: the access token is expired or invalid. ${body}`);
209
+ }
210
+ if (metaCode === 10 || (metaCode !== null && metaCode >= 200 && metaCode <= 299)) {
211
+ return new ConfigError(`${platform}_permission`, `${platform}: the token lacks a permission. ${body}`);
212
+ }
213
+ if (status === 413) return new QualityError(`${platform}_too_large`, `${platform}: the platform will not take this file. ${body}`);
214
+ return new ComplianceError(`${platform}_rejected`, body);
215
+ }
216
+
217
+ export function isMetaRate(status: number, json: unknown): boolean {
218
+ if (status === 429) return true;
219
+ const error = (json as { error?: { code?: number } } | null)?.error;
220
+ return error?.code === 4 || error?.code === 17 || error?.code === 32 || error?.code === 613;
221
+ }
222
+
223
+ function safeJson(text: string): unknown {
224
+ try {
225
+ return JSON.parse(text) as unknown;
226
+ } catch {
227
+ return null;
228
+ }
229
+ }
230
+
231
+ export function requiredEnv(env: Env, name: string): string {
232
+ const value = env[name];
233
+ if (!value) throw new ConfigError("missing_env", `Environment variable ${name} is not set`);
234
+ return value;
235
+ }
236
+
237
+ export function proofOf(session: PlatformSession): DisclosureProof {
238
+ if (!session.disclosure || session.disclosure.value !== true) {
239
+ throw new ComplianceError("missing_disclosure", "Publish finished without a disclosure proof. This is a compliance block.");
240
+ }
241
+ return session.disclosure;
242
+ }