@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,180 @@
1
+ import { mkdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
2
+ import net from "node:net";
3
+ import path from "node:path";
4
+ import { canonicalPath } from "./project/canonical.js";
5
+ import type { ResolvedHome } from "./home.js";
6
+
7
+ export const DEFAULT_DASHBOARD_PORT = 4317;
8
+
9
+ type Env = Record<string, string | undefined>;
10
+
11
+ /** The port `npx vfrey dashboard` tries first without --port. VFREY_DASHBOARD_PORT moves it, for a machine where 4317 is busy. */
12
+ export function defaultDashboardPort(env: Env): number {
13
+ const port = Number(env.VFREY_DASHBOARD_PORT);
14
+ return Number.isInteger(port) && port > 0 && port < 65536 ? port : DEFAULT_DASHBOARD_PORT;
15
+ }
16
+
17
+ /** Which desk a dashboard serves: its data folder, and the project when a project decided that folder. */
18
+ export interface Desk {
19
+ dataDir: string;
20
+ project: { id: string; name: string } | null;
21
+ }
22
+
23
+ export function deskOf(home: Pick<ResolvedHome, "dataDir" | "project">): Desk {
24
+ return { dataDir: home.dataDir, project: home.project ? { id: home.project.id, name: home.project.name } : null };
25
+ }
26
+
27
+ /** What `.vfrey/dashboard.json` (or the machine desk's `dashboard.json`) holds. */
28
+ export interface DashboardRecord {
29
+ port: number;
30
+ url: string;
31
+ pid: number;
32
+ projectId: string | null;
33
+ startedAt: string;
34
+ }
35
+
36
+ const recordFile = (dataDir: string) => path.join(dataDir, "dashboard.json");
37
+
38
+ export function readDashboardRecord(dataDir: string): DashboardRecord | null {
39
+ try {
40
+ const raw = JSON.parse(readFileSync(recordFile(dataDir), "utf8")) as Partial<DashboardRecord>;
41
+ if (typeof raw.port !== "number" || !Number.isInteger(raw.port) || raw.port <= 0 || raw.port >= 65536) return null;
42
+ return {
43
+ port: raw.port,
44
+ url: typeof raw.url === "string" ? raw.url : `http://127.0.0.1:${raw.port}`,
45
+ pid: typeof raw.pid === "number" ? raw.pid : 0,
46
+ projectId: typeof raw.projectId === "string" ? raw.projectId : null,
47
+ startedAt: typeof raw.startedAt === "string" ? raw.startedAt : "",
48
+ };
49
+ } catch {
50
+ return null;
51
+ }
52
+ }
53
+
54
+ /** The dashboard notes itself in the data folder. The write is atomic, and the last dashboard to start owns the record. */
55
+ export function writeDashboardRecord(dataDir: string, port: number, projectId: string | null = null, pid: number = process.pid): void {
56
+ try {
57
+ mkdirSync(dataDir, { recursive: true });
58
+ const record: DashboardRecord = { port, url: `http://127.0.0.1:${port}`, pid, projectId, startedAt: new Date().toISOString() };
59
+ const tmp = `${recordFile(dataDir)}.${pid}.tmp`;
60
+ writeFileSync(tmp, JSON.stringify(record) + "\n");
61
+ renameSync(tmp, recordFile(dataDir));
62
+ } catch {
63
+ /* the record is a convenience, never a reason to stop the dashboard */
64
+ }
65
+ }
66
+
67
+ /** Removes the record only while it still names this dashboard (pid and port). A newer dashboard's record is left alone. */
68
+ export function removeDashboardRecord(dataDir: string, port?: number, pid: number = process.pid): void {
69
+ try {
70
+ const record = readDashboardRecord(dataDir);
71
+ if (!record || record.pid !== pid) return;
72
+ if (port !== undefined && record.port !== port) return;
73
+ rmSync(recordFile(dataDir), { force: true });
74
+ } catch {
75
+ /* no record, or not ours */
76
+ }
77
+ }
78
+
79
+ const canon = canonicalPath;
80
+
81
+ /** Whether a GET /api/state body names this desk: the same data folder and the same project id (or both none). */
82
+ export function stateMatchesDesk(state: unknown, desk: Desk): boolean {
83
+ if (!state || typeof state !== "object") return false;
84
+ const body = state as { ok?: unknown; dataDir?: unknown; project?: { id?: unknown } | null };
85
+ if (body.ok !== true || typeof body.dataDir !== "string") return false;
86
+ if (canon(body.dataDir) !== canon(desk.dataDir)) return false;
87
+ const served = body.project && typeof body.project.id === "string" ? body.project.id : null;
88
+ return served === (desk.project?.id ?? null);
89
+ }
90
+
91
+ /** Probes one origin. True only when it answers /api/state and says it serves this desk. */
92
+ export async function servesDesk(origin: string, desk: Desk, timeoutMs = 300): Promise<boolean> {
93
+ try {
94
+ const response = await fetch(`${origin}/api/state`, { signal: AbortSignal.timeout(timeoutMs) });
95
+ if (!response.ok) return false;
96
+ return stateMatchesDesk(await response.json(), desk);
97
+ } catch {
98
+ return false;
99
+ }
100
+ }
101
+
102
+ /**
103
+ * The origin of a dashboard that is listening and serves this desk, or null. It tries the port the desk recorded,
104
+ * then the default one. A dashboard that serves another desk (another project, or the machine desk) does not count.
105
+ */
106
+ export async function findDashboard(desk: Desk, env: Env = process.env, timeoutMs = 300): Promise<string | null> {
107
+ const ports = [...new Set([readDashboardRecord(desk.dataDir)?.port ?? null, defaultDashboardPort(env)].filter((port): port is number => port !== null))];
108
+ for (const port of ports) {
109
+ const origin = `http://127.0.0.1:${port}`;
110
+ if (await servesDesk(origin, desk, timeoutMs)) return origin;
111
+ }
112
+ return null;
113
+ }
114
+
115
+ /** Whether a TCP port on loopback can be bound right now. */
116
+ export function canListen(port: number): Promise<boolean> {
117
+ return new Promise((resolve) => {
118
+ const server = net.createServer();
119
+ server.once("error", () => resolve(false));
120
+ server.listen(port, "127.0.0.1", () => server.close(() => resolve(true)));
121
+ });
122
+ }
123
+
124
+ /** The ports to try, in order: the one the desk recorded, then the default, then the ones above the default. */
125
+ export function portCandidates(recorded: number | null, preferred: number, span = 50): number[] {
126
+ const list = [...(recorded !== null ? [recorded] : []), preferred];
127
+ for (let offset = 1; offset <= span && preferred + offset < 65536; offset += 1) list.push(preferred + offset);
128
+ return [...new Set(list)];
129
+ }
130
+
131
+ /** The first candidate that is free: the recorded port, else 4317 (VFREY_DASHBOARD_PORT), else the next free port up. */
132
+ export async function pickPort(desk: Desk, env: Env, isFree: (port: number) => Promise<boolean> = canListen, skip: ReadonlySet<number> = new Set()): Promise<number> {
133
+ const candidates = portCandidates(readDashboardRecord(desk.dataDir)?.port ?? null, defaultDashboardPort(env));
134
+ for (const port of candidates) {
135
+ if (skip.has(port)) continue;
136
+ if (await isFree(port)) return port;
137
+ }
138
+ return 0;
139
+ }
140
+
141
+ const lockFile = (dataDir: string) => path.join(dataDir, "dashboard.lock");
142
+
143
+ function alive(pid: number): boolean {
144
+ try {
145
+ process.kill(pid, 0);
146
+ return true;
147
+ } catch (error) {
148
+ return (error as NodeJS.ErrnoException).code === "EPERM";
149
+ }
150
+ }
151
+
152
+ /**
153
+ * A short lock held while a dashboard picks its port and writes its record, so two starts at once (a tick and `open`)
154
+ * do not become two dashboards for one desk. Returns a release function, or null when another live start holds it.
155
+ */
156
+ export function acquireStartLock(dataDir: string, staleMs = 20_000): (() => void) | null {
157
+ mkdirSync(dataDir, { recursive: true });
158
+ for (let attempt = 0; attempt < 2; attempt += 1) {
159
+ try {
160
+ writeFileSync(lockFile(dataDir), String(process.pid), { flag: "wx" });
161
+ return () => {
162
+ try {
163
+ if (readFileSync(lockFile(dataDir), "utf8") === String(process.pid)) rmSync(lockFile(dataDir), { force: true });
164
+ } catch {
165
+ /* already gone */
166
+ }
167
+ };
168
+ } catch {
169
+ try {
170
+ const holder = Number(readFileSync(lockFile(dataDir), "utf8"));
171
+ const age = Date.now() - statSync(lockFile(dataDir)).mtimeMs;
172
+ if (holder && alive(holder) && age < staleMs) return null;
173
+ rmSync(lockFile(dataDir), { force: true });
174
+ } catch {
175
+ /* the holder released it between the two reads: try again */
176
+ }
177
+ }
178
+ }
179
+ return null;
180
+ }
@@ -0,0 +1,103 @@
1
+ import { spawn } from "node:child_process";
2
+ import { closeSync, mkdirSync, openSync, readFileSync } from "node:fs";
3
+ import { createRequire } from "node:module";
4
+ import path from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import { findDashboard, type Desk } from "./dashboard-link.js";
7
+
8
+ type Env = Record<string, string | undefined>;
9
+
10
+ export type EnsureResult = { ok: true; origin: string; started: boolean } | { ok: false; message: string; fix: string };
11
+
12
+ export interface EnsureOptions {
13
+ desk: Desk;
14
+ env: Env;
15
+ /** The folder the dashboard process starts in, so it finds the same project. Default is this process's folder. */
16
+ cwd?: string;
17
+ timeoutMs?: number;
18
+ /** Test hook: how to start the detached dashboard. Default spawns this CLI. */
19
+ spawnDashboard?: (logFile: string) => { pid?: number; exited: Promise<number | null> };
20
+ }
21
+
22
+ export const dashboardLogFile = (dataDir: string): string => path.join(dataDir, "dashboard.log");
23
+
24
+ /** Starts `vfrey dashboard` as its own process: output to the log file, no stdin, and it outlives this process. */
25
+ function spawnDetached(logFile: string, env: Env, cwd: string): { pid?: number; exited: Promise<number | null> } {
26
+ const here = path.dirname(fileURLToPath(import.meta.url));
27
+ const tsx = createRequire(import.meta.url).resolve("tsx");
28
+ mkdirSync(path.dirname(logFile), { recursive: true });
29
+ const fd = openSync(logFile, "a");
30
+ const child = spawn(
31
+ process.execPath,
32
+ ["--experimental-sqlite", "--disable-warning=ExperimentalWarning", "--import", tsx, path.join(here, "cli.ts"), "dashboard"],
33
+ { cwd, env: env as NodeJS.ProcessEnv, stdio: ["ignore", fd, fd], detached: true },
34
+ );
35
+ closeSync(fd);
36
+ const exited = new Promise<number | null>((resolve) => {
37
+ child.once("exit", (code) => resolve(code));
38
+ child.once("error", () => resolve(1));
39
+ });
40
+ child.unref();
41
+ return { pid: child.pid, exited };
42
+ }
43
+
44
+ function logTail(file: string): string {
45
+ try {
46
+ return readFileSync(file, "utf8").trim().split("\n").slice(-3).join(" | ");
47
+ } catch {
48
+ return "";
49
+ }
50
+ }
51
+
52
+ /**
53
+ * The one path to a live dashboard for a desk, used by `open` and by the tick summary: reuse the one that is listening,
54
+ * else start one detached and wait until /api/state answers and names this desk.
55
+ */
56
+ export async function ensureDashboard(options: EnsureOptions): Promise<EnsureResult> {
57
+ const { desk, env } = options;
58
+ const existing = await findDashboard(desk, env);
59
+ if (existing) return { ok: true, origin: existing, started: false };
60
+ const logFile = dashboardLogFile(desk.dataDir);
61
+ let child;
62
+ try {
63
+ child = (options.spawnDashboard ?? ((file) => spawnDetached(file, env, options.cwd ?? process.cwd())))(logFile);
64
+ } catch (error) {
65
+ return { ok: false, message: `Could not start the dashboard: ${error instanceof Error ? error.message : String(error)}.`, fix: "run `npx vfrey doctor`, or start it by hand with `npx vfrey dashboard`." };
66
+ }
67
+ let exitCode: number | null | undefined;
68
+ void child.exited.then((code) => {
69
+ exitCode = code;
70
+ });
71
+ const deadline = Date.now() + (options.timeoutMs ?? 15_000);
72
+ while (Date.now() < deadline) {
73
+ await new Promise((resolve) => setTimeout(resolve, 150));
74
+ const origin = await findDashboard(desk, env);
75
+ if (origin) return { ok: true, origin, started: true };
76
+ // A child that exited without a dashboard having come up for this desk has failed; its log says why.
77
+ if (exitCode !== undefined) {
78
+ const origin = await findDashboard(desk, env, 1000);
79
+ if (origin) return { ok: true, origin, started: true };
80
+ const tail = logTail(logFile);
81
+ return { ok: false, message: `The dashboard exited at start (code ${exitCode})${tail ? `: ${tail}` : ""}.`, fix: `read ${logFile}, or run \`npx vfrey dashboard\` in the foreground to see the error.` };
82
+ }
83
+ }
84
+ const tail = logTail(logFile);
85
+ return { ok: false, message: `The dashboard did not answer within ${Math.round((options.timeoutMs ?? 15_000) / 1000)} s${tail ? `: ${tail}` : ""}.`, fix: `read ${logFile}, or run \`npx vfrey dashboard\` in the foreground to see the error.` };
86
+ }
87
+
88
+ /** Opens a URL in the default browser. Never throws; the URL is printed anyway. */
89
+ export function openBrowser(url: string, platform: NodeJS.Platform = process.platform): void {
90
+ const [cmd, args]: [string, string[]] = platform === "darwin" ? ["open", [url]] : platform === "win32" ? ["cmd", ["/c", "start", "", url]] : ["xdg-open", [url]];
91
+ try {
92
+ const child = spawn(cmd, args, { stdio: "ignore", detached: true });
93
+ child.on("error", () => {});
94
+ child.unref();
95
+ } catch {
96
+ /* no browser: the printed URL is enough */
97
+ }
98
+ }
99
+
100
+ /** Whether to skip the browser: --no-browser, VFREY_NO_BROWSER=1, or a test runner. */
101
+ export function browserDisabled(argv: string[], env: Env): boolean {
102
+ return argv.includes("--no-browser") || env.VFREY_NO_BROWSER === "1" || env.VITEST !== undefined || env.NODE_ENV === "test";
103
+ }
@@ -0,0 +1,64 @@
1
+ import { credentialsKey, decryptSecret } from "@vfrey/contracts";
2
+ import type { Store } from "../queue/store.js";
3
+
4
+ export const CHANNEL_PLATFORMS = ["youtube", "tiktok", "instagram", "facebook"] as const;
5
+ export type ChannelPlatform = (typeof CHANNEL_PLATFORMS)[number];
6
+
7
+ /** Where each platform's channel or page id already lives. YouTube has none, so its id is stored in the platform config. */
8
+ const ID_FIELD: Partial<Record<ChannelPlatform, string>> = {
9
+ tiktok: "tiktokOpenId",
10
+ instagram: "igUserId",
11
+ facebook: "fbPageId",
12
+ };
13
+
14
+ export interface ChannelInfo {
15
+ platform: ChannelPlatform;
16
+ enabled: boolean;
17
+ /** The id on file, or null when none is known yet. */
18
+ id: string | null;
19
+ }
20
+
21
+ /**
22
+ * The channel or page id each platform of a series is known by: the TikTok open id, the Instagram user id and the Facebook
23
+ * page id come from the series' stored keys, the YouTube channel id from the platform's `channelId` setting.
24
+ * These are public identifiers, not tokens. An id that cannot be decrypted counts as unknown.
25
+ */
26
+ export function channelInfo(store: Store, seriesId: string, rawKey: string | undefined): ChannelInfo[] {
27
+ let key: Buffer | null = null;
28
+ try {
29
+ key = credentialsKey(rawKey ?? "");
30
+ } catch {
31
+ key = null;
32
+ }
33
+ const platforms = store.db.prepare(`SELECT platform, enabled, config_json FROM desk_platforms WHERE series_id = ?`).all(seriesId) as Array<{
34
+ platform: string;
35
+ enabled: number;
36
+ config_json: string;
37
+ }>;
38
+ return CHANNEL_PLATFORMS.map((platform) => {
39
+ const row = platforms.find((item) => item.platform === platform);
40
+ let id: string | null = null;
41
+ const field = ID_FIELD[platform];
42
+ if (field && key) {
43
+ const secret = store.db
44
+ .prepare(`SELECT ciphertext FROM desk_secrets WHERE owner_kind = 'series' AND owner_id = ? AND field = ?`)
45
+ .get(seriesId, field) as { ciphertext: string } | undefined;
46
+ if (secret) {
47
+ try {
48
+ id = decryptSecret(secret.ciphertext, key).trim() || null;
49
+ } catch {
50
+ id = null;
51
+ }
52
+ }
53
+ }
54
+ if (!id && row) {
55
+ try {
56
+ const config = JSON.parse(row.config_json) as { channelId?: unknown };
57
+ if (typeof config.channelId === "string" && config.channelId.trim()) id = config.channelId.trim();
58
+ } catch {
59
+ id = null;
60
+ }
61
+ }
62
+ return { platform, enabled: Boolean(row && Number(row.enabled) === 1), id };
63
+ });
64
+ }
@@ -0,0 +1,216 @@
1
+ import type { Store } from "../queue/store.js";
2
+ import type { SecretOwnerKind } from "./secrets.js";
3
+ import * as verbs from "./verbs.js";
4
+
5
+ export interface DeskIo {
6
+ env: Record<string, string | undefined>;
7
+ stdout: (text: string) => void;
8
+ stderr: (text: string) => void;
9
+ readStdin: () => Promise<string>;
10
+ }
11
+
12
+ type FlagMap = Record<string, string | undefined>;
13
+
14
+ class UsageError extends Error {
15
+ constructor(
16
+ message: string,
17
+ readonly fix: string,
18
+ ) {
19
+ super(message);
20
+ }
21
+ }
22
+
23
+ function flags(argv: string[]): FlagMap {
24
+ const out: FlagMap = {};
25
+ for (let i = 0; i < argv.length; i += 1) {
26
+ const token = argv[i];
27
+ if (!token?.startsWith("--")) continue;
28
+ const name = token.slice(2);
29
+ const next = argv[i + 1];
30
+ if (next && !next.startsWith("--")) {
31
+ out[name] = next;
32
+ i += 1;
33
+ } else {
34
+ out[name] = "true";
35
+ }
36
+ }
37
+ return out;
38
+ }
39
+
40
+ function need(map: FlagMap, name: string): string {
41
+ const value = map[name];
42
+ if (!value) throw new UsageError(`Missing --${name}.`, `Pass --${name} <value>.`);
43
+ return value;
44
+ }
45
+
46
+ function refusal(io: DeskIo, message: string, fix: string): number {
47
+ io.stderr(`✗ ${message}\n`);
48
+ io.stderr(`Fix: ${fix}\n`);
49
+ io.stdout(`${JSON.stringify({ ok: false, refusal: message, fix })}\n`);
50
+ return 1;
51
+ }
52
+
53
+ function secretTarget(map: FlagMap): { ownerKind: SecretOwnerKind; ownerId: string } {
54
+ if (map.series) return { ownerKind: "series", ownerId: map.series };
55
+ if (map.account) return { ownerKind: "account", ownerId: map.account };
56
+ if (map.owner && map.id) {
57
+ return { ownerKind: map.owner === "account" ? "account" : "series", ownerId: map.id };
58
+ }
59
+ throw new UsageError("Missing --series or --account.", "Pass --series <id> or --account <id>.");
60
+ }
61
+
62
+ function flagBool(value: string | undefined): boolean | undefined {
63
+ if (value === undefined) return undefined;
64
+ return value !== "false";
65
+ }
66
+
67
+ const list = (value: string | undefined): string[] => (value ? value.split(",").filter(Boolean) : []);
68
+
69
+ /** Prints a verb result in the CLI's style and returns the exit code. */
70
+ function print(io: DeskIo, result: verbs.VerbResult): number {
71
+ if (!result.ok) return refusal(io, result.refusal, result.fix);
72
+ io.stdout(`${JSON.stringify(result.data ?? { ok: true })}\n`);
73
+ return 0;
74
+ }
75
+
76
+ export async function runDesk(argv: string[], io: DeskIo, store: Store): Promise<number> {
77
+ const userId = io.env.VFREY_USER ?? "local";
78
+ const [group, action] = argv;
79
+ const map = flags(argv.slice(2));
80
+ const run = (result: verbs.VerbResult) => print(io, result);
81
+ try {
82
+ if (group === "snapshot") return run(verbs.snapshotRead(store, { userId }));
83
+ if (group === "account" && action === "create") {
84
+ return run(verbs.accountCreate(store, { id: need(map, "id"), name: need(map, "name"), ownerId: userId }));
85
+ }
86
+ if (group === "account" && action === "rename") {
87
+ return run(verbs.accountRename(store, { id: need(map, "id"), name: need(map, "name") }));
88
+ }
89
+ if (group === "account" && action === "delete") return run(verbs.accountDelete(store, { id: need(map, "id") }));
90
+ if (group === "secret" && action === "set") {
91
+ const value = await io.readStdin();
92
+ return run(
93
+ verbs.secretSet(store, { ...secretTarget(map), field: need(map, "field"), value, key: io.env.CREDENTIALS_KEY }),
94
+ );
95
+ }
96
+ if (group === "secret" && action === "clear") {
97
+ return run(verbs.secretClear(store, { ...secretTarget(map), field: need(map, "field") }));
98
+ }
99
+ if (group === "series" && action === "create") {
100
+ return run(
101
+ verbs.seriesCreate(store, {
102
+ id: need(map, "id"),
103
+ accountId: need(map, "account"),
104
+ niche: need(map, "niche"),
105
+ language: map.language,
106
+ timezone: map.timezone,
107
+ engine: map.engine,
108
+ }),
109
+ );
110
+ }
111
+ if (group === "series" && action === "update") {
112
+ return run(
113
+ verbs.seriesUpdate(store, {
114
+ id: need(map, "id"),
115
+ patch: {
116
+ niche: map.niche,
117
+ language: map.language,
118
+ timezone: map.timezone,
119
+ voices: map.voice ? [map.voice] : undefined,
120
+ autopilot: flagBool(map.autopilot),
121
+ hashtagPool: map.hashtags ? list(map.hashtags) : undefined,
122
+ engageEnabled: flagBool(map.engage),
123
+ publishEnabled: flagBool(map.publish),
124
+ reviewEnabled: flagBool(map.review),
125
+ allowSpicy: flagBool(map.spicy),
126
+ stylePath: map.style,
127
+ engine: map.engine,
128
+ },
129
+ }),
130
+ );
131
+ }
132
+ if (group === "series" && action === "delete") return run(verbs.seriesDelete(store, { id: need(map, "id") }));
133
+ if (group === "series" && action === "live") {
134
+ if (map.off) return run(verbs.seriesOff(store, { id: need(map, "id") }));
135
+ return run(verbs.seriesLive(store, { id: need(map, "id"), confirm: map.confirm, key: io.env.CREDENTIALS_KEY }));
136
+ }
137
+ if (group === "series" && action === "pause") return run(verbs.seriesPause(store, { id: need(map, "id") }));
138
+ if (group === "series" && action === "resume") return run(verbs.seriesResume(store, { id: need(map, "id") }));
139
+ if (group === "platform" && action === "set") {
140
+ const config: Record<string, unknown> = {};
141
+ for (const [key, value] of Object.entries(map)) {
142
+ if (key === "series" || key === "platform" || key === "enabled" || value === undefined) continue;
143
+ config[key] = value;
144
+ }
145
+ return run(
146
+ verbs.platformSet(store, {
147
+ seriesId: need(map, "series"),
148
+ platform: need(map, "platform"),
149
+ enabled: map.enabled !== "false",
150
+ config,
151
+ }),
152
+ );
153
+ }
154
+ if (group === "platform" && action === "remove") {
155
+ return run(verbs.platformRemove(store, { seriesId: need(map, "series"), platform: need(map, "platform") }));
156
+ }
157
+ if (group === "platform" && action === "disable") {
158
+ return run(verbs.platformDisable(store, { seriesId: need(map, "series"), platform: need(map, "platform") }));
159
+ }
160
+ if (group === "source" && action === "add") {
161
+ return run(verbs.sourceAdd(store, { seriesId: need(map, "series"), kind: need(map, "kind"), value: need(map, "value") }));
162
+ }
163
+ if (group === "source" && action === "update") {
164
+ return run(verbs.sourceUpdate(store, { id: need(map, "id"), value: need(map, "value") }));
165
+ }
166
+ if (group === "source" && action === "remove") return run(verbs.sourceRemove(store, { id: need(map, "id") }));
167
+ if (group === "mode" && action === "set") {
168
+ const mode = argv[2] === "api" ? "api" : argv[2] === "agent" ? "agent" : "";
169
+ return run(verbs.modeSet(store, { userId, mode, llmKeyPresent: Boolean(io.env.LLM_API_KEY) }));
170
+ }
171
+ if (group === "mode" && action === "clear") return run(verbs.modeClear(store, { userId }));
172
+ if (group === "job" && action === "retry") return run(verbs.jobRetry(store, { id: need(map, "id") }));
173
+ if (group === "job" && action === "approve") return run(verbs.jobApprove(store, { id: need(map, "id") }));
174
+ if (group === "job" && action === "reject") return run(verbs.jobReject(store, { id: need(map, "id"), reason: map.reason === "true" ? undefined : map.reason }));
175
+ if (group === "job" && action === "cancel") return run(verbs.jobCancel(store, { id: need(map, "id") }));
176
+ if (group === "job" && action === "delete") return run(verbs.jobDelete(store));
177
+ if (group === "publication" && action === "delete") return run(verbs.publicationDelete(store));
178
+ if (group === "metric" && action === "delete") return run(verbs.metricDelete(store));
179
+ if (group === "comment" && action === "delete") return run(verbs.commentDelete(store));
180
+ if (group === "cadence" && action === "set") {
181
+ return run(
182
+ verbs.cadenceSet(store, {
183
+ seriesId: need(map, "series"),
184
+ timezone: need(map, "timezone"),
185
+ minHoursBetweenPosts: Number(need(map, "min-hours")),
186
+ maxPostsPerDay: Number(need(map, "max-per-day")),
187
+ windows: list(map.windows),
188
+ }),
189
+ );
190
+ }
191
+ if (group === "cadence" && action === "clear") return run(verbs.cadenceClear(store));
192
+ if (group === "quota" && action === "set") {
193
+ return run(
194
+ verbs.quotaSet(store, {
195
+ seriesId: need(map, "series"),
196
+ youtubeDailyUploadCap: Number(need(map, "upload-cap")),
197
+ maxRepliesPerDay: Number(need(map, "replies")),
198
+ minSecondsBetween: Number(need(map, "seconds")),
199
+ }),
200
+ );
201
+ }
202
+ if (group === "quota" && action === "clear") return run(verbs.quotaClear(store));
203
+ if (group === "warning" && action === "ack") return run(verbs.warningAck(store, { id: need(map, "id") }));
204
+ if (group === "import") return run(verbs.deskImport(store, { from: need(flags(argv.slice(1)), "from") }));
205
+ if (group === "warning" && action === "delete") return run(verbs.warningDelete(store));
206
+ return refusal(
207
+ io,
208
+ `Unknown desk command ${group ?? ""} ${action ?? ""}.`.trim(),
209
+ "Use snapshot, account, secret, series, platform, source, mode, job, publication, metric, comment, cadence, quota, warning, tick, or import.",
210
+ );
211
+ } catch (error) {
212
+ if (error instanceof UsageError) return refusal(io, error.message, error.fix);
213
+ const message = error instanceof Error ? error.message : "desk command failed";
214
+ return refusal(io, message, "Check the command and try again.");
215
+ }
216
+ }