@vfrey/vfrey 0.0.1 → 1.0.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (603) 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 +282 -0
  40. package/src/api/commands.ts +330 -0
  41. package/src/api/config.ts +34 -0
  42. package/src/api/creators.ts +463 -0
  43. package/src/api/credentials.ts +152 -0
  44. package/src/api/mail.ts +333 -0
  45. package/src/api/topics.ts +207 -0
  46. package/src/api/types.ts +205 -0
  47. package/src/cli.ts +467 -0
  48. package/src/cloud/executor.ts +281 -0
  49. package/src/cloud/job.ts +106 -0
  50. package/src/cloud/render-job.ts +187 -0
  51. package/src/config.ts +309 -0
  52. package/src/dashboard-link.ts +180 -0
  53. package/src/dashboard-start.ts +103 -0
  54. package/src/desk/channel.ts +64 -0
  55. package/src/desk/cli.ts +216 -0
  56. package/src/desk/creators-route.ts +129 -0
  57. package/src/desk/fonts/JetBrainsMono-OFL.txt +93 -0
  58. package/src/desk/fonts/google-sans-flex-latin.woff2 +0 -0
  59. package/src/desk/fonts/jetbrains-mono-medium.woff2 +0 -0
  60. package/src/desk/http.ts +460 -0
  61. package/src/desk/mail-route.ts +103 -0
  62. package/src/desk/overlay.ts +112 -0
  63. package/src/desk/preview.ts +422 -0
  64. package/src/desk/screens.ts +149 -0
  65. package/src/desk/secrets.ts +143 -0
  66. package/src/desk/series.ts +127 -0
  67. package/src/desk/state.ts +77 -0
  68. package/src/desk/studio-route.ts +119 -0
  69. package/src/desk/tick-runner.ts +66 -0
  70. package/src/desk/tick.ts +56 -0
  71. package/src/desk/topics-route.ts +41 -0
  72. package/src/desk/ui/app.css +505 -0
  73. package/src/desk/ui/app.js +185 -0
  74. package/src/desk/ui/apple-icon.png +0 -0
  75. package/src/desk/ui/components.js +79 -0
  76. package/src/desk/ui/core.js +156 -0
  77. package/src/desk/ui/favicon.ico +0 -0
  78. package/src/desk/ui/icon.svg +1 -0
  79. package/src/desk/ui/icons.js +49 -0
  80. package/src/desk/ui/index.html +41 -0
  81. package/src/desk/ui/metrics-math.d.ts +22 -0
  82. package/src/desk/ui/metrics-math.js +135 -0
  83. package/src/desk/ui/theme.js +5 -0
  84. package/src/desk/ui/views-creators.js +209 -0
  85. package/src/desk/ui/views-keys.js +114 -0
  86. package/src/desk/ui/views-mail.js +188 -0
  87. package/src/desk/ui/views-misc.js +123 -0
  88. package/src/desk/ui/views-review.js +119 -0
  89. package/src/desk/ui/views-runs.js +350 -0
  90. package/src/desk/ui/views-screens.js +163 -0
  91. package/src/desk/ui/views-series.js +318 -0
  92. package/src/desk/ui/views-topics.js +87 -0
  93. package/src/desk/verbs.ts +361 -0
  94. package/src/doctor.ts +212 -0
  95. package/src/drain.ts +14 -0
  96. package/src/engage/index.ts +217 -0
  97. package/src/generate/index.ts +110 -0
  98. package/src/generate/script.ts +152 -0
  99. package/src/generate/title.ts +78 -0
  100. package/src/help.ts +233 -0
  101. package/src/home.ts +45 -0
  102. package/src/llm/client.ts +177 -0
  103. package/src/log.ts +7 -0
  104. package/src/oauth/tokens.ts +189 -0
  105. package/src/oauth/vault.ts +72 -0
  106. package/src/pipeline/review.ts +642 -0
  107. package/src/pipeline/run.ts +443 -0
  108. package/src/pipeline/series-guard.ts +26 -0
  109. package/src/pipeline/source-fetch.ts +69 -0
  110. package/src/pipeline/stages.ts +728 -0
  111. package/src/pipeline/topics-source.ts +90 -0
  112. package/src/product/commands.ts +296 -0
  113. package/src/product/config-file.ts +38 -0
  114. package/src/product/engine-choice.ts +89 -0
  115. package/src/product/engines.ts +103 -0
  116. package/src/product/insights.ts +50 -0
  117. package/src/product/keys.ts +67 -0
  118. package/src/product/paths.ts +18 -0
  119. package/src/product/probes.ts +88 -0
  120. package/src/product/push-flow.ts +90 -0
  121. package/src/product/render-video.ts +21 -0
  122. package/src/product/short.ts +62 -0
  123. package/src/product/update.ts +32 -0
  124. package/src/project/canonical.ts +14 -0
  125. package/src/project/commands.ts +177 -0
  126. package/src/project/dashboard-status.ts +28 -0
  127. package/src/project/discover.ts +19 -0
  128. package/src/project/index-file.ts +73 -0
  129. package/src/project/marker.ts +87 -0
  130. package/src/project/migrate.ts +201 -0
  131. package/src/project/names.ts +27 -0
  132. package/src/project/notice.ts +16 -0
  133. package/src/project/open.ts +88 -0
  134. package/src/project/scaffold.ts +112 -0
  135. package/src/project/templates.ts +42 -0
  136. package/src/project/videos.ts +64 -0
  137. package/src/publish/facebook.ts +91 -0
  138. package/src/publish/index.ts +12 -0
  139. package/src/publish/instagram.ts +97 -0
  140. package/src/publish/rate.ts +62 -0
  141. package/src/publish/session.ts +242 -0
  142. package/src/publish/tiktok.ts +202 -0
  143. package/src/publish/youtube.ts +177 -0
  144. package/src/queue/desk-ops.ts +1170 -0
  145. package/src/queue/seal.ts +3 -0
  146. package/src/queue/store.ts +1006 -0
  147. package/src/remotion/AiLabel.tsx +26 -0
  148. package/src/remotion/Root.tsx +73 -0
  149. package/src/remotion/defaults.ts +26 -0
  150. package/src/remotion/films/claude/ClaudeFilm.tsx +1014 -0
  151. package/src/remotion/films/engine/EngineMark.tsx +17 -0
  152. package/src/remotion/films/rea/ReaShort.tsx +552 -0
  153. package/src/remotion/films/rea/drawings.tsx +907 -0
  154. package/src/remotion/films/rea/timing.ts +144 -0
  155. package/src/remotion/index.ts +4 -0
  156. package/src/remotion/templates/EditorialSplit.tsx +39 -0
  157. package/src/remotion/templates/KineticType.tsx +39 -0
  158. package/src/remotion/templates/SignalBoard.tsx +50 -0
  159. package/src/remotion/timing.ts +22 -0
  160. package/src/render/chrome.ts +78 -0
  161. package/src/render/engine-mark.ts +29 -0
  162. package/src/render/framey-template.ts +130 -0
  163. package/src/render/html-engine.ts +149 -0
  164. package/src/render/node-crypto-stub.ts +11 -0
  165. package/src/render/probe.ts +29 -0
  166. package/src/render/props.ts +17 -0
  167. package/src/render/vertical-short.ts +35 -0
  168. package/src/render/webpack.ts +33 -0
  169. package/src/render/worker.ts +65 -0
  170. package/src/scene/assets/fonts/TitanOne-OFL.txt +94 -0
  171. package/src/scene/assets/fonts/titan-one.woff2 +0 -0
  172. package/src/scene/assets/gsap/LICENSE.txt +8 -0
  173. package/src/scene/assets/gsap/gsap.min.js +11 -0
  174. package/src/scene/audio.ts +54 -0
  175. package/src/scene/blocks/LICENSE +190 -0
  176. package/src/scene/blocks/NOTICE +5 -0
  177. package/src/scene/blocks/bar-chart-race/bar-chart-race.html +585 -0
  178. package/src/scene/blocks/beat-freeze-cut/beat-freeze-cut.html +874 -0
  179. package/src/scene/blocks/camcorder-hud/camcorder-hud.html +164 -0
  180. package/src/scene/blocks/camera-dolly-zoom/camera-dolly-zoom.html +355 -0
  181. package/src/scene/blocks/code-diff/code-diff.html +817 -0
  182. package/src/scene/blocks/code-snippet-apple-terminal-pro/code-snippet-apple-terminal-pro.html +248 -0
  183. package/src/scene/blocks/code-typing/code-typing.html +764 -0
  184. package/src/scene/blocks/data-chart/data-chart.html +452 -0
  185. package/src/scene/blocks/flowchart-vertical/flowchart-vertical.html +454 -0
  186. package/src/scene/blocks/hw-scribble-transition/hw-scribble-transition.html +153 -0
  187. package/src/scene/blocks/index.json +824 -0
  188. package/src/scene/blocks/lt-accent-underline/lt-accent-underline.html +107 -0
  189. package/src/scene/blocks/lt-bold-block/lt-bold-block.html +109 -0
  190. package/src/scene/blocks/lt-clean-bar/lt-clean-bar.html +112 -0
  191. package/src/scene/blocks/lt-dark-card/lt-dark-card.html +111 -0
  192. package/src/scene/blocks/lt-kicker-name/lt-kicker-name.html +114 -0
  193. package/src/scene/blocks/lt-mask-reveal/lt-mask-reveal.html +120 -0
  194. package/src/scene/blocks/lt-neon-border/lt-neon-border.html +391 -0
  195. package/src/scene/blocks/lt-stack-bars/lt-stack-bars.html +109 -0
  196. package/src/scene/blocks/macos-notification/macos-notification.html +194 -0
  197. package/src/scene/blocks/mk-background/mk-background.html +236 -0
  198. package/src/scene/blocks/mk-callout-highlight/mk-callout-highlight.html +183 -0
  199. package/src/scene/blocks/mk-clone-wall-transition/mk-clone-wall-transition.html +226 -0
  200. package/src/scene/blocks/mk-line-graph/mk-line-graph.html +293 -0
  201. package/src/scene/blocks/mk-progress-stat/mk-progress-stat.html +186 -0
  202. package/src/scene/blocks/mk-specs-list/mk-specs-list.html +223 -0
  203. package/src/scene/blocks/news-ticker/news-ticker.html +409 -0
  204. package/src/scene/blocks/organic-light-leak-overlay/organic-light-leak-overlay.html +126 -0
  205. package/src/scene/blocks/oscilloscope-trace/oscilloscope-trace.html +519 -0
  206. package/src/scene/blocks/snow/snow.html +205 -0
  207. package/src/scene/blocks/split-flap-board/split-flap-board.html +347 -0
  208. package/src/scene/blocks/spotify-card/spotify-card.html +294 -0
  209. package/src/scene/blocks/starfield/starfield.html +188 -0
  210. package/src/scene/blocks/thread-message-stack/thread-message-stack.html +239 -0
  211. package/src/scene/blocks/week-in-merges/week-in-merges.html +470 -0
  212. package/src/scene/blocks/x-post/x-post.html +437 -0
  213. package/src/scene/blocks/yt-comment-card/yt-comment-card.html +319 -0
  214. package/src/scene/blocks/yt-lcd-background/yt-lcd-background.html +307 -0
  215. package/src/scene/blocks/yt-prism-title/yt-prism-title.html +205 -0
  216. package/src/scene/blocks.ts +229 -0
  217. package/src/scene/command.ts +272 -0
  218. package/src/scene/figure.ts +181 -0
  219. package/src/scene/glyphs.ts +107 -0
  220. package/src/scene/init.ts +86 -0
  221. package/src/scene/inspect/css-tree.d.ts +6 -0
  222. package/src/scene/inspect/page-scripts.ts +272 -0
  223. package/src/scene/inspect/report.ts +405 -0
  224. package/src/scene/inspect/run.ts +200 -0
  225. package/src/scene/inspect/source.ts +236 -0
  226. package/src/scene/label.ts +131 -0
  227. package/src/scene/net-guard.ts +74 -0
  228. package/src/scene/render.ts +292 -0
  229. package/src/scene/runtime/browser.ts +199 -0
  230. package/src/scene/runtime/core.ts +181 -0
  231. package/src/scene/runtime/react.tsx +115 -0
  232. package/src/scene/runtime/transitions.ts +133 -0
  233. package/src/scene/sanitize.ts +181 -0
  234. package/src/scene/scene.ts +152 -0
  235. package/src/scene/studio-command.ts +48 -0
  236. package/src/scene/studio-skin.ts +59 -0
  237. package/src/scene/studio.ts +337 -0
  238. package/src/scene/supply.ts +111 -0
  239. package/src/scene/timing.ts +288 -0
  240. package/src/scene/transitions/LICENSE +190 -0
  241. package/src/scene/transitions/NOTICE +5 -0
  242. package/src/scene/transitions/SOURCE.json +24 -0
  243. package/src/scene/transitions/transitions.global.js +38 -0
  244. package/src/scene/transitions.ts +26 -0
  245. package/src/service/all-projects.ts +153 -0
  246. package/src/service/commands.ts +256 -0
  247. package/src/service/due.ts +82 -0
  248. package/src/service/lock.ts +101 -0
  249. package/src/service/log-file.ts +56 -0
  250. package/src/service/state.ts +41 -0
  251. package/src/service/units.ts +90 -0
  252. package/src/service/worker.ts +51 -0
  253. package/src/skill-agents.ts +74 -0
  254. package/src/skill-install.ts +334 -0
  255. package/src/sync-write.ts +21 -0
  256. package/src/tick-report.ts +205 -0
  257. package/src/tts.ts +124 -0
  258. package/vendor/@vfrey/contracts/dist/account.d.ts +97 -0
  259. package/vendor/@vfrey/contracts/dist/account.js +27 -0
  260. package/vendor/@vfrey/contracts/dist/checks.d.ts +8 -0
  261. package/vendor/@vfrey/contracts/dist/checks.js +1 -0
  262. package/vendor/@vfrey/contracts/dist/credentials/crypto.d.ts +4 -0
  263. package/vendor/@vfrey/contracts/dist/credentials/crypto.js +38 -0
  264. package/vendor/@vfrey/contracts/dist/domain/advice-persona.d.ts +10 -0
  265. package/vendor/@vfrey/contracts/dist/domain/advice-persona.js +24 -0
  266. package/vendor/@vfrey/contracts/dist/domain/backoff.d.ts +13 -0
  267. package/vendor/@vfrey/contracts/dist/domain/backoff.js +27 -0
  268. package/vendor/@vfrey/contracts/dist/domain/cadence.d.ts +29 -0
  269. package/vendor/@vfrey/contracts/dist/domain/cadence.js +95 -0
  270. package/vendor/@vfrey/contracts/dist/domain/creators.d.ts +146 -0
  271. package/vendor/@vfrey/contracts/dist/domain/creators.js +2 -0
  272. package/vendor/@vfrey/contracts/dist/domain/disclosure.d.ts +127 -0
  273. package/vendor/@vfrey/contracts/dist/domain/disclosure.js +250 -0
  274. package/vendor/@vfrey/contracts/dist/domain/errors.d.ts +34 -0
  275. package/vendor/@vfrey/contracts/dist/domain/errors.js +68 -0
  276. package/vendor/@vfrey/contracts/dist/domain/redact.d.ts +1 -0
  277. package/vendor/@vfrey/contracts/dist/domain/redact.js +21 -0
  278. package/vendor/@vfrey/contracts/dist/domain/revenue.d.ts +13 -0
  279. package/vendor/@vfrey/contracts/dist/domain/revenue.js +16 -0
  280. package/vendor/@vfrey/contracts/dist/domain/safety.d.ts +20 -0
  281. package/vendor/@vfrey/contracts/dist/domain/safety.js +61 -0
  282. package/vendor/@vfrey/contracts/dist/domain/similarity.d.ts +13 -0
  283. package/vendor/@vfrey/contracts/dist/domain/similarity.js +100 -0
  284. package/vendor/@vfrey/contracts/dist/domain/templates.d.ts +18 -0
  285. package/vendor/@vfrey/contracts/dist/domain/templates.js +67 -0
  286. package/vendor/@vfrey/contracts/dist/domain/tiktok-chunks.d.ts +19 -0
  287. package/vendor/@vfrey/contracts/dist/domain/tiktok-chunks.js +52 -0
  288. package/vendor/@vfrey/contracts/dist/domain/topics.d.ts +78 -0
  289. package/vendor/@vfrey/contracts/dist/domain/topics.js +4 -0
  290. package/vendor/@vfrey/contracts/dist/domain/types.d.ts +216 -0
  291. package/vendor/@vfrey/contracts/dist/domain/types.js +1 -0
  292. package/vendor/@vfrey/contracts/dist/domain/variation.d.ts +57 -0
  293. package/vendor/@vfrey/contracts/dist/domain/variation.js +278 -0
  294. package/vendor/@vfrey/contracts/dist/events.d.ts +20 -0
  295. package/vendor/@vfrey/contracts/dist/events.js +1 -0
  296. package/vendor/@vfrey/contracts/dist/http.d.ts +11 -0
  297. package/vendor/@vfrey/contracts/dist/http.js +37 -0
  298. package/vendor/@vfrey/contracts/dist/index.d.ts +22 -0
  299. package/vendor/@vfrey/contracts/dist/index.js +22 -0
  300. package/vendor/@vfrey/contracts/dist/log.d.ts +2 -0
  301. package/vendor/@vfrey/contracts/dist/log.js +10 -0
  302. package/vendor/@vfrey/contracts/dist/mail.d.ts +55 -0
  303. package/vendor/@vfrey/contracts/dist/mail.js +1 -0
  304. package/vendor/@vfrey/contracts/package.json +18 -0
  305. package/vendor/@vfrey/ingest/dist/index.d.ts +41 -0
  306. package/vendor/@vfrey/ingest/dist/index.js +221 -0
  307. package/vendor/@vfrey/ingest/dist/parse.d.ts +5 -0
  308. package/vendor/@vfrey/ingest/dist/parse.js +165 -0
  309. package/vendor/@vfrey/ingest/package.json +21 -0
  310. package/vendor/framey/LICENSE +190 -0
  311. package/vendor/framey/NOTICE +15 -0
  312. package/vendor/framey/SOURCE.json +24 -0
  313. package/vendor/framey/STUDIO-SOURCE.json +9 -0
  314. package/vendor/framey/bin/framey-localize-fonts.mjs +12 -0
  315. package/vendor/framey/bin/framey.mjs +16 -0
  316. package/vendor/framey/dist/add-VMJD5KBS.js +75 -0
  317. package/vendor/framey/dist/animatedGifPrep-QPKGJ6PM.js +43 -0
  318. package/vendor/framey/dist/api.d.ts +211 -0
  319. package/vendor/framey/dist/api.js +76 -0
  320. package/vendor/framey/dist/appHistory-WW56FZTT.js +31 -0
  321. package/vendor/framey/dist/asset-WQKGOELE.js +26 -0
  322. package/vendor/framey/dist/audio-RSJWVWRA.js +1936 -0
  323. package/vendor/framey/dist/batchRender-3HPICDCT.js +366 -0
  324. package/vendor/framey/dist/beats-HL2BDUZI.js +273 -0
  325. package/vendor/framey/dist/benchmark-RWCWHI5Z.js +230 -0
  326. package/vendor/framey/dist/browser-ECYET3Q2.js +206 -0
  327. package/vendor/framey/dist/bundleWithLocalizedFonts-ALYBQEQT.js +52 -0
  328. package/vendor/framey/dist/captureCompositionFrame-4DA6SEH2.js +94 -0
  329. package/vendor/framey/dist/catalog-5JVDEUWK.js +979 -0
  330. package/vendor/framey/dist/catch-up-NEBCL5NO.js +106 -0
  331. package/vendor/framey/dist/check-F5N544ML.js +111 -0
  332. package/vendor/framey/dist/checkBrowser-LZ66CMJG.js +110 -0
  333. package/vendor/framey/dist/chunk-2K7Z2HVP.js +372 -0
  334. package/vendor/framey/dist/chunk-2UZQHDM7.js +323 -0
  335. package/vendor/framey/dist/chunk-2X4PQEYM.js +34 -0
  336. package/vendor/framey/dist/chunk-3422KMTD.js +168 -0
  337. package/vendor/framey/dist/chunk-3FYD4YEK.js +72 -0
  338. package/vendor/framey/dist/chunk-3GXQPCJC.js +682 -0
  339. package/vendor/framey/dist/chunk-3LM6YPBR.js +179 -0
  340. package/vendor/framey/dist/chunk-442JNXRM.js +20 -0
  341. package/vendor/framey/dist/chunk-4BNN5FGX.js +182 -0
  342. package/vendor/framey/dist/chunk-4P7NX5BM.js +11532 -0
  343. package/vendor/framey/dist/chunk-4UMPIPCQ.js +88 -0
  344. package/vendor/framey/dist/chunk-4YYJOFKS.js +28 -0
  345. package/vendor/framey/dist/chunk-54HJ63Q2.js +29 -0
  346. package/vendor/framey/dist/chunk-5ETJX6WM.js +89 -0
  347. package/vendor/framey/dist/chunk-5SG54JRS.js +68 -0
  348. package/vendor/framey/dist/chunk-634HSJ3J.js +58 -0
  349. package/vendor/framey/dist/chunk-66LREXGS.js +48 -0
  350. package/vendor/framey/dist/chunk-6BEFEWFB.js +25 -0
  351. package/vendor/framey/dist/chunk-6BNTGNGD.js +286 -0
  352. package/vendor/framey/dist/chunk-6DDPOHMK.js +140 -0
  353. package/vendor/framey/dist/chunk-6GV5XSOR.js +214 -0
  354. package/vendor/framey/dist/chunk-6JR2XUEE.js +127 -0
  355. package/vendor/framey/dist/chunk-73QVATIM.js +362 -0
  356. package/vendor/framey/dist/chunk-7FMUTGOH.js +279 -0
  357. package/vendor/framey/dist/chunk-7JQFJ6JJ.js +291 -0
  358. package/vendor/framey/dist/chunk-AHPGAESX.js +113 -0
  359. package/vendor/framey/dist/chunk-AHX34EFP.js +15 -0
  360. package/vendor/framey/dist/chunk-AJ5CSAEZ.js +487 -0
  361. package/vendor/framey/dist/chunk-B7RJV74O.js +5047 -0
  362. package/vendor/framey/dist/chunk-BL4U6U52.js +254 -0
  363. package/vendor/framey/dist/chunk-BSQ7XANM.js +210 -0
  364. package/vendor/framey/dist/chunk-BZ56MTDX.js +6 -0
  365. package/vendor/framey/dist/chunk-C5BO7PMT.js +282 -0
  366. package/vendor/framey/dist/chunk-CFUCHO4T.js +358 -0
  367. package/vendor/framey/dist/chunk-CZGHFATT.js +31 -0
  368. package/vendor/framey/dist/chunk-DLA44CMS.js +5649 -0
  369. package/vendor/framey/dist/chunk-DPVXBVLL.js +31292 -0
  370. package/vendor/framey/dist/chunk-DQBY7FRR.js +397 -0
  371. package/vendor/framey/dist/chunk-EAMLBAHA.js +154 -0
  372. package/vendor/framey/dist/chunk-EOVYQ3IG.js +143 -0
  373. package/vendor/framey/dist/chunk-EVMY7IPL.js +612 -0
  374. package/vendor/framey/dist/chunk-FG4BBFZI.js +122 -0
  375. package/vendor/framey/dist/chunk-FI4I7CME.js +56 -0
  376. package/vendor/framey/dist/chunk-FLREUEHW.js +2081 -0
  377. package/vendor/framey/dist/chunk-FUFP7A7S.js +1126 -0
  378. package/vendor/framey/dist/chunk-FV3PIKGH.js +56 -0
  379. package/vendor/framey/dist/chunk-FXMKHHPO.js +123 -0
  380. package/vendor/framey/dist/chunk-GGZAM5VH.js +1187 -0
  381. package/vendor/framey/dist/chunk-GKZEVJIB.js +39 -0
  382. package/vendor/framey/dist/chunk-GMECXFAN.js +57 -0
  383. package/vendor/framey/dist/chunk-GNEFDLFB.js +621 -0
  384. package/vendor/framey/dist/chunk-GPMLSOEG.js +19274 -0
  385. package/vendor/framey/dist/chunk-GPYVT5EE.js +59 -0
  386. package/vendor/framey/dist/chunk-H6IEMZEV.js +40 -0
  387. package/vendor/framey/dist/chunk-HEFQCIXR.js +170 -0
  388. package/vendor/framey/dist/chunk-HFQ4CWHA.js +118 -0
  389. package/vendor/framey/dist/chunk-HPDTCOKE.js +304 -0
  390. package/vendor/framey/dist/chunk-HTUHQKDB.js +548 -0
  391. package/vendor/framey/dist/chunk-HU2SPFNG.js +103 -0
  392. package/vendor/framey/dist/chunk-HVW7ABXD.js +19 -0
  393. package/vendor/framey/dist/chunk-IG7NDKXO.js +26 -0
  394. package/vendor/framey/dist/chunk-II3J3WXK.js +246 -0
  395. package/vendor/framey/dist/chunk-IR35BCLK.js +13 -0
  396. package/vendor/framey/dist/chunk-ISN5GRYI.js +20 -0
  397. package/vendor/framey/dist/chunk-IUONB55J.js +669 -0
  398. package/vendor/framey/dist/chunk-IVSUQR2B.js +161 -0
  399. package/vendor/framey/dist/chunk-IXU6PQSW.js +259 -0
  400. package/vendor/framey/dist/chunk-IZCW2NKT.js +29 -0
  401. package/vendor/framey/dist/chunk-JGXJU5PF.js +68 -0
  402. package/vendor/framey/dist/chunk-JHOCK3DG.js +27 -0
  403. package/vendor/framey/dist/chunk-JVBRJMOP.js +121 -0
  404. package/vendor/framey/dist/chunk-K4MJX7SR.js +17 -0
  405. package/vendor/framey/dist/chunk-K4WKGXRC.js +18 -0
  406. package/vendor/framey/dist/chunk-KGPLIH7B.js +127 -0
  407. package/vendor/framey/dist/chunk-KLIKCX6Q.js +63 -0
  408. package/vendor/framey/dist/chunk-KXTKKBOC.js +61 -0
  409. package/vendor/framey/dist/chunk-KZI44OPA.js +185 -0
  410. package/vendor/framey/dist/chunk-LEKZNRKI.js +45 -0
  411. package/vendor/framey/dist/chunk-LFGJWOVA.js +61 -0
  412. package/vendor/framey/dist/chunk-LLMSX6V6.js +17 -0
  413. package/vendor/framey/dist/chunk-LTGTN2TH.js +195 -0
  414. package/vendor/framey/dist/chunk-M2WBOQ2Q.js +54 -0
  415. package/vendor/framey/dist/chunk-MI5FTLYK.js +399 -0
  416. package/vendor/framey/dist/chunk-MLQP2QUF.js +1809 -0
  417. package/vendor/framey/dist/chunk-MMQ52NIZ.js +956 -0
  418. package/vendor/framey/dist/chunk-ND46XKK3.js +742 -0
  419. package/vendor/framey/dist/chunk-NYEXNSHM.js +504 -0
  420. package/vendor/framey/dist/chunk-O4YRB27L.js +57 -0
  421. package/vendor/framey/dist/chunk-O7BGSQ5F.js +92 -0
  422. package/vendor/framey/dist/chunk-OFQWTKPW.js +117 -0
  423. package/vendor/framey/dist/chunk-P5ZBLXJ6.js +91 -0
  424. package/vendor/framey/dist/chunk-P6LJ4Y32.js +4662 -0
  425. package/vendor/framey/dist/chunk-PKZ7RK5N.js +281 -0
  426. package/vendor/framey/dist/chunk-PURHAXFL.js +15 -0
  427. package/vendor/framey/dist/chunk-Q5X2B5WT.js +29 -0
  428. package/vendor/framey/dist/chunk-QAMKOG67.js +248 -0
  429. package/vendor/framey/dist/chunk-R6YBK4XX.js +514 -0
  430. package/vendor/framey/dist/chunk-RGLQURIX.js +2997 -0
  431. package/vendor/framey/dist/chunk-RNV3DM4S.js +3573 -0
  432. package/vendor/framey/dist/chunk-ROEW726S.js +4638 -0
  433. package/vendor/framey/dist/chunk-RQVDTPGD.js +6 -0
  434. package/vendor/framey/dist/chunk-RVZHGOGR.js +26 -0
  435. package/vendor/framey/dist/chunk-RXATE6FZ.js +385 -0
  436. package/vendor/framey/dist/chunk-S3S4TGEH.js +424 -0
  437. package/vendor/framey/dist/chunk-SDYDNHEE.js +11202 -0
  438. package/vendor/framey/dist/chunk-SSWBU67I.js +173 -0
  439. package/vendor/framey/dist/chunk-ST35C4K6.js +28 -0
  440. package/vendor/framey/dist/chunk-SZNTYTIV.js +990 -0
  441. package/vendor/framey/dist/chunk-T2EUWCJE.js +29 -0
  442. package/vendor/framey/dist/chunk-T6AGUTZJ.js +444 -0
  443. package/vendor/framey/dist/chunk-THJALHFJ.js +11131 -0
  444. package/vendor/framey/dist/chunk-TIQB63MX.js +18128 -0
  445. package/vendor/framey/dist/chunk-U242RTCT.js +134 -0
  446. package/vendor/framey/dist/chunk-UJZREAV6.js +3901 -0
  447. package/vendor/framey/dist/chunk-UMD3V7CU.js +264 -0
  448. package/vendor/framey/dist/chunk-UOFAE5RF.js +42 -0
  449. package/vendor/framey/dist/chunk-UTEIUGAZ.js +29 -0
  450. package/vendor/framey/dist/chunk-VAS6HQX3.js +138 -0
  451. package/vendor/framey/dist/chunk-VC76R6W2.js +27 -0
  452. package/vendor/framey/dist/chunk-VFZ7W5W7.js +226 -0
  453. package/vendor/framey/dist/chunk-VHNLZAE2.js +348 -0
  454. package/vendor/framey/dist/chunk-VMC4WSH5.js +278 -0
  455. package/vendor/framey/dist/chunk-VORQIH64.js +108 -0
  456. package/vendor/framey/dist/chunk-VTXFRIHO.js +186 -0
  457. package/vendor/framey/dist/chunk-W2LGJSEN.js +182 -0
  458. package/vendor/framey/dist/chunk-WKBM7NDG.js +32 -0
  459. package/vendor/framey/dist/chunk-WMQNIQDJ.js +2057 -0
  460. package/vendor/framey/dist/chunk-WN4XZC5F.js +504 -0
  461. package/vendor/framey/dist/chunk-WQ6ZXHFQ.js +133 -0
  462. package/vendor/framey/dist/chunk-X7HBSTCZ.js +53 -0
  463. package/vendor/framey/dist/chunk-X7MTZ4T2.js +6 -0
  464. package/vendor/framey/dist/chunk-XPTOJZJF.js +308 -0
  465. package/vendor/framey/dist/chunk-XT6MXC7A.js +6 -0
  466. package/vendor/framey/dist/chunk-XT7X6QCP.js +6 -0
  467. package/vendor/framey/dist/chunk-XTMDWNEC.js +2820 -0
  468. package/vendor/framey/dist/chunk-XUHOIDKG.js +6 -0
  469. package/vendor/framey/dist/chunk-XX7LTW5Y.js +138 -0
  470. package/vendor/framey/dist/chunk-Y3YGTZEG.js +382 -0
  471. package/vendor/framey/dist/chunk-Y5SBSGN7.js +346 -0
  472. package/vendor/framey/dist/chunk-YCMVI4MZ.js +625 -0
  473. package/vendor/framey/dist/chunk-YOR4CTCE.js +261 -0
  474. package/vendor/framey/dist/chunk-YPRBGITP.js +2748 -0
  475. package/vendor/framey/dist/chunk-ZKDMAOFU.js +32 -0
  476. package/vendor/framey/dist/chunk-ZW6DURPF.js +1318 -0
  477. package/vendor/framey/dist/clean-ZJOGDKDM.js +336 -0
  478. package/vendor/framey/dist/cli.js +439 -0
  479. package/vendor/framey/dist/cloudrun-HGJ3B6ZV.js +767 -0
  480. package/vendor/framey/dist/commands/contrast-audit.browser.js +643 -0
  481. package/vendor/framey/dist/commands/layout-audit.browser.js +2914 -0
  482. package/vendor/framey/dist/commands/motion-sample.browser.js +74 -0
  483. package/vendor/framey/dist/commands/motion-signature.browser.js +475 -0
  484. package/vendor/framey/dist/compare-W7UY3BAZ.js +463 -0
  485. package/vendor/framey/dist/compiler-TM3W6GYF.js +136 -0
  486. package/vendor/framey/dist/component-QF5VGYMU.js +176 -0
  487. package/vendor/framey/dist/compositions-XT3PMNDF.js +238 -0
  488. package/vendor/framey/dist/contactSheet-7W6GYHBQ.js +24 -0
  489. package/vendor/framey/dist/deploy-6FJPWKF2.js +299 -0
  490. package/vendor/framey/dist/destroy-HE2RIPKZ.js +57 -0
  491. package/vendor/framey/dist/deterministicFonts-RADD3BSX.js +50 -0
  492. package/vendor/framey/dist/dist-4S2FGGVY.js +422 -0
  493. package/vendor/framey/dist/dist-BE4B2EZE.js +32 -0
  494. package/vendor/framey/dist/dist-EWXJGN63.js +129 -0
  495. package/vendor/framey/dist/docs-VKIRFEEX.js +131 -0
  496. package/vendor/framey/dist/doctor-2UNJ2G7U.js +74 -0
  497. package/vendor/framey/dist/dom-37K6YOH4.js +14 -0
  498. package/vendor/framey/dist/dotEnv-7PZQTVIM.js +54 -0
  499. package/vendor/framey/dist/events-ACY3P2PA.js +76 -0
  500. package/vendor/framey/dist/events-IHB3B2MK.js +69 -0
  501. package/vendor/framey/dist/ffmpeg-DR5LGGSK.js +63 -0
  502. package/vendor/framey/dist/figma-BODVUPHB.js +63 -0
  503. package/vendor/framey/dist/fontData.generated-NXAOI4GH.js +13 -0
  504. package/vendor/framey/dist/fontLocalizeCli.js +142 -0
  505. package/vendor/framey/dist/gpuPolicy-XLEXORZY.js +49 -0
  506. package/vendor/framey/dist/grade-compare-GAXFQ74C.js +661 -0
  507. package/vendor/framey/dist/gsapParser-IEUSLYDJ.js +2885 -0
  508. package/vendor/framey/dist/gsapParserAcorn-LZJKCFQI.js +30 -0
  509. package/vendor/framey/dist/help-VCB5X2HS.js +184 -0
  510. package/vendor/framey/dist/history-ENGZEVFT.js +471 -0
  511. package/vendor/framey/dist/hyperframe.manifest.json +22 -0
  512. package/vendor/framey/dist/hyperframe.runtime.iife.js +1132 -0
  513. package/vendor/framey/dist/hyperframes-player.global.js +478 -0
  514. package/vendor/framey/dist/hyperframes-slideshow.global.js +148 -0
  515. package/vendor/framey/dist/info-WPCJHBMU.js +182 -0
  516. package/vendor/framey/dist/init-G4WGLYGD.js +1176 -0
  517. package/vendor/framey/dist/inspect-5IQ6CNEQ.js +106 -0
  518. package/vendor/framey/dist/installer-ML4HWUV7.js +40 -0
  519. package/vendor/framey/dist/keyframes-Q6XJO7VW.js +2755 -0
  520. package/vendor/framey/dist/lambda-A7JC74JF.js +468 -0
  521. package/vendor/framey/dist/layout-YET6XNXA.js +95 -0
  522. package/vendor/framey/dist/lint-I7FZN2VJ.js +162 -0
  523. package/vendor/framey/dist/lintProject-PDB3UN72.js +30 -0
  524. package/vendor/framey/dist/manager-CD6LJ7ZN.js +45 -0
  525. package/vendor/framey/dist/manager-WA4F2GST.js +67 -0
  526. package/vendor/framey/dist/media-treatment-BXYS4WVA.js +1030 -0
  527. package/vendor/framey/dist/media-use-LWH3MANK.js +113 -0
  528. package/vendor/framey/dist/models-K363FYM4.js +195 -0
  529. package/vendor/framey/dist/motionShot-GPAVWXLH.js +640 -0
  530. package/vendor/framey/dist/normalize-FASIPBUM.js +24 -0
  531. package/vendor/framey/dist/normalize-audio-VDWYRHN6.js +268 -0
  532. package/vendor/framey/dist/open-E3TB6P6J.js +128 -0
  533. package/vendor/framey/dist/parakeetRunner-UP34UOI2.js +58 -0
  534. package/vendor/framey/dist/pipeline-VXIUYEBU.js +64 -0
  535. package/vendor/framey/dist/play-BUTDQ4DV.js +353 -0
  536. package/vendor/framey/dist/policies-OLDYGFK5.js +309 -0
  537. package/vendor/framey/dist/preflight-2KZ2GWAZ.js +55 -0
  538. package/vendor/framey/dist/present-GXYSM7NX.js +353 -0
  539. package/vendor/framey/dist/preview-EKZ33JOJ.js +1910 -0
  540. package/vendor/framey/dist/producer-SKU5EV3J.js +13 -0
  541. package/vendor/framey/dist/progress-ZWB4Z4ET.js +76 -0
  542. package/vendor/framey/dist/projectConfig-3MZAYVTS.js +60 -0
  543. package/vendor/framey/dist/publication-DTKWNF74.js +33 -0
  544. package/vendor/framey/dist/remove-background-OY5L7XN2.js +265 -0
  545. package/vendor/framey/dist/render-CDV4GDFX.js +187 -0
  546. package/vendor/framey/dist/render-YE5YAMOD.js +2620 -0
  547. package/vendor/framey/dist/render-batch-ZAMFPCLL.js +274 -0
  548. package/vendor/framey/dist/renderSetupWorker.js +120 -0
  549. package/vendor/framey/dist/resolver-D5BIPSEW.js +42 -0
  550. package/vendor/framey/dist/runtimeVersion.js +18 -0
  551. package/vendor/framey/dist/sam-WFZXI4F7.js +23 -0
  552. package/vendor/framey/dist/shaderTransitionWorker.js +44 -0
  553. package/vendor/framey/dist/sherpa-RQWLV7I3.js +79 -0
  554. package/vendor/framey/dist/sherpaWorker.js +84 -0
  555. package/vendor/framey/dist/sites-AMOVABTK.js +54 -0
  556. package/vendor/framey/dist/skill-ZHVLH7KX.js +15 -0
  557. package/vendor/framey/dist/slideshow-SJGK4RQS.js +24 -0
  558. package/vendor/framey/dist/snapshot-TUXL6LBQ.js +1515 -0
  559. package/vendor/framey/dist/src-JHQGB2S6.js +215 -0
  560. package/vendor/framey/dist/src-Z5PGEMXB.js +532 -0
  561. package/vendor/framey/dist/state-B6H3WHCF.js +28 -0
  562. package/vendor/framey/dist/staticProjectServer-DFVZOUGI.js +63 -0
  563. package/vendor/framey/dist/studio/assets/hyperframes-player-DLaKjw0L.js +478 -0
  564. package/vendor/framey/dist/studio/assets/index-BX3KHhGX.js +71 -0
  565. package/vendor/framey/dist/studio/assets/index-CYbvOVp-.css +1 -0
  566. package/vendor/framey/dist/studio/assets/index-Dp3QEUGL.js +402 -0
  567. package/vendor/framey/dist/studio/assets/index-qrTAXbqc.js +531 -0
  568. package/vendor/framey/dist/studio/favicon.svg +14 -0
  569. package/vendor/framey/dist/studio/icons/timeline/audio.svg +7 -0
  570. package/vendor/framey/dist/studio/icons/timeline/captions.svg +5 -0
  571. package/vendor/framey/dist/studio/icons/timeline/composition.svg +12 -0
  572. package/vendor/framey/dist/studio/icons/timeline/image.svg +18 -0
  573. package/vendor/framey/dist/studio/icons/timeline/music.svg +10 -0
  574. package/vendor/framey/dist/studio/icons/timeline/text.svg +3 -0
  575. package/vendor/framey/dist/studio/index.html +23 -0
  576. package/vendor/framey/dist/studio-api-2NECGRMB.js +179 -0
  577. package/vendor/framey/dist/studioSelectionClient-IUUPKUHZ.js +25 -0
  578. package/vendor/framey/dist/studioServer-N2OPUMOQ.js +1569 -0
  579. package/vendor/framey/dist/synthesize-PKTB5ATG.js +183 -0
  580. package/vendor/framey/dist/system-7F3UEHQT.js +27 -0
  581. package/vendor/framey/dist/telemetry-DQGPEKM6.js +145 -0
  582. package/vendor/framey/dist/telemetry-UN55N6U6.js +77 -0
  583. package/vendor/framey/dist/timeline-LHUUMK63.js +1295 -0
  584. package/vendor/framey/dist/tokens-OLECWDFK.js +92 -0
  585. package/vendor/framey/dist/transcribe-O7HQKMIV.js +331 -0
  586. package/vendor/framey/dist/transcribe-UJESPBT5.js +76 -0
  587. package/vendor/framey/dist/tts-YCCQHZD4.js +234 -0
  588. package/vendor/framey/dist/urlDownloader-H2LXLG4U.js +48 -0
  589. package/vendor/framey/dist/usage-HG4WMM7A.js +2167 -0
  590. package/vendor/framey/dist/validate-F2XMN6EV.js +87 -0
  591. package/vendor/framey/dist/vendor/npm/gsap@3.15.0/GSAP-LICENSE.txt +8 -0
  592. package/vendor/framey/dist/vendor/npm/gsap@3.15.0/dist/CustomEase.min.js +11 -0
  593. package/vendor/framey/dist/vendor/npm/gsap@3.15.0/dist/MotionPathPlugin.min.js +11 -0
  594. package/vendor/framey/dist/vendor/npm/gsap@3.15.0/dist/gsap.min.js +11 -0
  595. package/vendor/framey/dist/warmCheck.js +220 -0
  596. package/vendor/framey/dist/warmCheckClient.js +139 -0
  597. package/vendor/framey/dist/workspaceBuildCheck-XYD2YTFW.js +133 -0
  598. package/vendor/framey-player/LICENSE +190 -0
  599. package/vendor/framey-player/NOTICE +16 -0
  600. package/vendor/framey-player/SOURCE.json +6 -0
  601. package/vendor/framey-player/hyperframes-player.global.js +478 -0
  602. package/vendor/framey.pin +1 -0
  603. package/bin/index.js +0 -3
@@ -0,0 +1,41 @@
1
+ import { mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
2
+ import path from "node:path";
3
+
4
+ export interface ProjectTickState {
5
+ /** ISO time of the last cycle that ticked this project. */
6
+ lastTick: string | null;
7
+ /** ticked, not due, busy, missing, error */
8
+ outcome: string;
9
+ detail?: string;
10
+ /** Jobs waiting for review in this project at the last cycle. */
11
+ waiting?: number;
12
+ }
13
+
14
+ export interface WorkerState {
15
+ pid: number | null;
16
+ lastCycle: string | null;
17
+ projects: Record<string, ProjectTickState>;
18
+ }
19
+
20
+ export const stateFile = (machineHome: string): string => path.join(machineHome, "worker-state.json");
21
+
22
+ export function readState(machineHome: string): WorkerState {
23
+ try {
24
+ const parsed = JSON.parse(readFileSync(stateFile(machineHome), "utf8")) as Partial<WorkerState>;
25
+ return { pid: typeof parsed.pid === "number" ? parsed.pid : null, lastCycle: parsed.lastCycle ?? null, projects: parsed.projects ?? {} };
26
+ } catch {
27
+ return { pid: null, lastCycle: null, projects: {} };
28
+ }
29
+ }
30
+
31
+ export function writeState(machineHome: string, state: WorkerState): void {
32
+ try {
33
+ mkdirSync(machineHome, { recursive: true });
34
+ const file = stateFile(machineHome);
35
+ const tmp = `${file}.${process.pid}.tmp`;
36
+ writeFileSync(tmp, JSON.stringify(state, null, 2) + "\n");
37
+ renameSync(tmp, file);
38
+ } catch {
39
+ /* the state is for `service status` only */
40
+ }
41
+ }
@@ -0,0 +1,90 @@
1
+ import path from "node:path";
2
+
3
+ export const LAUNCHD_LABEL = "ai.vfrey.worker";
4
+ export const SYSTEMD_UNIT = "vfrey-worker.service";
5
+
6
+ export interface UnitInput {
7
+ node: string;
8
+ bin: string;
9
+ machineHome: string;
10
+ /** Environment the worker needs (PATH, HOME, voice and Chrome settings). */
11
+ env: Record<string, string>;
12
+ logFile: string;
13
+ }
14
+
15
+ /** The environment a background job does not inherit but a render and a voice need. Secrets are not here: each project's .env holds them. */
16
+ const KEPT_ENV = ["PATH", "HOME", "LANG", "VFREY_HOME", "BROWSER_EXECUTABLE", "PRODUCER_HEADLESS_SHELL_PATH", "HYPERFRAMES_BIN", "PRODUCT_HOME"];
17
+
18
+ export function serviceEnv(env: Record<string, string | undefined>): Record<string, string> {
19
+ const kept: Record<string, string> = {};
20
+ for (const name of KEPT_ENV) {
21
+ const value = env[name];
22
+ if (value) kept[name] = value;
23
+ }
24
+ return kept;
25
+ }
26
+
27
+ const xml = (text: string): string => text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
28
+
29
+ export const plistPath = (homedir: string): string => path.join(homedir, "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
30
+ export const unitPath = (homedir: string): string => path.join(homedir, ".config", "systemd", "user", SYSTEMD_UNIT);
31
+
32
+ export function renderPlist(input: UnitInput): string {
33
+ const args = [input.node, input.bin, "worker", "--all-projects"].map((item) => ` <string>${xml(item)}</string>`).join("\n");
34
+ const env = Object.entries(input.env)
35
+ .map(([key, value]) => ` <key>${xml(key)}</key>\n <string>${xml(value)}</string>`)
36
+ .join("\n");
37
+ return `<?xml version="1.0" encoding="UTF-8"?>
38
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
39
+ <plist version="1.0">
40
+ <dict>
41
+ <key>Label</key>
42
+ <string>${LAUNCHD_LABEL}</string>
43
+ <key>ProgramArguments</key>
44
+ <array>
45
+ ${args}
46
+ </array>
47
+ <key>WorkingDirectory</key>
48
+ <string>${xml(input.machineHome)}</string>
49
+ <key>EnvironmentVariables</key>
50
+ <dict>
51
+ ${env}
52
+ </dict>
53
+ <key>RunAtLoad</key>
54
+ <true/>
55
+ <key>KeepAlive</key>
56
+ <true/>
57
+ <key>ThrottleInterval</key>
58
+ <integer>30</integer>
59
+ <key>StandardOutPath</key>
60
+ <string>${xml(input.logFile)}</string>
61
+ <key>StandardErrorPath</key>
62
+ <string>${xml(input.logFile)}</string>
63
+ </dict>
64
+ </plist>
65
+ `;
66
+ }
67
+
68
+ const quote = (text: string): string => `"${text.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
69
+
70
+ export function renderUnit(input: UnitInput): string {
71
+ const env = Object.entries(input.env)
72
+ .map(([key, value]) => `Environment=${quote(`${key}=${value}`)}`)
73
+ .join("\n");
74
+ return `[Unit]
75
+ Description=vfrey background worker (all projects)
76
+
77
+ [Service]
78
+ Type=simple
79
+ ExecStart=${quote(input.node)} ${quote(input.bin)} worker --all-projects
80
+ WorkingDirectory=${input.machineHome}
81
+ ${env}
82
+ Restart=always
83
+ RestartSec=30
84
+ StandardOutput=append:${input.logFile}
85
+ StandardError=append:${input.logFile}
86
+
87
+ [Install]
88
+ WantedBy=default.target
89
+ `;
90
+ }
@@ -0,0 +1,51 @@
1
+ import { createFileLog, workerLogPath } from "./log-file.js";
2
+ import { runCycle, spawnProjectTick } from "./all-projects.js";
3
+ import { readState, writeState } from "./state.js";
4
+
5
+ export const CYCLE_MS = 60_000;
6
+
7
+ /** `vfrey worker --all-projects [--once] [--dry-run]`: ticks every project in the machine index each cycle. */
8
+ export async function runAllProjectsWorker(options: {
9
+ machineHome: string;
10
+ env: Record<string, string | undefined>;
11
+ dryRun: boolean;
12
+ once: boolean;
13
+ echo: boolean;
14
+ stderr: (text: string) => void;
15
+ }): Promise<number> {
16
+ const log = createFileLog(workerLogPath(options.machineHome), { echo: options.echo ? (line) => options.stderr(line) : undefined });
17
+ const interval = Number(options.env.VFREY_WORKER_INTERVAL_MS) > 0 ? Number(options.env.VFREY_WORKER_INTERVAL_MS) : CYCLE_MS;
18
+ const spawned = spawnProjectTick(options.machineHome, options.env, options.dryRun ? ["--dry-run"] : []);
19
+ let stopped = false;
20
+ let wake: (() => void) | null = null;
21
+ const stop = (signal: NodeJS.Signals) => {
22
+ if (stopped) return;
23
+ stopped = true;
24
+ log.write("info", "worker stopping", { signal });
25
+ spawned.stopChildren(signal);
26
+ wake?.();
27
+ };
28
+ process.on("SIGINT", () => stop("SIGINT"));
29
+ process.on("SIGTERM", () => stop("SIGTERM"));
30
+ log.write("info", "worker started", { allProjects: true, dryRun: options.dryRun, pid: process.pid });
31
+ while (!stopped) {
32
+ try {
33
+ await runCycle({ machineHome: options.machineHome, now: () => new Date(), log, tickProject: spawned.run });
34
+ } catch (error) {
35
+ // One bad cycle must not stop the service; launchd would only restart it into the same state.
36
+ log.write("error", "cycle failed", { message: error instanceof Error ? error.message : String(error) });
37
+ }
38
+ if (options.once || stopped) break;
39
+ await new Promise<void>((resolve) => {
40
+ const timer = setTimeout(resolve, interval);
41
+ wake = () => {
42
+ clearTimeout(timer);
43
+ resolve();
44
+ };
45
+ });
46
+ }
47
+ const state = readState(options.machineHome);
48
+ writeState(options.machineHome, { ...state, pid: null });
49
+ log.write("info", "worker stopped", {});
50
+ return 0;
51
+ }
@@ -0,0 +1,74 @@
1
+ import os from "node:os";
2
+ import path from "node:path";
3
+
4
+ type Env = Record<string, string | undefined>;
5
+
6
+ export const SKILL_NAME = "vfrey";
7
+
8
+ export const AGENT_IDS = ["claude", "codex", "grok", "cursor", "gemini"] as const;
9
+ export type AgentId = (typeof AGENT_IDS)[number];
10
+
11
+ export interface SkillAgent {
12
+ id: AgentId;
13
+ label: string;
14
+ /** Personal skills folder, relative to the home folder. */
15
+ personal: readonly string[];
16
+ /** Project skills folder, relative to the project folder. */
17
+ project: readonly string[];
18
+ /** What the person does after an install so the agent sees the skill. */
19
+ nextStep: string;
20
+ }
21
+
22
+ /** Where each coding agent looks for skills. The one table every skill command and the future project scaffolder read. */
23
+ export const SKILL_AGENTS: Record<AgentId, SkillAgent> = {
24
+ claude: { id: "claude", label: "Claude Code", personal: [".claude", "skills"], project: [".claude", "skills"], nextStep: "Restart Claude Code, then ask it for a video." },
25
+ codex: { id: "codex", label: "Codex", personal: [".agents", "skills"], project: [".agents", "skills"], nextStep: "Codex picks it up live. Ask it for a video." },
26
+ grok: { id: "grok", label: "Grok", personal: [".grok", "skills"], project: [".grok", "skills"], nextStep: "Grok picks it up live. Ask it for a video." },
27
+ cursor: { id: "cursor", label: "Cursor", personal: [".cursor", "skills"], project: [".cursor", "skills"], nextStep: "Restart Cursor, then ask it for a video." },
28
+ gemini: { id: "gemini", label: "Gemini CLI", personal: [".gemini", "skills"], project: [".gemini", "skills"], nextStep: "In Gemini CLI run /skills reload, then ask it for a video." },
29
+ };
30
+
31
+ export const CUSTOM_NEXT_STEP = "Restart or reload your agent, then ask it for a video.";
32
+
33
+ export function isAgentId(value: string): value is AgentId {
34
+ return (AGENT_IDS as readonly string[]).includes(value);
35
+ }
36
+
37
+ /** The skills folder (the parent of the vfrey folder) for one agent. CLAUDE_SKILLS_DIR still overrides Claude Code's personal folder. */
38
+ export function agentSkillsDir(agent: AgentId, env: Env, cwd: string, project: boolean, homedir: string = os.homedir()): string {
39
+ if (project) return path.join(cwd, ...SKILL_AGENTS[agent].project);
40
+ if (agent === "claude" && env.CLAUDE_SKILLS_DIR) return path.resolve(env.CLAUDE_SKILLS_DIR);
41
+ return path.join(homedir, ...SKILL_AGENTS[agent].personal);
42
+ }
43
+
44
+ /** The folder a skill named vfrey lives in for one agent. */
45
+ export function agentSkillTarget(agent: AgentId, env: Env, cwd: string, project: boolean, homedir: string = os.homedir()): string {
46
+ return path.join(agentSkillsDir(agent, env, cwd, project, homedir), SKILL_NAME);
47
+ }
48
+
49
+ export interface SkillLocation {
50
+ agent: AgentId;
51
+ label: string;
52
+ dir: string;
53
+ kind: "project" | "personal";
54
+ }
55
+
56
+ /** Every known place a vfrey skill can live: project copies first (they win in this folder), then personal. Same folder listed once. */
57
+ export function skillLocations(env: Env, cwd: string, homedir: string = os.homedir()): SkillLocation[] {
58
+ const seen = new Set<string>();
59
+ const out: SkillLocation[] = [];
60
+ for (const kind of ["project", "personal"] as const) {
61
+ for (const id of AGENT_IDS) {
62
+ const dir = agentSkillTarget(id, env, cwd, kind === "project", homedir);
63
+ if (seen.has(dir)) continue;
64
+ seen.add(dir);
65
+ out.push({ agent: id, label: SKILL_AGENTS[id].label, dir, kind });
66
+ }
67
+ }
68
+ return out;
69
+ }
70
+
71
+ /** The install command that refreshes one copy. */
72
+ export function installCommand(agent: AgentId, kind: "project" | "personal"): string {
73
+ return `npx vfrey skill install${agent === "claude" ? "" : ` --agent ${agent}`}${kind === "project" ? " --project" : ""}`;
74
+ }
@@ -0,0 +1,334 @@
1
+ import { createHash } from "node:crypto";
2
+ import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+
7
+ import {
8
+ AGENT_IDS,
9
+ CUSTOM_NEXT_STEP,
10
+ SKILL_AGENTS,
11
+ SKILL_NAME,
12
+ agentSkillTarget,
13
+ installCommand,
14
+ isAgentId,
15
+ skillLocations,
16
+ type AgentId,
17
+ type SkillLocation,
18
+ } from "./skill-agents.js";
19
+
20
+ type Env = Record<string, string | undefined>;
21
+
22
+ export { SKILL_NAME, skillLocations };
23
+ const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
24
+ const SOURCE_DIR = path.join(packageRoot, "skills", "vfrey-product");
25
+
26
+ export interface SkillIo {
27
+ env: Env;
28
+ cwd: string;
29
+ homedir?: string;
30
+ stdout: (text: string) => void;
31
+ stderr: (text: string) => void;
32
+ }
33
+
34
+ /** Where Claude Code looks for a skill named vfrey. */
35
+ export function skillTarget(env: Env, cwd: string, project: boolean, homedir: string = os.homedir()): string {
36
+ return agentSkillTarget("claude", env, cwd, project, homedir);
37
+ }
38
+
39
+ /** The copy's frontmatter name must match its folder. */
40
+ function withFolderName(text: string): string {
41
+ return text.replace(/^(---\r?\n(?:(?!name:)[^\n]*\n)*)name:[^\r\n]*/, `$1name: ${SKILL_NAME}`);
42
+ }
43
+
44
+ function walk(dir: string, rel = ""): string[] {
45
+ const out: string[] = [];
46
+ for (const entry of readdirSync(path.join(dir, rel), { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
47
+ const next = path.join(rel, entry.name);
48
+ if (entry.isDirectory()) out.push(...walk(dir, next));
49
+ else if (entry.isFile()) out.push(next);
50
+ }
51
+ return out;
52
+ }
53
+
54
+ export const STAMP_FILE = ".vfrey-skill.json";
55
+
56
+ export interface SkillStamp {
57
+ version: string;
58
+ hash: string;
59
+ }
60
+
61
+ /** The files of the package's skill, with the folder-name rule applied and no stamp yet. */
62
+ function sourceFiles(): Map<string, Buffer> {
63
+ const files = new Map<string, Buffer>();
64
+ for (const rel of walk(SOURCE_DIR)) {
65
+ const data = readFileSync(path.join(SOURCE_DIR, rel));
66
+ files.set(rel, rel === "SKILL.md" ? Buffer.from(withFolderName(data.toString("utf8"))) : data);
67
+ }
68
+ return files;
69
+ }
70
+
71
+ function hashFiles(files: Map<string, Buffer>): string {
72
+ const hash = createHash("sha256");
73
+ for (const rel of [...files.keys()].sort()) {
74
+ hash.update(rel.split(path.sep).join("/"));
75
+ hash.update("\0");
76
+ hash.update(files.get(rel)!);
77
+ hash.update("\0");
78
+ }
79
+ return hash.digest("hex").slice(0, 12);
80
+ }
81
+
82
+ export function packageVersion(): string {
83
+ try {
84
+ return String(JSON.parse(readFileSync(path.join(packageRoot, "package.json"), "utf8")).version ?? "0.0.0");
85
+ } catch {
86
+ return "0.0.0";
87
+ }
88
+ }
89
+
90
+ let cachedStamp: SkillStamp | undefined;
91
+ /** The version and content hash of the skill this package ships. */
92
+ export function packageSkillStamp(): SkillStamp {
93
+ cachedStamp ??= { version: packageVersion(), hash: hashFiles(sourceFiles()) };
94
+ return cachedStamp;
95
+ }
96
+
97
+ function stampLine(stamp: SkillStamp): string {
98
+ return `<!-- vfrey-skill ${stamp.version} ${stamp.hash} -->`;
99
+ }
100
+
101
+ /** Inserts the human-readable stamp right after the frontmatter. */
102
+ function withStampLine(text: string, stamp: SkillStamp): string {
103
+ const line = stampLine(stamp);
104
+ const match = /^---\r?\n[\s\S]*?\r?\n---[ \t]*\r?\n/.exec(text);
105
+ if (!match) return `${line}\n${text}`;
106
+ return `${text.slice(0, match[0].length)}${line}\n${text.slice(match[0].length)}`;
107
+ }
108
+
109
+ /** The files the install writes, as relative path to content. */
110
+ function expectedFiles(): Map<string, Buffer> {
111
+ const files = sourceFiles();
112
+ const stamp = packageSkillStamp();
113
+ files.set("SKILL.md", Buffer.from(withStampLine(files.get("SKILL.md")!.toString("utf8"), stamp)));
114
+ files.set(STAMP_FILE, Buffer.from(JSON.stringify(stamp, null, 2) + "\n"));
115
+ return files;
116
+ }
117
+
118
+ /**
119
+ * Writes this package's skill into `dir` (a folder named vfrey) only when `dir` does not exist, with the same stamp and
120
+ * folder-name rule as `skill install`. An existing folder is left alone: `skill install` and doctor handle stale copies.
121
+ */
122
+ export function installSkillIfMissing(dir: string): "written" | "kept" {
123
+ if (existsSync(dir)) return "kept";
124
+ for (const [rel, data] of expectedFiles()) {
125
+ const file = path.join(dir, rel);
126
+ mkdirSync(path.dirname(file), { recursive: true });
127
+ writeFileSync(file, data);
128
+ }
129
+ return "written";
130
+ }
131
+
132
+ export type InstalledSkill =
133
+ | { kind: "stamped"; version: string; hash: string }
134
+ | { kind: "unstamped" }
135
+ | { kind: "foreign" };
136
+
137
+ function readStamp(dir: string): SkillStamp | null {
138
+ try {
139
+ const parsed = JSON.parse(readFileSync(path.join(dir, STAMP_FILE), "utf8"));
140
+ if (typeof parsed?.version === "string" && typeof parsed?.hash === "string") return { version: parsed.version, hash: parsed.hash };
141
+ } catch {
142
+ /* no stamp */
143
+ }
144
+ return null;
145
+ }
146
+
147
+ /** True when SKILL.md's frontmatter says `name: vfrey`: how skills installed before the stamp are recognised. */
148
+ function hasVfreyName(dir: string): boolean {
149
+ try {
150
+ const front = /^---\r?\n([\s\S]*?)\r?\n---/.exec(readFileSync(path.join(dir, "SKILL.md"), "utf8"));
151
+ return Boolean(front && /^name:\s*["']?vfrey["']?\s*$/m.test(front[1]!));
152
+ } catch {
153
+ return false;
154
+ }
155
+ }
156
+
157
+ /** Reads what a skill folder is: vfrey's own with a stamp, vfrey's own from before stamps, or somebody else's. */
158
+ export function inspectInstalledSkill(dir: string): InstalledSkill {
159
+ const stamp = readStamp(dir);
160
+ if (stamp) return { kind: "stamped", ...stamp };
161
+ return hasVfreyName(dir) ? { kind: "unstamped" } : { kind: "foreign" };
162
+ }
163
+
164
+ export interface StaleSkill extends SkillLocation {
165
+ file: string;
166
+ /** The stamped version, or "unversioned". */
167
+ was: string;
168
+ }
169
+
170
+ export interface SkillCopy extends SkillLocation {
171
+ file: string;
172
+ /** current: this package's version. stale: vfrey's own but older or different. foreign: not vfrey's skill. */
173
+ state: "current" | "stale" | "foreign";
174
+ /** The stamped version, or "unversioned". Set when stale. */
175
+ was?: string;
176
+ }
177
+
178
+ /** Every skill folder named vfrey in any known agent location (personal and project in cwd). Cheap: one stat per location. */
179
+ export function findSkillCopies(env: Env, cwd: string, homedir: string = os.homedir()): SkillCopy[] {
180
+ const copies: SkillCopy[] = [];
181
+ const current = packageSkillStamp();
182
+ for (const location of skillLocations(env, cwd, homedir)) {
183
+ const file = path.join(location.dir, "SKILL.md");
184
+ if (!existsSync(file)) continue;
185
+ const found = inspectInstalledSkill(location.dir);
186
+ if (found.kind === "foreign") copies.push({ ...location, file, state: "foreign" });
187
+ else if (found.kind === "stamped" && found.version === current.version && found.hash === current.hash) copies.push({ ...location, file, state: "current" });
188
+ else copies.push({ ...location, file, state: "stale", was: found.kind === "stamped" ? found.version : "unversioned" });
189
+ }
190
+ return copies;
191
+ }
192
+
193
+ /** The vfrey copies, in any agent folder, that are older than or different from this package's. */
194
+ export function staleAgentSkills(env: Env, cwd: string, homedir: string = os.homedir()): StaleSkill[] {
195
+ return findSkillCopies(env, cwd, homedir)
196
+ .filter((copy) => copy.state === "stale")
197
+ .map(({ state: _state, was, ...rest }) => ({ ...rest, was: was ?? "unversioned" }));
198
+ }
199
+
200
+ /** The line every command prints to stderr, once per stale copy. */
201
+ export function staleSkillWarning(stale: StaleSkill): string {
202
+ return `! Your vfrey skill for ${stale.label} at ${stale.dir} is older than this package. Run: ${installCommand(stale.agent, stale.kind)}\n`;
203
+ }
204
+
205
+ /** Moves a skill folder aside. The SKILL.md files get a .old name so the agent never loads the backup as a second vfrey skill. */
206
+ function backUp(target: string): string {
207
+ const stamp = new Date().toISOString().replace(/[:.]/g, "-");
208
+ let backup = `${target}.bak-${stamp}`;
209
+ for (let n = 2; existsSync(backup); n += 1) backup = `${target}.bak-${stamp}-${n}`;
210
+ renameSync(target, backup);
211
+ for (const rel of walk(backup)) {
212
+ if (path.basename(rel) === "SKILL.md") renameSync(path.join(backup, rel), path.join(backup, `${rel}.old`));
213
+ }
214
+ return backup;
215
+ }
216
+
217
+ function sameAsExpected(target: string, expected: Map<string, Buffer>): boolean {
218
+ let actual: string[];
219
+ try {
220
+ actual = walk(target);
221
+ } catch {
222
+ return false;
223
+ }
224
+ if (actual.length !== expected.size) return false;
225
+ return actual.every((rel) => {
226
+ const want = expected.get(rel);
227
+ return want !== undefined && want.equals(readFileSync(path.join(target, rel)));
228
+ });
229
+ }
230
+
231
+ interface Target {
232
+ /** Agent id, or null for a --dir target. */
233
+ agent: AgentId | null;
234
+ label: string;
235
+ dir: string;
236
+ project: boolean;
237
+ }
238
+
239
+ function parseFlag(flags: string[], name: string): string | null | undefined {
240
+ const at = flags.findIndex((flag) => flag === name || flag.startsWith(`${name}=`));
241
+ if (at < 0) return undefined;
242
+ if (flags[at]!.includes("=")) return flags[at]!.slice(name.length + 1) || null;
243
+ const value = flags[at + 1];
244
+ return value && !value.startsWith("--") ? value : null;
245
+ }
246
+
247
+ const SKILL_USAGE = "use `npx vfrey skill install [--agent claude|codex|grok|cursor|gemini|all] [--dir PATH] [--project] [--force]` or `npx vfrey skill uninstall` with the same flags.";
248
+
249
+ function resolveTargets(flags: string[], io: SkillIo): Target[] | string {
250
+ const project = flags.includes("--project");
251
+ const agentFlag = parseFlag(flags, "--agent");
252
+ const dirFlag = parseFlag(flags, "--dir");
253
+ if (agentFlag === null) return `--agent needs a value: ${[...AGENT_IDS, "all"].join(", ")}.`;
254
+ if (dirFlag === null) return "--dir needs a folder path.";
255
+ if (dirFlag !== undefined) {
256
+ if (agentFlag !== undefined) return "use --dir or --agent, not both.";
257
+ if (project) return "--project does not combine with --dir. Pass the exact skills folder to --dir.";
258
+ return [{ agent: null, label: "custom folder", dir: path.join(path.resolve(io.cwd, dirFlag), SKILL_NAME), project: false }];
259
+ }
260
+ const choice = agentFlag ?? "claude";
261
+ const ids = choice === "all" ? [...AGENT_IDS] : isAgentId(choice) ? [choice] : null;
262
+ if (!ids) return `Unknown agent "${choice}". Use one of: ${[...AGENT_IDS, "all"].join(", ")}.`;
263
+ return ids.map((id) => ({ agent: id, label: SKILL_AGENTS[id].label, dir: agentSkillTarget(id, io.env, io.cwd, project, io.homedir), project }));
264
+ }
265
+
266
+ function installOne(target: Target, force: boolean, io: SkillIo): { code: number; next: string | null } {
267
+ const expected = expectedFiles();
268
+ const where = `${target.dir} (${target.label})`;
269
+ const next = target.agent ? SKILL_AGENTS[target.agent].nextStep : CUSTOM_NEXT_STEP;
270
+ let updatedFrom: string | null = null;
271
+ let backup = "";
272
+ if (existsSync(target.dir)) {
273
+ if (sameAsExpected(target.dir, expected)) {
274
+ io.stdout(`✓ The vfrey skill is already up to date at ${where}\n`);
275
+ return { code: 0, next: null };
276
+ }
277
+ const found = inspectInstalledSkill(target.dir);
278
+ if (found.kind === "foreign" && !force) {
279
+ const fix =
280
+ target.project || target.agent === null
281
+ ? "run with --force to replace it."
282
+ : "run with --force to replace it, or with --project to install the skill in this folder instead.";
283
+ io.stderr(`✗ A different vfrey skill is already at ${target.dir}\nFix: ${fix}\n`);
284
+ return { code: 1, next: null };
285
+ }
286
+ updatedFrom = found.kind === "stamped" ? found.version : "unversioned";
287
+ backup = backUp(target.dir);
288
+ }
289
+ for (const [rel, data] of expected) {
290
+ const file = path.join(target.dir, rel);
291
+ mkdirSync(path.dirname(file), { recursive: true });
292
+ writeFileSync(file, data);
293
+ }
294
+ io.stdout(
295
+ updatedFrom !== null
296
+ ? `✓ Updated the vfrey skill (was ${updatedFrom}) at ${where}\nOld copy saved at ${backup}\n`
297
+ : `✓ Installed the vfrey skill at ${where}\n`,
298
+ );
299
+ return { code: 0, next };
300
+ }
301
+
302
+ export function runSkillCommand(argv: string[], io: SkillIo): number {
303
+ const [verb, ...flags] = argv;
304
+ if (verb !== "install" && verb !== "uninstall") {
305
+ io.stderr(`✗ Unknown skill command "${verb ?? ""}".\nFix: ${SKILL_USAGE}\n`);
306
+ return 1;
307
+ }
308
+ const targets = resolveTargets(flags, io);
309
+ if (typeof targets === "string") {
310
+ io.stderr(`✗ ${targets}\nFix: ${SKILL_USAGE}\n`);
311
+ return 1;
312
+ }
313
+ if (verb === "install") {
314
+ const force = flags.includes("--force");
315
+ let code = 0;
316
+ const steps: string[] = [];
317
+ for (const target of targets) {
318
+ const result = installOne(target, force, io);
319
+ if (result.code !== 0) code = result.code;
320
+ if (result.next) steps.push(targets.length > 1 ? `${target.label}: ${result.next}` : result.next);
321
+ }
322
+ for (const step of steps) io.stdout(`${step}\n`);
323
+ return code;
324
+ }
325
+ for (const target of targets) {
326
+ if (!existsSync(target.dir) || !statSync(target.dir).isDirectory()) {
327
+ io.stdout(`✓ vfrey skill is not installed at ${target.dir} (${target.label})\n`);
328
+ continue;
329
+ }
330
+ rmSync(target.dir, { recursive: true, force: true });
331
+ io.stdout(`✓ Removed the vfrey skill from ${target.dir} (${target.label})\n`);
332
+ }
333
+ return 0;
334
+ }
@@ -0,0 +1,21 @@
1
+ import { writeSync } from "node:fs";
2
+
3
+ /**
4
+ * Writes straight to a file descriptor and returns when the bytes are in the pipe, file, or terminal.
5
+ * `process.stdout.write` queues on a pipe and flushes on a later turn of the event loop. A line that
6
+ * must reach a log at once (the dashboard's ready line) does not wait for that turn.
7
+ * A full non-blocking pipe (EAGAIN) is retried briefly; a closed pipe or any other failure is dropped.
8
+ */
9
+ export function writeNow(fd: 1 | 2, text: string): void {
10
+ let data = Buffer.from(text);
11
+ for (let attempt = 0; data.length > 0 && attempt < 200; ) {
12
+ try {
13
+ const written = writeSync(fd, data);
14
+ data = data.subarray(written);
15
+ } catch (error) {
16
+ if ((error as NodeJS.ErrnoException).code !== "EAGAIN") return;
17
+ attempt += 1;
18
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 5);
19
+ }
20
+ }
21
+ }